You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
12 KiB
389 lines
12 KiB
Routing and Multi-Page Apps |
|
=========================== |
|
|
|
BadGUI supports creating multi-page applications with Vue Router integration, allowing you to build complex single-page applications with client-side routing. |
|
|
|
Page Management |
|
--------------- |
|
|
|
Pages in BadGUI are created using the ``page()`` function, which acts as both a page definer and a context manager. |
|
|
|
Creating Pages |
|
~~~~~~~~~~~~~~ |
|
|
|
.. code-block:: python |
|
|
|
import badgui as bg |
|
|
|
# Create a page with context manager |
|
with bg.page("/", "HomePage", "Home"): |
|
bg.label("Welcome to the Home Page") |
|
bg.label("This is the main landing page") |
|
|
|
**Parameters:** |
|
|
|
- ``path``: URL path (e.g., ``"/"``, ``"/about"``, ``"/contact"``) |
|
- ``name``: Component name (e.g., ``"HomePage"``, ``"AboutPage"``) |
|
- ``title``: Page title displayed in browser tab (optional) |
|
|
|
Page Context |
|
~~~~~~~~~~~~ |
|
|
|
All components created within a page context are automatically added to that page: |
|
|
|
.. code-block:: python |
|
|
|
with bg.page("/about", "AboutPage", "About Us"): |
|
# These components belong to the About page |
|
bg.label("About Our Company") |
|
bg.label("Founded in 2025") |
|
|
|
with bg.row(): |
|
bg.label("Mission") |
|
bg.label("Vision") |
|
|
|
Navigation |
|
---------- |
|
|
|
Link Component |
|
~~~~~~~~~~~~~~ |
|
|
|
Create navigation between pages using the ``link()`` component: |
|
|
|
.. code-block:: python |
|
|
|
# Basic link |
|
bg.link("Go to About", "/about") |
|
|
|
# Styled as button |
|
bg.link("Contact Us", "/contact").classes("q-btn q-btn-primary") |
|
|
|
# With additional props |
|
bg.link("Home", "/").props("exact").classes("nav-link") |
|
|
|
**Generated Output:** ``<router-link to="/about">Go to About</router-link>`` |
|
|
|
Navigation Menu |
|
~~~~~~~~~~~~~~~ |
|
|
|
Create a navigation menu across multiple pages: |
|
|
|
.. code-block:: python |
|
|
|
def create_nav(): |
|
\"\"\"Create consistent navigation menu.\"\"\" |
|
with bg.row().classes("q-gutter-md q-mb-lg justify-center") as nav: |
|
nav.classes("bg-primary text-white q-pa-md rounded") |
|
bg.link("Home", "/").classes("text-white no-underline q-btn q-btn-flat") |
|
bg.link("About", "/about").classes("text-white no-underline q-btn q-btn-flat") |
|
bg.link("Services", "/services").classes("text-white no-underline q-btn q-btn-flat") |
|
bg.link("Contact", "/contact").classes("text-white no-underline q-btn q-btn-flat") |
|
|
|
# Use in each page |
|
with bg.page("/", "HomePage", "Home"): |
|
create_nav() |
|
bg.label("Home Page Content") |
|
|
|
with bg.page("/about", "AboutPage", "About"): |
|
create_nav() |
|
bg.label("About Page Content") |
|
|
|
Route Parameters |
|
---------------- |
|
|
|
Dynamic Routes |
|
~~~~~~~~~~~~~~ |
|
|
|
Create routes with parameters: |
|
|
|
.. code-block:: python |
|
|
|
# Route with parameter |
|
with bg.page("/user/:id", "UserPage", "User Profile"): |
|
bg.label("User Profile Page") |
|
bg.label("User ID will be available in Vue component") |
|
|
|
# Navigation to dynamic route |
|
bg.link("View User 123", "/user/123") |
|
|
|
Nested Routes |
|
~~~~~~~~~~~~~ |
|
|
|
Create nested route structures: |
|
|
|
.. code-block:: python |
|
|
|
# Parent route |
|
with bg.page("/dashboard", "DashboardPage", "Dashboard"): |
|
bg.label("Dashboard").classes("text-h3") |
|
|
|
# Navigation to child routes |
|
with bg.row().classes("q-gutter-md"): |
|
bg.link("Analytics", "/dashboard/analytics") |
|
bg.link("Settings", "/dashboard/settings") |
|
bg.link("Profile", "/dashboard/profile") |
|
|
|
# Child routes |
|
with bg.page("/dashboard/analytics", "AnalyticsPage", "Analytics"): |
|
bg.label("Analytics Dashboard") |
|
bg.label("View your analytics data here") |
|
|
|
with bg.page("/dashboard/settings", "SettingsPage", "Settings"): |
|
bg.label("Dashboard Settings") |
|
bg.input(placeholder="Setting 1") |
|
bg.input(placeholder="Setting 2") |
|
|
|
Multi-Page Application Example |
|
------------------------------ |
|
|
|
Complete Blog Application |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
Here's a complete multi-page blog application: |
|
|
|
.. code-block:: python |
|
|
|
import badgui as bg |
|
|
|
def create_header(): |
|
\"\"\"Create site header with navigation.\"\"\" |
|
with bg.row().classes("bg-primary text-white q-pa-md justify-between items-center") as header: |
|
bg.label("My Blog").classes("text-h5 text-weight-bold") |
|
|
|
with bg.row().classes("q-gutter-md"): |
|
bg.link("Home", "/").classes("text-white q-btn q-btn-flat") |
|
bg.link("Posts", "/posts").classes("text-white q-btn q-btn-flat") |
|
bg.link("About", "/about").classes("text-white q-btn q-btn-flat") |
|
bg.link("Contact", "/contact").classes("text-white q-btn q-btn-flat") |
|
|
|
def create_footer(): |
|
\"\"\"Create site footer.\"\"\" |
|
with bg.row().classes("bg-grey-8 text-white q-pa-lg justify-center q-mt-xl"): |
|
bg.label("© 2025 My Blog. All rights reserved.").classes("text-body2") |
|
|
|
# Home Page |
|
with bg.page("/", "HomePage", "My Blog - Home"): |
|
create_header() |
|
|
|
# Hero section |
|
with bg.column().classes("text-center q-pa-xl bg-gradient-to-r from-blue-500 to-purple-600 text-white"): |
|
bg.label("Welcome to My Blog").classes("text-h2 q-mb-md") |
|
bg.label("Sharing thoughts and ideas").classes("text-h6 q-mb-lg") |
|
bg.link("Read Latest Posts", "/posts").classes("q-btn q-btn-lg q-btn-white text-primary") |
|
|
|
# Featured posts |
|
with bg.column().classes("q-pa-xl"): |
|
bg.label("Featured Posts").classes("text-h4 q-mb-lg text-center") |
|
|
|
with bg.row().classes("q-col-gutter-lg"): |
|
for i in range(3): |
|
with bg.column().classes("col-12 col-md-4"): |
|
with bg.column().classes("bg-white rounded-lg shadow-2 q-pa-lg"): |
|
bg.label(f"Post Title {i+1}").classes("text-h6 q-mb-md") |
|
bg.label("Post excerpt goes here...").classes("text-body2 q-mb-md") |
|
bg.link("Read More", f"/post/{i+1}").classes("q-btn q-btn-primary") |
|
|
|
create_footer() |
|
|
|
# Posts Page |
|
with bg.page("/posts", "PostsPage", "All Posts"): |
|
create_header() |
|
|
|
with bg.column().classes("q-pa-xl"): |
|
bg.label("All Posts").classes("text-h3 q-mb-lg") |
|
|
|
# Post list |
|
for i in range(10): |
|
with bg.row().classes("bg-white rounded q-pa-lg q-mb-md shadow-1"): |
|
with bg.column().classes("col-12"): |
|
bg.label(f"Blog Post {i+1}").classes("text-h6 q-mb-sm") |
|
bg.label("Published on January 1, 2025").classes("text-caption text-grey-6 q-mb-md") |
|
bg.label("This is a preview of the blog post content...").classes("text-body2 q-mb-md") |
|
bg.link("Read Full Post", f"/post/{i+1}").classes("q-btn q-btn-outline") |
|
|
|
create_footer() |
|
|
|
# Individual Post Page |
|
with bg.page("/post/:id", "PostPage", "Blog Post"): |
|
create_header() |
|
|
|
with bg.column().classes("q-pa-xl max-width-md mx-auto"): |
|
bg.label("Blog Post Title").classes("text-h3 q-mb-md") |
|
bg.label("Published on January 1, 2025 by Author").classes("text-caption text-grey-6 q-mb-lg") |
|
|
|
bg.label("Blog post content goes here...").classes("text-body1 q-mb-lg") |
|
bg.label("More content and paragraphs...").classes("text-body1 q-mb-lg") |
|
|
|
with bg.row().classes("q-mt-xl"): |
|
bg.link("← Back to Posts", "/posts").classes("q-btn q-btn-outline") |
|
|
|
create_footer() |
|
|
|
# About Page |
|
with bg.page("/about", "AboutPage", "About"): |
|
create_header() |
|
|
|
with bg.column().classes("q-pa-xl max-width-md mx-auto text-center"): |
|
bg.label("About Me").classes("text-h3 q-mb-lg") |
|
bg.label("Welcome to my personal blog...").classes("text-body1 q-mb-lg") |
|
bg.label("I write about technology, life, and everything in between.").classes("text-body1 q-mb-lg") |
|
|
|
create_footer() |
|
|
|
# Contact Page |
|
with bg.page("/contact", "ContactPage", "Contact"): |
|
create_header() |
|
|
|
with bg.column().classes("q-pa-xl max-width-sm mx-auto"): |
|
bg.label("Contact Me").classes("text-h3 q-mb-lg text-center") |
|
|
|
# Contact form |
|
bg.input(placeholder="Your Name").classes("q-mb-md").props("filled required") |
|
bg.input(placeholder="Your Email").classes("q-mb-md").props("filled required type=email") |
|
bg.input(placeholder="Subject").classes("q-mb-md").props("filled required") |
|
bg.input(placeholder="Your Message").classes("q-mb-lg").props("filled required type=textarea rows=4") |
|
|
|
bg.button("Send Message").classes("q-btn q-btn-primary full-width") |
|
|
|
create_footer() |
|
|
|
# Build the application |
|
bg.build("blog-app") |
|
|
|
Route Configuration |
|
------------------- |
|
|
|
Generated Router Files |
|
~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
BadGUI automatically generates Vue Router configuration files: |
|
|
|
**``src/router/routes.js``:** |
|
|
|
.. code-block:: javascript |
|
|
|
const routes = [ |
|
{ |
|
path: '/', |
|
component: () => import('layouts/MainLayout.vue'), |
|
children: [ |
|
{ path: '', component: () => import('pages/HomePage.vue') }, |
|
{ path: '/about', component: () => import('pages/AboutPage.vue') }, |
|
{ path: '/posts', component: () => import('pages/PostsPage.vue') }, |
|
{ path: '/post/:id', component: () => import('pages/PostPage.vue') }, |
|
{ path: '/contact', component: () => import('pages/ContactPage.vue') } |
|
] |
|
} |
|
] |
|
|
|
**``src/router/index.js``:** |
|
|
|
.. code-block:: javascript |
|
|
|
import { createRouter, createWebHistory } from 'vue-router' |
|
import routes from './routes' |
|
|
|
const router = createRouter({ |
|
history: createWebHistory(), |
|
routes |
|
}) |
|
|
|
export default router |
|
|
|
Page Components |
|
~~~~~~~~~~~~~~~ |
|
|
|
Each page generates a corresponding Vue component: |
|
|
|
**``src/pages/HomePage.vue``:** |
|
|
|
.. code-block:: vue |
|
|
|
<template> |
|
<q-page class="row items-center justify-evenly"> |
|
<!-- Generated components --> |
|
</q-page> |
|
</template> |
|
|
|
<script setup> |
|
import { ref } from 'vue' |
|
// Component logic |
|
</script> |
|
|
|
Best Practices |
|
-------------- |
|
|
|
URL Structure |
|
~~~~~~~~~~~~~ |
|
|
|
1. **Use clear, descriptive URLs**: ``/about``, ``/contact``, ``/blog/post-title`` |
|
2. **Keep URLs short and readable**: Avoid deep nesting |
|
3. **Use hyphens for multi-word URLs**: ``/user-profile`` not ``/userprofile`` |
|
4. **Be consistent**: Choose a pattern and stick to it |
|
|
|
Page Organization |
|
~~~~~~~~~~~~~~~~~ |
|
|
|
1. **Group related pages**: Keep similar functionality together |
|
2. **Use consistent layouts**: Share common elements like headers/footers |
|
3. **Plan your navigation flow**: Make it easy for users to move between pages |
|
4. **Consider SEO**: Use descriptive page titles |
|
|
|
Performance Considerations |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
1. **Code splitting**: Vue Router automatically splits pages into separate chunks |
|
2. **Lazy loading**: Pages are loaded only when needed |
|
3. **Minimize page complexity**: Keep individual pages focused and lightweight |
|
|
|
Error Handling |
|
-------------- |
|
|
|
404 Error Page |
|
~~~~~~~~~~~~~~ |
|
|
|
BadGUI automatically generates a 404 error page: |
|
|
|
.. code-block:: python |
|
|
|
# This is generated automatically |
|
with bg.page("/:catchAll(.*)*", "ErrorNotFound", "Page Not Found"): |
|
bg.label("Oops. Nothing here...").classes("text-h2") |
|
bg.link("Go Home", "/").classes("q-btn q-btn-primary") |
|
|
|
The generated project includes proper error handling for routes that don't exist. |
|
|
|
Advanced Routing |
|
---------------- |
|
|
|
Route Guards |
|
~~~~~~~~~~~~ |
|
|
|
While BadGUI generates static routes, you can add route guards in the generated Vue project: |
|
|
|
.. code-block:: javascript |
|
|
|
// In the generated router/index.js, you can add: |
|
router.beforeEach((to, from, next) => { |
|
// Add authentication, analytics, etc. |
|
next() |
|
}) |
|
|
|
Programmatic Navigation |
|
~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
In the generated Vue components, you can add programmatic navigation: |
|
|
|
.. code-block:: vue |
|
|
|
<script setup> |
|
import { useRouter } from 'vue-router' |
|
|
|
const router = useRouter() |
|
|
|
const navigateToAbout = () => { |
|
router.push('/about') |
|
} |
|
</script> |
|
|
|
This allows for dynamic navigation based on user actions or application state. |