]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
meta_tool generates correct list, edit, create and delete customer files
authorHamatoma <author.hamatoma.de>
Tue, 12 Oct 2021 20:22:58 +0000 (22:22 +0200)
committerHamatoma <author.hamatoma.de>
Tue, 12 Oct 2021 20:22:58 +0000 (22:22 +0200)
21 files changed:
lib/meta/module_meta_data.dart
lib/meta/modules.dart
lib/meta/roles_meta.dart
lib/meta/structures_meta.dart
lib/meta/users_meta.dart
lib/page/roles/create_role_page.dart
lib/page/roles/edit_role_page.dart
lib/page/roles/list_role_page.dart
lib/page/structures/create_structure_page.dart
lib/page/structures/delete_structure_page.dart
lib/page/structures/edit_structure_page.dart
lib/page/structures/list_structure_page.dart
lib/page/users/create_user_custom.dart
lib/page/users/create_user_page.dart
lib/page/users/delete_user_custom.dart
lib/page/users/delete_user_page.dart
lib/page/users/edit_user_custom.dart
lib/page/users/edit_user_page.dart
lib/page/users/list_user_custom.dart
lib/page/users/list_user_page.dart
metatool/bin/page_generator.dart

index b7a105d8271f97d5492a82ffca0f697f87f8177c..c6a237f01c2e7abea56649db3a38f7399447c14f 100644 (file)
@@ -12,6 +12,11 @@ class ButtonMetaData extends WidgetMetaData {
   }
 }
 
+class CopyDbFields extends FieldMetaData {
+  CopyDbFields(String name, {String options = ''})
+      : super(name, options, dataType: DataType.undefined);
+}
+
 /// Describes a field related to a database column.
 class DbFieldMetaData extends WidgetMetaData {
   PropertyMetaData? reference;
@@ -22,10 +27,10 @@ class DbFieldMetaData extends WidgetMetaData {
 }
 
 enum DisplayType {
-  text,
-  combobox,
   checkbox,
+  combobox,
   custom,
+  text,
 }
 
 class DummyModule extends ModuleMetaData {
@@ -42,6 +47,19 @@ class FieldMetaData extends WidgetMetaData {
       : super(name, WidgetType.field);
 }
 
+class ListPageMetaData extends PageMetaData {
+  final String tableHeaders;
+  final String tableColumns;
+  ListPageMetaData(String label,
+      {String name = '',
+      required List<WidgetMetaData> fields,
+      required this.tableColumns,
+      required this.tableHeaders,
+      String globalComboBoxes = ''})
+      : super(label, PageType.list,
+            name: name, fields: fields, globalComboBoxes: globalComboBoxes);
+}
+
 class MetaException extends FormatException {}
 
 /// Stores the meta data of a module.
@@ -207,7 +225,9 @@ class ModuleMetaData {
   ///
   /// Override it if needed.
   void onInitialized() {
-    // do nothing
+    for (var page in this.pageList){
+      page.onInitialized();
+    }
   }
 
   /// Returns the meta data of a page given by its [name] or null if missing.
@@ -247,21 +267,51 @@ class PageMetaData {
   final String label;
   final PageType pageType;
   ModuleMetaData module = DummyModule();
-  final List<FieldMetaData>? field = [];
-  PageMetaData(this.label, this.pageType, {String name = ''}) {
+  final List<WidgetMetaData> fields;
+  final String globalComboBoxes;
+  PageMetaData(this.label, this.pageType,
+      {String name = '', required this.fields, this.globalComboBoxes = ''}) {
     if (name.isEmpty) {
       name = enumToString(pageType);
     }
     this.name = name;
   }
+  void onInitialized(){
+    var newFields = <WidgetMetaData>[];
+    var toDelete = <WidgetMetaData>[];
+    for (var field in fields){
+      if (field is CopyDbFields){
+        String excluded = '';
+        final start = field.options.indexOf('excluded=');
+        if (start >= 0){
+          var end = field.options.indexOf(':', start);
+          if (end < 0){
+            end = field.options.length;
+          }
+          excluded = ' ' + field.options.substring(start, end) + ' ';
+        }
+        for (var property in module.propertyList){
+          if (excluded.contains(" ${property.name} ")){
+            continue;
+          }
+          if (! property.hasOption(':hidden:')) {
+            newFields.add(property);
+          }
+        }
+        toDelete.add(field);
+      }
+    }
+    for (var field in toDelete){
+      fields.remove(field);
+    }
+    fields.addAll(newFields);
+  }
 }
 
 enum PageType { create, custom, delete, edit, list }
 
 /// Stores the meta data of a module property stored as column in a database.
-class PropertyMetaData {
-  /// Name of the property (Dart name).
-  final String name;
+class PropertyMetaData extends WidgetMetaData {
   ModuleMetaData module = DummyModule();
   final String label;
 
@@ -277,16 +327,18 @@ class PropertyMetaData {
   /// The size if dataType is DataType.string.
   final int size;
   String columnName;
-
+  var validators = <String>[];
   /// The foreign key if dataType is DataType.reference, e.g. 'users.user_id'
   String? foreignKey;
-  PropertyMetaData(this.name, this.label, this.dataType, this.options,
+  PropertyMetaData(String name, this.label, this.dataType, this.options,
       {this.displayType = DisplayType.text,
       this.columnName = '',
       this.size = 0,
       this.foreignKey,
-      int weight = 6}) {
+      int weight = 6, List<String>? validators})
+      : super(name, WidgetType.property) {
     this.weight = weight > 12 ? 12 : weight;
+    this.validators = validators ?? [];
   }
 
   /// Returns whether a given [option] is set.
@@ -307,4 +359,4 @@ class WidgetMetaData {
   WidgetMetaData(this.name, this.widgetType);
 }
 
-enum WidgetType { button, field, dbField }
+enum WidgetType { button, field, dbField, property }
index 2ff718cbe1e7dd5b623c7dfe4e2c30058e8dda99..5d0d3ed1c17aa985f5c79ee47caf6afd36796783 100644 (file)
@@ -3,7 +3,6 @@ import 'module_meta_data.dart';
 import 'roles_meta.dart';
 import 'structures_meta.dart';
 import 'users_meta.dart';
-
 /// Returns the meta data of the module given by [name].
 /// Returns null if not found.
 ModuleMetaData? moduleByName(String name) {
@@ -23,9 +22,8 @@ ModuleMetaData? moduleByName(String name) {
   }
   return rc;
 }
-
 /// Returns the module names as string list.
-List<String> moduleNames() {
+List<String> moduleNames(){
   return [
     'Roles',
     'Structures',
index 7e1225abd6674cf97486d4bfe7619cca8670b4de..e91e3fc6b5c7f95d4d8a9e5e160fb6a9f1996a6e 100644 (file)
@@ -28,10 +28,23 @@ class RoleMeta extends ModuleMetaData {
               'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:',
               size: 32),
         ], [
-          PageMetaData('New Role', PageType.create),
-          PageMetaData('Change Role', PageType.edit),
-          PageMetaData('Roles Overview', PageType.list),
+          PageMetaData('New Role', PageType.create,
+              fields: [CopyDbFields('filters')]),
+          PageMetaData('Change Role', PageType.edit,
+              fields: [CopyDbFields('filters')]),
+          ListPageMetaData(
+            'Roles Overview',
+            fields: [
+              PropertyMetaData('text', i18n.tr('Text'), DataType.string,
+                  ':where=!text IS NONE OR (role_name like !text):',
+                  size: 64),
+            ],
+            tableColumns: 'role_id;role_name',
+            tableHeaders: i18n.tr('Id;Name'),
+          ),
         ]);
   @override
-  void onInitialized() {}
+  void onInitialized() {
+    super.onInitialized();
+  }
 }
index 8f0f5e74ddc5efa85f0d37f57d02315ba302a568..7bfccdc0da4a99167ade466dce98590287e5877a 100644 (file)
@@ -36,9 +36,26 @@ class StructureMeta extends ModuleMetaData {
               'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:',
               size: 32),
         ], [
-          PageMetaData('New Structure', PageType.create),
-          PageMetaData('Change Structure', PageType.edit),
-          PageMetaData('Delete Structure', PageType.delete),
-          PageMetaData('Structures Overview', PageType.list),
+          PageMetaData('New Structure', PageType.create,
+              fields: [CopyDbFields('filters')]),
+          PageMetaData('Change Structure', PageType.edit,
+              fields: [CopyDbFields('filters')]),
+          PageMetaData('Delete Structure', PageType.delete,
+              fields: [CopyDbFields('filters')]),
+          ListPageMetaData(
+            'Structures Overview',
+            fields: [
+              PropertyMetaData('text', i18n.tr('Text'), DataType.string,
+                  ':where=!text IS NONE OR structure_name like !text OR structure_value like !text:',
+                  size: 64),
+            ],
+            tableColumns:
+                'structure_id;structure_scope;structure_name;structure_value;structure_position',
+            tableHeaders: i18n.tr('Id;Scope;Name;Value;Position'),
+          ),
         ]);
+  @override
+  void onInitialized() {
+    super.onInitialized();
+  }
 }
index 0eba0ff48468d4191ccf6f3e83c238f8be9b1532..9b396d7b20eb457f20ded0aa244769a4660a3d18 100644 (file)
@@ -22,7 +22,7 @@ class UserMeta extends ModuleMetaData {
               size: 32),
           PropertyMetaData(
               'email', i18n.tr('EMail', M), DataType.string, ':unique:notnull:',
-              size: 255),
+              size: 255, validators: ['isEmail(input)']),
           PropertyMetaData(
               'role', i18n.tr('Role'), DataType.reference, ':notnull:',
               displayType: DisplayType.combobox,
@@ -38,9 +38,40 @@ class UserMeta extends ModuleMetaData {
               'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:',
               size: 32),
         ], [
-          PageMetaData('New User', PageType.create),
-          PageMetaData('Change User', PageType.edit),
-          PageMetaData('Delete User', PageType.delete),
-          PageMetaData('Users Overview', PageType.list),
+          PageMetaData(
+            'New User',
+            PageType.create,
+            fields: [CopyDbFields('filters')],
+            globalComboBoxes: 'comboRoles',
+          ),
+          PageMetaData(
+            'Change User',
+            PageType.edit,
+            fields: [CopyDbFields('filters')],
+            globalComboBoxes: 'comboRoles',
+          ),
+          PageMetaData(
+            'Delete User',
+            PageType.delete,
+            fields: [CopyDbFields('filters')],
+            globalComboBoxes: 'comboRoles',
+          ),
+          ListPageMetaData(
+            'Users Overview',
+            fields: [
+              PropertyMetaData('text', i18n.tr('Text'), DataType.string, '',
+                  size: 64),
+              PropertyMetaData('role', i18n.tr('Role'), DataType.reference, '',
+                  displayType: DisplayType.combobox,
+                  foreignKey: 'roles.role_id;role_name;role'),
+            ],
+            tableColumns: 'user_id;user_displayname;user_email;role',
+            tableHeaders: i18n.tr('Id;Display Name;Email;Role'),
+            globalComboBoxes: 'comboRoles',
+          ),
         ]);
+  @override
+  void onInitialized() {
+    super.onInitialized();
+  }
 }
index 2cd8530ff6ee1cb07d75cb0e9c8dbfc5e5abf649..4ec6c88d247f4f8f2e59f42a4159ecb4d1dbb6e0 100644 (file)
@@ -20,7 +20,7 @@ class CreateRolePage extends StatefulWidget {
 }
 
 class _CreateRolePageState extends CreateRoleCustom {
-  _CreateRolePageState() : super();
+  _CreateRolePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 6c32f014345b856ae772492995f4f13872267312..ef15ac45d8661adb74e1558911f0d05741b1b6b9 100644 (file)
@@ -21,7 +21,7 @@ class EditRolePage extends StatefulWidget {
 }
 
 class _EditRolePageState extends EditRoleCustom {
-  _EditRolePageState(int primaryKey) : super(primaryKey);
+  _EditRolePageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 0915e10602594259aefa4ae513f0e1c5d86433d2..b42f052403af7c5e5350e744c837b61cbd7126b8 100644 (file)
@@ -20,7 +20,7 @@ class ListRolePage extends StatefulWidget {
 }
 
 class _ListRolePageState extends ListRoleCustom {
-  _ListRolePageState() : super();
+  _ListRolePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index e27c01ef4d389921765ffa79c6ec298b77d72a38..88aa0fbe5e25be84fab24c4989cfcb8ab4c49d92 100644 (file)
@@ -12,15 +12,15 @@ class CreateStructurePage extends StatefulWidget {
   @override
   _CreateStructurePageState createState() {
     final rc = _CreateStructurePageState();
-    rc.attendedPage = AttendedPage(
-        this, rc, GlobalData(), StructureMeta.instance, pageStates);
+    rc.attendedPage =
+        AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _CreateStructurePageState extends CreateStructureCustom {
-  _CreateStructurePageState() : super();
+  _CreateStructurePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index cc888a8d54a22aeed3b844d899b1fcdfd35390d2..2bdedf31d7acb9ce08674906ee7eb17e63100090 100644 (file)
@@ -13,15 +13,15 @@ class DeleteStructurePage extends StatefulWidget {
   @override
   _DeleteStructurePageState createState() {
     final rc = _DeleteStructurePageState(this.primaryKey);
-    rc.attendedPage = AttendedPage(
-        this, rc, GlobalData(), StructureMeta.instance, pageStates);
+    rc.attendedPage =
+        AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _DeleteStructurePageState extends DeleteStructureCustom {
-  _DeleteStructurePageState(int primaryKey) : super(primaryKey);
+  _DeleteStructurePageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index a689d34c69fec6ed85983cb17934339c8cad2bd2..c151f661d5bdbd2569f26da42d8f1c67298cc1af 100644 (file)
@@ -13,15 +13,15 @@ class EditStructurePage extends StatefulWidget {
   @override
   _EditStructurePageState createState() {
     final rc = _EditStructurePageState(this.primaryKey);
-    rc.attendedPage = AttendedPage(
-        this, rc, GlobalData(), StructureMeta.instance, pageStates);
+    rc.attendedPage =
+        AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _EditStructurePageState extends EditStructureCustom {
-  _EditStructurePageState(int primaryKey) : super(primaryKey);
+  _EditStructurePageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 55c901c8bcbfdd9dcea86dd63d0b80206cc58b03..e286fbc4ae0f50a1a783ec73762412bb8bc7e659 100644 (file)
@@ -12,15 +12,15 @@ class ListStructurePage extends StatefulWidget {
   @override
   _ListStructurePageState createState() {
     final rc = _ListStructurePageState();
-    rc.attendedPage = AttendedPage(
-        this, rc, GlobalData(), StructureMeta.instance, pageStates);
+    rc.attendedPage =
+        AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
     pageStates.attendedPage = rc.attendedPage;
     return rc;
   }
 }
 
 class _ListStructurePageState extends ListStructureCustom {
-  _ListStructurePageState() : super();
+  _ListStructurePageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 99296b19f01c62ecfeede137d1304b74444796ed..ec8d94edb5e6c154e427774bca7bb04d2124b6be 100644 (file)
@@ -19,31 +19,32 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
   final _fieldData = _FieldData();
   final GlobalKey<FormState> _formKey =
       GlobalKey<FormState>(debugLabel: 'CreateUser');
+  final nameController = TextEditingController();
+  final displayNameController = TextEditingController();
+  final emailController = TextEditingController();
   CreateUserCustom();
   @override
   Widget build(BuildContext context) {
     final padding = GlobalThemeData.padding;
     comboRolesFromBackend(
         attendedPage: attendedPage!, onDone: () => setState(() => 1));
-    final itemsRole = comboRoles(
+    final itemsRoles = comboRoles(
         i18n.trDyn(GlobalTranslations.comboboxSelect), attendedPage!);
+    nameController.text = _fieldData.name;
+    displayNameController.text = _fieldData.displayName;
+    emailController.text = _fieldData.email;
     final formItems = <FormItem>[
       FormItem(
           TextFormField(
-            initialValue: _fieldData.name,
-            style: TextStyle(
-                backgroundColor: GlobalThemeData.formTextBackgroundColor,
-                decorationStyle: TextDecorationStyle.double),
-            decoration: InputDecoration(
-              labelText: i18n.tr('Name'),
-            ),
+            controller: nameController,
+            decoration: InputDecoration(labelText: i18n.tr('Name')),
             validator: (input) => notEmpty(input),
             onSaved: (value) => _fieldData.name = value ?? '',
           ),
           weight: 6),
       FormItem(
           TextFormField(
-            initialValue: _fieldData.displayName,
+            controller: displayNameController,
             decoration: InputDecoration(labelText: i18n.tr('Display name')),
             validator: (input) => notEmpty(input),
             onSaved: (value) => _fieldData.displayName = value ?? '',
@@ -51,17 +52,16 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
           weight: 6),
       FormItem(
           TextFormField(
-            initialValue: _fieldData.email,
-            decoration: InputDecoration(labelText: i18n.tr('Email')),
-            validator: (input) => validateMultiple(
-                input, [(input) => notEmpty(input), (input) => isEmail(input)]),
+            controller: emailController,
+            decoration: InputDecoration(labelText: i18n.tr('EMail')),
+            validator: (input) => notEmpty(input),
             onSaved: (value) => _fieldData.email = value ?? '',
           ),
           weight: 6),
       FormItem(
           DropdownButtonFormField<int>(
             value: _fieldData.role,
-            items: itemsRole,
+            items: itemsRoles,
             isExpanded: true,
             decoration: InputDecoration(labelText: i18n.tr('Role')),
             onChanged: (value) => _fieldData.role = value ?? 0,
@@ -71,10 +71,11 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
           ElevatedButton(
               onPressed: () => verifyAndStore(), child: Text(i18n.tr('Save'))),
           weight: 8,
-          gapAbove: padding),
+          gapAbove: 2 * padding),
       FormItem(
           ElevatedButton(
             onPressed: () {
+              attendedPage!.pageStates.dbDataState.clear();
               globalData.navigate(context, '/Users/list');
             },
             child: Text(i18n.tr('Cancel')),
@@ -82,22 +83,22 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
           weight: 4)
     ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Create an User')),
+        appBar: globalData.appBarBuilder(i18n.tr('New User')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
-            child: Card(
-                color: GlobalThemeData.formBackgroundColor,
-                elevation: GlobalThemeData.formElevation,
-                margin: EdgeInsets.symmetric(
-                    vertical: padding, horizontal: padding),
-                child: Padding(
-                    padding: EdgeInsets.symmetric(
+            child: Form(
+                key: _formKey,
+                child: Card(
+                    margin: EdgeInsets.symmetric(
                         vertical: padding, horizontal: padding),
-                    child: WidgetForm.flexibleGrid(
-                      formItems,
-                      screenWidth: attendedPage!.pageStates.screenWidth,
-                      padding: padding,
-                    )))));
+                    child: Padding(
+                        padding: EdgeInsets.symmetric(
+                            vertical: padding, horizontal: padding),
+                        child: WidgetForm.flexibleGrid(
+                          formItems,
+                          screenWidth: attendedPage!.pageStates.screenWidth,
+                          padding: padding,
+                    ))))));
     return rc;
   }
 
@@ -121,6 +122,7 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
           setError(i18n.tr('Saving data failed: $answer'));
           setState(() => 1);
         } else {
+          attendedPage!.pageStates.dbDataState.clear();
           globalData.navigate(context, '/Users/edit;$id');
         }
       }
@@ -133,6 +135,14 @@ class CreateUserCustom extends State<CreateUserPage> with MessageLine {
       store();
     }
   }
+
+  @override
+  void dispose() {
+    nameController.dispose();
+    displayNameController.dispose();
+    emailController.dispose();
+    super.dispose();
+  }
 }
 
 class _FieldData {
@@ -140,6 +150,7 @@ class _FieldData {
   String displayName = '';
   String email = '';
   int role = 0;
+
   void toMap(Map<String, dynamic> map) {
     map[':name'] = name;
     map[':displayName'] = displayName;
index 71a2c05f8e2aab205a665c480213d491711ed3e2..14e69e258185a24630f5a76f39e934f03133ccc6 100644 (file)
@@ -20,7 +20,7 @@ class CreateUserPage extends StatefulWidget {
 }
 
 class _CreateUserPageState extends CreateUserCustom {
-  _CreateUserPageState() : super();
+  _CreateUserPageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 7ab1bce031b92f046c26f214edb5f753dbfc4478..8415fb270f76d4ae9dc813b85ab338530b7dfbd0 100644 (file)
 import 'package:flutter/material.dart';
 
 import '../../base/i18n.dart';
+import '../../base/validators.dart';
+import '../../services/global_widget.dart';
 import '../../setting/global_data.dart';
 import '../../widget/attended_page.dart';
+import '../../widget/message_line.dart';
 import '../../widget/widget_form.dart';
 import 'delete_user_page.dart';
 
 final i18n = I18N();
 
-class DeleteUserCustom extends State<DeleteUserPage> {
+class DeleteUserCustom extends State<DeleteUserPage> with MessageLine {
   final int primaryKey;
   final globalData = GlobalData();
   AttendedPage? attendedPage;
-
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'DeleteUser');
+  final nameController = TextEditingController();
+  final displayNameController = TextEditingController();
+  final emailController = TextEditingController();
   DeleteUserCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
     final padding = GlobalThemeData.padding;
+    comboRolesFromBackend(
+        attendedPage: attendedPage!, onDone: () => setState(() => 1));
+    final itemsRoles = comboRoles(
+        i18n.trDyn(GlobalTranslations.comboboxSelect), attendedPage!);
+    attendedPage?.loadRecord(
+        name: 'record',
+        reload: () => setState(() => 1),
+        onDone: (record) => _fieldData.fromMap(record),
+        parameters: {'module': 'Users', 'sql': 'byId', ':id': primaryKey});
+    nameController.text = _fieldData.name;
+    displayNameController.text = _fieldData.displayName;
+    emailController.text = _fieldData.email;
+    final formItems = <FormItem>[
+      FormItem(
+          TextFormField(
+            controller: nameController,
+            decoration: InputDecoration(labelText: i18n.tr('Name')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.name = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            controller: displayNameController,
+            decoration: InputDecoration(labelText: i18n.tr('Display name')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.displayName = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            controller: emailController,
+            decoration: InputDecoration(labelText: i18n.tr('EMail')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.email = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          DropdownButtonFormField<int>(
+            value: _fieldData.role,
+            items: itemsRoles,
+            isExpanded: true,
+            decoration: InputDecoration(labelText: i18n.tr('Role')),
+            onChanged: (value) => _fieldData.role = value ?? 0,
+          ),
+          weight: 6),
+      FormItem(
+          ElevatedButton(
+              onPressed: () => verifyAndDelete(), child: Text(i18n.tr('Delete'))),
+          weight: 8,
+          gapAbove: 2 * padding),
+      FormItem(
+          ElevatedButton(
+            onPressed: () {
+              attendedPage!.pageStates.dbDataState.clear();
+              globalData.navigate(context, '/Users/list');
+            },
+            child: Text(i18n.tr('Cancel')),
+          ),
+          weight: 4)
+    ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+        appBar: globalData.appBarBuilder(i18n.tr('Delete User')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
-            child: WidgetForm.flexibleGridAttended(
-                attendedPage!.attendedWidgets(),
-                screenWidth: attendedPage!.pageStates.screenWidth,
-                padding: padding)));
+            child: Form(
+                key: _formKey,
+                child: Card(
+                    margin: EdgeInsets.symmetric(
+                        vertical: padding, horizontal: padding),
+                    child: Padding(
+                        padding: EdgeInsets.symmetric(
+                            vertical: padding, horizontal: padding),
+                        child: WidgetForm.flexibleGrid(
+                          formItems,
+                          screenWidth: attendedPage!.pageStates.screenWidth,
+                          padding: padding,
+                    ))))));
     return rc;
   }
 
@@ -34,4 +112,46 @@ class DeleteUserCustom extends State<DeleteUserPage> {
   void initState() {
     super.initState();
   }
+
+  void delete() {
+    final parameters = <String, dynamic>{
+      'module': 'Users',
+      'sql': 'delete',
+    };
+    _fieldData.toMap(parameters);
+    globalData.restPersistence!
+        .store(what: 'store', map: parameters)
+        .then((answer) {
+      globalData.navigate(context, '/Users/list');
+    });
+  }
+
+  void verifyAndDelete() {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+      delete();
+    }
+  }
+
+  @override
+  void dispose() {
+    nameController.dispose();
+    displayNameController.dispose();
+    emailController.dispose();
+    super.dispose();
+  }
+}
+
+class _FieldData {
+  String name = '';
+  String displayName = '';
+  String email = '';
+  int role = 0;
+
+  void fromMap(Map<String, dynamic> map) {
+    name = map['user_name'];
+    displayName = map['user_displayname'];
+    email = map['user_email'];
+    role = map['user_role'];
+  }
 }
index 8bbef4c01f9cafe001d2b38d6e01af670ac93a85..85cbfc907ceebe2c637ac1bc32c7ee0433342d34 100644 (file)
@@ -21,7 +21,7 @@ class DeleteUserPage extends StatefulWidget {
 }
 
 class _DeleteUserPageState extends DeleteUserCustom {
-  _DeleteUserPageState(int primaryKey) : super(primaryKey);
+  _DeleteUserPageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index bf6fc649558ce1a2494cc37af2eade86ce217919..4727f34b0538117bcf0a77126b8cd205ecfbe708 100644 (file)
@@ -29,7 +29,7 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
     final padding = GlobalThemeData.padding;
     comboRolesFromBackend(
         attendedPage: attendedPage!, onDone: () => setState(() => 1));
-    final itemsRole = comboRoles(
+    final itemsRoles = comboRoles(
         i18n.trDyn(GlobalTranslations.comboboxSelect), attendedPage!);
     attendedPage?.loadRecord(
         name: 'record',
@@ -59,16 +59,15 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
       FormItem(
           TextFormField(
             controller: emailController,
-            decoration: InputDecoration(labelText: i18n.tr('Email')),
-            validator: (input) => validateMultiple(
-                input, [(input) => notEmpty(input), (input) => isEmail(input)]),
+            decoration: InputDecoration(labelText: i18n.tr('EMail')),
+            validator: (input) => notEmpty(input),
             onSaved: (value) => _fieldData.email = value ?? '',
           ),
           weight: 6),
       FormItem(
           DropdownButtonFormField<int>(
             value: _fieldData.role,
-            items: itemsRole,
+            items: itemsRoles,
             isExpanded: true,
             decoration: InputDecoration(labelText: i18n.tr('Role')),
             onChanged: (value) => _fieldData.role = value ?? 0,
@@ -90,7 +89,7 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
           weight: 4)
     ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change an User')),
+        appBar: globalData.appBarBuilder(i18n.tr('Change User')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
             child: Form(
@@ -98,11 +97,14 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
                 child: Card(
                     margin: EdgeInsets.symmetric(
                         vertical: padding, horizontal: padding),
-                    child: WidgetForm.flexibleGrid(
-                      formItems,
-                      screenWidth: attendedPage!.pageStates.screenWidth,
-                      padding: padding,
-                    )))));
+                    child: Padding(
+                        padding: EdgeInsets.symmetric(
+                            vertical: padding, horizontal: padding),
+                        child: WidgetForm.flexibleGrid(
+                          formItems,
+                          screenWidth: attendedPage!.pageStates.screenWidth,
+                          padding: padding,
+                    ))))));
     return rc;
   }
 
@@ -115,27 +117,12 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
     final parameters = <String, dynamic>{
       'module': 'Users',
       'sql': 'update',
-      ':id': primaryKey,
-      ':name': _fieldData.name,
-      ':displayName': _fieldData.displayName,
-      ':email': _fieldData.email,
-      ':role': _fieldData.role,
-      ':createdBy': GlobalData.loginUserName,
     };
     _fieldData.toMap(parameters);
     globalData.restPersistence!
         .store(what: 'store', map: parameters)
         .then((answer) {
-      if (answer.startsWith('id:')) {
-        final id = int.tryParse(answer.substring(3));
-        if (id == null || id == 0) {
-          setError(i18n.tr('Saving data failed: $answer'));
-          setState(() => 1);
-        } else {
-          attendedPage!.pageStates.dbDataState.clear();
-          globalData.navigate(context, '/Users/edit;$id');
-        }
-      }
+
     });
   }
 
@@ -145,6 +132,14 @@ class EditUserCustom extends State<EditUserPage> with MessageLine {
       store();
     }
   }
+
+  @override
+  void dispose() {
+    nameController.dispose();
+    displayNameController.dispose();
+    emailController.dispose();
+    super.dispose();
+  }
 }
 
 class _FieldData {
@@ -152,18 +147,19 @@ class _FieldData {
   String displayName = '';
   String email = '';
   int role = 0;
+
   void fromMap(Map<String, dynamic> map) {
     name = map['user_name'];
     displayName = map['user_displayname'];
     email = map['user_email'];
     role = map['user_role'];
   }
-
   void toMap(Map<String, dynamic> map) {
+    map[':id'] = primaryKey;
     map[':name'] = name;
     map[':displayName'] = displayName;
     map[':email'] = email;
     map[':role'] = role;
-    map[':createdBy'] = GlobalData.loginUserName;
+    map[':changedBy'] = GlobalData.loginUserName;
   }
 }
index 4b2e75f96c40371887a1ff257b12738ea63d3dad..11796632306f7b6a0d8a5c1b26bd2b9bd80e6d06 100644 (file)
@@ -21,7 +21,7 @@ class EditUserPage extends StatefulWidget {
 }
 
 class _EditUserPageState extends EditUserCustom {
-  _EditUserPageState(int primaryKey) : super(primaryKey);
+  _EditUserPageState(int primaryKey): super(primaryKey);
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index 3cf2c19698756b449bcd7ee29010b8040a40cc2e..f481a2ceecf2b8462bb0521ba1ece428d0a09e51 100644 (file)
@@ -24,20 +24,20 @@ class ListUserCustom extends State<ListUserPage> {
     final padding = GlobalThemeData.padding;
     comboRolesFromBackend(
         attendedPage: attendedPage!, onDone: () => setState(() => 1));
-    final itemsRole = comboRoles(
+    final itemsRoles = comboRoles(
         i18n.trDyn(GlobalTranslations.comboboxItemAll), attendedPage!);
     final formItems = <FormItem>[
       FormItem(
           TextFormField(
             controller: textController,
             decoration: InputDecoration(labelText: i18n.tr('Text')),
-            onChanged: (value) => _fieldData.text = value,
+            onSaved: (value) => _fieldData.text = value ?? '',
           ),
           weight: 6),
       FormItem(
           DropdownButtonFormField<int>(
             value: _fieldData.role,
-            items: itemsRole,
+            items: itemsRoles,
             isExpanded: true,
             decoration: InputDecoration(labelText: i18n.tr('Role')),
             onChanged: (value) => _fieldData.role = value ?? 0,
@@ -47,7 +47,7 @@ class ListUserCustom extends State<ListUserPage> {
           ElevatedButton(
               onPressed: () => search(), child: Text(i18n.tr('Search'))),
           weight: 12,
-          gapAbove: padding)
+          gapAbove: padding),
     ];
     final form = Form(
         key: _formKey,
@@ -62,7 +62,6 @@ class ListUserCustom extends State<ListUserPage> {
                 child: WidgetForm.flexibleGrid(formItems,
                     screenWidth: attendedPage!.pageStates.screenWidth,
                     padding: padding))));
-
     final rows = attendedPage!.getRows(
         columnList: 'user_id;user_displayname;user_email;role',
         what: 'query',
@@ -83,10 +82,10 @@ class ListUserCustom extends State<ListUserPage> {
           label: Text(i18n.tr('Id')),
         ),
         DataColumn(
-          label: Text(i18n.tr('Name')),
+          label: Text(i18n.tr('Display Name')),
         ),
         DataColumn(
-          label: Text(i18n.tr('EMail')),
+          label: Text(i18n.tr('Email')),
         ),
         DataColumn(
           label: Text(i18n.tr('Role')),
@@ -120,6 +119,11 @@ class ListUserCustom extends State<ListUserPage> {
   void initState() {
     super.initState();
   }
+  @override
+  void dispose(){
+    textController.dispose();
+    super.dispose();
+  }
 }
 
 class _FieldData {
index 2723f48184f044e8e5216c00a0c1bfb8e4cbba5f..1938d3d9a841fa556eb29744f91b85a73d9fde16 100644 (file)
@@ -20,7 +20,7 @@ class ListUserPage extends StatefulWidget {
 }
 
 class _ListUserPageState extends ListUserCustom {
-  _ListUserPageState() : super();
+  _ListUserPageState(): super();
   @override
   void didChangeDependencies() {
     final size = MediaQuery.of(context).size;
index c241876ad521dba7fdd68c6b9f977d7f3df6af2d..ac6333aaba5441b9c4efdbd916974e5156056a18 100644 (file)
@@ -2,6 +2,7 @@ import 'dart:io';
 
 import 'package:dart_bones/dart_bones.dart';
 
+import '../lib/base/defines.dart';
 import '../lib/meta/module_meta_data.dart';
 import '../lib/meta/modules.dart';
 import 'generator.dart';
@@ -43,7 +44,13 @@ class _EditUserPageState extends EditUserCustom {
 }
 ''';
 
-  static final templateCustom = '''// This file is created by the meta_tool. But it can be customized.
+  static final templateGlobalComboBox = '''    comboRolesFromBackend(
+        attendedPage: attendedPage!, onDone: () => setState(() => 1));
+    final itemsRoles = comboRoles(
+        i18n.trDyn(GlobalTranslations.combobox#UNDEF), attendedPage!);''';
+
+  static final templateListCustom =
+      '''// This file is created by the meta_tool. But it can be customized.
 // It will never overridden by the meta_tool.
 import 'package:flutter/material.dart';
 
@@ -51,39 +58,214 @@ import '../../base/i18n.dart';
 import '../../setting/global_data.dart';
 import '../../widget/attended_page.dart';
 import '../../widget/widget_form.dart';
-import 'edit_user_page.dart';
+import '../../services/global_widget.dart';
+import 'list_user_page.dart';
 
 final i18n = I18N();
 
-class EditUserCustom extends State<EditUserPage> {
-DEF_PRIMARY  final globalData = GlobalData();
+class ListUserCustom extends State<ListUserPage> {
+  final globalData = GlobalData();
   AttendedPage? attendedPage;
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'CreateUser');
+#DEF_CONTROLLERS  ListUserCustom();
+  @override
+  Widget build(BuildContext context) {
+    final padding = GlobalThemeData.padding;
+#INIT_COMBOS    final formItems = <FormItem>[
+#FORM_ITEMS      FormItem(
+          ElevatedButton(
+              onPressed: () => search(), child: Text(i18n.tr('Search'))),
+          weight: 12,
+          gapAbove: padding),
+    ];
+    final form = Form(
+        key: _formKey,
+        child: Card(
+            color: GlobalThemeData.formBackgroundColor,
+            elevation: GlobalThemeData.formElevation,
+            margin:
+                EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+            child: Padding(
+                padding: EdgeInsets.symmetric(
+                    vertical: padding, horizontal: padding),
+                child: WidgetForm.flexibleGrid(formItems,
+                    screenWidth: attendedPage!.pageStates.screenWidth,
+                    padding: padding))));
+    final rows = attendedPage!.getRows(
+        columnList: '#ROW_COLUMNS',
+        what: 'query',
+        parameters: {
+          'module': 'Users',
+          'sql': 'list',
+          'offset': '0',
+          'size': '10',
+#PARAM_DEF        },
+        onDone: () => setState(() => 1),
+        route#EDIT1: '/Users/#EDIT2',
+        context: context);
+    final table = DataTable(
+      columns: <DataColumn>[
+#TABLE_HEADER      ],
+      rows: rows,
+    );
+    final frameWidget = Column(children: [
+      form,
+      SizedBox(height: padding),
+      SizedBox(width: double.infinity, child: table),
+    ]);
+    final rc = Scaffold(
+        appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
+        drawer: globalData.drawerBuilder(context),
+        floatingActionButton: FloatingActionButton(
+          onPressed: () {
+            globalData.navigate(context, '/Users/create');
+          },
+          child: const Icon(Icons.add),
+          //backgroundColor: Colors.green,
+        ),
+        body: SafeArea(child: frameWidget));
+    return rc;
+  }
 
-  EditUserCustom(THIS_PRIMARY);
+  void search() {
+    //@ToDo
+  }
+  @override
+  void initState() {
+    super.initState();
+  }
+  @override
+  void dispose(){
+#DISPOSE_CONTROLLER    super.dispose();
+  }
+}
+
+class _FieldData {
+#DEF_FIELDS}
+''';
+
+  static final templateRecordCustom =
+      '''// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../base/validators.dart';
+import '../../services/global_widget.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/message_line.dart';
+import '../../widget/widget_form.dart';
+import 'edit_user_page.dart';
+
+final i18n = I18N();
+
+class EditUserCustom extends State<EditUserPage> with MessageLine {
+#DEF_PRIMARY  final globalData = GlobalData();
+  AttendedPage? attendedPage;
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'EditUser');
+#DEF_CONTROLLER  EditUserCustom(#THIS_PRIMARY);
   @override
   Widget build(BuildContext context) {
     final padding = GlobalThemeData.padding;
+#INIT_COMBO#LOAD_RECORD#ASSIGN_CONTROLLER    final formItems = <FormItem>[
+#FORM_ITEMS      FormItem(
+          ElevatedButton(
+              onPressed: () => #ACTION2(), child: Text(i18n.tr('#BUTTON'))),
+          weight: 8,
+          gapAbove: 2 * padding),
+      FormItem(
+          ElevatedButton(
+            onPressed: () {
+              attendedPage!.pageStates.dbDataState.clear();
+              globalData.navigate(context, '/Users/list');
+            },
+            child: Text(i18n.tr('Cancel')),
+          ),
+          weight: 4)
+    ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+        appBar: globalData.appBarBuilder(i18n.tr('#PAGE_LABEL')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
-            child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
-                screenWidth: attendedPage!.pageStates.screenWidth,
-                padding: padding)));
+            child: Form(
+                key: _formKey,
+                child: Card(
+                    margin: EdgeInsets.symmetric(
+                        vertical: padding, horizontal: padding),
+                    child: Padding(
+                        padding: EdgeInsets.symmetric(
+                            vertical: padding, horizontal: padding),
+                        child: WidgetForm.flexibleGrid(
+                          formItems,
+                          screenWidth: attendedPage!.pageStates.screenWidth,
+                          padding: padding,
+                    ))))));
     return rc;
   }
+
   @override
   void initState() {
     super.initState();
   }
+
+  void #ACTION1() {
+    final parameters = <String, dynamic>{
+      'module': 'Users',
+      'sql': '#SQL_TYPE',
+    };
+    _fieldData.toMap(parameters);
+    globalData.restPersistence!
+        .store(what: 'store', map: parameters)
+        .then((answer) {
+#STORAGE_DONE
+    });
+  }
+
+  void #ACTION2() {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+      #ACTION1();
+    }
+  }
+
+  @override
+  void dispose() {
+#DISP_CONTROLLER    super.dispose();
+  }
 }
+
+class _FieldData {
+#DEF_FIELD
+#FROM_MAP#TO_MAP}
+''';
+  static final templateStorageDoneCreate =
+      '''      if (answer.startsWith('id:')) {
+        final id = int.tryParse(answer.substring(3));
+        if (id == null || id == 0) {
+          setError(i18n.tr('Saving data failed: \$answer'));
+          setState(() => 1);
+        } else {
+          attendedPage!.pageStates.dbDataState.clear();
+          globalData.navigate(context, '/#MODULE/edit;\$id');
+        }
+      }''';
+  static final templateStorageDoneEdit = '      setState(() => 1);';
+  static final templateStorageDoneDelete =
+      "      globalData.navigate(context, '/#MODULE/list');";
+  static final templateFromMap = '''  void fromMap(Map<String, dynamic> map) {
+#BODY_FROM  }
 ''';
-  static final templatePageManager = '''// DO NOT CHANGE. This file is created by the meta_tool!
+  static final templatePageManager =
+      '''// DO NOT CHANGE. This file is created by the meta_tool!
 import 'package:flutter/material.dart';
 import 'page_manager_custom.dart';
 
 #IMPORTS#
-
 /// Manages all meta data driven pages of the program.
 class PageManager {
   static PageManager? _instance;
@@ -116,7 +298,9 @@ class PageManager {
   }
 }
 ''';
-  static final templatePageManagerCustom = '''// This file is created by the meta_tool. But it can be customized.
+
+  static final templatePageManagerCustom =
+      '''// This file is created by the meta_tool. But it can be customized.
 // It will never overridden by the meta_tool.
 import 'package:flutter/material.dart';
 import 'info_page.dart';
@@ -142,48 +326,320 @@ StatefulWidget? customPageByRoute(String route) {
   return rc;
 }
 ''';
+
   static final templateCase = '''      case '/Users/edit':
         rc = EditUserPage(ARG1);
         break;
 ''';
+
   PageGenerator(BaseLogger logger) : super(logger);
 
-  String replaceVariables(String template, PageMetaData page) {
-    final camelModules = toCamelCase(page.module.moduleName);
-    final camelModule = toCamelCase(page.module.moduleNameSingular);
-    final camelPageName = toCamelCase(page.name);
-    final rc = template
-        .replaceAll('Users', camelModules)
-        .replaceAll('User', camelModule)
-        .replaceAll('Edit', camelPageName)
-        .replaceAll('edit', page.name)
-        .replaceAll('users', page.module.moduleName.toLowerCase())
-        .replaceAll('user', page.module.moduleNameSingular.toLowerCase());
-        return rc;
+  String buildAssignControllers(PageMetaData page) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      if (field.widgetType == WidgetType.property &&
+          (field as PropertyMetaData).displayType == DisplayType.text) {
+        buffer.writeln(
+            '    ${field.name}Controller.text = _fieldData.${field.name};');
+      }
+    }
+    return buffer.toString();
+  }
+
+  String buildButton(
+      {required String onPressed, required String label, String indent = ''}) {
+    final rc = '''^ElevatedButton(
+^    onPressed: () => ONPRESSED,
+^    child: Text(i18n.tr('$label')))'''
+        .replaceAll('^', indent)
+        .replaceFirst('ONPRESSED', onPressed)
+        .replaceFirst('LABEL', label);
+    return rc;
+  }
+
+  String buildDefinitionControllers(PageMetaData page) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      if (field.widgetType == WidgetType.property &&
+          (field as PropertyMetaData).displayType == DisplayType.text) {
+        buffer.writeln(
+            '  final ${field.name}Controller = TextEditingController();');
+      }
+    }
+    return buffer.toString();
+  }
+
+  String buildDefinitionFields(PageMetaData page) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      if (field.widgetType != WidgetType.property ||
+          (field as PropertyMetaData).hasOption(':primary:')) {
+        continue;
+      }
+      final name = field.name;
+      final field2 = field as PropertyMetaData;
+      String type;
+      String value;
+      switch (field2.dataType) {
+        case DataType.bool:
+          type = 'bool';
+          value = 'false';
+          break;
+        case DataType.reference:
+        case DataType.int:
+        case DataType.nat:
+          type = 'int';
+          value = '0';
+          break;
+        case DataType.currency:
+        case DataType.float:
+          type = 'double';
+          value = '0';
+          break;
+        case DataType.date:
+          type = 'Date';
+          value = 'Date()';
+          break;
+        case DataType.datetime:
+          type = 'DateTime';
+          value = 'DateTime()';
+          break;
+        case DataType.string:
+          type = 'String';
+          value = "''";
+          break;
+        default:
+          type = 'Object';
+          value = "''";
+          break;
+        // throw FormatException('buildDefinitionFields(): missing ${field2.dataType}');
+      }
+      buffer.writeln("  $type $name = $value;");
+    }
+    return buffer.toString();
+  }
+
+  String buildDisposeControllers(PageMetaData page) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      if (field.widgetType == WidgetType.property &&
+          (field as PropertyMetaData).displayType == DisplayType.text) {
+        buffer.writeln('    ${field.name}Controller.dispose();');
+      }
+    }
+    return buffer.toString();
   }
+
+  String buildField(PropertyMetaData field,
+      {String indent = '', bool withController = false}) {
+    final name = field.name;
+    String rc = '';
+    switch (field.displayType) {
+      case DisplayType.text:
+        final validators = <String>[];
+        if (field.hasOption(':notnull:')) {
+          validators.add('notEmpty(input)');
+        }
+        var validator = '';
+        if (validators.length == 1) {
+          validator = '            validator: (input) => ${validators[0]},\n';
+        } else if (validators.length > 1) {
+          validator =
+              '            validator: (input) => validateMultiple(input, [\n';
+          for (var item in validators) {
+            validator += '              (input) => $item(input),\n';
+          }
+          validator += '            ]),\n';
+        }
+        rc = '''^TextFormField(
+#CONTR^  decoration: InputDecoration(labelText: i18n.tr('${field.label}')),
+#VALIDATOR^  onSaved: (value) => _fieldData.$name = value ?? '',
+^)'''
+            .replaceFirst('#CONTR',
+                withController ? '^  controller: ${name}Controller,\n' : '')
+            .replaceFirst('#VALIDATOR', validator)
+            .replaceAll('^', indent);
+        break;
+      case DisplayType.combobox:
+        String name2;
+        if (field.dataType == DataType.reference) {
+          name2 = toCamelCase(field.foreignKey?.split('.')[0] ?? field.name);
+        } else {
+          name2 = field.name;
+        }
+        rc = '''^DropdownButtonFormField<int>(
+^  value: _fieldData.$name,
+^  items: items$name2,
+^  isExpanded: true,
+^  decoration: InputDecoration(labelText: i18n.tr('${field.label}')),
+^  onChanged: (value) => _fieldData.$name = value ?? 0,
+^)'''
+            .replaceAll('^', indent);
+        break;
+      default:
+        throw FormatException(
+            'buildField(): not implemented: ${field.displayType}');
+    }
+    return rc;
+  }
+
+  String buildFormItems(PageMetaData page, {bool withController = false}) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      if (field is PropertyMetaData && field.hasOption(':primary:')) {
+        continue;
+      }
+      final weight = (field.widgetType != WidgetType.property)
+          ? 6
+          : (field as PropertyMetaData).weight;
+      buffer.writeln('      FormItem(');
+      if (field is PropertyMetaData) {
+        buffer.writeln(buildField(field,
+                indent: '          ', withController: withController) +
+            ',');
+        buffer.writeln('          weight: $weight),');
+      } else {
+        throw FormatException(
+            'buildFormItems(): not implemented: ${field.widgetType}');
+      }
+    }
+    return buffer.toString();
+  }
+
+  String buildFromMap(PageMetaData page) {
+    String? rc;
+    if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
+      final buffer =
+          StringBuffer('  void fromMap(Map<String, dynamic> map) {\n');
+      for (var field in page.fields) {
+        if (field is PropertyMetaData && !field.hasOption(':primary:')) {
+          buffer.writeln("    ${field.name} = map['${field.columnName}'];");
+        }
+      }
+      buffer.writeln('  }');
+      rc = buffer.toString();
+    }
+    return rc ?? '';
+  }
+
+  String buildInitializeComboBoxes(PageMetaData page) {
+    final buffer = StringBuffer();
+    if (page.globalComboBoxes.isNotEmpty) {
+      for (var item in page.globalComboBoxes.split(';')) {
+        buffer.writeln(templateGlobalComboBox
+            .replaceFirst(
+                '#UNDEF', page.pageType == PageType.list ? 'ItemAll' : 'Select')
+            .replaceAll('comboRoles', item)
+            .replaceAll('Roles', item.substring(5)));
+      }
+    }
+    return buffer.toString();
+  }
+
+  String buildLoadRecord(PageMetaData page) {
+    String? rc;
+    if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
+      rc = '''    attendedPage?.loadRecord(
+        name: 'record',
+        reload: () => setState(() => 1),
+        onDone: (record) => _fieldData.fromMap(record),
+        parameters: {'module': '${page.module.moduleName}', 'sql': 'byId', ':id': primaryKey});
+''';
+    }
+    return rc ?? '';
+  }
+
+  String buildParamDefinitions(PageMetaData page,
+      {bool withController = false}) {
+    final buffer = StringBuffer();
+    for (var field in page.fields) {
+      final name = field.name;
+      final suffix = field.widgetType != WidgetType.property ||
+              (field as PropertyMetaData).displayType != DisplayType.text
+          ? '.toString()'
+          : '';
+      buffer.writeln("          ':$name': _fieldData.$name$suffix,");
+    }
+    return buffer.toString();
+  }
+
+  String buildTableHeader(ListPageMetaData page) {
+    final buffer = StringBuffer();
+    for (var item in page.tableHeaders.split(';')) {
+      buffer.writeln('''        DataColumn(
+          label: Text(i18n.tr('$item')),
+        ),''');
+    }
+    return buffer.toString();
+  }
+
+  String buildToMap(PageMetaData page) {
+    String? rc;
+    if (page.pageType == PageType.edit || page.pageType == PageType.create) {
+      final buffer = StringBuffer('  void toMap(Map<String, dynamic> map) {\n');
+      if (page.pageType == PageType.edit) {
+        buffer.writeln("    map[':id'] = primaryKey;");
+      }
+      for (var field in page.fields) {
+        if (!(field is PropertyMetaData) || field.hasOption(':primary:')) {
+          continue;
+        }
+        final name = field.name;
+        buffer.writeln("    map[':$name'] = $name;");
+      }
+      buffer.writeln("    map[':#'] = GlobalData.loginUserName;".replaceFirst(
+          '#', page.pageType == PageType.edit ? 'changedBy' : 'createdBy'));
+      buffer.writeln('  }');
+      rc = buffer.toString();
+    }
+    return rc ?? '';
+  }
+
   /// Returns a Dart class definition of the [page] that should not be modified.
   String createCustomized(PageMetaData page) {
-    var rc = replaceVariables(templateCustom, page);
-    if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
-      rc = rc.replaceAll('DEF_PRIMARY', '  final int primaryKey;\n')
-          .replaceAll('THIS_PRIMARY', 'this.primaryKey');
-    } else {
-      rc = rc.replaceAll('DEF_PRIMARY', '')
-          .replaceAll('THIS_PRIMARY', '');
+    String rc;
+    switch (page.pageType) {
+      case PageType.list:
+        rc = createListPage(page as ListPageMetaData);
+        break;
+      case PageType.edit:
+      case PageType.create:
+      case PageType.delete:
+      case PageType.custom:
+        rc = createRecordCustomized(page);
+        break;
     }
     return rc;
   }
 
+  /// Returns a Dart class definition of the [page] that should not be modified.
+  String createListPage(ListPageMetaData page) {
+    var rc = replaceVariables(templateListCustom, page)
+        .replaceFirst('#DEF_CONTROLLERS', buildDefinitionControllers(page))
+        .replaceFirst('#INIT_COMBOS', buildInitializeComboBoxes(page))
+        .replaceFirst('#FORM_ITEMS', buildFormItems(page, withController: true))
+        .replaceFirst('#PARAM_DEF', buildParamDefinitions(page))
+        .replaceFirst('#ROW_COLUMNS', page.tableColumns)
+        .replaceFirst('#TABLE_HEADER', buildTableHeader(page))
+        .replaceFirst('#DISPOSE_CONTROLLER', buildDisposeControllers(page))
+        .replaceFirst('#DEF_FIELDS', buildDefinitionFields(page))
+        .replaceFirst('#EDIT1', 'Edit')
+        .replaceFirst('#EDIT2', 'edit');
+    return rc;
+  }
+
   /// Returns a Dart class definition of the [page] that should not be modified.
   String createPage(PageMetaData page) {
     var rc = replaceVariables(templatePage, page);
-    if (page.pageType == PageType.edit || page.pageType == PageType.delete){
-      rc = rc.replaceAll('DEF_PRIMARY', '  final int primaryKey;\n')
+    if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
+      rc = rc
+          .replaceAll('DEF_PRIMARY', '  final int primaryKey;\n')
           .replaceAll('THIS_PRIMARY', 'this.primaryKey')
           .replaceAll('PARAM_PRIMARY', 'int primaryKey')
           .replaceAll('VAL_PRIMARY', 'primaryKey');
     } else {
-      rc = rc.replaceAll('DEF_PRIMARY', '')
+      rc = rc
+          .replaceAll('DEF_PRIMARY', '')
           .replaceAll('THIS_PRIMARY', '')
           .replaceAll('PARAM_PRIMARY', '')
           .replaceAll('VAL_PRIMARY', '');
@@ -191,19 +647,100 @@ StatefulWidget? customPageByRoute(String route) {
     return rc;
   }
 
+  /// Creates the customized part of a page displaying one record.
+  ///
+  /// [page] contains the meta data information about the page.
+  ///
+  /// Returns the file content.
+  String createRecordCustomized(PageMetaData page) {
+    var hasPrimary = false;
+    page.pageType == PageType.edit || page.pageType == PageType.delete;
+    var action1 = 'store';
+    var action2 = 'verifyAndStore';
+    var storageDone = '';
+    var buttonText = 'Save';
+    String sqlType = '';
+    page.pageType == PageType.edit
+        ? 'update'
+        : (page.pageType == PageType.delete ? 'delete' : 'insert');
+
+    switch (page.pageType) {
+      case PageType.create:
+        sqlType = 'insert';
+        storageDone = templateStorageDoneCreate.replaceFirst(
+            '#MODULE', page.module.moduleName);
+        break;
+      case PageType.edit:
+        sqlType = 'update';
+        storageDone = templateStorageDoneEdit;
+        hasPrimary = true;
+        storageDone = '';
+        break;
+      case PageType.delete:
+        sqlType = 'delete';
+        buttonText = 'Delete';
+        action1 = 'delete';
+        action2 = 'verifyAndDelete';
+        storageDone = templateStorageDoneDelete.replaceFirst(
+            '#MODULE', page.module.moduleName);
+        hasPrimary = true;
+        break;
+      case PageType.custom:
+        break;
+      case PageType.list:
+        break;
+    }
+    var rc = replaceVariables(templateRecordCustom, page)
+        .replaceFirst('#DEF_CONTROLLER', buildDefinitionControllers(page))
+        .replaceFirst('#INIT_COMBO', buildInitializeComboBoxes(page))
+        .replaceFirst('#FORM_ITEMS', buildFormItems(page, withController: true))
+        .replaceFirst('#LOAD_RECORD', buildLoadRecord(page))
+        .replaceFirst('#PAGE_LABEL', page.label)
+        .replaceFirst('#SQL_TYPE', sqlType)
+        .replaceAll('#ACTION1', action1)
+        .replaceAll('#ACTION2', action2)
+        .replaceFirst('#BUTTON', buttonText)
+        .replaceFirst('#STORAGE_DONE', storageDone)
+        .replaceFirst('#DEF_PARAMS', buildParamDefinitions(page))
+        .replaceFirst('#DISP_CONTROLLER', buildDisposeControllers(page))
+        .replaceFirst('#DEF_FIELD', buildDefinitionFields(page))
+        .replaceFirst('#ASSIGN_CONTROLLER', buildAssignControllers(page))
+        .replaceFirst(
+            '#DEF_PRIMARY', hasPrimary ? '  final int primaryKey;\n' : '')
+        .replaceFirst('#THIS_PRIMARY', hasPrimary ? 'this.primaryKey' : '')
+        .replaceFirst('#TO_MAP', buildToMap(page))
+        .replaceFirst('#FROM_MAP', buildFromMap(page));
+    return rc;
+  }
+
+  String replaceVariables(String template, PageMetaData page) {
+    final camelModules = toCamelCase(page.module.moduleName);
+    final camelModule = toCamelCase(page.module.moduleNameSingular);
+    final camelPageName = toCamelCase(page.name);
+    final rc = template
+        .replaceAll('Users', camelModules)
+        .replaceAll('User', camelModule)
+        .replaceAll('Edit', camelPageName)
+        .replaceAll('edit', page.name)
+        .replaceAll('users', page.module.moduleName.toLowerCase())
+        .replaceAll('user', page.module.moduleNameSingular.toLowerCase());
+    return rc;
+  }
+
   /// Creates the file page_manager.dart and (if it does not exist) the file
   /// page_manager_custom.dart.
-  void updatePageManager(){
+  void updatePageManager() {
     final modules = moduleNames();
     final imports = StringBuffer();
     final cases = StringBuffer();
-    for (var name in modules){
+    for (var name in modules) {
       ModuleMetaData? module = moduleByName(name);
       for (var page in module!.pageList) {
         imports.writeln("import '${name.toLowerCase()}/${page.name}_"
-        "${module.moduleNameSingular.toLowerCase()}_page.dart';");
+            "${module.moduleNameSingular.toLowerCase()}_page.dart';");
         var case1 = replaceVariables(templateCase, page);
-        if (page.pageType == PageType.edit || page.pageType == PageType.delete){
+        if (page.pageType == PageType.edit ||
+            page.pageType == PageType.delete) {
           case1 = case1.replaceFirst('ARG1', 'arg1');
         } else {
           case1 = case1.replaceFirst('ARG1', '');
@@ -211,16 +748,18 @@ StatefulWidget? customPageByRoute(String route) {
         cases.write(case1);
       }
     }
-    final content = templatePageManager.replaceFirst('#IMPORTS#', imports.toString())
-    .replaceFirst('#CASES#', cases.toString());
+    final content = templatePageManager
+        .replaceFirst('#IMPORTS#', imports.toString())
+        .replaceFirst('#CASES#', cases.toString());
     writeFile('../lib/page/page_manager.dart', content);
     final full = '../lib/page/page_manager_custom.dart';
-    if (File(full).existsSync()){
+    if (File(full).existsSync()) {
       logger.log('$full already exists. We do not override');
     } else {
       writeFile(full, templatePageManagerCustom);
     }
   }
+
   /// Generates all modules defined in the meta data.
   void updatePages(Generator generator) {
     if (!Directory('bin').existsSync() ||
@@ -234,6 +773,7 @@ StatefulWidget? customPageByRoute(String route) {
         if (module == null) {
           logger.error('+++ unknown module: $name');
         } else {
+          module.onInitialized();
           for (var page in module.pageList) {
             String filename = page.name +
                 '_' +