feat: Enhance button and dialog components with new event handling and styling options
- Updated button function to accept icon and color parameters for better customization. - Introduced button_with_handler for enhanced event handling with JavaScript and Python functions. - Added dialog component with open/close functionality and persistent option. - Implemented notify function for displaying notifications with customizable options. - Added script function for including custom JavaScript in the application. - Improved component structure to support Vue refs and reactive data binding. - Updated generator to process scripts and manage external JavaScript files. - Created tests for button handling and simplified card usage demonstrating new features.
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# BadGUI bg.script() Component Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
The `bg.script()` component has been successfully implemented in BadGUI v0.2.0, providing a clean and flexible way to include custom JavaScript in your applications. This component bridges Python declarative UI code with JavaScript runtime functionality.
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### 1. Inline JavaScript
|
||||
```python
|
||||
# Simple inline script
|
||||
bg.script("""
|
||||
console.log('Hello from BadGUI!');
|
||||
|
||||
window.myFunction = function() {
|
||||
alert('Custom function executed!');
|
||||
};
|
||||
""")
|
||||
```
|
||||
|
||||
### 2. External JavaScript Files
|
||||
```python
|
||||
# From file path
|
||||
bg.script(src="/path/to/script.js")
|
||||
|
||||
# From URL
|
||||
bg.script(src="https://cdn.example.com/library.js")
|
||||
```
|
||||
|
||||
### 3. Script Attributes
|
||||
```python
|
||||
# Deferred execution
|
||||
bg.script("console.log('Deferred script');", defer=True)
|
||||
|
||||
# ES6 module
|
||||
bg.script("export const myVar = 'test';", type="module")
|
||||
|
||||
# Async loading (using **kwargs to avoid Python keyword conflict)
|
||||
bg.script("console.log('Async script');", **{"async": True})
|
||||
```
|
||||
|
||||
### 4. Vue.js Integration
|
||||
```python
|
||||
# Vue 3 Composition API integration
|
||||
bg.script("""
|
||||
// Reactive data
|
||||
const counter = ref(0);
|
||||
const message = ref('Hello BadGUI!');
|
||||
|
||||
// Computed properties
|
||||
const doubled = computed(() => counter.value * 2);
|
||||
|
||||
// Methods
|
||||
const increment = () => counter.value++;
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
console.log('Component mounted with BadGUI scripts!');
|
||||
});
|
||||
""")
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Component Architecture
|
||||
- **Component Type**: `script`
|
||||
- **Props Handling**: Special `_script_content`, `_external_file`, `_is_inline` props
|
||||
- **Template Generation**: Custom `_generate_script_template()` method
|
||||
- **Build Integration**: Scripts processed during Vue.js project generation
|
||||
|
||||
### Generator Integration
|
||||
- **Script Collection**: `_process_page_scripts()` method collects all scripts per page
|
||||
- **File Copying**: External files copied to `public/js/` directory
|
||||
- **HTML Injection**: External scripts added to `index.html` as `<script src="">` tags
|
||||
- **Vue Integration**: Inline scripts included in component `<script setup>` section
|
||||
|
||||
### Vue.js Template Integration
|
||||
- **Global Access**: `window` object made available via `const window = globalThis.window`
|
||||
- **Quasar Integration**: `useQuasar()` provides `$q` for notifications/dialogs
|
||||
- **Event Handlers**: Button handlers can call global functions with `() => window.myFunction()`
|
||||
|
||||
## Generated Output Structure
|
||||
|
||||
### Vue Component Template
|
||||
```vue
|
||||
<template>
|
||||
<q-page class="row items-center justify-evenly">
|
||||
<!-- Component templates -->
|
||||
<!-- Script components show as HTML comments -->
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
|
||||
// Component state
|
||||
const state = ref({})
|
||||
const $q = useQuasar()
|
||||
|
||||
// Make window object available to template
|
||||
const window = globalThis.window
|
||||
|
||||
// Inline script content inserted here
|
||||
// Vue component reactive data and methods
|
||||
</script>
|
||||
```
|
||||
|
||||
### Index.html Integration
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Meta tags -->
|
||||
<script src="/js/external-script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
project-output/
|
||||
├── src/
|
||||
│ ├── pages/
|
||||
│ │ └── PageName.vue (with integrated scripts)
|
||||
│ └── ...
|
||||
├── public/
|
||||
│ ├── js/
|
||||
│ │ └── external-script.js (copied external files)
|
||||
│ └── ...
|
||||
└── index.html (with script tags)
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### ✅ Implemented
|
||||
- Inline JavaScript code inclusion
|
||||
- External file inclusion with automatic copying
|
||||
- Script attributes (defer, async, type, etc.)
|
||||
- Vue.js 3 Composition API integration
|
||||
- Global function definitions
|
||||
- Automatic window object access in templates
|
||||
- HTML injection for external scripts
|
||||
- Build-time script processing
|
||||
|
||||
### 🔧 Technical Highlights
|
||||
- **No Template Rendering**: Script components render as HTML comments in templates
|
||||
- **Build-Time Processing**: Scripts processed during project generation, not runtime
|
||||
- **Vue Integration**: Seamless integration with Vue 3 reactive system
|
||||
- **File Management**: Automatic copying and path resolution for external files
|
||||
- **Conflict Resolution**: Proper handling of reserved Python keywords (`async`)
|
||||
|
||||
### 🎯 Use Cases
|
||||
- Custom utility functions
|
||||
- External library integration
|
||||
- Vue.js reactive data and methods
|
||||
- DOM manipulation and event handling
|
||||
- API integration patterns
|
||||
- Complex business logic
|
||||
- Third-party service integration
|
||||
|
||||
## Example Integration
|
||||
|
||||
```python
|
||||
import badgui as bg
|
||||
|
||||
with bg.page("/", "ScriptDemo", "Script Demo"):
|
||||
with bg.column().classes("q-pa-lg"):
|
||||
bg.label("BadGUI Script Integration").classes("text-h3")
|
||||
|
||||
# Inline reactive data
|
||||
bg.script("""
|
||||
const count = ref(0);
|
||||
const increment = () => count.value++;
|
||||
""")
|
||||
|
||||
# External utilities
|
||||
bg.script(src="./utils.js")
|
||||
|
||||
# UI with JavaScript integration
|
||||
bg.label("Count: {{ count }}").classes("text-h4")
|
||||
bg.button_with_handler("Increment", js_handler="increment")
|
||||
bg.button_with_handler("External Function", js_handler="() => window.myUtility()")
|
||||
|
||||
if __name__ == "__main__":
|
||||
bg.dev()
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
The `bg.script()` component provides a solid foundation for JavaScript integration. Future enhancements could include:
|
||||
|
||||
- **TypeScript Support**: Type checking for inline scripts
|
||||
- **Module System**: Better ES6 module integration
|
||||
- **Server Communication**: Python callback patterns for backend integration
|
||||
- **Hot Reload**: Script changes trigger browser updates
|
||||
- **Validation**: JavaScript syntax checking during build
|
||||
- **Minification**: Script optimization for production builds
|
||||
|
||||
The implementation successfully bridges the gap between Python's declarative UI approach and JavaScript's runtime interactivity, enabling developers to build rich, interactive web applications with BadGUI.
|
||||
+28
-2
@@ -20,14 +20,18 @@ def label(text: str, **kwargs):
|
||||
"""Create a label component."""
|
||||
return app.label(text, **kwargs)
|
||||
|
||||
def button(text: str, **kwargs):
|
||||
def button(text: str, icon: str = None, color: str = 'primary', **kwargs):
|
||||
"""Create a button component."""
|
||||
return app.button(text, **kwargs)
|
||||
return app.button(text, icon, color, **kwargs)
|
||||
|
||||
def input(placeholder: str = "", **kwargs):
|
||||
"""Create an input component."""
|
||||
return app.input(placeholder, **kwargs)
|
||||
|
||||
def div(**kwargs):
|
||||
"""Create a div container component."""
|
||||
return app.div(**kwargs)
|
||||
|
||||
def build(output_dir: str = "./badgui-output", **kwargs):
|
||||
"""Build the Vue.js/Quasar project."""
|
||||
return app.build(output_dir, **kwargs)
|
||||
@@ -92,6 +96,28 @@ def icon(name: str, size: str = None, color: str = None, **kwargs):
|
||||
"""Create an icon component."""
|
||||
return app.icon(name, size, color, **kwargs)
|
||||
|
||||
# Event handling and interactive components
|
||||
def button_with_handler(text: str, on_click=None, js_handler: str = None, **kwargs):
|
||||
"""Create a button with enhanced event handling."""
|
||||
return app.button_with_handler(text, on_click, js_handler, **kwargs)
|
||||
|
||||
def dialog(opened: bool = False, persistent: bool = False, **kwargs):
|
||||
"""Create a dialog component."""
|
||||
return app.dialog(opened, persistent, **kwargs)
|
||||
|
||||
def notify(message: str, type_: str = None, position: str = "bottom",
|
||||
close_button: str = None, color: str = None, multiline: bool = False, **kwargs):
|
||||
"""Create a notification."""
|
||||
return app.notify(message, type_, position, close_button, color, multiline, **kwargs)
|
||||
|
||||
def on_event(component, event_type: str, handler=None, js_handler: str = None, **kwargs):
|
||||
"""Add event handler to a component."""
|
||||
return app.on_event(component, event_type, handler, js_handler, **kwargs)
|
||||
|
||||
def script(content=None, src=None, **kwargs):
|
||||
"""Add custom JavaScript to the page."""
|
||||
return app.script(content, src, **kwargs)
|
||||
|
||||
def dev(port: int = 9000, host: str = "localhost", auto_reload: bool = False, project_name: str = "badgui-dev", **kwargs):
|
||||
"""Create a temporary build and run it in development mode."""
|
||||
return app.dev(port, host, auto_reload, project_name, **kwargs)
|
||||
+368
-11
@@ -19,6 +19,7 @@ class Component:
|
||||
self.id = f"{component_type}_{id(self)}"
|
||||
self._classes = props.get('classes', [])
|
||||
self._style = props.get('style', {})
|
||||
self._vue_ref = None
|
||||
|
||||
def add_child(self, child: 'Component'):
|
||||
"""Add a child component."""
|
||||
@@ -46,11 +47,19 @@ class Component:
|
||||
"""Convert component to Vue template string."""
|
||||
spaces = " " * indent
|
||||
|
||||
# Special handling for script components
|
||||
if self.component_type == 'script':
|
||||
return self._generate_script_template(indent)
|
||||
|
||||
# Build attributes
|
||||
attrs = []
|
||||
|
||||
# Add ID
|
||||
attrs.append(f'id="{self.id}"')
|
||||
# Add ID (use manual id if provided, otherwise automatic)
|
||||
manual_id = self._props.get('id')
|
||||
if manual_id:
|
||||
attrs.append(f'id="{manual_id}"')
|
||||
else:
|
||||
attrs.append(f'id="{self.id}"')
|
||||
|
||||
# Add classes
|
||||
if self._classes:
|
||||
@@ -71,7 +80,7 @@ class Component:
|
||||
|
||||
# Add other props
|
||||
for key, value in self._props.items():
|
||||
if key not in ['classes', 'style']:
|
||||
if key not in ['classes', 'style', 'id']: # Skip id since we handle it above
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
attrs.append(key)
|
||||
@@ -85,7 +94,19 @@ class Component:
|
||||
attrs_str = " " + " ".join(attrs) if attrs else ""
|
||||
|
||||
# Generate template
|
||||
if self.children:
|
||||
if self.component_type == 'card' and self._props.get('_card_title'):
|
||||
# Simplified card with automatic title section
|
||||
title = self._props.get('_card_title')
|
||||
template = f"{spaces}<{self.get_vue_tag()}{attrs_str}>\n"
|
||||
template += f"{spaces} <q-card-section>\n"
|
||||
template += f"{spaces} <div class=\"text-h6\">{title}</div>\n"
|
||||
template += f"{spaces} </q-card-section>\n"
|
||||
template += f"{spaces} <q-card-section>\n"
|
||||
for child in self.children:
|
||||
template += child.to_vue_template(indent + 2)
|
||||
template += f"{spaces} </q-card-section>\n"
|
||||
template += f"{spaces}</{self.get_vue_tag()}>\n"
|
||||
elif self.children:
|
||||
template = f"{spaces}<{self.get_vue_tag()}{attrs_str}>\n"
|
||||
for child in self.children:
|
||||
template += child.to_vue_template(indent + 1)
|
||||
@@ -118,6 +139,8 @@ class Component:
|
||||
'footer': 'q-footer',
|
||||
'image': 'q-img',
|
||||
'icon': 'q-icon',
|
||||
'dialog': 'q-dialog',
|
||||
'script': 'script',
|
||||
'div': 'div',
|
||||
'router-link': 'router-link'
|
||||
}
|
||||
@@ -127,6 +150,36 @@ class Component:
|
||||
"""Get the text content of the component."""
|
||||
return self._props.get('text', self._props.get('content', ''))
|
||||
|
||||
def _generate_script_template(self, indent: int = 0) -> str:
|
||||
"""Generate script tag template for script components."""
|
||||
spaces = " " * indent
|
||||
|
||||
# Script components are usually not rendered in the main template
|
||||
# They are handled separately during build
|
||||
if self._props.get('_is_inline'):
|
||||
# Inline script - return as HTML comment for template
|
||||
content = self._props.get('_script_content', '')
|
||||
return f"{spaces}<!-- Inline script: {len(content)} chars -->\n"
|
||||
elif self._props.get('_external_file'):
|
||||
# External file - return as HTML comment for template
|
||||
filename = self._props.get('_external_file')
|
||||
return f"{spaces}<!-- External script: {filename} -->\n"
|
||||
elif self._props.get('src'):
|
||||
# External URL - might be rendered in final build
|
||||
src = self._props.get('src')
|
||||
attrs = []
|
||||
for key, value in self._props.items():
|
||||
if key not in ['_script_content', '_is_inline', '_external_file', '_file_content']:
|
||||
if isinstance(value, bool) and value:
|
||||
attrs.append(key)
|
||||
elif not isinstance(value, bool):
|
||||
attrs.append(f'{key}="{value}"')
|
||||
|
||||
attrs_str = " " + " ".join(attrs) if attrs else ""
|
||||
return f"{spaces}<script{attrs_str}></script>\n"
|
||||
|
||||
return f"{spaces}<!-- Script component -->\n"
|
||||
|
||||
def classes(self, add: str = None, *, remove: str = None, replace: str = None):
|
||||
"""Add, remove, or replace CSS classes (Tailwind, Quasar, custom)."""
|
||||
# Get current classes list
|
||||
@@ -200,6 +253,62 @@ class Component:
|
||||
self._props[key] = value
|
||||
return self
|
||||
|
||||
def vue_ref(self, ref_name: str):
|
||||
"""Set a Vue ref name for this component to access it from JavaScript."""
|
||||
self._vue_ref = ref_name
|
||||
self._props['ref'] = ref_name
|
||||
return self
|
||||
|
||||
def get_vue_ref(self) -> Optional[str]:
|
||||
"""Get the Vue ref name for this component."""
|
||||
return self._vue_ref
|
||||
|
||||
def bind_text(self, reactive_var: str):
|
||||
"""Bind component text content to a reactive variable using v-model or text interpolation."""
|
||||
if self.component_type == 'label':
|
||||
# For labels, use text interpolation (Vue templates don't need .value)
|
||||
self._props['text'] = f"{{{{{reactive_var}}}}}"
|
||||
else:
|
||||
# For other components, try v-model
|
||||
self._props['v-model'] = reactive_var
|
||||
return self
|
||||
|
||||
def bind_value(self, reactive_var: str):
|
||||
"""Bind component value to a reactive variable using v-model."""
|
||||
self._props['v-model'] = reactive_var
|
||||
return self
|
||||
|
||||
def on(self, event_type: str, handler: Union[str, callable]):
|
||||
"""
|
||||
Add an event handler to the component (NiceGUI-style).
|
||||
|
||||
Args:
|
||||
event_type: Event type (e.g., 'click', 'input', 'change')
|
||||
handler: JavaScript function name/expression or Python function (for future server support)
|
||||
|
||||
Usage:
|
||||
button.on('click', 'myJsFunction') # JavaScript function name
|
||||
button.on('click', '() => alert("Hi")') # JavaScript expression
|
||||
button.on('click', 'handleClick') # JavaScript function name
|
||||
input.on('input', 'handleInput') # JavaScript function name
|
||||
"""
|
||||
if callable(handler):
|
||||
# Python handler - for future server-side support
|
||||
# For now, convert to JavaScript function name
|
||||
handler_name = handler.__name__ if hasattr(handler, '__name__') else str(handler)
|
||||
self._props[f'@{event_type}'] = handler_name
|
||||
elif isinstance(handler, str):
|
||||
# JavaScript handler (function name or expression)
|
||||
self._props[f'@{event_type}'] = handler
|
||||
else:
|
||||
raise ValueError("Handler must be a string (JavaScript) or callable (Python function)")
|
||||
|
||||
return self
|
||||
|
||||
def on_click(self, handler: Union[str, callable]):
|
||||
"""Convenience method for click events."""
|
||||
return self.on('click', handler)
|
||||
|
||||
def style(self, *args, **kwargs):
|
||||
"""Set or update inline CSS styles."""
|
||||
if not isinstance(self._style, dict):
|
||||
@@ -284,11 +393,15 @@ class App:
|
||||
component = Component('label', text=text, **kwargs)
|
||||
return self._add_component(component)
|
||||
|
||||
def button(self, text: str, on_click: str = None, **kwargs) -> Component:
|
||||
def button(self, text: str, icon: str = None, color: str = 'primary', **kwargs) -> Component:
|
||||
"""Create a button component."""
|
||||
props = {'label': text, **kwargs}
|
||||
if on_click:
|
||||
props['@click'] = on_click
|
||||
|
||||
if icon:
|
||||
props['icon'] = icon
|
||||
if color:
|
||||
props['color'] = color
|
||||
|
||||
component = Component('button', **props)
|
||||
return self._add_component(component)
|
||||
|
||||
@@ -300,6 +413,23 @@ class App:
|
||||
component = Component('input', **props)
|
||||
return self._add_component(component)
|
||||
|
||||
@contextmanager
|
||||
def div(self, **kwargs):
|
||||
"""Create a div container component."""
|
||||
component = Component('div', **kwargs)
|
||||
component = self._add_component(component)
|
||||
|
||||
# Enter container context
|
||||
page = self._get_current_page()
|
||||
page._container_stack.append(page.current_container)
|
||||
page.current_container = component
|
||||
|
||||
try:
|
||||
yield component
|
||||
finally:
|
||||
# Exit container context
|
||||
page.current_container = page._container_stack.pop()
|
||||
|
||||
def page(self, path: str, name: str = None, title: str = None):
|
||||
"""Create or switch to a page."""
|
||||
if name is None:
|
||||
@@ -325,15 +455,30 @@ class App:
|
||||
return self._add_component(component)
|
||||
|
||||
@contextmanager
|
||||
def card(self, **kwargs):
|
||||
"""Create a card container with shadow and padding."""
|
||||
classes = kwargs.pop('classes', []) # Remove classes from kwargs to avoid conflict
|
||||
def card(self, title: str = None, flat: bool = False, **kwargs):
|
||||
"""
|
||||
Create a simplified card container.
|
||||
|
||||
Args:
|
||||
title: Optional title for the card
|
||||
flat: If True, removes the shadow for a flat appearance
|
||||
**kwargs: Additional properties
|
||||
"""
|
||||
classes = kwargs.pop('classes', [])
|
||||
if isinstance(classes, str):
|
||||
classes = [classes]
|
||||
elif not isinstance(classes, list):
|
||||
classes = []
|
||||
|
||||
component = Component('card', classes=classes, **kwargs)
|
||||
# Add default card styling
|
||||
if flat:
|
||||
classes.append('q-card--flat')
|
||||
|
||||
props = {'classes': classes, **kwargs}
|
||||
if title:
|
||||
props['_card_title'] = title
|
||||
|
||||
component = Component('card', **props)
|
||||
component = self._add_component(component)
|
||||
|
||||
# Enter container context
|
||||
@@ -602,6 +747,218 @@ class App:
|
||||
component = Component('icon', **props)
|
||||
return self._add_component(component)
|
||||
|
||||
# Event Handling and Interactive Components
|
||||
|
||||
def button_with_handler(self, text: str, on_click=None, js_handler: str = None, **kwargs) -> Component:
|
||||
"""Create a button with enhanced event handling."""
|
||||
props = {'label': text, **kwargs}
|
||||
|
||||
# Handle different types of click handlers
|
||||
if on_click:
|
||||
if callable(on_click):
|
||||
# Python function - we'll need to generate a unique handler ID
|
||||
handler_id = f"handler_{id(on_click)}"
|
||||
props['@click'] = f"handleEvent('{handler_id}', $event)"
|
||||
# TODO: Register the Python handler for server communication
|
||||
elif isinstance(on_click, str):
|
||||
if on_click.startswith('js:'):
|
||||
# JavaScript function
|
||||
props['@click'] = on_click[3:] # Remove 'js:' prefix
|
||||
else:
|
||||
# Assume it's a method name or Vue expression
|
||||
props['@click'] = on_click
|
||||
|
||||
if js_handler:
|
||||
# Custom JavaScript handler
|
||||
props['@click'] = js_handler
|
||||
|
||||
component = Component('button', **props)
|
||||
return self._add_component(component)
|
||||
|
||||
@contextmanager
|
||||
def dialog(self, opened: bool = False, persistent: bool = False, **kwargs):
|
||||
"""Create a dialog component (NiceGUI-style)."""
|
||||
classes = kwargs.pop('classes', [])
|
||||
if isinstance(classes, str):
|
||||
classes = [classes]
|
||||
elif not isinstance(classes, list):
|
||||
classes = []
|
||||
|
||||
props = {'v-model': 'dialogOpen', **kwargs}
|
||||
if persistent:
|
||||
props['persistent'] = True
|
||||
|
||||
component = Component('dialog', classes=classes, **props)
|
||||
component = self._add_component(component)
|
||||
|
||||
# Add dialog methods
|
||||
component.opened = opened
|
||||
component.open = lambda: self._dialog_open(component)
|
||||
component.close = lambda: self._dialog_close(component)
|
||||
component.submit = lambda result: self._dialog_submit(component, result)
|
||||
|
||||
# Enter container context
|
||||
page = self._get_current_page()
|
||||
page._container_stack.append(page.current_container)
|
||||
page.current_container = component
|
||||
|
||||
try:
|
||||
yield component
|
||||
finally:
|
||||
# Exit container context
|
||||
page.current_container = page._container_stack.pop()
|
||||
|
||||
def _dialog_open(self, dialog_component):
|
||||
"""Open a dialog - this would be implemented via JS calls in real app."""
|
||||
# For static generation, we'll add the JS methods to the component
|
||||
dialog_component._props['ref'] = f"dialog_{dialog_component.id}"
|
||||
return f"this.$refs.dialog_{dialog_component.id}.show()"
|
||||
|
||||
def _dialog_close(self, dialog_component):
|
||||
"""Close a dialog."""
|
||||
return f"this.$refs.dialog_{dialog_component.id}.hide()"
|
||||
|
||||
def _dialog_submit(self, dialog_component, result):
|
||||
"""Submit a dialog with a result."""
|
||||
return f"this.$refs.dialog_{dialog_component.id}.hide(); this.dialogResult = '{result}'"
|
||||
|
||||
def notify(self, message: str, type_: str = None, position: str = "bottom",
|
||||
close_button: str = None, color: str = None, multiline: bool = False,
|
||||
js_call: bool = True, **kwargs):
|
||||
"""Create a notification (NiceGUI-style)."""
|
||||
# Build notification options
|
||||
notify_options = {
|
||||
'message': message,
|
||||
'position': position,
|
||||
**kwargs
|
||||
}
|
||||
|
||||
if type_:
|
||||
notify_options['type'] = type_
|
||||
if color:
|
||||
notify_options['color'] = color
|
||||
if close_button:
|
||||
notify_options['actions'] = [{'label': close_button, 'color': 'white', 'handler': 'dismiss'}]
|
||||
if multiline:
|
||||
notify_options['multiLine'] = True
|
||||
|
||||
# For static generation, create a notification call
|
||||
if js_call:
|
||||
# Generate JavaScript call for Vue component
|
||||
options_str = json.dumps(notify_options)
|
||||
js_code = f"this.$q.notify({options_str})"
|
||||
|
||||
# Add to current page's notification calls (we'll collect these during build)
|
||||
page = self._get_current_page()
|
||||
if not hasattr(page, '_notifications'):
|
||||
page._notifications = []
|
||||
page._notifications.append(js_code)
|
||||
|
||||
return js_code
|
||||
else:
|
||||
# Return options for manual handling
|
||||
return notify_options
|
||||
|
||||
def script(self, content: Union[str, os.PathLike] = None, src: Union[str, os.PathLike] = None,
|
||||
import_mode: bool = False, **kwargs) -> Component:
|
||||
"""
|
||||
Add custom JavaScript to the page.
|
||||
|
||||
Args:
|
||||
content: JavaScript code as a string
|
||||
src: Path to external JavaScript file
|
||||
import_mode: If True, external files are imported as ES6 modules in the component
|
||||
**kwargs: Additional attributes for the script tag
|
||||
|
||||
Usage:
|
||||
# Inline script
|
||||
bg.script("console.log('Hello from BadGUI!');")
|
||||
|
||||
# External file (traditional script tag)
|
||||
bg.script(src="path/to/script.js")
|
||||
|
||||
# External file (ES6 module import)
|
||||
bg.script(src="path/to/utils.js", import_mode=True)
|
||||
|
||||
# With attributes
|
||||
bg.script(content="...", type="module", defer=True)
|
||||
"""
|
||||
props = {**kwargs}
|
||||
|
||||
if content is not None and src is not None:
|
||||
raise ValueError("Cannot specify both 'content' and 'src' parameters")
|
||||
|
||||
if content is None and src is None:
|
||||
raise ValueError("Must specify either 'content' or 'src' parameter")
|
||||
|
||||
if content is not None:
|
||||
# Inline script content
|
||||
if os.path.isfile(content):
|
||||
# If content looks like a file path, read it
|
||||
try:
|
||||
with open(content, 'r', encoding='utf-8') as f:
|
||||
props['_script_content'] = f.read()
|
||||
props['_is_inline'] = True
|
||||
except (OSError, IOError):
|
||||
# If file reading fails, treat as literal content
|
||||
props['_script_content'] = str(content)
|
||||
props['_is_inline'] = True
|
||||
else:
|
||||
# Literal JavaScript content
|
||||
props['_script_content'] = str(content)
|
||||
props['_is_inline'] = True
|
||||
|
||||
elif src is not None:
|
||||
# External file reference
|
||||
if os.path.isfile(src):
|
||||
# File exists, we'll copy it to the build output
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
file_content = f.read()
|
||||
|
||||
# Store file content and path for build process
|
||||
filename = os.path.basename(src)
|
||||
props['_external_file'] = filename
|
||||
props['_file_content'] = file_content
|
||||
props['_import_mode'] = import_mode
|
||||
|
||||
if import_mode:
|
||||
# For import mode, we'll copy to src/utils/ and import
|
||||
props['_import_path'] = f"../utils/{filename}"
|
||||
else:
|
||||
# Traditional script tag in public/js/
|
||||
props['src'] = f"/js/{filename}"
|
||||
else:
|
||||
# Assume it's a URL or will be provided later
|
||||
props['src'] = str(src)
|
||||
props['_import_mode'] = False
|
||||
|
||||
# Add to current page's script collection
|
||||
page = self._get_current_page()
|
||||
if not hasattr(page, '_scripts'):
|
||||
page._scripts = []
|
||||
|
||||
component = Component('script', **props)
|
||||
page._scripts.append(component)
|
||||
|
||||
# Also add as regular component for template generation
|
||||
return self._add_component(component)
|
||||
|
||||
def on_event(self, component: Component, event_type: str, handler=None, js_handler: str = None, **kwargs):
|
||||
"""Add event handler to a component (NiceGUI-style)."""
|
||||
if handler and callable(handler):
|
||||
# Python handler - generate unique ID and register
|
||||
handler_id = f"handler_{id(handler)}_{event_type}"
|
||||
component._props[f'@{event_type}'] = f"handleEvent('{handler_id}', $event)"
|
||||
# TODO: Store handler for server communication
|
||||
elif js_handler:
|
||||
# JavaScript handler
|
||||
component._props[f'@{event_type}'] = js_handler
|
||||
elif isinstance(handler, str):
|
||||
# String handler (Vue expression or method name)
|
||||
component._props[f'@{event_type}'] = handler
|
||||
|
||||
return component
|
||||
|
||||
def build(self, output_dir: str = "./badgui-output", project_name: str = "badgui-app", **kwargs):
|
||||
"""Build the Vue.js/Quasar project."""
|
||||
from .generator import VueGenerator
|
||||
|
||||
+118
-6
@@ -221,6 +221,9 @@ module.exports = configure(function (ctx) {
|
||||
# Generate the template from page components
|
||||
template_content = self._generate_vue_template_for_page(page)
|
||||
|
||||
# Process scripts for this page
|
||||
script_imports, script_content = self._process_page_scripts(page, output_dir)
|
||||
|
||||
page_content = f'''<template>
|
||||
<q-page class="row items-center justify-evenly">
|
||||
{template_content}
|
||||
@@ -228,15 +231,26 @@ module.exports = configure(function (ctx) {
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {{ ref }} from 'vue'
|
||||
import {{ ref, onMounted, watch }} from 'vue'
|
||||
import {{ useQuasar }} from 'quasar'
|
||||
{script_imports}
|
||||
|
||||
// Component state
|
||||
const state = ref({{}})
|
||||
const $q = useQuasar()
|
||||
|
||||
// Dialog state for Quasar dialogs
|
||||
const dialogOpen = ref(false)
|
||||
|
||||
// Make window object available to template
|
||||
const window = globalThis.window
|
||||
|
||||
// Event handlers
|
||||
const handleClick = (event) => {{
|
||||
console.log('Button clicked:', event)
|
||||
}}
|
||||
|
||||
{script_content}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -263,13 +277,105 @@ const handleClick = (event) => {{
|
||||
"""Generate Vue template from page components."""
|
||||
if not page.components:
|
||||
return " <div>No components defined</div>"
|
||||
|
||||
|
||||
template = ""
|
||||
for component in page.components:
|
||||
template += component.to_vue_template(2) # 2 spaces indent for page content
|
||||
|
||||
|
||||
return template
|
||||
|
||||
def _process_page_scripts(self, page, output_dir: str) -> tuple:
|
||||
"""Process script components for a page and return imports and content."""
|
||||
script_imports = ""
|
||||
script_content = ""
|
||||
|
||||
# Collect Vue refs from all components
|
||||
vue_refs = self._collect_vue_refs(page)
|
||||
if vue_refs:
|
||||
# Create template refs using Vue 3 Composition API
|
||||
for ref_name in vue_refs:
|
||||
script_content += f"\n// Vue ref for component\nconst {ref_name} = ref(null);\n"
|
||||
|
||||
# Check if page has scripts
|
||||
if hasattr(page, '_scripts'):
|
||||
for script_component in page._scripts:
|
||||
props = script_component._props
|
||||
|
||||
if props.get('_is_inline'):
|
||||
# Add inline script content
|
||||
content = props.get('_script_content', '')
|
||||
script_content += f"\n// Inline script\n{content}\n"
|
||||
|
||||
elif props.get('_external_file'):
|
||||
filename = props.get('_external_file')
|
||||
file_content = props.get('_file_content', '')
|
||||
import_mode = props.get('_import_mode', False)
|
||||
|
||||
if import_mode:
|
||||
# Copy to src/utils directory and add import
|
||||
utils_dir = os.path.join(output_dir, 'src', 'utils')
|
||||
os.makedirs(utils_dir, exist_ok=True)
|
||||
|
||||
# Write the external file to utils directory
|
||||
js_file_path = os.path.join(utils_dir, filename)
|
||||
with open(js_file_path, 'w') as f:
|
||||
f.write(file_content)
|
||||
|
||||
# Generate import statement
|
||||
import_path = props.get('_import_path', f"../utils/{filename}")
|
||||
module_name = os.path.splitext(filename)[0] # Remove extension
|
||||
script_imports += f"import * as {module_name} from '{import_path}';\n"
|
||||
script_content += f"\n// Imported module: {filename}\n// Available as: {module_name}\n"
|
||||
else:
|
||||
# Copy external file to public/js directory (traditional way)
|
||||
js_dir = os.path.join(output_dir, 'public', 'js')
|
||||
os.makedirs(js_dir, exist_ok=True)
|
||||
|
||||
# Write the external file
|
||||
js_file_path = os.path.join(js_dir, filename)
|
||||
with open(js_file_path, 'w') as f:
|
||||
f.write(file_content)
|
||||
|
||||
script_content += f"\n// External script: {filename} (loaded via <script> tag)\n"
|
||||
|
||||
return script_imports, script_content
|
||||
|
||||
def _collect_vue_refs(self, page) -> list:
|
||||
"""Collect all Vue ref names from page components."""
|
||||
refs = []
|
||||
|
||||
def collect_refs_recursive(component):
|
||||
if hasattr(component, '_vue_ref') and component._vue_ref:
|
||||
refs.append(component._vue_ref)
|
||||
for child in component.children:
|
||||
collect_refs_recursive(child)
|
||||
|
||||
# Collect from all page components
|
||||
for component in page.components:
|
||||
collect_refs_recursive(component)
|
||||
|
||||
return refs
|
||||
|
||||
def _collect_external_scripts(self) -> list:
|
||||
"""Collect all external script files from all pages."""
|
||||
external_scripts = []
|
||||
|
||||
for path, page in self.app.pages.items():
|
||||
if hasattr(page, '_scripts') and page._scripts:
|
||||
for script_comp in page._scripts:
|
||||
if script_comp._props.get('_external_file'):
|
||||
filename = script_comp._props.get('_external_file')
|
||||
script_src = f"/js/{filename}"
|
||||
if script_src not in external_scripts:
|
||||
external_scripts.append(script_src)
|
||||
elif script_comp._props.get('src') and not script_comp._props.get('_is_inline'):
|
||||
# External URL
|
||||
src = script_comp._props.get('src')
|
||||
if src not in external_scripts:
|
||||
external_scripts.append(src)
|
||||
|
||||
return external_scripts
|
||||
|
||||
def _generate_router(self, output_dir: str):
|
||||
"""Generate Vue Router configuration."""
|
||||
router_content = '''import { route } from 'quasar/wrappers'
|
||||
@@ -416,8 +522,14 @@ $warning : #F2C037;
|
||||
def _copy_static_files(self, output_dir: str):
|
||||
"""Copy static files and create additional necessary files."""
|
||||
|
||||
# Collect all external script files from all pages
|
||||
external_scripts = self._collect_external_scripts()
|
||||
script_tags = ""
|
||||
for script_src in external_scripts:
|
||||
script_tags += f' <script src="{script_src}"></script>\n'
|
||||
|
||||
# Create index.html in root directory
|
||||
index_html = '''<!DOCTYPE html>
|
||||
index_html = f'''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
@@ -425,14 +537,14 @@ $warning : #F2C037;
|
||||
<meta name="description" content="<%= productDescription %>">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.theme && ctx.theme.capacitor || ctx.mode && ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.theme && ctx.theme.capacitor || ctx.mode && ctx.mode.capacitor) {{ %>, viewport-fit=cover<% }} %>">
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
|
||||
<link rel="icon" type="image/ico" href="favicon.ico">
|
||||
</head>
|
||||
{script_tags} </head>
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Button Handler Comparison
|
||||
Shows the evolution from confusing parameters to clean NiceGUI-style .on() method.
|
||||
"""
|
||||
|
||||
import badgui as bg
|
||||
|
||||
with bg.page("/", "ButtonComparison", "Button Handler Comparison"):
|
||||
with bg.column() as main_col:
|
||||
main_col.classes("q-pa-lg q-gutter-md")
|
||||
bg.label("Button Handler Evolution").classes("text-h3 q-mb-lg")
|
||||
|
||||
with bg.card():
|
||||
bg.label("❌ Old Confusing Way (multiple parameters)").classes("text-h5 text-red q-mb-md")
|
||||
bg.label("Don't use these - confusing and redundant:").classes("text-body2 q-mb-sm")
|
||||
|
||||
# These would be the old confusing ways (DON'T USE):
|
||||
# bg.button("Old Way 1", js_handler="myFunction").classes("q-btn-red q-mb-sm")
|
||||
# bg.button("Old Way 2", on_click="myFunction").classes("q-btn-red q-mb-sm")
|
||||
# bg.button("Old Way 3", js_handler="() => alert('hi')").classes("q-btn-red q-mb-sm")
|
||||
|
||||
bg.label("✅ New Clean Way (.on() method)").classes("text-h5 text-green q-mb-md q-mt-lg")
|
||||
bg.label("Much cleaner and follows NiceGUI patterns:").classes("text-body2 q-mb-sm")
|
||||
|
||||
# ✅ NEW CLEAN WAY:
|
||||
bg.button("Function Name").on('click', 'myFunction').classes("q-btn-primary q-mb-sm")
|
||||
bg.button("Inline Expression").on('click', '() => alert("Hello!")').classes("q-btn-secondary q-mb-sm")
|
||||
bg.button("With Notification").on('click', '() => $q.notify("Clicked!")').classes("q-btn-accent q-mb-sm")
|
||||
|
||||
# ✅ CONVENIENCE METHODS:
|
||||
bg.button("On Click").on_click('testClick').classes("q-btn-positive q-mb-sm")
|
||||
bg.button("On Input").on('input', 'handleInput').classes("q-btn-info q-mb-sm")
|
||||
|
||||
# ✅ CHAINING WITH STYLING:
|
||||
bg.button("Save", icon="save", color="green").on_click('saveData').classes("q-mb-sm")
|
||||
bg.button("Delete", icon="delete", color="red").on_click('deleteData').classes("q-mb-sm")
|
||||
|
||||
# JavaScript handlers
|
||||
bg.script("""
|
||||
const myFunction = () => {
|
||||
console.log('myFunction called!');
|
||||
$q.notify('Function name handler works!');
|
||||
};
|
||||
|
||||
const testClick = () => {
|
||||
console.log('testClick called!');
|
||||
$q.notify('on_click convenience method works!');
|
||||
};
|
||||
|
||||
const handleInput = () => {
|
||||
console.log('handleInput called!');
|
||||
$q.notify('Input handler works!');
|
||||
};
|
||||
|
||||
const saveData = () => {
|
||||
$q.notify({
|
||||
message: 'Data saved!',
|
||||
color: 'green',
|
||||
icon: 'check'
|
||||
});
|
||||
};
|
||||
|
||||
const deleteData = () => {
|
||||
$q.notify({
|
||||
message: 'Data deleted!',
|
||||
color: 'red',
|
||||
icon: 'warning'
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('Clean .on() method pattern loaded!');
|
||||
});
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Button Handler Comparison")
|
||||
print("Shows the clean .on() method vs old confusing parameters")
|
||||
|
||||
# Run dev server
|
||||
bg.dev(port=3027)
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Simple Button Test
|
||||
Testing the new .on() method pattern (like NiceGUI).
|
||||
"""
|
||||
|
||||
import badgui as bg
|
||||
|
||||
with bg.page("/", "ButtonTest", "Button Test"):
|
||||
with bg.column() as main_col:
|
||||
main_col.classes("q-pa-lg q-gutter-md")
|
||||
bg.label("Button .on() Method Test").classes("text-h3 q-mb-lg")
|
||||
|
||||
with bg.card():
|
||||
bg.label("Test the new .on() method pattern").classes("text-h5 q-mb-md")
|
||||
|
||||
# Using .on('click') method (NiceGUI style) - much cleaner!
|
||||
bg.button("Click Handler").on('click', 'testClick').classes("q-btn-primary q-mb-sm")
|
||||
bg.button("Alert Button").on('click', '() => window.alert("Hello!")').classes("q-btn-secondary q-mb-sm")
|
||||
bg.button("Notify Button").on('click', '() => $q.notify("Hello from Quasar!")').classes("q-btn-accent q-mb-sm")
|
||||
|
||||
# Using .on_click() convenience method
|
||||
bg.button("On Click").on_click('testOnClick').classes("q-btn-positive q-mb-sm")
|
||||
|
||||
# Buttons with icons and colors
|
||||
bg.button("Save", icon="save", color="green").on_click('saveData').classes("q-mb-sm")
|
||||
bg.button("Delete", icon="delete", color="red").on_click('deleteData').classes("q-mb-sm")
|
||||
|
||||
# JavaScript handlers
|
||||
bg.script("""
|
||||
const testClick = () => {
|
||||
console.log('Click handler called!');
|
||||
$q.notify({
|
||||
message: 'Click handler executed!',
|
||||
color: 'positive',
|
||||
position: 'top'
|
||||
});
|
||||
};
|
||||
|
||||
const testOnClick = () => {
|
||||
console.log('On Click handler called!');
|
||||
$q.notify({
|
||||
message: 'On Click method executed!',
|
||||
color: 'info',
|
||||
position: 'top-right'
|
||||
});
|
||||
};
|
||||
|
||||
const saveData = () => {
|
||||
console.log('Save data called!');
|
||||
$q.notify({
|
||||
message: 'Data saved successfully!',
|
||||
color: 'green',
|
||||
icon: 'check',
|
||||
position: 'center'
|
||||
});
|
||||
};
|
||||
|
||||
const deleteData = () => {
|
||||
console.log('Delete data called!');
|
||||
$q.notify({
|
||||
message: 'Data deleted!',
|
||||
color: 'red',
|
||||
icon: 'warning',
|
||||
position: 'bottom'
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('Button test with .on() method mounted!');
|
||||
});
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Simple Button Test")
|
||||
print("Testing unified button() method")
|
||||
|
||||
# Run dev server
|
||||
bg.dev(port=3025)
|
||||
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Simplified Card Test
|
||||
Demonstrates the new simplified card component usage.
|
||||
"""
|
||||
|
||||
import badgui as bg
|
||||
|
||||
with bg.page("/", "SimplifiedCards", "Simplified Card Test"):
|
||||
with bg.column() as main_col:
|
||||
main_col.classes("q-pa-lg q-gutter-md")
|
||||
bg.label("Simplified Card Examples").classes("text-h3 q-mb-lg")
|
||||
|
||||
# 1. Simple card with title (automatic sections)
|
||||
with bg.card(title="User Information"):
|
||||
bg.label("This card automatically gets a title section and content section!")
|
||||
name_input = bg.input("Enter your name")
|
||||
name_input.classes("q-mb-md")
|
||||
bg.button_with_handler("Submit", js_handler="handleSubmit").classes("q-btn-primary")
|
||||
|
||||
# 2. Simple card without title (just a container)
|
||||
with bg.card():
|
||||
bg.label("Simple Card Content").classes("text-h5 q-mb-md")
|
||||
bg.label("This card works like a simple container with card styling.")
|
||||
bg.label("No need for card_section or card_actions!")
|
||||
|
||||
with bg.row():
|
||||
bg.button_with_handler("Action 1", js_handler="action1").classes("q-btn-primary q-mr-sm")
|
||||
bg.button_with_handler("Action 2", js_handler="action2").classes("q-btn-secondary")
|
||||
|
||||
# 3. Flat card (no shadow)
|
||||
with bg.card(title="Settings", flat=True):
|
||||
bg.label("This is a flat card with no shadow").classes("q-mb-md")
|
||||
|
||||
with bg.row():
|
||||
bg.label("Dark Mode:")
|
||||
# You could add a switch here
|
||||
bg.button_with_handler("Toggle", js_handler="toggleDarkMode").classes("q-btn-outline")
|
||||
|
||||
# 4. Card with custom classes
|
||||
with bg.card(title="Custom Styled Card") as custom_card:
|
||||
custom_card.classes("bg-primary text-white")
|
||||
|
||||
bg.label("This card has custom background and text color")
|
||||
bg.button_with_handler("White Button", js_handler="whiteButton").classes("q-btn text-primary bg-white")
|
||||
|
||||
# Simple JavaScript handlers
|
||||
bg.script("""
|
||||
const handleSubmit = () => {
|
||||
console.log('Submit clicked');
|
||||
};
|
||||
|
||||
const action1 = () => {
|
||||
console.log('Action 1 clicked');
|
||||
};
|
||||
|
||||
const action2 = () => {
|
||||
console.log('Action 2 clicked');
|
||||
};
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
console.log('Dark mode toggled');
|
||||
};
|
||||
|
||||
const whiteButton = () => {
|
||||
console.log('White button clicked');
|
||||
};
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Simplified Card Test")
|
||||
print("Testing the new simplified card component")
|
||||
|
||||
# Build and run
|
||||
bg.dev(port=3022)
|
||||
@@ -0,0 +1,37 @@
|
||||
// Reactive data - these automatically sync with v-model bound components
|
||||
const statusMessage = ref('Dialog is closed');
|
||||
const greetingMessage = ref('Enter your name below');
|
||||
const userName = ref('');
|
||||
|
||||
const openDialog = () => {
|
||||
console.log('Opening dialog...');
|
||||
dialogOpen.value = true;
|
||||
statusMessage.value = 'Dialog is open';
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
console.log('Closing dialog...');
|
||||
dialogOpen.value = false;
|
||||
statusMessage.value = 'Dialog is closed';
|
||||
greetingMessage.value = 'Enter your name below'; // Reset greeting
|
||||
userName.value = ''; // Clear input
|
||||
};
|
||||
|
||||
const sayHello = () => {
|
||||
const name = userName.value || 'Anonymous';
|
||||
console.log('User name from v-model:', name);
|
||||
greetingMessage.value = `Hello, ${name}! Nice to meet you!`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('Component mounted!');
|
||||
console.log('Reactive variables initialized:');
|
||||
console.log('- statusMessage:', statusMessage.value);
|
||||
console.log('- greetingMessage:', greetingMessage.value);
|
||||
console.log('- userName:', userName.value);
|
||||
|
||||
// Watch for changes to demonstrate reactivity
|
||||
watch(userName, (newValue) => {
|
||||
console.log('userName changed to:', newValue);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
V-Model Vue Ref Dialog Test
|
||||
Uses v-model for reactive two-way data binding instead of DOM manipulation.
|
||||
"""
|
||||
|
||||
import badgui as bg
|
||||
|
||||
with bg.page("/", "VModelDialog", "V-Model Dialog Test"):
|
||||
with bg.column() as main_col:
|
||||
main_col.classes("q-pa-lg q-gutter-md")
|
||||
bg.label("V-Model Vue Ref Dialog Test").classes("text-h3 q-mb-lg")
|
||||
|
||||
# Simple dialog with simplified card
|
||||
with bg.dialog() as my_dialog:
|
||||
with bg.card(title="Hello from Dialog!"):
|
||||
# Use v-model for reactive binding instead of Vue ref
|
||||
greeting_label = bg.label("Loading...")
|
||||
greeting_label.bind_text("greetingMessage") # Bind to reactive variable
|
||||
|
||||
# Input with v-model binding
|
||||
name_input = bg.input("Your name")
|
||||
name_input.bind_value("userName") # Bind to reactive variable
|
||||
name_input.classes("q-mb-md")
|
||||
|
||||
# Actions can go directly in the card
|
||||
with bg.row():
|
||||
bg.button("Say Hello").on_click("sayHello").classes("q-btn-primary q-mr-sm")
|
||||
bg.button("Close").on_click("closeDialog").classes("q-btn-secondary")
|
||||
|
||||
my_dialog.vue_ref("myDialog")
|
||||
|
||||
# Main content - use reactive binding for status
|
||||
status_label = bg.label("Loading...")
|
||||
status_label.bind_text("statusMessage") # Bind to reactive variable
|
||||
status_label.classes("text-h6 q-mb-md")
|
||||
|
||||
bg.button("Open Dialog").on_click("openDialog").classes("q-btn-primary")
|
||||
|
||||
# JavaScript functions - V-Model version with reactive variables
|
||||
bg.script("vmodel.js")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 V-Model Vue Ref Dialog Test")
|
||||
print("Testing v-model reactive data binding instead of DOM manipulation")
|
||||
|
||||
# Build to examine output
|
||||
#bg.app.build("./vmodel-vue-ref-output", "vmodel-vue-ref")
|
||||
|
||||
# Run dev server
|
||||
bg.dev(port=3019)
|
||||
Reference in New Issue
Block a user