# WDynamic `WDynamic` renders a Flutter widget tree from a JSON/Map configuration at runtime, with built-in action handling and form state management. - [Basic Usage](#basic-usage) - [Constructor](#constructor) - [Props](#props) - [JSON Schema](#json-schema) - [Action Handling](#action-handling) - [State Management](#state-management) - [Custom Builders](#custom-builders) - [Security](#security) - [Error Handling](#error-handling) - [Related Documentation](#related-documentation) ```dart WDynamic( json: const { 'type': 'WDiv', 'props': { 'className': 'p-6 bg-white rounded-xl shadow-sm' }, 'children': [ { 'type': 'WText', 'props': { 'text': 'Hello from JSON!', 'className': 'text-xl font-bold text-gray-800' } }, ], }, ) ``` ## Basic Usage The `WDynamic` widget converts JSON/Map structures into live Flutter widgets. This enables server-driven UI, dynamic form generation, and runtime-configurable layouts without rebuilding your app. ```dart WDynamic( json: const { 'type': 'WDiv', 'props': {'className': 'flex gap-4 p-4'}, 'children': [ { 'type': 'WButton', 'props': { 'className': 'bg-blue-500 text-white px-4 py-2 rounded', 'onTap': {'action': 'submit'}, }, 'children': [ {'type': 'WText', 'props': {'text': 'Submit'}} ], } ], }, actions: { 'submit': (args) => print('Button tapped'), }, ) ``` ## Constructor ```dart const WDynamic({ Key? key, required Map json, Map actions = const {}, WDynamicController? controller, Set? denyWidgets, Map? builders, Map? customIcons, int maxDepth = 50, Widget Function(String type, Object error)? onError, Widget Function(String type, Map props)? onUnknownWidget, }) ``` ## Props | Prop | Type | Default | Description | |:-----|:-----|:--------|:------------| | `json` | `Map` | **Required** | JSON configuration defining the widget tree. Must include `type`, optional `props` and `children`. | | `actions` | `Map` | `{}` | Action handlers keyed by name. Called when interactive widgets trigger events. | | `controller` | `WDynamicController?` | `null` | Optional controller for external state access (read/write form values). | | `denyWidgets` | `Set?` | `null` | Widget types to remove from the default whitelist. | | `builders` | `Map?` | `null` | Custom widget builders keyed by type name. Signature: `Widget Function(Map props, List children)`. | | `customIcons` | `Map?` | `null` | Custom icon mappings for `WIcon` widgets. Keys are icon names used in the JSON `icon` prop, values are `IconData` instances. Extends the default Material icon map. | | `maxDepth` | `int` | `50` | Maximum recursion depth for nested widgets. Prevents infinite loops. | | `onError` | `Widget Function(String, Object)?` | `null` | Error handler for widget build failures. Returns a fallback widget. | | `onUnknownWidget` | `Widget Function(String, Map)?` | `null` | Handler for unknown or denied widget types. Returns a fallback widget. | ## JSON Schema Every widget node in the JSON structure follows this format: ```dart { 'type': 'WidgetType', // Required: Wind widget, Flutter widget, or custom builder 'props': { // Optional: properties passed to the widget 'className': 'flex gap-4', 'text': 'Hello', 'onTap': {'action': 'handleTap', 'args': {'id': 1}}, 'id': 'username', // Auto-tracked form state key }, 'children': [ // Optional: nested widget array { /* child node */ } ] } ``` ### Type Field The `type` field determines which widget to render: - **Wind Widgets**: `WDiv`, `WText`, `WButton`, `WInput`, `WCheckbox`, `WSelect`, `WDatePicker`, `WIcon`, `WImage`, `WSvg`, `WPopover`, `WAnchor`, `WSpacer` - **Flutter Widgets**: `Column`, `Row`, `Center`, `SizedBox`, `Expanded`, `Container`, `Wrap`, `Stack`, `Positioned`, `Padding`, `Align`, `Opacity`, `AspectRatio`, `FittedBox`, `ClipRRect`, `Spacer` - **Custom Widgets**: Any key from the `builders` map ### Props Field Widget properties are passed directly to the constructor. Common props include: ```dart { 'type': 'WDiv', 'props': { 'className': 'flex gap-4 p-6 bg-white rounded-xl', // Wind utilities 'id': 'formContainer', // Auto-tracked state key } } ``` ```dart { 'type': 'WText', 'props': { 'text': 'Welcome!', // Text content 'className': 'text-lg font-bold', // Wind utilities } } ``` ```dart { 'type': 'WInput', 'props': { 'id': 'email', // Auto-tracked in controller state 'placeholder': 'Enter email...', 'className': 'border rounded-lg', } } ``` ### Children Field An array of nested widget nodes. Used by layout widgets like `WDiv`, `Column`, `Row`, etc. ```dart { 'type': 'WDiv', 'props': {'className': 'flex gap-2'}, 'children': [ {'type': 'WText', 'props': {'text': 'First'}}, {'type': 'WText', 'props': {'text': 'Second'}}, ] } ``` ## Action Handling Interactive widgets like `WButton` can trigger actions defined in the `actions` map. Actions are referenced in the JSON using an object format. ### Action Format ```dart { 'type': 'WButton', 'props': { 'onTap': { 'action': 'actionName', // Required: key from actions map 'args': {'key': 'value'} // Optional: arguments passed to handler } } } ``` ### Action Signatures Actions can accept arguments only, or both arguments and state: ```dart // Simple: (Map args) WDynamic( actions: { 'showAlert': (Map args) { print('Alert: ${args['message']}'); }, }, ) ``` ```dart // With state access: (Map args, WDynamicState state) WDynamic( actions: { 'submitForm': (Map args, WDynamicState state) { final email = state.get('email'); final password = state.get('password'); print('Submitting: $email'); }, }, ) ``` ### Example ```dart WDynamic( json: const { 'type': 'WButton', 'props': { 'className': 'bg-blue-500 text-white px-4 py-2 rounded', 'onTap': { 'action': 'increment', 'args': {'step': 1} }, }, 'children': [ {'type': 'WText', 'props': {'text': 'Tap Me'}} ], }, actions: { 'increment': (Map args, WDynamicState state) { final step = args['step'] ?? 1; setState(() => count += step); }, }, ) ``` ## State Management `WDynamic` automatically tracks form widget values using the `id` prop. Values are stored in an internal state object accessible via `WDynamicController` or action handlers. ### Auto-Tracking with ID Any widget with an `id` prop automatically stores its value in state: ```dart { 'type': 'WInput', 'props': { 'id': 'username', // Auto-tracked 'placeholder': 'Username...', } } ``` ### Using the Controller Create a `WDynamicController` to access state externally: ```dart final controller = WDynamicController(); // Pre-fill values controller.setValue('email', 'user@example.com'); // Read values final email = controller.getValue('email'); final allValues = controller.getAll(); // Listen for changes final dispose = controller.addListener('email', (value) { print('Email changed: $value'); }); // Pass to WDynamic WDynamic( controller: controller, json: myJson, ) // Clean up controller.dispose(); ``` ### Accessing State in Actions Action handlers receive the state object as a second parameter: ```dart WDynamic( json: const { 'type': 'WDiv', 'children': [ { 'type': 'WInput', 'props': {'id': 'name', 'placeholder': 'Your name...'} }, { 'type': 'WButton', 'props': { 'onTap': {'action': 'greet'}, 'className': 'bg-green-500 text-white px-4 py-2 rounded', }, 'children': [ {'type': 'WText', 'props': {'text': 'Greet'}} ], } ], }, actions: { 'greet': (args, state) { final name = state.get('name') ?? 'World'; print('Hello, $name!'); }, }, ) ``` ## Custom Builders Extend `WDynamic` with custom widget types using the `builders` prop. ### WWidgetBuilder Signature ```dart typedef WWidgetBuilder = Widget Function( Map props, List children, ); ``` ### Example ```dart WDynamic( json: const { 'type': 'InfoCard', 'props': { 'title': 'Custom Widget', 'subtitle': 'Built with a custom builder' }, }, builders: { 'InfoCard': (Map props, List children) { return WDiv( className: 'p-4 bg-blue-50 rounded-xl border border-blue-200', child: WDiv( className: 'flex items-center gap-3', children: [ WIcon(Icons.info, className: 'text-blue-500 text-2xl'), WDiv( className: 'flex flex-col', children: [ WText(props['title'] ?? '', className: 'font-bold text-blue-800'), WText(props['subtitle'] ?? '', className: 'text-sm text-blue-600'), ], ), ], ), ); }, }, ) ``` Custom builders have priority over default widgets. You can override built-in widgets by providing a builder with the same type name. ### Custom Icons Map string icon names to `IconData` for use in JSON `WIcon` widget nodes. Custom icons extend (not replace) the built-in Material icon map. ```dart WDynamic( json: const { 'type': 'WDiv', 'props': {'className': 'flex gap-4 items-center'}, 'children': [ { 'type': 'WIcon', 'props': {'icon': 'heart', 'className': 'text-red-500 text-2xl'}, }, { 'type': 'WIcon', 'props': {'icon': 'star', 'className': 'text-yellow-500 text-2xl'}, }, ], }, customIcons: { 'heart': Icons.favorite, 'star': Icons.star, 'settings': Icons.settings, }, ) ``` ## Security `WDynamic` uses a whitelist approach for security. Only explicitly allowed widget types can be rendered. ### Default Whitelist **Wind Widgets**: - `WDiv`, `WText`, `WButton`, `WImage`, `WIcon`, `WAnchor`, `WInput`, `WCheckbox`, `WSvg`, `WSelect`, `WPopover`, `WDatePicker`, `WSpacer` **Flutter Widgets**: - `Column`, `Row`, `Center`, `SizedBox`, `Expanded`, `Container`, `Wrap`, `Stack`, `Positioned`, `Padding`, `Align`, `Opacity`, `AspectRatio`, `FittedBox`, `ClipRRect`, `Spacer` ### Denying Widgets Remove specific widget types from the whitelist: ```dart WDynamic( json: myJson, denyWidgets: const {'WButton', 'WInput'}, // Block buttons and inputs ) ``` ### Unknown Widget Handler Provide a fallback for unknown or denied widget types: ```dart WDynamic( json: myJson, onUnknownWidget: (String type, Map props) { return WDiv( className: 'p-2 bg-red-100 border border-red-300 rounded', child: WText('Unknown widget: $type', className: 'text-red-700 text-sm'), ); }, ) ``` ## Error Handling Handle widget build failures gracefully with the `onError` callback. ```dart WDynamic( json: myJson, onError: (String type, Object error) { return WDiv( className: 'p-4 bg-yellow-50 border border-yellow-300 rounded', child: WDiv( className: 'flex flex-col gap-2', children: [ WText('Error rendering $type', className: 'font-bold text-yellow-800'), WText(error.toString(), className: 'text-sm text-yellow-700'), ], ), ); }, ) ``` ### Max Depth Protection Prevent infinite recursion with the `maxDepth` prop (default: 50): ```dart WDynamic( json: deeplyNestedJson, maxDepth: 100, // Allow deeper nesting ) ``` When max depth is exceeded, rendering stops and returns an empty `SizedBox.shrink()` or calls `onError` if provided. ## Related Documentation - [WDiv](./w-div.md) - Primary layout container - [WButton](./w-button.md) - Interactive button widget - [WInput](./w-input.md) - Text input widget - [WSelect](./w-select.md) - Dropdown select widget - [State Management](../core-concepts/state-management.md) - State handling in Wind - [Dynamic Rendering](../core-concepts/dynamic-rendering.md) - Server-driven UI concepts