]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work
authorHamatoma <author@hamatoma.de>
Mon, 16 Nov 2020 15:13:40 +0000 (16:13 +0100)
committerHamatoma <author@hamatoma.de>
Tue, 17 Nov 2020 22:38:53 +0000 (23:38 +0100)
54 files changed:
data/ddl/menu.sql
data/ddl/starter.sql [new file with mode: 0644]
data/rest/configuration.yaml
data/rest/menu.yaml
data/rest/role.yaml
data/rest/starter.yaml [new file with mode: 0644]
data/rest/user.yaml
lib/app.dart
lib/flutter_bones.dart
lib/src/helper/string_helper.dart
lib/src/model/all_db_fields_model.dart
lib/src/model/model_helper.dart [new file with mode: 0644]
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/standard/configuration_model.dart
lib/src/model/standard/menu_model.dart
lib/src/model/standard/role_model.dart
lib/src/model/standard/standard_modules.dart [deleted file]
lib/src/model/standard/starter_model.dart [new file with mode: 0644]
lib/src/model/standard/user_model.dart
lib/src/page/application_data.dart
lib/src/page/demo_page.dart
lib/src/page/menu/menu_converter.dart
lib/src/page/role/role_change_page.dart
lib/src/page/role/role_create_page.dart
lib/src/page/role/role_list_page.dart
lib/src/page/starter/starter_change_page.dart [new file with mode: 0644]
lib/src/page/starter/starter_controller.dart [new file with mode: 0644]
lib/src/page/starter/starter_create_page.dart [new file with mode: 0644]
lib/src/page/starter/starter_list_page.dart [new file with mode: 0644]
lib/src/page/user/user_change_page.dart
lib/src/page/user/user_create_page.dart
lib/src/page/user/user_list_page.dart
lib/src/page/user/user_login_page.dart
lib/src/persistence/rest_persistence.dart
lib/src/private/bdrawer.dart
lib/src/private/bfooter.dart [new file with mode: 0644]
lib/src/private/bsettings.dart
lib/src/widget/edit_form.dart
lib/src/widget/page_controller_bones.dart
lib/src/widget/simple_form.dart
lib/src/widget/view.dart
model_tool/lib/main.dart
model_tool/lib/src/model_tools.dart [new file with mode: 0644]
model_tool/lib/src/page [new symlink]
model_tool/pubspec.yaml
pubspec.yaml
test/helpers/string_helper_test.dart
test/model/model_test.dart
test/page/application_test.dart
test/tool/tool_test.dart
test/widget/widget_test.dart
test/widget/widget_validators_test.dart

index 44cc7f361b146c3ff7a07fb5045794b9440d1c9a..cf8a24eb4f742b641f6ced6587721d0207afd6a1 100644 (file)
@@ -1,8 +1,8 @@
 DROP TABLE IF EXISTS menu;
 CREATE TABLE menu (
   menu_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
-  menu_name VARCHAR(64) UNIQUE NOT NULL,
-  menu_icon INT(10) UNSIGNED,
+  menu_role INT(10) UNSIGNED,
+  menu_starter INT(10) UNSIGNED,
   menu_createdat TIMESTAMP NULL,
   menu_createdby VARCHAR(16),
   menu_changedat TIMESTAMP NULL,
diff --git a/data/ddl/starter.sql b/data/ddl/starter.sql
new file mode 100644 (file)
index 0000000..5c7b4b4
--- /dev/null
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS starter;
+CREATE TABLE starter (
+  starter_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+  starter_name VARCHAR(64) UNIQUE NOT NULL,
+  starter_icon INT(10) UNSIGNED,
+  starter_createdat TIMESTAMP NULL,
+  starter_createdby VARCHAR(16),
+  starter_changedat TIMESTAMP NULL,
+  starter_changedby VARCHAR(16),
+  PRIMARY KEY(starter_id)
+);
index 6fd0ce367fa53f2c52bb8fb2930ec1f930caa408..9c40415946bb375185bbea7ab0ebe3f58fa68c72 100644 (file)
@@ -1,6 +1,6 @@
 ---
 # configuration of the bones backend for configuration:
-created: 2020.11.10 23:47:43
+created: 2020.11.16 18:30:01
 author: flutter_bones.module_model.exportSqlBackend()
 version: 1.0.0
 modules:
index 54966a881f7995230a4a7236d1b7abed0f29cdeb..6b4a82120af847ea670caae9626399f181ac4e43 100644 (file)
@@ -1,6 +1,6 @@
 ---
 # configuration of the bones backend for menu:
-created: 2020.11.10 23:47:43
+created: 2020.11.16 18:30:01
 author: flutter_bones.module_model.exportSqlBackend()
 version: 1.0.0
 modules:
@@ -8,12 +8,12 @@ modules:
     sqlInfos:
       - name: insert
         type: insert
-        sql: "INSERT INTO menu(menu_name,menu_icon,menu_createdat,menu_createdby)
-          VALUES(:menu_name,:menu_icon,NOW(),:menu_createdby);"
+        sql: "INSERT INTO menu(menu_role,menu_starter,menu_createdat,menu_createdby)
+          VALUES(:menu_role,:menu_starter,NOW(),:menu_createdby);"
       - name: update
         type: update
         sql: "UPDATE menu SET
-          menu_name=:menu_name,menu_icon=:menu_icon,menu_changedat=NOW(),menu_changedby=:menu_changedby
+          menu_role=:menu_role,menu_starter=:menu_starter,menu_changedat=NOW(),menu_changedby=:menu_changedby
           WHERE menu_id=:menu_id;"
       - name: delete
         type: delete
@@ -21,11 +21,9 @@ modules:
       - name: record
         type: record
         sql: "SELECT * from menu WHERE menu_id=:menu_id;"
-      - name: by_menu_name
-        type: record
-        sql: "SELECT * from menu WHERE menu_name=:menu_name&&menu_id!=:excluded;"
       - name: list
         type: list
-        sql: "SELECT t0.*,t1.configuration_property as menu_icon from menu t0
-          left join configuration t1 on t1.configuration_id=t0.menu_icon
-          WHERE menu_name like :menu_name;"
+        sql: "SELECT t0.*,t1.role_name as menu_role,t2.starter_name as menu_starter from menu t0
+          left join role t1 on t1.role_id=t0.menu_role
+          left join starter t2 on t2.starter_id=t0.menu_starter
+          WHERE menu_role like :menu_role;"
index f59f8a29d50588b7eb5bab58d463d25c553a4d0f..23da879f5cc1247e37a4d43c57e6c7c3daf172fb 100644 (file)
@@ -1,6 +1,6 @@
 ---
 # configuration of the bones backend for role:
-created: 2020.11.10 23:47:43
+created: 2020.11.16 18:30:01
 author: flutter_bones.module_model.exportSqlBackend()
 version: 1.0.0
 modules:
diff --git a/data/rest/starter.yaml b/data/rest/starter.yaml
new file mode 100644 (file)
index 0000000..4eedbd2
--- /dev/null
@@ -0,0 +1,31 @@
+---
+# configuration of the bones backend for starter:
+created: 2020.11.16 18:30:01
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+  - module: starter
+    sqlInfos:
+      - name: insert
+        type: insert
+        sql: "INSERT INTO starter(starter_name,starter_icon,starter_createdat,starter_createdby)
+          VALUES(:starter_name,:starter_icon,NOW(),:starter_createdby);"
+      - name: update
+        type: update
+        sql: "UPDATE starter SET
+          starter_name=:starter_name,starter_icon=:starter_icon,starter_changedat=NOW(),starter_changedby=:starter_changedby
+          WHERE starter_id=:starter_id;"
+      - name: delete
+        type: delete
+        sql: "DELETE from starter WHERE starter_id=:starter_id;"
+      - name: record
+        type: record
+        sql: "SELECT * from starter WHERE starter_id=:starter_id;"
+      - name: by_starter_name
+        type: record
+        sql: "SELECT * from starter WHERE starter_name=:starter_name&&starter_id!=:excluded;"
+      - name: list
+        type: list
+        sql: "SELECT t0.*,t1.configuration_value as starter_icon from starter t0
+          left join configuration t1 on t1.configuration_id=t0.starter_icon
+          WHERE starter_name like :starter_name;"
index f99d0acb12f62fc2afda717ed5c574600ddcff3b..fe0759eb1b5fd88c5b8f680472cb3d479a9a4ff1 100644 (file)
@@ -1,6 +1,6 @@
 ---
 # configuration of the bones backend for user:
-created: 2020.11.10 23:47:43
+created: 2020.11.16 18:30:01
 author: flutter_bones.module_model.exportSqlBackend()
 version: 1.0.0
 modules:
index 452ebfe9d1d9e73fe8bb277b0f66909dc69ee8dd..5e4670dfccdc6ba34cd9393a786fdd85316dd534 100644 (file)
@@ -13,6 +13,8 @@ import 'src/page/role/role_list_page.dart';
 import 'src/page/user/user_create_page.dart';
 import 'src/page/user/user_list_page.dart';
 import 'src/page/user/user_login_page.dart';
+import 'src/page/starter/starter_list_page.dart';
+import 'src/page/starter/starter_create_page.dart';
 import 'src/private/bsettings.dart';
 
 class BoneApp extends StatefulWidget {
@@ -35,12 +37,13 @@ class BoneAppState extends State<BoneApp> {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
-      title: 'Test Standardseiten',
+      debugShowCheckedModeBanner: true,
+      title: 'Flutter Bones Demo',
       theme: ThemeData(
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
-      initialRoute: '/menu/list',
+      initialRoute: '/role/list',
       //initialRoute: '/async',
       onGenerateRoute: _getRoute,
     );
@@ -81,6 +84,12 @@ Route<dynamic> _getRoute(RouteSettings settings) {
     case '/menu/create':
       page = MenuCreatePage(BSettings.lastInstance.pageData);
       break;
+    case '/starter/list':
+      page = StarterListPage(BSettings.lastInstance.pageData);
+      break;
+    case '/starter/create':
+      page = StarterCreatePage(BSettings.lastInstance.pageData);
+      break;
     case '/user/login':
     default:
       page = UserLoginPage(BSettings.lastInstance.pageData);
index 18f3e844781834395cf29f389002ee3ac9d37167..ad289448dde01bf11741c94a3250a1c6c3d4f932 100644 (file)
@@ -20,14 +20,12 @@ export 'src/model/section_model.dart';
 export 'src/model/standard/configuration_model.dart';
 export 'src/model/standard/menu_model.dart';
 export 'src/model/standard/role_model.dart';
-export 'src/model/standard/standard_modules.dart';
 export 'src/model/standard/user_model.dart';
 export 'src/model/text_field_model.dart';
 export 'src/model/text_model.dart';
 export 'src/model/widget_model.dart';
 export 'src/page/application_data.dart';
 export 'src/page/configuration/configuration_create_page.dart';
-export 'src/page/login_page.dart.01';
 export 'src/page/role/role_create_page.dart';
 export 'src/page/user/user_create_page.dart';
 export 'src/page/user_page.dart';
index ea9e5ee57f84d4bc75cf0474f4b82799d928e785..b0ced96ee8946790fc8a64b01fde3f52448a79f3 100644 (file)
@@ -150,6 +150,40 @@ class StringHelper {
     return rc;
   }
 
+  /// Returns null or the index of the first match of a [string]
+  /// or a regular expression defined by [regExp] or a string [pattern].
+  /// Note: exactly one of [string], [regExp] or [pattern] must be not null.
+  static int listIndexOf(List<String> list,
+      {String string, RegExp regExp, String pattern}) {
+    int rc;
+    int ix = -1;
+    if (list != null) {
+      if (string != null) {
+        for (var line in list) {
+          ix++;
+          if (line.contains(string)) {
+            rc = ix;
+            break;
+          }
+        }
+      } else {
+        if (pattern != null) {
+          regExp = RegExp(pattern);
+        }
+        if (regExp != null) {
+          for (var line in list) {
+            ix++;
+            if (regExp.firstMatch(line) != null) {
+              rc = ix;
+              break;
+            }
+          }
+        }
+      }
+    }
+    return rc;
+  }
+
   /// Splits a argument list into an [option] list and a true arguments list.
   /// [args]: the argument list to split.
   /// [options]: OUT: the options list
@@ -165,4 +199,10 @@ class StringHelper {
     }
     return rc;
   }
+
+  /// Converts a [string] to camelCase with an uppercase first character.
+  static String capitalize(String string) {
+    final rc = string[0].toUpperCase() + string.substring(1);
+    return rc;
+  }
 }
index 65b591bc2ae93cff4586f5c8183ca10fc53ab32d..9e8506afb40ba1c7e47d1535fe1272df83c44a4b 100644 (file)
@@ -40,9 +40,9 @@ class AllDbFieldsModel extends WidgetModel {
       if (col.hasOption('doStore') ||
           (!col.hasOption('hidden') &&
               (!isCreatePage || !col.hasOption('primary')))) {
-        final field = DbReferenceModel.fromColumn(section, page, col, logger);
-        page.addField(field);
-        page.widgets.add(field);
+        final model = DbReferenceModel.fromColumn(section, page, col, logger);
+        page.addModel(model);
+        section.children.add(model);
       }
     });
   }
diff --git a/lib/src/model/model_helper.dart b/lib/src/model/model_helper.dart
new file mode 100644 (file)
index 0000000..4ccd334
--- /dev/null
@@ -0,0 +1,40 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'module_model.dart';
+import 'standard/role_model.dart';
+import 'standard/user_model.dart';
+import 'standard/menu_model.dart';
+import 'standard/starter_model.dart';
+import 'standard/configuration_model.dart';
+
+class ModelHelper {
+  /// Returns an instance of a module given by [name].
+  ModuleModel moduleByName(String name, BaseLogger logger) {
+    var rc;
+    switch (name) {
+      case 'role':
+        rc = RoleModel(logger);
+        break;
+      case 'user':
+        rc = UserModel(logger);
+        break;
+      case 'configuration':
+        rc = ConfigurationModel(logger);
+        break;
+      case 'menu':
+        rc = MenuModel(logger);
+        break;
+      case 'starter':
+        rc = StarterModel(logger);
+        break;
+      default:
+        logger.error('unknown standard module: $name');
+        break;
+    }
+    return rc;
+  }
+
+  /// Returns the names of the modules.
+  List<String> moduleNames() =>
+      ['configuration', 'menu', 'role', 'starter', 'user'];
+
+}
index 9ecc131839b1801dcb75668e5b5bb2a94fccf151..ac8d7b80143aa0b8699122bd891a71b24c462ee6 100644 (file)
@@ -119,32 +119,33 @@ class ModuleModel extends ModelBase {
         type: list
         sql: "SELECT $select from ${table.name} t0\n$buffer2''');
       // @ToDo: joins
-      final fields = page.fields
+      final models = page.models.values
           .where((item) => item is FieldModel && item.filterType != null);
-      if (fields.isEmpty) {
+      if (models.isEmpty) {
         buffer.write('          WHERE 1;"');
       } else {
         buffer.write('          WHERE ');
         var first = true;
-        for (var field in fields) {
+        for (var item in models) {
+          FieldModel model = item as FieldModel;
           if (!first) {
             buffer.write(' AND ');
           }
-          switch (field.filterType) {
+          switch (model.filterType) {
             case FilterType.dateFrom:
             case FilterType.dateTimeFrom:
-              buffer.write('${field.name}>=:${field.name}');
+              buffer.write('${model.name}>=:${model.name}');
               break;
             case FilterType.dateTil:
             case FilterType.dateTimeTil:
-              buffer.write('${field.name}<=:${field.name}');
+              buffer.write('${model.name}<=:${model.name}');
               break;
             case FilterType.equals:
               buffer.write(
-                  '(:${field.name} IS NULL OR ${field.name}=:${field.name})');
+                  '(:${model.name} IS NULL OR ${model.name}=:${model.name})');
               break;
             case FilterType.pattern:
-              buffer.write('${field.name} like :${field.name}');
+              buffer.write('${model.name} like :${model.name}');
               break;
           }
           first = false;
index 536a84329c67b23bb580db41834dbaa22f790763..01bb896076783f0e3532134660eabddd4e9937ad 100644 (file)
@@ -16,10 +16,12 @@ class PageModel extends ModelBase {
   final ModuleModel module;
   final List<SectionModel> sections = [];
   PageModelType pageModelType;
-  final fields = <FieldModel>[];
-  final buttons = <ButtonModel>[];
-  final widgets = <WidgetModel>[];
+  final fieldsDeprecated = <FieldModel>[];
+  final buttonsDeprecated = <ButtonModel>[];
+  final widgetsDeprecated = <WidgetModel>[];
+  final models = <String, WidgetModel>{};
   String sql;
+  String title;
   List<String> tableTitles;
   List<String> tableColumns;
   NameBuilder nameBuilder;
@@ -29,34 +31,22 @@ class PageModel extends ModelBase {
     nameBuilder = NameBuilder(logger);
   }
 
-  /// Adds a [button] to the [this.buttons].
+  /// Adds a [model] to the [this.fields].
   /// If already defined and error is logged.
-  void addButton(ButtonModel button) {
-    if (buttonByName(button.name, required: false) != null) {
-      logger.error('button ${button.fullName()} already defined: ' +
-          buttonByName(button.name, required: false).fullName());
-    } else {
-      buttons.add(button);
-    }
-  }
-
-  /// Adds a [field] to the [this.fields].
-  /// If already defined and error is logged.
-  void addField(FieldModel field) {
-    final name = field.name;
-    if (hasField(name)) {
-      logger.error('field ${field.fullName()} already defined: ' +
+  void addModel(WidgetModel model) {
+    final name = model.name;
+    if (models.containsKey(name)) {
+      logger.error('model ${model.fullName()} already defined: ' +
           fieldByName(name).fullName());
     } else {
-      fields.add(field);
+      models[model.name] = model;
     }
   }
 
   /// Returns a button named [name] or null if not found.
   ButtonModel buttonByName(String name, {bool required = true}) {
-    final rc =
-        buttons.firstWhere((item) => item.name == name, orElse: () => null);
-    if (rc == null && required) {
+    final rc = models.containsKey(name) ? models[name] : null;
+    if ((rc == null || rc.modelType != ModelTypeBones.button) && required) {
       logger.error('missing button $name in page ${fullName()}');
     }
     return rc;
@@ -74,9 +64,10 @@ class PageModel extends ModelBase {
 
   /// Returns a field by [name] or null on error.
   FieldModel fieldByName(String name, {bool required = true}) {
-    final rc = fields.firstWhere((element) => element.name == name,
-        orElse: () => null);
-    if (required && rc == null) {
+    final rc = !models.containsKey(name)
+        ? null
+        : (models[name] is FieldModel ? models[name] : null);
+    if ((rc == null || !(rc is FieldModel)) && required) {
       logger.error('missing field $name in page ${fullName()}');
     }
     return rc;
@@ -90,8 +81,8 @@ class PageModel extends ModelBase {
   /// If [filter] is none all widgets will be returned.
   List<dynamic> getWidgets(FilterWidget filter) {
     final rc = filter == null
-        ? widgets
-        : widgets.fold([], (prevValue, item) {
+        ? sections[0].children
+        : sections[0].children.fold([], (prevValue, item) {
             if (filter(item)) {
               prevValue.add(item);
             }
@@ -102,9 +93,7 @@ class PageModel extends ModelBase {
 
   /// Tests whether the field with [name] is part of the page.
   bool hasField(String name) {
-    final first = fields.firstWhere((element) => element.name == name,
-        orElse: () => null);
-    final rc = first != null;
+    final rc = models.containsKey(name) && models[name] is FieldModel;
     return rc;
   }
 
@@ -116,11 +105,12 @@ class PageModel extends ModelBase {
     }
     checkSuperfluousAttributes(
         map,
-        'options page pageType sections sql tableColumns tableTitles'
+        'options page pageType sections sql tableColumns tableTitles title'
             .split(' '));
     pageModelType = parseEnum<PageModelType>(
         'pageType', map, PageModelType.values,
         required: true);
+    title = parseString('title', map, required: true);
     if (!map.containsKey('sections')) {
       logger.error('missing sections in page ${fullName()}');
     } else {
@@ -149,23 +139,29 @@ class PageModel extends ModelBase {
     }
 
     if (!options.contains('noAutoButton')) {
-      final section = null;
+      final section = sections[0];
       switch (pageModelType) {
         case PageModelType.list:
           if (buttonByName('search', required: false) == null) {
-            addButton(ButtonModel.direct(section, this, 'search', 'Suchen',
-                ButtonModelType.search, [], logger));
+            final model = ButtonModel.direct(section, this, 'search', 'Suchen',
+                ButtonModelType.search, [], logger);
+            addModel(model);
+            sections[0].buttonBar.insert(0, model);
           }
           break;
         case PageModelType.create:
         case PageModelType.change:
           if (buttonByName('cancel', required: false) == null) {
-            addButton(ButtonModel.direct(section, this, 'cancel', 'Abbruch',
-                ButtonModelType.cancel, [], logger));
+            final model = ButtonModel.direct(section, this, 'cancel', 'Abbruch',
+                ButtonModelType.cancel, [], logger);
+            addModel(model);
+            sections[0].buttonBar.insert(0, model);
           }
           if (buttonByName('store', required: false) == null) {
-            addButton(ButtonModel.direct(section, this, 'store', 'Speichern',
-                ButtonModelType.store, [], logger));
+            final model = ButtonModel.direct(section, this, 'store',
+                'Speichern', ButtonModelType.store, [], logger);
+            addModel(model);
+            sections[0].buttonBar.insert(0, model);
           }
           break;
         default:
index fff24198c5654e96a311cfc9d9cfaa4e8938e1d7..e44c4c7878826cb6149f959c105fbc60c170ca9f 100644 (file)
@@ -17,10 +17,11 @@ class SectionModel extends WidgetModel {
   static final regExprOptions = RegExp(r'^(unknown)$');
   SectionModelType sectionModelType;
   final children = <WidgetModel>[];
-  final buttons = <WidgetModel>[];
-  final int no;
+  final buttonBar = <WidgetModel>[];
+  final int sectionNo;
 
-  SectionModel(this.no, PageModel page, SectionModel section, BaseLogger logger)
+  SectionModel(
+      this.sectionNo, PageModel page, SectionModel section, BaseLogger logger)
       : super(section, page, ModelTypeBones.section, null, logger);
 
   /// Dumps the internal structure into a [stringBuffer]
@@ -39,6 +40,58 @@ class SectionModel extends WidgetModel {
   String fullName() =>
       section == null ? '${page.name}.$name' : '${section.name}.$name';
 
+  WidgetModel buildModel(child, String parentName, int no) {
+    WidgetModel model;
+    if (!ModelBase.isMap(child)) {
+      logger.error('child $no of "children" is not a map in ${fullName()}: '
+          '${StringUtils.limitString(child.toString(), 80)}');
+    } else {
+      if (!child.containsKey('modelType')) {
+        logger.error(
+            'child $no of "$parentName" does not have "modelType" in ${fullName()}: '
+            '${StringUtils.limitString(child.toString(), 80)}');
+      } else {
+        final modelType = StringUtils.stringToEnum<ModelTypeBones>(
+            child['modelType'].toString(), ModelTypeBones.values);
+        switch (modelType) {
+          case ModelTypeBones.allDbFields:
+            model = AllDbFieldsModel(this, page, logger);
+            break;
+          case ModelTypeBones.checkbox:
+            model = CheckboxModel(this, page, logger);
+            break;
+          case ModelTypeBones.combobox:
+            model = ComboboxModel(this, page, logger);
+            break;
+          case ModelTypeBones.textField:
+            model = TextFieldModel(this, page, logger);
+            break;
+          case ModelTypeBones.button:
+            model = ButtonModel(this, page, logger);
+            break;
+          case ModelTypeBones.text:
+            model = TextModel(this, page, logger);
+            break;
+          case ModelTypeBones.emptyLine:
+            model = EmptyLineModel(this, page, logger);
+            break;
+          case ModelTypeBones.dbReference:
+            model = DbReferenceModel(this, page, logger);
+            break;
+          default:
+            //@ToDo: nested section
+            logger.error(
+                'Section: unknown "modelType" ${child["modelType"]} in ${fullName()}');
+            break;
+        }
+        if (model != null){
+          model.parse(child);
+        }
+      }
+    }
+    return model;
+  }
+
   /// Parses the [map]and stores the data in the instance.
   void parse(Map map) {
     super.parseBase(map, nameLabel: 'section');
@@ -47,8 +100,10 @@ class SectionModel extends WidgetModel {
     }
     sectionModelType = parseEnum<SectionModelType>(
         'sectionType', map, SectionModelType.values);
-    checkSuperfluousAttributes(map,
-        'children fields modelType section options sectionType'.split(' '));
+    checkSuperfluousAttributes(
+        map,
+        'buttons children fields modelType section options sectionType'
+            .split(' '));
 
     checkOptionsByRegExpr(regExprOptions);
     if (!map.containsKey('children')) {
@@ -62,73 +117,32 @@ class SectionModel extends WidgetModel {
         int no = 0;
         for (var child in childrenList) {
           no++;
-          if (!ModelBase.isMap(child)) {
-            logger
-                .error('child $no of "children" is not a map in ${fullName()}: '
-                    '${StringUtils.limitString(child.toString(), 80)}');
-          } else {
-            if (!child.containsKey('modelType')) {
-              logger.error(
-                  'child $no of "children" does not have "modelType" in ${fullName()}: '
-                  '${StringUtils.limitString(child.toString(), 80)}');
-            } else {
-              final modelType = StringUtils.stringToEnum<ModelTypeBones>(
-                  child['modelType'].toString(), ModelTypeBones.values);
-              WidgetModel widget;
-              switch (modelType) {
-                case ModelTypeBones.allDbFields:
-                  widget = AllDbFieldsModel(this, page, logger);
-                  break;
-                case ModelTypeBones.checkbox:
-                  widget = CheckboxModel(this, page, logger);
-                  break;
-                case ModelTypeBones.combobox:
-                  widget = ComboboxModel(this, page, logger);
-                  break;
-                case ModelTypeBones.textField:
-                  widget = TextFieldModel(this, page, logger);
-                  break;
-                case ModelTypeBones.button:
-                  widget = ButtonModel(this, page, logger);
-                  break;
-                case ModelTypeBones.text:
-                  widget = TextModel(this, page, logger);
-                  break;
-                case ModelTypeBones.emptyLine:
-                  widget = EmptyLineModel(this, page, logger);
-                  break;
-                case ModelTypeBones.dbReference:
-                  widget = DbReferenceModel(this, page, logger);
-                  break;
-                default:
-                  //@ToDo: nested section
-                  logger.error(
-                      'Section: unknown "modelType" ${child["modelType"]} in ${fullName()}');
-                  break;
-              }
-              if (widget != null) {
-                children.add(widget);
-                widget.parse(child);
-                page.widgets.add(widget);
-                switch (widget.modelType) {
-                  case ModelTypeBones.button:
-                    page.addButton(widget);
-                    break;
-                  case ModelTypeBones.textField:
-                  case ModelTypeBones.combobox:
-                  case ModelTypeBones.checkbox:
-                  case ModelTypeBones.dbReference:
-                    page.addField(widget);
-                    break;
-                  default:
-                    break;
-                }
-              }
-            }
+          WidgetModel widget = buildModel(child, 'children', no);
+          if (widget != null) {
+            children.add(widget);
+            page.addModel(widget);
+          }
+        }
+      }
+      //parseSections(page, this, childrenList, logger);
+    }
+    if (map.containsKey('buttonBar')) {
+      final childrenList = map['buttonBar'];
+      if (!ModelBase.isList(childrenList)) {
+        logger.error('"children" is not a list in ${fullName()}: '
+            '${StringUtils.limitString(childrenList.toString(), 80)}');
+      } else {
+        int no = 0;
+        for (var child in childrenList) {
+          no++;
+          WidgetModel model = buildModel(child, 'buttonBar', no);
+          if (model != null) {
+            buttonBar.add(model);
+            page.addModel(model);
           }
         }
-        //parseSections(page, this, childrenList, logger);
       }
+      //parseSections(page, this, childrenList, logger);
     }
   }
 
index f9b47dfe0fbc0bd30d9a5ff9eddb9e398fd38ccd..537727031fbc2589b1fa0759e050c4782a3b8289 100644 (file)
@@ -6,8 +6,8 @@ import '../module_model.dart';
 class ConfigurationModel extends ModuleModel {
   static final yamlMap = <String, dynamic>{
     //
-    "module": "configuration",
-    "tables": [
+    'module': 'configuration',
+    'tables': [
       {
         // configuration_id | configuration_scope | configuration_property | configuration_order | configuration_type
         // | configuration_value | configuration_description
@@ -66,55 +66,58 @@ class ConfigurationModel extends ModuleModel {
     ],
     'pages': [
       {
-        "page": "create",
-        "pageType": "create",
-        "sections": [
+        'page': 'create',
+        'title': 'Konfiguration hinzufĂĽgen',
+        'pageType': 'create',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               }
             ]
           }
         ]
       },
       {
-        "page": "change",
-        "pageType": "change",
-        "sections": [
+        'page': 'change',
+        'title': 'Konfiguration Ă¤ndern',
+        'pageType': 'change',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               }
             ]
           }
         ]
       },
       {
-        "page": "list",
-        "pageType": "list",
-        "tableColumns":
-            "configuration_id configuration_scope configuration_property configuration_order configuration_value configuration_type",
-        "tableTitles": ";Id;Bereich;Eigenschaft;Reihe;Wert;Typ",
-        "sections": [
+        'page': 'list',
+        'title': 'Konfiguration',
+        'pageType': 'list',
+        'tableColumns':
+            'configuration_id configuration_scope configuration_property configuration_order configuration_value configuration_type',
+        'tableTitles': ';Id;Bereich;Eigenschaft;Reihe;Wert;Typ',
+        'sections': [
           {
-            "sectionType": "filterPanel",
-            "children": [
+            'sectionType': 'filterPanel',
+            'children': [
               {
-                "name": "configuration_scope",
-                "label": "Bereich",
-                'modelType': "textField",
-                "filterType": "pattern",
-                //"options": 'undef',
+                'name': 'configuration_scope',
+                'label': 'Bereich',
+                'modelType': 'textField',
+                'filterType': 'pattern',
+                //'options': 'undef',
               },
               {
-                "name": "configuration_property",
-                "label": "Eigenschaft",
-                'modelType': "textField",
-                "filterType": "pattern",
+                'name': 'configuration_property',
+                'label': 'Eigenschaft',
+                'modelType': 'textField',
+                'filterType': 'pattern',
               },
             ]
           }
index f4bf5ec7e0c18ee897d6f5ce9c393cfc699b05dd..e3c768798519ae1361ca49d7f5e66c68c63a7050 100644 (file)
@@ -16,20 +16,20 @@ class MenuModel extends ModuleModel {
             'options': 'primary',
           },
           {
-            'column': 'menu_name',
-            'dataType': 'string',
-            'label': 'Name',
-            'size': 64,
-            'options': 'unique notnull',
+            'column': 'menu_role',
+            'dataType': 'reference',
+            'label': 'Rolle',
+            'foreignKey': 'role.role_id role_name',
+            'listType': 'dbColumn',
+            'listOption': 'all.role.list;role_name role_id;',
           },
           {
-            'column': 'menu_icon',
+            'column': 'menu_starter',
             'dataType': 'reference',
-            'label': 'Bild',
-            'foreignKey': 'configuration.configuration_id configuration_value',
-            'listType': 'configuration',
-            'listOption':
-                'std.scope.icons;configuration_value configuration_id;',
+            'label': 'Programmpunkt',
+            'foreignKey': 'starter.starter_id starter_name',
+            'listType': 'dbColumn',
+            'listOption': 'all.starter.list;starter_name starter_id;',
           },
         ]
       },
@@ -37,6 +37,7 @@ class MenuModel extends ModuleModel {
     'pages': [
       {
         'page': 'create',
+        'title': 'StartmenĂĽ hinzufĂĽgen',
         'pageType': 'create',
         'sections': [
           {
@@ -51,6 +52,7 @@ class MenuModel extends ModuleModel {
       },
       {
         'page': 'change',
+        'title': 'StartmenĂĽ Ă¤ndern',
         'pageType': 'change',
         'sections': [
           {
@@ -65,9 +67,10 @@ class MenuModel extends ModuleModel {
       },
       {
         'page': 'list',
+        'title': 'StartmenĂĽ',
         'pageType': 'list',
-        'tableColumns': 'menu_id menu_name menu_icon',
-        'tableTitles': ';Id;Name;Bild',
+        'tableColumns': 'menu_id menu_role menu_order menu_name',
+        'tableTitles': ';Id;Rolle;Reihe;Programmpunkt',
         'sections': [
           {
             'sectionType': 'filterPanel',
@@ -75,10 +78,10 @@ class MenuModel extends ModuleModel {
               {
                 'modelType': 'dbReference',
                 'filterType': 'pattern',
-                'name': 'menu_name',
-                'column': 'menu_name',
+                'name': 'menu_role',
+                'column': 'menu_role',
                 'toolTip':
-                    "Filter bezĂĽglich des Namens der anzuzeigenden Einträge: Joker '*' (beliebiger String) ist erlaubt."
+                    'Filter bezĂĽglich der Rolle der anzuzeigenden Einträge.'
               },
             ]
           }
index 937f3034f69f3fcae300adebdab059396b010657..6cb2a396fabefc26c42e5f4a494de390ab06b04e 100644 (file)
@@ -3,9 +3,11 @@ import 'package:dart_bones/dart_bones.dart';
 import '../module_model.dart';
 
 class RoleModel extends ModuleModel {
+  RoleModel(BaseLogger logger) : super(yamlMap, logger);
+
   static final yamlMap = <String, dynamic>{
-    "module": "role",
-    "tables": [
+    'module': 'role',
+    'tables': [
       {
         'table': 'role',
         'columns': [
@@ -38,59 +40,69 @@ class RoleModel extends ModuleModel {
     ],
     'pages': [
       {
-        "page": "create",
-        "pageType": "create",
-        "sections": [
+        'page': 'create',
+        'title': 'Rolle hinzufĂĽgen',
+        'pageType': 'create',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               }
             ]
           }
         ]
       },
       {
-        "page": "change",
-        "pageType": "change",
-        "sections": [
+        'page': 'change',
+        'title': 'Rolle Ă¤ndern',
+        'pageType': 'change',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               }
             ]
           }
         ]
       },
       {
-        "page": "list",
-        "pageType": "list",
-        "tableColumns": "role_id role_name role_priority",
-        "tableTitles": ";Id;Rolle;Priorität",
-        "sections": [
+        'page': 'list',
+        'title': 'Rollen',
+        'pageType': 'list',
+        'tableColumns': 'role_id role_name role_priority',
+        'tableTitles': ';Id;Rolle;Priorität',
+        'sections': [
           {
-            "sectionType": "filterPanel",
-            "children": [
+            'sectionType': 'filterPanel',
+            'children': [
               {
-                'modelType': "textField",
-                "filterType": "pattern",
-                "name": "role_name",
-                "label": "Name",
-                "toolTip":
-                    "Suchmuster des Rollennamens: Joker: '*' (beliebiger Text), z.B. '*min*'"
-              }
-            ]
+                'modelType': 'textField',
+                'filterType': 'pattern',
+                'name': 'role_name',
+                'label': 'Name',
+                'toolTip':
+                    'Suchmuster des Rollennamens: Joker: "*" (beliebiger Text), z.B. "*min*"'
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neue Rolle',
+                'toolTip': 'Erzeugen einer neuen Rolle'
+              },
+            ],
           }
-        ]
-      },
-    ],
+        ],
+      }
+    ]
   };
 
-  RoleModel(BaseLogger logger) : super(yamlMap, logger);
-
   /// Returns the name including the names of the parent
   @override
   String fullName() => name;
diff --git a/lib/src/model/standard/standard_modules.dart b/lib/src/model/standard/standard_modules.dart
deleted file mode 100644 (file)
index efd1217..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-import '../module_model.dart';
-import 'role_model.dart';
-import 'user_model.dart';
-import 'menu_model.dart';
-import 'configuration_model.dart';
-
-/// Returns an instance of a standard module given by [name].
-ModuleModel standardModule(String name, BaseLogger logger) {
-  var rc;
-  switch (name) {
-    case 'role':
-      rc = RoleModel(logger);
-      break;
-    case 'user':
-      rc = UserModel(logger);
-      break;
-    case 'configuration':
-      rc = ConfigurationModel(logger);
-      break;
-    case 'menu':
-      rc = MenuModel(logger);
-      break;
-    default:
-      logger.error('unknown standard module: $name');
-      break;
-  }
-  return rc;
-}
-
-/// Returns the names of the standard modules.
-List<String> standardModules() => ['configuration', 'menu', 'role', 'user'];
diff --git a/lib/src/model/standard/starter_model.dart b/lib/src/model/standard/starter_model.dart
new file mode 100644 (file)
index 0000000..eb090c3
--- /dev/null
@@ -0,0 +1,101 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class StarterModel extends ModuleModel {
+  static final mapStarter = <String, dynamic>{
+    'module': 'starter',
+    'tables': [
+      {
+        'table': 'starter',
+        'columns': [
+          {
+            'column': 'starter_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'starter_name',
+            'dataType': 'string',
+            'label': 'Name',
+            'size': 64,
+            'options': 'unique notnull',
+          },
+          {
+            'column': 'starter_icon',
+            'dataType': 'reference',
+            'label': 'Bild',
+            'foreignKey': 'configuration.configuration_id configuration_value',
+            'listType': 'configuration',
+            'listOption':
+                'std.scope.icons;configuration_value configuration_id;',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Programmpunkt hinzufĂĽgen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Programmpunkt hinzufĂĽgen',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              },
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Programmpunkte',
+        'pageType': 'list',
+        'tableColumns': 'starter_id starter_name starter_icon',
+        'tableTitles': ';Id;Name;Bild',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'modelType': 'dbReference',
+                'filterType': 'pattern',
+                'name': 'starter_name',
+                'column': 'starter_name',
+                'toolTip':
+                    'Filter bezĂĽglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
+              },
+            ]
+          }
+        ]
+      },
+    ]
+  };
+
+  StarterModel(BaseLogger logger) : super(mapStarter, logger);
+
+  /// Returns the name including the names of the parent
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
index ef8a81e2ab54c95ef5fccecfa8661c432d0425d3..23c64236da77b53f305b886ff2d0686325a51d9a 100644 (file)
@@ -4,8 +4,8 @@ import '../module_model.dart';
 
 class UserModel extends ModuleModel {
   static final mapUser = <String, dynamic>{
-    "module": "user",
-    "tables": [
+    'module': 'user',
+    'tables': [
       {
         'table': 'user',
         'columns': [
@@ -49,136 +49,141 @@ class UserModel extends ModuleModel {
             'dataType': 'reference',
             'label': 'Rolle',
             'foreignKey': 'role.role_id role_name',
-            "listType": "dbColumn",
-            "listOption": "all.role.list;role_name role_id;:role_name=%",
-            "options": "undef",
-            "defaultValue": "4",
+            'listType': 'dbColumn',
+            'listOption': 'all.role.list;role_name role_id;:role_name=%',
+            'options': 'undef',
+            'defaultValue': '4',
           },
         ]
       },
     ],
     'pages': [
       {
-        "page": "create",
-        "pageType": "create",
-        "sections": [
+        'page': 'create',
+        'title': 'Benutzer hinzufĂĽgen',
+        'pageType': 'create',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               }
             ]
           }
         ]
       },
       {
-        "page": "change",
-        "pageType": "change",
-        "sections": [
+        'page': 'change',
+        'title': 'Benutzer Ă¤ndern',
+        'pageType': 'change',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "allDbFields",
+                'modelType': 'allDbFields',
               },
               {
-                'modelType': "button",
-                "name": "set_password",
-                "label": "Passwort Ă¤ndern",
-                "buttonType": "custom",
+                'modelType': 'button',
+                'name': 'set_password',
+                'label': 'Passwort Ă¤ndern',
+                'buttonType': 'custom',
               },
             ]
           }
         ]
       },
       {
-        "page": "password",
-        "pageType": "change",
-        "sql": "update_pw",
-        "sections": [
+        'page': 'password',
+        'title': 'Passwort Ă¤ndern',
+        'pageType': 'change',
+        'sql': 'update_pw',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "text",
-                "text": "Ă„ndern des Passworts von Benutzer ~user~",
-                "options": "placeholder h3"
+                'modelType': 'text',
+                'text': 'Ă„ndern des Passworts von Benutzer ~user~',
+                'options': 'placeholder h3'
               },
               {
-                'modelType': "textField",
-                "name": "user_password",
-                "label": "Passwort",
-                "options": "password",
-                "validators": "required",
+                'modelType': 'textField',
+                'name': 'user_password',
+                'label': 'Passwort',
+                'options': 'password',
+                'validators': 'required',
               },
               {
-                'modelType': "textField",
-                "name": "repetition",
-                "label": "Wiederholung",
-                "options": "password",
-                "validators": "required equals=user_password"
+                'modelType': 'textField',
+                'name': 'repetition',
+                'label': 'Wiederholung',
+                'options': 'password',
+                'validators': 'required equals=user_password'
               },
             ]
           }
         ]
       },
       {
-        "page": "list",
-        "pageType": "list",
-        "tableColumns":
-            "user_id user_name user_displayname user_email user_role",
-        "tableTitles": ";Id;Name;Anzeigename;EMail;Rolle",
-        "sections": [
+        'page': 'list',
+        'title': 'Benutzer',
+        'pageType': 'list',
+        'tableColumns':
+            'user_id user_name user_displayname user_email user_role',
+        'tableTitles': ';Id;Name;Anzeigename;EMail;Rolle',
+        'sections': [
           {
-            "sectionType": "filterPanel",
-            "children": [
+            'sectionType': 'filterPanel',
+            'children': [
               {
-                'modelType': "dbReference",
-                "filterType": "pattern",
-                "name": "user_name",
-                "column": "user_name",
-                "toolTip":
-                    "Filter bezĂĽglich des Namens der anzuzeigenden Einträge: Joker '*' (beliebiger String) ist erlaubt."
+                'modelType': 'dbReference',
+                'filterType': 'pattern',
+                'name': 'user_name',
+                'column': 'user_name',
+                'toolTip':
+                    'Filter bezĂĽglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
               },
               {
-                'modelType': "dbReference",
-                "name": "user_role",
-                "filterType": "equals",
-                "column": "user_role",
-                "toolTip":
-                    "Filter bezĂĽglich der Rolle der anzuzeigenden Einträge. '-' bedeutet keine Einschränkung",
+                'modelType': 'dbReference',
+                'name': 'user_role',
+                'filterType': 'equals',
+                'column': 'user_role',
+                'toolTip':
+                    'Filter bezĂĽglich der Rolle der anzuzeigenden Einträge. "-" bedeutet keine Einschränkung',
               }
             ]
           }
         ]
       },
       {
-        "page": "login",
-        "pageType": "change",
-        "options": "noAutoButton",
-        "sections": [
+        'page': 'login',
+        'title': 'Anmelden',
+        'pageType': 'change',
+        'options': 'noAutoButton',
+        'sections': [
           {
-            "sectionType": "simpleForm",
-            "children": [
+            'sectionType': 'simpleForm',
+            'children': [
               {
-                'modelType': "textField",
-                "name": "user",
-                "label": "Benutzer oder EMail",
-                "validators": "required",
+                'modelType': 'textField',
+                'name': 'user',
+                'label': 'Benutzer oder EMail',
+                'validators': 'required',
               },
               {
-                'modelType': "textField",
-                "name": "password",
-                "label": "Password",
-                "options": "password",
-                "validators": "required",
+                'modelType': 'textField',
+                'name': 'password',
+                'label': 'Password',
+                'options': 'password',
+                'validators': 'required',
               },
               {
-                'modelType': "button",
-                "buttonType": "custom",
-                "name": "login",
-                "label": "Anmelden",
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'login',
+                'label': 'Anmelden',
               },
             ]
           }
index 3db0d3e7287c318aa020d193d2a772cceea9ac1b..be00740a7a723029fea5df101f056856100099d5 100644 (file)
@@ -5,6 +5,10 @@ import '../persistence/persistence.dart';
 import '../persistence/persistence_cache.dart';
 import '../widget/page_controller_bones.dart';
 
+abstract class FooterBones {
+  List<Widget> widgets(PageControllerBones controller);
+}
+
 /// Data class for storing parameter to build a page.
 class ApplicationData {
   final BaseConfiguration configuration;
@@ -13,6 +17,7 @@ class ApplicationData {
   /// Signature: AppBar func(String title)
   final AppBar Function(String title) appBarBuilder;
   final Drawer Function(dynamic context) drawerBuilder;
+  final FooterBones Function() footerBuilder;
   final Persistence persistence;
   PersistenceCache persistenceCache;
   int currentUserId;
@@ -33,9 +38,10 @@ class ApplicationData {
   /// Constructor.
   /// [configuration] is a map with the widget data (e.g. padding)
   /// [appBarBuilder] is a factory of a function returning a outside designed AppBar
-  /// [drawerBuilder] is a factory of a function returning a Drawer which handles the "Hamburger menu"
+  /// [footerBuilder] is a factory of a function returning a footer area (as one widget)
+  ///
   ApplicationData(this.configuration, this.appBarBuilder, this.drawerBuilder,
-      this.persistence, this.logger) {
+      this.footerBuilder, this.persistence, this.logger) {
     currentUserId = 0;
     currentUserName = 'Gast';
     currentRoleId = 0;
index fddb64901433e4530d5e541add75fb9b7de40a2e..e217a4b532c6b65495b46b7bfa05319bbd9aaf34 100644 (file)
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_markdown/flutter_markdown.dart';
 
 class DemoPage extends StatefulWidget {
   final ApplicationData pageData;
@@ -30,32 +31,58 @@ class DemoPageState extends State<DemoPage> {
     final items = texts
         .map((text) => DropdownMenuItem<String>(value: text, child: Text(text)))
         .toList();
-    return Scaffold(
-        appBar: pageData.appBarBuilder('Demo'),
-        drawer: pageData.drawerBuilder(context),
-        body: SimpleForm.simpleForm(
-          key: _formKey,
-          configuration: pageData.configuration,
-          fields: <Widget>[
-            TextFormField(
-              validator: checkNotEmpty,
-              decoration: InputDecoration(labelText: 'Name'),
-              onSaved: (input) => item.name = input,
-            ),
-            DropdownButtonFormField<String>(
-              value: 'two',
-              items: items,
-              decoration: InputDecoration(labelText: 'Number'),
-              onChanged: (value) => item.number = value,
-            ),
-          ],
-          buttons: <Widget>[
-            RaisedButton(
-              onPressed: () => login(context),
-              child: Text('Anmelden'),
-            ),
-          ],
-        ));
+    const String _markdownData = """
+# Minimal Markdown Test
+---
+This is a simple Markdown test. Provide a text string with Markdown tags
+to the Markdown widget and it will display the formatted output in a
+scrollable widget.
+
+## Section 1
+Maecenas eget **arcu egestas**, mollis ex vitae, posuere magna. Nunc eget
+aliquam tortor. Vestibulum porta sodales efficitur. Mauris interdum turpis
+eget est condimentum, vitae porttitor diam ornare.
+
+### Subsection A
+Sed et massa finibus, blandit massa vel, vulputate velit. Vestibulum vitae
+venenatis libero. **__Curabitur sem lectus, feugiat eu justo in, eleifend
+accumsan ante.__** Sed a fermentum elit. Curabitur sodales metus id mi
+ornare, in ullamcorper magna congue.
+""";
+    if ('a'.startsWith('a')) {
+      return Scaffold(
+          appBar: pageData.appBarBuilder('Demo'),
+          drawer: pageData.drawerBuilder(context),
+          body: SafeArea(child: Markdown(data: _markdownData)));
+    } else {
+      return Scaffold(
+          appBar: pageData.appBarBuilder('Demo'),
+          drawer: pageData.drawerBuilder(context),
+          body: SimpleForm.simpleForm(
+            key: _formKey,
+            configuration: pageData.configuration,
+            intro: [Markdown(data: _markdownData)],
+            fields: <Widget>[
+              TextFormField(
+                validator: checkNotEmpty,
+                decoration: InputDecoration(labelText: 'Name'),
+                onSaved: (input) => item.name = input,
+              ),
+              DropdownButtonFormField<String>(
+                value: 'two',
+                items: items,
+                decoration: InputDecoration(labelText: 'Number'),
+                onChanged: (value) => item.number = value,
+              ),
+            ],
+            buttons: <Widget>[
+              RaisedButton(
+                onPressed: () => login(context),
+                child: Text('Anmelden'),
+              ),
+            ],
+          ));
+    }
   }
 
   void login(context) async {
index 391455aaedf5a64a5fe2f6243d8560329bc44efe..5347a98f8296de5edd2afe0fc01691535402953e 100644 (file)
@@ -1,5 +1,6 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/page/starter/starter_list_page.dart';
 
 import '../application_data.dart';
 import '../configuration/configuration_list_page.dart';
@@ -38,6 +39,9 @@ class MenuConverter {
         rc = UserPasswordPage(applicationData.currentUserId,
             applicationData.currentUserName, applicationData, null);
         break;
+      case 'starter.list':
+        rc = StarterListPage(applicationData);
+        break;
       case 'demo':
         rc = DemoPage(applicationData);
         break;
@@ -92,6 +96,9 @@ class MenuConverter {
       case 'construction_outlined':
         rc = Icons.construction_outlined;
         break;
+      case 'radio_button_checked_outlined':
+        rc = Icons.radio_button_checked_outlined;
+        break;
       default:
         logger.error('MenuConverter.iconByName(): unknown icon $name');
         break;
index 3c12af1eaa05bd05f75f8fe51899b8467141d32e..73b942a3306bb2d8e034aa47111556a4083cc309 100644 (file)
@@ -1,25 +1,35 @@
 import 'package:flutter/material.dart';
+
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'role_controller.dart';
 
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+//! pageType: change
+
 class RoleChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
 
-  //RoleChangePageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   RoleChangePage(this.primaryId, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   RoleChangePageState createState() {
-    final rc = RoleChangePageState(primaryId, applicationData, initialRow);
+    //! === BeginOfCall ===
+    final rc =
+        RoleChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -27,23 +37,32 @@ class RoleChangePage extends StatefulWidget {
   }
 }
 
-class RoleChangePageState extends State<RoleChangePage> implements RedrawPage {
+abstract class RoleChangePageState extends State<RoleChangePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'role_change');
+  GlobalKey<FormState>(debugLabel: 'role_change');
 
   RoleController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   RoleChangePageState(this.primaryId, this.applicationData, this.initialRow);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Rolle Ă¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        persistentFooterButtons:
+            applicationData.footerBuilder().widgets(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -51,6 +70,15 @@ class RoleChangePageState extends State<RoleChangePage> implements RedrawPage {
           primaryId: primaryId,
           initialRow: initialRow,
         ));
+    //! === EndOfScaffold ===
+  }
+
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
+  void dispose() {
+    controller.dispose();
+    super.dispose();
   }
 
   @override
@@ -59,6 +87,7 @@ class RoleChangePageState extends State<RoleChangePage> implements RedrawPage {
     controller =
         RoleController(_formKey, this, 'change', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -69,9 +98,17 @@ class RoleChangePageState extends State<RoleChangePage> implements RedrawPage {
           customString: customString, callback: callback);
     });
   }
+}
 
-  void dispose() {
-    controller.dispose();
-    super.dispose();
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class RoleChangePageStateCustomized extends RoleChangePageState {
+  RoleChangePageStateCustomized(int primaryId, ApplicationData applicationData,
+      Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
   }
 }
index 0ba6a1b102380463257f89e2fb3c555fb76f9304..ed578ac80efa07a2ef755283114dc79a654ccad7 100644 (file)
@@ -6,15 +6,25 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'role_controller.dart';
 
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+//! pageType: change
+
 class RoleCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   RoleCreatePage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   RoleCreatePageState createState() {
-    final rc = RoleCreatePageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = RoleCreatePageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,7 +32,8 @@ class RoleCreatePage extends StatefulWidget {
   }
 }
 
-class RoleCreatePageState extends State<RoleCreatePage> implements RedrawPage {
+abstract class RoleCreatePageState extends State<RoleCreatePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
@@ -30,27 +41,40 @@ class RoleCreatePageState extends State<RoleCreatePage> implements RedrawPage {
 
   RoleController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   RoleCreatePageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Neue Rolle'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        persistentFooterButtons:
+            applicationData.footerBuilder().widgets(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
         ));
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
   @override
   void initState() {
     super.initState();
     controller =
         RoleController(_formKey, this, 'create', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -62,3 +86,15 @@ class RoleCreatePageState extends State<RoleCreatePage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class RoleCreatePageStateCustomized extends RoleCreatePageState {
+  RoleCreatePageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index d5966f5db55a49264cbadbfbfc17bb73322ca109..77af1b718e6803bbc37f4dfd54c94817de46b5b3 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/page_model.dart';
 import '../../widget/list_form.dart';
@@ -6,15 +7,24 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'role_controller.dart';
 
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+//! pageType: change
+
 class RoleListPage extends StatefulWidget {
   final ApplicationData applicationData;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   RoleListPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   RoleListPageState createState() {
-    // RoleListPageState.setPageData(pageData);
-    final rc = RoleListPageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = RoleListPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,7 +32,8 @@ class RoleListPage extends StatefulWidget {
   }
 }
 
-class RoleListPageState extends State<RoleListPage> implements RedrawPage {
+abstract class RoleListPageState extends State<RoleListPage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
@@ -30,14 +41,23 @@ class RoleListPageState extends State<RoleListPage> implements RedrawPage {
   Iterable<dynamic> rowsDeprecated;
   RoleController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   RoleListPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+
+    //! === BeginOfScaffold ===
     return Scaffold(
-      appBar: applicationData.appBarBuilder('Rollen'),
+      appBar: applicationData.appBarBuilder(controller.page.title),
       drawer: applicationData.drawerBuilder(context),
+      persistentFooterButtons:
+          applicationData.footerBuilder().widgets(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -47,29 +67,27 @@ class RoleListPageState extends State<RoleListPage> implements RedrawPage {
         showEditIcon: true,
         pageController: controller,
         buttons: <Widget>[
-          ButtonBar(alignment: MainAxisAlignment.center, children: [
-            controller.searchButton(),
-            RaisedButton(
-              child: Text('Neue Rolle'),
-              onPressed: () {
-                controller.goTo(pageType: PageModelType.create);
-              },
-            ),
-          ]),
-        ],
+          ButtonBar(alignment: MainAxisAlignment.center,
+              children: View().modelsToWidgets(controller.page.sections[0].buttonBar, controller),
+          )],
         filters: controller.modelList,
         errorMessage:
             applicationData.lastErrorMessage(controller.page.fullName()),
       ),
     );
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
   @override
   void initState() {
     super.initState();
     controller =
         RoleController(_formKey, this, 'list', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -81,3 +99,19 @@ class RoleListPageState extends State<RoleListPage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class RoleListPageStateCustomized extends RoleListPageState {
+  RoleListPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    final button = controller.page.buttonByName('new');
+    button.onPressed = () {
+      controller.goTo(pageType: PageModelType.create);
+    };
+    controller.modelList.addModel(button.name, button);
+  }
+}
diff --git a/lib/src/page/starter/starter_change_page.dart b/lib/src/page/starter/starter_change_page.dart
new file mode 100644 (file)
index 0000000..453c516
--- /dev/null
@@ -0,0 +1,85 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'starter_controller.dart';
+
+class StarterChangePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //StarterChangePageState lastState;
+
+  StarterChangePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  @override
+  StarterChangePageState createState() {
+    final rc = StarterChangePageState(primaryId, applicationData, initialRow);
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+class StarterChangePageState extends State<StarterChangePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'starter_change');
+
+  StarterController controller;
+
+  StarterChangePageState(this.primaryId, this.applicationData, this.initialRow);
+
+  @override
+  Widget build(BuildContext context) {
+    controller.beginOfBuild(context);
+    // controller.buildWidgetList() is called in editForm
+    return Scaffold(
+        appBar: applicationData.appBarBuilder('StartmenĂĽ Ă¤ndern'),
+        drawer: applicationData.drawerBuilder(context),
+        body: EditForm.editForm(
+          key: _formKey,
+          pageController: controller,
+          configuration: applicationData.configuration,
+          primaryId: primaryId,
+          initialRow: initialRow,
+        ));
+  }
+
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    controller = StarterController(
+      _formKey,
+      this,
+      'change',
+      context,
+      applicationData,
+    );
+    controller.initialize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
+  }
+}
diff --git a/lib/src/page/starter/starter_controller.dart b/lib/src/page/starter/starter_controller.dart
new file mode 100644 (file)
index 0000000..44d5426
--- /dev/null
@@ -0,0 +1,26 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/standard/starter_model.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'starter_change_page.dart';
+
+class StarterController extends PageControllerBones {
+  /// Controller for a page named [pageName].
+  StarterController(GlobalKey<FormState> formKey, RedrawPage parent,
+      String pageName, BuildContext context, ApplicationData applicationData,
+      {Function redrawCallback})
+      : super(formKey, parent, StarterModel(Settings().logger), pageName,
+            context, applicationData, redrawCallback) {
+    moduleModel.parse();
+  }
+  @override
+  void startChange(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) => StarterChangePage(id, applicationData, row)));
+  }
+}
diff --git a/lib/src/page/starter/starter_create_page.dart b/lib/src/page/starter/starter_create_page.dart
new file mode 100644 (file)
index 0000000..eab2be4
--- /dev/null
@@ -0,0 +1,65 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'starter_controller.dart';
+
+class StarterCreatePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final logger = Settings().logger;
+
+  StarterCreatePage(this.applicationData, {Key key}) : super(key: key);
+
+  @override
+  StarterCreatePageState createState() {
+    final rc = StarterCreatePageState(applicationData);
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+class StarterCreatePageState extends State<StarterCreatePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'starter_create');
+
+  StarterController controller;
+
+  StarterCreatePageState(this.applicationData);
+
+  @override
+  Widget build(BuildContext context) {
+    controller.beginOfBuild(context);
+    return Scaffold(
+        appBar: applicationData.appBarBuilder('Neues StartmenĂĽ'),
+        drawer: applicationData.drawerBuilder(context),
+        body: EditForm.editForm(
+          key: _formKey,
+          pageController: controller,
+          configuration: applicationData.configuration,
+        ));
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    controller =
+        StarterController(_formKey, this, 'create', context, applicationData);
+    controller.initialize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
+  }
+}
diff --git a/lib/src/page/starter/starter_list_page.dart b/lib/src/page/starter/starter_list_page.dart
new file mode 100644 (file)
index 0000000..20e0f06
--- /dev/null
@@ -0,0 +1,85 @@
+import 'package:flutter/material.dart';
+
+import '../../model/page_model.dart';
+import '../../widget/list_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'starter_controller.dart';
+
+class StarterListPage extends StatefulWidget {
+  final ApplicationData applicationData;
+
+  StarterListPage(this.applicationData, {Key key}) : super(key: key);
+
+  @override
+  StarterListPageState createState() {
+    // StarterListPageState.setPageData(pageData);
+    final rc = StarterListPageState(applicationData);
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+class StarterListPageState extends State<StarterListPage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'starter_list');
+  Iterable<dynamic> rowsDeprecated;
+  StarterController controller;
+
+  StarterListPageState(this.applicationData);
+
+  @override
+  Widget build(BuildContext context) {
+    controller.beginOfBuild(context);
+    return Scaffold(
+        appBar: applicationData.appBarBuilder('Programmpunkte'),
+        drawer: applicationData.drawerBuilder(context),
+        body: ListForm.listForm(
+          key: _formKey,
+          configuration: applicationData.configuration,
+          titles: ListForm.stringsToTitles(controller.page.tableTitles),
+          columnNames: controller.page.tableColumns ?? [],
+          rows: controller.listRows ?? [],
+          showEditIcon: true,
+          pageController: controller,
+          buttons: <Widget>[
+            ButtonBar(alignment: MainAxisAlignment.center, children: [
+              controller.searchButton(),
+              RaisedButton(
+                child: Text('Neuer Programmpunkt'),
+                onPressed: () {
+                  controller.goTo(pageType: PageModelType.create);
+                },
+              ),
+            ]),
+          ],
+          filters: controller.modelList,
+          errorMessage:
+              applicationData.lastErrorMessage(controller.page.fullName()),
+        ),
+        persistentFooterButtons:
+            applicationData.footerBuilder().widgets(controller));
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    controller =
+        StarterController(_formKey, this, 'list', context, applicationData);
+    controller.initialize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
+  }
+}
index 3409db8392e1850fef0535ce7e48dfd91b761f86..e60bea77fe285b74dadd3c3e0e7781eefe5e66b1 100644 (file)
@@ -8,21 +8,30 @@ import '../application_data.dart';
 import 'user_controller.dart';
 import 'user_password_page.dart';
 
+//! === BeginOfGeneratedCode: Below you can change manually:
+//! pageType: change
+
 class UserChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
 
-  //UserChangePageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   UserChangePage(this.primaryId, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   UserChangePageState createState() {
-    final rc = UserChangePageState(primaryId, applicationData, initialRow);
+    //! === BeginOfCall ===
+    final rc =
+        UserChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -30,23 +39,32 @@ class UserChangePage extends StatefulWidget {
   }
 }
 
-class UserChangePageState extends State<UserChangePage> implements RedrawPage {
+abstract class UserChangePageState extends State<UserChangePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user_change');
+  GlobalKey<FormState>(debugLabel: 'user_change');
 
   UserController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   UserChangePageState(this.primaryId, this.applicationData, this.initialRow);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Benutzer Ă¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        persistentFooterButtons:
+            applicationData.footerBuilder().widgets(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -54,20 +72,11 @@ class UserChangePageState extends State<UserChangePage> implements RedrawPage {
           primaryId: primaryId,
           initialRow: initialRow,
         ));
+    //! === EndOfScaffold ===
   }
 
-  void customize() {
-    ButtonModel button = controller.page.buttonByName('set_password');
-    button?.onPressed = () {
-      String userName = controller.page.fieldByName('user_name').value;
-      applicationData.pushCaller(controller);
-      Navigator.push(
-          context,
-          MaterialPageRoute(
-              builder: (context) => UserPasswordPage(
-                  primaryId, userName, applicationData, null)));
-    };
-  }
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
 
   void dispose() {
     controller.dispose();
@@ -92,3 +101,25 @@ class UserChangePageState extends State<UserChangePage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class UserChangePageStateCustomized extends UserChangePageState {
+  UserChangePageStateCustomized(int primaryId, ApplicationData applicationData,
+      Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    ButtonModel button = controller.page.buttonByName('set_password');
+    button?.onPressed = () {
+      String userName = controller.page.fieldByName('user_name').value;
+      applicationData.pushCaller(controller);
+      Navigator.push(
+          context,
+          MaterialPageRoute(
+              builder: (context) => UserPasswordPage(
+                  primaryId, userName, applicationData, null)));
+    };
+  }
+}
index cb100c93fbc84f4b1d6c1163af32a9441c5fc85d..09689a25f00cf14768fc865b2aabd3a06dce9bac 100644 (file)
@@ -6,15 +6,25 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
 
+//! === BeginOfGeneratedCode: Below you can change manually:
+//! pageType: change
+
 class UserCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   UserCreatePage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   UserCreatePageState createState() {
-    final rc = UserCreatePageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = UserCreatePageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,7 +32,8 @@ class UserCreatePage extends StatefulWidget {
   }
 }
 
-class UserCreatePageState extends State<UserCreatePage> implements RedrawPage {
+abstract class UserCreatePageState extends State<UserCreatePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
@@ -30,27 +41,40 @@ class UserCreatePageState extends State<UserCreatePage> implements RedrawPage {
 
   UserController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   UserCreatePageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
-    controller.beginOfBuild(context);
+    controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Neuer Benutzer'),
+        appBar: applicationData.appBarBuilder('Neue Rolle'),
         drawer: applicationData.drawerBuilder(context),
+        persistentFooterButtons:
+            applicationData.footerBuilder().widgets(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
         ));
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
   @override
   void initState() {
     super.initState();
     controller =
         UserController(_formKey, this, 'create', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -62,3 +86,15 @@ class UserCreatePageState extends State<UserCreatePage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class UserCreatePageStateCustomized extends UserCreatePageState {
+  UserCreatePageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index ee7b4c965b7abd99dc72a598cd4c9140ce74398c..16e6939fa7674b62e961d36393a925926076909d 100644 (file)
@@ -7,15 +7,24 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
 
+//! === BeginOfGeneratedCode: Below you can change manually:
+//! pageType: change
+
 class UserListPage extends StatefulWidget {
   final ApplicationData applicationData;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   UserListPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   UserListPageState createState() {
-    // UserListPageState.setPageData(pageData);
-    final rc = UserListPageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = UserListPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -23,7 +32,8 @@ class UserListPage extends StatefulWidget {
   }
 }
 
-class UserListPageState extends State<UserListPage> implements RedrawPage {
+abstract class UserListPageState extends State<UserListPage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
@@ -31,14 +41,23 @@ class UserListPageState extends State<UserListPage> implements RedrawPage {
   Iterable<dynamic> rowsDeprecated;
   UserController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   UserListPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+
+    //! === BeginOfScaffold ===
     return Scaffold(
-      appBar: applicationData.appBarBuilder('Benutzer'),
+      appBar: applicationData.appBarBuilder('Rollen'),
       drawer: applicationData.drawerBuilder(context),
+      persistentFooterButtons:
+          applicationData.footerBuilder().widgets(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -63,14 +82,19 @@ class UserListPageState extends State<UserListPage> implements RedrawPage {
             applicationData.lastErrorMessage(controller.page.fullName()),
       ),
     );
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
   @override
   void initState() {
     super.initState();
     controller =
         UserController(_formKey, this, 'list', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -82,3 +106,15 @@ class UserListPageState extends State<UserListPage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class UserListPageStateCustomized extends UserListPageState {
+  UserListPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 0e6da0a2b098557454e467c96d15f56c012714e8..abf35653f5a05084246dc64113e1ad68359b538d 100644 (file)
@@ -9,6 +9,7 @@ import '../application_data.dart';
 import 'hash.dart';
 import 'user_controller.dart';
 
+// === BeginOfGeneratedCode: Do not change manually
 class UserLoginPage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
@@ -19,7 +20,7 @@ class UserLoginPage extends StatefulWidget {
 
   @override
   UserLoginPageState createState() {
-    final rc = UserLoginPageState(applicationData);
+    final rc = UserLoginPageStateCustomized(applicationData);
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -27,7 +28,8 @@ class UserLoginPage extends StatefulWidget {
   }
 }
 
-class UserLoginPageState extends State<UserLoginPage> implements RedrawPage {
+abstract class UserLoginPageState extends State<UserLoginPage>
+    implements RedrawPage {
   final ApplicationData applicationData;
   final GlobalKey<FormState> _formKey =
       GlobalKey<FormState>(debugLabel: 'user.login');
@@ -50,6 +52,36 @@ class UserLoginPageState extends State<UserLoginPage> implements RedrawPage {
         ));
   }
 
+  void customize();
+
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    controller =
+        UserController(_formKey, this, 'login', context, applicationData);
+    controller.initialize();
+    customize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
+  }
+}
+// === EndOfGeneratedCode: Do not change manually
+
+class UserLoginPageStateCustomized extends UserLoginPageState {
+  UserLoginPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
   void customize() {
     ButtonModel button = controller.page.buttonByName('login');
     button?.onPressed = () {
@@ -85,27 +117,4 @@ class UserLoginPageState extends State<UserLoginPage> implements RedrawPage {
       }
     };
   }
-
-  void dispose() {
-    controller.dispose();
-    super.dispose();
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    controller =
-        UserController(_formKey, this, 'login', context, applicationData);
-    controller.initialize();
-    customize();
-  }
-
-  @override
-  void redraw(RedrawReason reason,
-      {String customString, RedrawCallbackFunctionSimple callback}) {
-    setState(() {
-      controller.afterSetState(reason,
-          customString: customString, callback: callback);
-    });
-  }
 }
index 94c7cc3e51b1a79c70be3eebb665b70f319d4338..9ab874bf8ea8ebe1200cfb7db9533c3554341987 100644 (file)
@@ -86,8 +86,8 @@ class RestPersistence extends Persistence {
   Future delete(
       {@required String module, String sqlName, @required int id}) async {
     sqlName ??= 'delete';
-    await runRequest(module, sqlName, 'delete', body: '{":${module}_id":$id}',
-        headers: jsonHeader);
+    await runRequest(module, sqlName, 'delete',
+        body: '{":${module}_id":$id}', headers: jsonHeader);
   }
 
   @override
@@ -98,8 +98,8 @@ class RestPersistence extends Persistence {
     sqlName ??= 'insert';
     var rc = 0;
     final data2 = data == null ? '{}' : convert.jsonEncode(data);
-    final answer = await runRequest(
-        module, sqlName, 'insert', body: data2, headers: jsonHeader);
+    final answer = await runRequest(module, sqlName, 'insert',
+        body: data2, headers: jsonHeader);
     if (answer.isNotEmpty) {
       final map = convert.jsonDecode(answer);
       rc = map['id'];
@@ -113,8 +113,8 @@ class RestPersistence extends Persistence {
     sqlName ??= 'list';
     Iterable<dynamic> rc;
     final body = params == null ? '{}' : convert.jsonEncode(params);
-    final answer = await runRequest(
-        module, sqlName, 'list', body: body, headers: jsonHeader);
+    final answer = await runRequest(module, sqlName, 'list',
+        body: body, headers: jsonHeader);
     if (answer.isNotEmpty) {
       rc = convert.jsonDecode(answer);
     }
index e7bb134a993a8f26d8d60ff115a746566c6c116d..1ccc01222d9bd14eccb18437d8b7382a81196e16 100644 (file)
@@ -32,8 +32,12 @@ class MenuItem {
           converter.iconByName('build_outlined', logger)),
       MenuItem(
           'StartmenĂĽ',
-          () => converter.pageByName('menu.list', settings.pageData),
+          () => converter.pageByName('starter.list', settings.pageData),
           converter.iconByName('menu_open_outlined', logger)),
+      MenuItem(
+          'Programmpunkte',
+          () => converter.pageByName('menu.list', settings.pageData),
+          converter.iconByName('radio_button_checked_outlined', logger)),
       MenuItem('Demo', () => converter.pageByName('demo', settings.pageData),
           converter.iconByName('article_outlined', logger)),
     ];
@@ -51,11 +55,10 @@ class BonesDrawer extends Drawer {
     final list = MenuItem.menuItems(converter);
     final rc = Card(
         child: GridView.count(
-          crossAxisCount: 2,
-          crossAxisSpacing: 16.0,
-          children: list
-              .map((item) =>
-              GridTile(
+      crossAxisCount: 2,
+      crossAxisSpacing: 16.0,
+      children: list
+          .map((item) => GridTile(
                 child: new InkResponse(
                   enableFeedback: true,
                   child: Card(
@@ -90,12 +93,11 @@ class BonesDrawer extends Drawer {
             shrinkWrap: true,
             physics: ClampingScrollPhysics(),
             children: list
-                .map((item) =>
-                ListTile(
-                  title: Text(item.title),
-                  onTap: () {
-                    // What happens after you tap the navigation item
-                    Navigator.push(
+                .map((item) => ListTile(
+                      title: Text(item.title),
+                      onTap: () {
+                        // What happens after you tap the navigation item
+                        Navigator.push(
                             context,
                             MaterialPageRoute(
                                 builder: (context) => item.page()));
diff --git a/lib/src/private/bfooter.dart b/lib/src/private/bfooter.dart
new file mode 100644 (file)
index 0000000..c13753b
--- /dev/null
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class BFooter implements FooterBones {
+  List<Widget> widgets(PageControllerBones controller) {
+    final rc = [
+      Row(children: <Widget>[
+        InkWell(
+          child: Text('Impressum'),
+          onTap: () => launch('https://public.hamatoma.de'),
+        ),
+        SizedBox(
+          width: 100,
+        ),
+        InkWell(
+          child: Text('Datenschutz'),
+          onTap: () => launch('https://public.hamatoma.de'),
+        ),
+        SizedBox(
+          width: 150,
+        ),
+        Text('Hallo ${controller.applicationData.currentUserName}'),
+      ])
+    ];
+    // final rc2 = [
+    //   GridView.count(crossAxisCount: 3, children: [
+    //     Text('a'),
+    //     Text('b'),
+    //     Text('c'),
+    //   ])
+    // ];
+    return rc;
+  }
+
+  /// Returns a method creating a footer.
+  static BFooter builder() => BFooter();
+}
index 7236b4e90597da7453ea75a10f614a6018907bc7..e73d4f72034227001aff3f476d07f21d4c51c06b 100644 (file)
@@ -2,6 +2,7 @@ import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 import 'package:flutter_bones/src/private/bappbar.dart';
 import 'package:flutter_bones/src/private/bdrawer.dart';
+import 'package:flutter_bones/src/private/bfooter.dart';
 
 class BSettings {
   final BaseConfiguration configuration;
@@ -23,8 +24,13 @@ class BSettings {
         port: 58011,
         host: 'localhost',
         logger: logger);
-    final applicationData = ApplicationData(BaseConfiguration(map, logger),
-        BAppBar.builder, BonesDrawer.builder, persistence, logger);
+    final applicationData = ApplicationData(
+        BaseConfiguration(map, logger),
+        BAppBar.builder,
+        BonesDrawer.builder,
+        BFooter.builder,
+        persistence,
+        logger);
     final rc = BSettings.internal(
         BaseConfiguration(map, logger), applicationData, persistence, logger);
     return lastInstance = rc;
index 161cb9995b86affe33def41a971905d7e1b33163..292b72c5ec434517c39d2272254b3b06d8ef7741 100644 (file)
@@ -25,7 +25,7 @@ class EditForm {
     final widgets = pageController.getWidgets();
     final view = View();
     final buttons =
-        view.modelsToWidgets(pageController.page.buttons, pageController);
+        view.modelsToWidgets(pageController.page.sections[0].buttonBar, pageController);
     return Form(
       key: key,
       child: Card(
index 9bd8edbb33ca78ddaa3bc67dc4454d1f1d50e3f9..3062ed33ca7965740a2f28bfd1dddf9bd95fda11 100644 (file)
@@ -100,12 +100,12 @@ class PageControllerBones implements ValidatorController {
   /// Builds the SQL parameters of all members of the [widgets].
   Map<String, String> buildSqlParams() {
     final rc = <String, String>{};
-    page.fields.forEach((element) {
-      if (element.filterType != null) {
-        dynamic value = element.value;
-        DataType dataType = element.dataType;
-        String name = element.name;
-        if (element.filterType == FilterType.pattern) {
+    page.models.values.forEach((model) {
+      if (model is FieldModel && model.filterType != null) {
+        dynamic value = model.value;
+        DataType dataType = model.dataType;
+        String name = model.name;
+        if (model.filterType == FilterType.pattern) {
           if (value == null || value.isEmpty) {
             value = '%';
           } else if (value is String) {
@@ -125,7 +125,7 @@ class PageControllerBones implements ValidatorController {
   /// e.g. { 'role_name': 'admin', ...}
   void buildModelList([Map initialRow]) {
     modelList.clear();
-    page.widgets.forEach((model) {
+    page.sections[0].children.forEach((model) {
       if (initialRow != null && model is FieldModel) {
         model.valueFromRow(initialRow);
       }
@@ -138,7 +138,11 @@ class PageControllerBones implements ValidatorController {
       completeModels(model);
       modelList.addModel(name, model);
     });
-    page.fields.forEach((element) => prepareModel(element));
+    page.sections[0].children.forEach((element) {
+      if (element is FieldModel) {
+        prepareModel(element);
+      }
+    });
   }
 
   /// Completes the widgets asynchronously if needed.
@@ -195,12 +199,14 @@ class PageControllerBones implements ValidatorController {
   }
 
   /// Gets the data from the database using the [primaryId].
-  fetchData(int primaryId) async {
+  void fetchData(int primaryId) async {
     applicationData.persistence
         .recordById(module: moduleModel.name, id: primaryId)
         .then((row) {
-      page.fields.forEach((model) {
-        model.value = row[model.name];
+      page.models.values.forEach((model) {
+        if (model is FieldModel) {
+          model.value = row[model.name];
+        }
       });
 
       /// navigate to the page "change":
@@ -279,9 +285,7 @@ class PageControllerBones implements ValidatorController {
               globalKey.currentState.save();
               final params = modelList.buildSqlParams(this);
               PageModelType pageType =
-                  moduleModel
-                      .pageByName(pageName)
-                      .pageModelType;
+                  moduleModel.pageByName(pageName).pageModelType;
               switch (pageType) {
                 case PageModelType.create:
                   applicationData.persistence
@@ -388,9 +392,7 @@ class PageControllerBones implements ValidatorController {
   void initializeFields(Map initialRow) {
     modelList.models.forEach((model) {
       if (model is FieldModel && initialRow.containsKey(model.name)) {
-        page
-            .fieldByName(model.name)
-            ?.value = initialRow[model.name];
+        page.fieldByName(model.name)?.value = initialRow[model.name];
       }
     });
   }
index e2d6252f802fa9126ab616cafdd368ec573f3ac5..5e00cdfdfa4347d9267f6cb101d1364fc70a9352 100644 (file)
@@ -6,7 +6,8 @@ class SimpleForm {
       {@required Key key,
       @required List<Widget> fields,
       @required List<Widget> buttons,
-      @required BaseConfiguration configuration}) {
+      @required BaseConfiguration configuration,
+      List<Widget> intro}) {
     final padding =
         configuration.asFloat('form.card.padding', defaultValue: 16.0);
     return Form(
@@ -16,6 +17,7 @@ class SimpleForm {
           child: Padding(
               padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
               child: ListView(children: <Widget>[
+                ...intro ?? [],
                 ...fields,
                 SizedBox(
                     height: configuration.asFloat(
index 53d8e394b76a8e0b768deea7773e0e71af862ed0..446b36ac6ffd00999538c08ca0eee3dc4868eb29 100644 (file)
@@ -58,11 +58,11 @@ class View {
     return rc;
   }
 
-  /// Creates a list of buttons from a list of [controllers].
+  /// Creates a list of buttons from a list of [models].
   List<Widget> buttonList(
-      List<ButtonModel> buttonModels, PageControllerBones controller) {
+      List<ButtonModel> models, PageControllerBones controller) {
     final rc = <Widget>[];
-    for (var model in buttonModels) {
+    for (var model in models) {
       rc.add(button(model, controller));
     }
     return rc;
@@ -276,15 +276,15 @@ class View {
     return rc;
   }
 
-  /// Returns a form with the properties given by the [model]
+  /// Returns a form with the properties given by the [sectionModel]
   /// [formKey] identifies the form. Used for form validation and saving.
   Form simpleForm(
-      {SectionModel model, PageControllerBones controller, Key formKey}) {
+      {SectionModel sectionModel, PageControllerBones controller, Key formKey}) {
     assert(formKey != null);
     final padding =
         widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
-    final children = modelsToWidgets(model.children, controller);
-    final buttons = buttonList(model.buttons, controller);
+    final children = modelsToWidgets(sectionModel.children, controller);
+    final buttons = buttonList(sectionModel.buttonBar, controller);
     final rc = Form(
         key: formKey,
         child: Card(
index 17a49469888e1c5daafa013c720f0f1585c07355..c67d095ea0513e5169c3ce1565efe62f882b3644 100644 (file)
@@ -1,78 +1,14 @@
 import 'package:dart_bones/dart_bones.dart';
-
-import 'src/model/module_model.dart';
-import 'src/model/standard/standard_modules.dart';
-import 'src/helper/string_helper.dart';
+import 'package:flutter_bones_tool/src/model/model_helper.dart';
+import 'package:flutter_bones_tool/src/model_tools.dart';
 
 void main(List<String> argv) async {
   final logger = MemoryLogger(LEVEL_FINE);
-  final dbHelper = DbHelper(logger);
+  final ModelHelper modelHelper = ModelHelper();
+  final ModelTools modelTools = ModelTools(modelHelper, logger);
   if (argv.isEmpty) {
     argv = ['export-sql'];
   }
-  final mode = argv[0];
-  argv.removeAt(0);
-  final options = <String>[];
-  argv = StringHelper.splitArgv(argv, options);
-  switch (mode) {
-    case 'export-sql':
-      dbHelper.exportSql(argv, options);
-      break;
-    default:
-      logger.error('unknown mode: $mode');
-      break;
-  }
+  modelTools.run(argv);
 }
 
-class DbHelper {
-  final BaseLogger logger;
-
-  DbHelper(this.logger);
-
-  void exportSql(List<String> args, List<String> options) {
-    String directory;
-    String value;
-    for (var opt in options) {
-      value = StringUtils.stringOption('directory', 'd', opt);
-      if (value != null) {
-        directory = value;
-      }
-    }
-    directory ??= 'data';
-    final dirDDL = FileSync.joinPaths(directory, 'ddl');
-    final dirREST = FileSync.joinPaths(directory, 'rest');
-    FileSync.ensureDirectory(dirDDL);
-    FileSync.ensureDirectory(dirREST);
-    if (args.isEmpty) {
-      args = standardModules();
-    }
-    while (args.isNotEmpty) {
-      final name = args[0];
-      args.removeAt(0);
-      ModuleModel module = standardModule(name, logger);
-      if (module != null) {
-        module.parse();
-        var filename = FileSync.joinPaths(dirDDL, '$name.sql');
-        FileSync.toFile(filename, module.exportSqlCreateTable());
-        print('exported: $filename');
-        filename = FileSync.joinPaths(dirREST, '$name.yaml');
-        FileSync.toFile(filename, module.exportSqlBackend());
-        print('exported: $filename');
-      }
-    }
-  }
-
-  void usage(String error) {
-    print('''usage: dbhelper <mode> [<args>]
-<mode>:
-  create-sql [<module>] [<opt>]
-    <module>: 'role', 'user' ...
-    <opt>:
-      -d<dir> or --directory=<dir>:
-        the base directory used for the exported files.
-Examples:
-dbhelper create-sql role role.sql -d/tmp
-dbhelper create-sql --directory=/opt/sql-data
-''');
-  }
-}
diff --git a/model_tool/lib/src/model_tools.dart b/model_tool/lib/src/model_tools.dart
new file mode 100644 (file)
index 0000000..8fb97f6
--- /dev/null
@@ -0,0 +1,239 @@
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones_tool/src/model/page_model.dart';
+
+import 'helper/string_helper.dart';
+import 'model/model_helper.dart';
+import 'model/module_model.dart';
+
+class ModelTools {
+  final BaseLogger logger;
+  final ModelHelper modelHelper;
+  ModelTools(this.modelHelper, this.logger);
+
+  /// Copies a range of [lines] defined by [start] and [end] markers into [buffer].
+  /// [includeStart]: true: the line containing [start] will be copied too.
+  /// [includeEnd]: true: the line containing [end] will be copied too.
+  /// [replacements]: null or a map with (key, replacement) pairs. All
+  /// occurrences of key in the copied lines will be replaced by the replacement.
+  /// Returns the count of copied lines.
+  int copyToBuffer(List<String> lines, StringBuffer buffer,
+      {String start,
+      bool includeStart = true,
+      String end,
+      bool includeEnd = true,
+      Map<String, String> replacements}) {
+    var mode = start == null ? 'copy' : 'ignore';
+    var count = 0;
+    for (var line in lines) {
+      if (includeStart && start != null && line.contains(start)) {
+        mode == 'copy';
+      }
+      if (!includeEnd && end != null && line.contains(end)) {
+        break;
+      }
+      if (mode == 'copy') {
+        if (replacements != null) {
+          for (var key in replacements.keys) {
+            line = line.replaceAll(key, replacements[key]);
+          }
+        }
+        buffer.write(line);
+        buffer.write('\n');
+        count++;
+      }
+      if (start != null && line.contains(start)) {
+        mode = 'copy';
+      }
+      if (end != null && line.contains(end)) {
+        break;
+      }
+    }
+    return count;
+  }
+
+  void exportSql(List<String> args, List<String> options) {
+    String directory;
+    String value;
+    for (var opt in options) {
+      value = StringUtils.stringOption('directory', 'd', opt);
+      if (value != null) {
+        directory = value;
+        continue;
+      }
+      logger.error('unknown option: $opt');
+    }
+    directory ??= 'data';
+    final dirDDL = FileSync.joinPaths(directory, 'ddl');
+    final dirREST = FileSync.joinPaths(directory, 'rest');
+    FileSync.ensureDirectory(dirDDL);
+    FileSync.ensureDirectory(dirREST);
+    if (args.isEmpty) {
+      args = modelHelper.moduleNames();
+    }
+    while (args.isNotEmpty) {
+      final name = args[0];
+      args.removeAt(0);
+      ModuleModel module = modelHelper.moduleByName(name, logger);
+      if (module != null) {
+        module.parse();
+        var filename = FileSync.joinPaths(dirDDL, '$name.sql');
+        FileSync.toFile(filename, module.exportSqlCreateTable());
+        print('exported: $filename');
+        filename = FileSync.joinPaths(dirREST, '$name.yaml');
+        FileSync.toFile(filename, module.exportSqlBackend());
+        print('exported: $filename');
+      }
+    }
+  }
+
+  void modifyModule(List<String> args, List<String> options) {
+    String baseDirectory;
+    String value;
+    for (var opt in options) {
+      value = StringUtils.stringOption('directory', 'd', opt);
+      if (value != null) {
+        baseDirectory = value;
+        continue;
+      }
+      logger.error('unknown option: $opt');
+    }
+    baseDirectory ??= 'lib/src/model';
+    if (args.isEmpty) {
+      args = modelHelper.moduleNames();
+    }
+    final regExprFilename = RegExp(r'_(\w+)_page\.dart');
+    for (var name in args) {
+      if (name == 'role') {
+        logger.log('module "role" ignored.');
+      }
+      final sourceDir = 'lib/src/page/$name';
+      logger.log('current directory: ${Directory.current}');
+      final files = Directory(sourceDir).listSync();
+      final pathSafe = FileSync.tempDirectory(
+          name +
+              '.' +
+              (DateTime.now().millisecondsSinceEpoch / 1000 % 86400)
+                  .round()
+                  .toString(),
+          subDirs: 'model_tools');
+      for (var file in files) {
+        final lines = FileSync.fileAsList(file.path);
+        final fnTemplate = file.path.replaceAll(name, 'role');
+        final templateLines = FileSync.fileAsList(fnTemplate);
+        if (templateLines.isEmpty) {
+          logger.log('ignoring ${file.path}: no template found');
+          continue;
+        }
+        final matcher = regExprFilename.firstMatch(file.path);
+        if (matcher == null) {
+          logger.error('cannot recognize page type: ${file.path}');
+          continue;
+        }
+        final pageType = StringUtils.stringToEnum<PageModelType>(
+            matcher.group(1), PageModelType.values);
+        modifyPage(
+            name,
+            pageType,
+            lines,
+            templateLines,
+            FileSync.joinPaths(baseDirectory, name, FileSync.nodeOf(file.path)),
+            false,
+            pathSafe);
+      }
+    }
+  }
+
+  void modifyPage(
+      String module,
+      PageModelType type,
+      List<String> lines,
+      List<String> templateLines,
+      String filename,
+      bool customizeConstructors,
+      String pathSafe) {
+    final moduleCapital = StringHelper.capitalize(module);
+    FileSync.ensureDirectory(FileSync.parentOf(filename, trailingSlash: false));
+    final buffer = StringBuffer();
+    var countTemplate = 0;
+    final replacements = <String, String>{
+      'role': module,
+      'Role': moduleCapital
+    };
+    var countOrigin = 0;
+    int ix;
+    if (!customizeConstructors) {
+      if (StringHelper.listIndexOf(lines, string: 'EndOfGeneratedCode') != null) {
+        countOrigin = copyToBuffer(lines, buffer,
+            end: 'BeginOfGeneratedCode', includeEnd: false);
+      } else {
+        countOrigin = copyToBuffer(templateLines, buffer,
+            end: 'BeginOfGeneratedCode',
+            includeEnd: false,
+            replacements: replacements);
+      }
+      countTemplate = copyToBuffer(templateLines, buffer,
+          start: 'BeginOfGeneratedCode',
+          end: 'EndOfGeneratedCode',
+          replacements: replacements);
+      countOrigin = copyToBuffer(lines, buffer,
+          start: 'EndOfGeneratedCode', includeStart: false);
+      ix = StringHelper.listIndexOf(lines, string: 'EndOfGeneratedCode');
+      // -3: possible empty lines at the end
+      if (ix == null || ix > lines.length - 3) {
+        countTemplate += copyToBuffer(templateLines, buffer,
+            start: 'EndOfGeneratedCode',
+            includeStart: false,
+            replacements: replacements);
+      }
+    } else {
+      //
+    }
+    FileSync.ensureDirectory(pathSafe);
+    final fnSafe = FileSync.joinPaths(pathSafe, FileSync.nodeOf(filename));
+    logger.log('saving to $fnSafe');
+    FileSync.toFile(fnSafe, lines.join('\n'));
+    logger.log('writing $filename lines: $countOrigin + $countTemplate');
+    FileSync.toFile(filename, buffer.toString());
+  }
+
+  void run(List<String> args) {
+    final mode = args[0];
+    final options = <String>[];
+    args = StringHelper.splitArgv(args.sublist(1), options);
+    switch (mode) {
+      case 'export-sql':
+        exportSql(args, options);
+        break;
+      case 'modify-module':
+        modifyModule(args, options);
+        break;
+      default:
+        logger.error('unknown mode: $mode');
+        break;
+    }
+  }
+
+  void usage(String error) {
+    print('''usage: main <mode> [<args>]
+<mode>:
+  create-sql [<module1> [<module2>...] [<opt>]
+    Generates the DDL ("create table...") statements and the yaml files
+    describing the insert... SQL statements.  
+    <moduleN>: 'role', 'user' ...
+    <opt>:
+      -d<dir> or --directory=<dir>:
+        the base directory used for the exported files.
+  help
+    Display this usage messages.
+  modify-module [<module1> [<module2>...]
+    Modifies the files of the given modules to renew the generated code.
+Examples:
+main create-sql role user -d/tmp
+main create-sql --directory=/opt/sql-data
+main modify-pages role
+main modify-pages
+''');
+  }
+}
diff --git a/model_tool/lib/src/page b/model_tool/lib/src/page
new file mode 120000 (symlink)
index 0000000..6203f10
--- /dev/null
@@ -0,0 +1 @@
+../../../lib/src/page/
\ No newline at end of file
index 09ffb06244b83df17ec41ba8fd60aa1e62275320..25684ba5f78ab83670c22757cbd5c9614db0b3e9 100644 (file)
@@ -21,7 +21,7 @@ environment:
   sdk: ">=2.11.0-234.0.dev <3.0.0"
 
 dependencies:
-  dart_bones: "^0.4.6"
+  dart_bones: "^0.4.7"
 
 
   # The following adds the Cupertino Icons font to your application.
index 6cb3154b800650b96dc0dd44d2d4d6fcf2009af2..fd62faab9384ed5d47093284049f4e7c299548fe 100644 (file)
@@ -25,8 +25,10 @@ dependencies:
     sdk: flutter
   flutter_localizations:
     sdk: flutter
-  dart_bones: "^0.4.5"
+  dart_bones: "^0.4.7"
   crypto: ^2.1.5
+  flutter_markdown: ^0.5.0
+  url_launcher: ^5.7.10
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
index 32f0766e4f39b427e340db1e3e067ce8c2cf5e3b..e33d2848fee17d5f2406cc952d32dccf26934ac7 100644 (file)
@@ -142,4 +142,25 @@ void main() {
       expect(options, equals([]));
     });
   });
+  group('StringList', (){
+    test('listIndexOf-string', (){
+      final list = <String>['a', 'bab', 'cAC'];
+      expect(StringHelper.listIndexOf(list, string: 'b'), equals(1));
+      expect(StringHelper.listIndexOf(list, string: 'a'), equals(0));
+      expect(StringHelper.listIndexOf(list, string: 'A'), equals(2));
+      expect(StringHelper.listIndexOf(list, string: 'x'), isNull);
+      expect(StringHelper.listIndexOf(null, string: 'x'), isNull);
+    });
+    test('listIndexOf-regEx', (){
+      final list = <String>['a', 'bab', 'cAC'];
+      expect(StringHelper.listIndexOf(list, pattern: r'...'), equals(1));
+      expect(StringHelper.listIndexOf(list, pattern: r'C$'), equals(2));
+      expect(StringHelper.listIndexOf(list, pattern: 'x'), isNull);
+      expect(StringHelper.listIndexOf(null, pattern: 'x'), isNull);
+
+      expect(StringHelper.listIndexOf(list, regExp: RegExp(r'a', caseSensitive: false)), equals(0));
+      expect(StringHelper.listIndexOf(list, regExp: RegExp(r'd')), isNull);
+      expect(StringHelper.listIndexOf(null, regExp: RegExp(r'd')), isNull);
+    });
+  });
 }
index 755193d1b0bff757077066a822201539cae775c6..2d630ec5af7e38c4b0278fec8b44a08cbc5086ed 100644 (file)
@@ -103,7 +103,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final page = module.pageByName('list');
-      page.addButton(ButtonModel.direct(
+      page.addModel(ButtonModel.direct(
           null,
           page,
           'a',
@@ -111,7 +111,7 @@ void main() {
           ButtonModelType.store,
           [],
           logger));
-      page.addButton(ButtonModel.direct(
+      page.addModel(ButtonModel.direct(
           null,
           page,
           'a',
@@ -120,7 +120,7 @@ void main() {
           [],
           logger));
       page.buttonByName('unknown');
-      page.addField(TextFieldModel.direct(
+      page.addModel(TextFieldModel.direct(
           null,
           page,
           'n',
@@ -130,7 +130,7 @@ void main() {
           <String>[],
           33,
           logger));
-      page.addField(TextFieldModel.direct(
+      page.addModel(TextFieldModel.direct(
           null,
           page,
           'n',
@@ -425,7 +425,7 @@ void main() {
       expect(errors.length, equals(0));
       final page = module.pageByName('change');
       expect(page, isNotNull);
-      expect(page.fields.length, equals(3));
+      expect(page.models.values.length, equals(3));
       expect(page.hasField('user_id'), isTrue);
       expect(page.hasField('user_name'), isTrue);
       expect(page.hasField('user_role'), isTrue);
index 02bdac1ead8c2eb2382b6db94e68c77d7fba86ca..1a216f1848b1d1084e666dccab937c6fb90a3e1c 100644 (file)
@@ -23,7 +23,7 @@ void main() async {
     pageControllerBones = PageControllerBones(
         null, null, UserModel(logger), 'list', null, applicationData);
     applicationData =
-          ApplicationData(configuration, null, null, persistence, logger);
+          ApplicationData(configuration, null, null, null, persistence, logger);
   });
   group('Basics', () {
     test('basic', () async {
index 42c4295bfa3cb0bb20198ce642f494693b4d0aa2..881d0d36d6d4c92ce25a0eb39eb6dddafec87fd0 100644 (file)
@@ -1,5 +1,6 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/model/model_helper.dart';
 import 'package:test/test.dart';
 
 void main() {
@@ -66,10 +67,11 @@ modules:
 '''));
     });
     test('standard_modules', () {
+      ModelHelper helper = ModelHelper();
       logger.clear();
       final dir = FileSync.tempDirectory('data', subDirs: 'unittest');
-      for (var name in standardModules()) {
-        final module = standardModule(name, logger);
+      for (var name in helper.moduleNames()) {
+        final module = helper.moduleByName(name, logger);
         module.parse();
         expect(logger.errors.isEmpty, isTrue);
         final content = module.exportSqlBackend();
@@ -78,7 +80,8 @@ modules:
     });
     test('standard_modules-errors', () {
       logger.clear();
-      final module = standardModule('not-exists', logger);
+      final helper = ModelHelper();
+      final module = helper.moduleByName('not-exists', logger);
       expect(module, isNull);
       expect(logger.contains('unknown standard module: not-exists'), isTrue);
     });
index cf0956404ad7ef5897d08ffa9a14a758784a7290..2814ea76b25d0751c1d5d060c70112bb3814f582 100644 (file)
@@ -41,7 +41,7 @@ void main() {
   group('ModuleController', () {
     test('basic', () {
       ApplicationData appData = ApplicationData(BaseConfiguration({}, logger),
-          (title) => null, (context) => null, persistence, logger);
+          (title) => null, (context) => null, () => null, persistence, logger);
       final role = RoleCreatePage(appData);
       if (appData.lastModuleState == null) {
         role.createState();
index 11d6b01a8e96883505a7cd118c72bf76c9dbcbcf..ef43f2d9d13f187b6666aecccaa1a5afce598ed9 100644 (file)
@@ -192,7 +192,7 @@ void main() {
       expect(table, isNotNull);
       var model = table.columnByName('test');
       final page = PageModel(module, logger);
-      page.addField(model);
+      page.addModel(model);
       model.page = page;
       expect(model, isNotNull);
       model.parseFinish();
@@ -213,7 +213,7 @@ void main() {
           'Unzulässige Eingabe "5" in str');
       model = table.columnByName('test2');
       expect(model, isNotNull);
-      page.addField(model);
+      page.addModel(model);
       model.page = page;
       model.parseFinish();
       prepareModel(model);