]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work
authorHamatoma <author@hamatoma.de>
Wed, 18 Nov 2020 04:51:21 +0000 (05:51 +0100)
committerHamatoma <author@hamatoma.de>
Mon, 23 Nov 2020 08:09:16 +0000 (09:09 +0100)
51 files changed:
lib/src/helper/string_helper.dart
lib/src/helper/url_helper.dart [new file with mode: 0644]
lib/src/model/all_db_fields_model.dart
lib/src/model/button_model.dart
lib/src/model/column_model.dart
lib/src/model/empty_line_model.dart
lib/src/model/field_model.dart
lib/src/model/model_base.dart
lib/src/model/model_helper.dart
lib/src/model/model_tool.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/starter_model.dart
lib/src/model/standard/user_model.dart
lib/src/model/table_model.dart
lib/src/model/text_model.dart
lib/src/page/application_data.dart
lib/src/page/configuration/configuration_change_page.dart
lib/src/page/configuration/configuration_create_page.dart
lib/src/page/configuration/configuration_list_page.dart
lib/src/page/menu/menu_change_page.dart
lib/src/page/menu/menu_create_page.dart
lib/src/page/menu/menu_list_page.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
lib/src/page/starter/starter_create_page.dart
lib/src/page/starter/starter_list_page.dart
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/page/user/user_password_page.dart
lib/src/private/bfooter.dart
lib/src/widget/edit_form.dart
lib/src/widget/view.dart
model_tool/lib/main.dart
model_tool/lib/src/model_tool_io.dart [new file with mode: 0644]
model_tool/lib/src/model_tools.dart [deleted file]
model_tool/pubspec.yaml
model_tool/test/model_tool_test.dart [new file with mode: 0644]
pubspec.yaml
test/helpers/string_helper_test.dart
test/model/db_model_test.dart
test/model/model_test.dart
test/tool/tool_test.dart

index b0ced96ee8946790fc8a64b01fde3f52448a79f3..1473e0446527a86d36faa982da86029fe79e4bac 100644 (file)
@@ -3,11 +3,105 @@ import 'package:intl/intl.dart';
 
 import '../model/model_types.dart';
 
+typedef LineConverter = String Function(String input);
+
 class StringHelper {
   static final regExpTrue = RegExp(r'^(true|yes|t)$', caseSensitive: false);
   static final regExpFalse = RegExp(r'^(false|no|f)$', caseSensitive: false);
   static const locale = 'de_DE';
 
+  /// Copies a range of lines from [input] to the end of [output].
+  /// The range is defined by a start and an end.
+  /// The start can be defined as [startString], [startRegExp] or [startPattern].
+  /// The end can be defined as [endString], [endRegExp] or [endPattern].
+  /// [startString] and [endString] are strings that are substrings of the start/end.
+  /// [startPattern] and [endPattern] are strings representing regular
+  /// expressions defining substrings in the start/end.
+  /// [includeStart]: true: the line containing the start will be copied too.
+  /// [includeEnd]: true: the line containing the end will be copied too.
+  /// [firstIndex] is the first index of [input] to process (searching and
+  /// copying). If [firstIndex] < 0: take it from the end, e.g. -1 means the
+  /// last line of [input].
+  /// [lastIndexIncluded] is the last index to process (searching and copying).
+  /// If [lastIndexIncluded] < 0: index is relative to the end.
+  /// [lastIndexExcluded] is one below the last index to process (searching and copying).
+  /// If [lastIndexExcluded] < 0: index is relative to the end.
+  /// [converter]: null a callback function for modifying the copied lines,
+  /// e.g. (line) => line.toLowerCase()
+  /// Returns [output] (for chaining).
+  static List<String> addRangeToList(List<String> input, List<String> output,
+      {int firstIndex,
+      int lastIndexExcluded,
+      lastIndexIncluded,
+      String startString,
+      RegExp startRegExp,
+      String startPattern,
+      bool includeStart = true,
+      String endString,
+      RegExp endRegExp,
+      String endPattern,
+      bool includeEnd = true,
+      LineConverter converter}) {
+    output ??= [];
+    if (input != null) {
+      var doCopy =
+          startString == null && startRegExp == null && startPattern == null;
+      firstIndex ??= 0;
+      if (firstIndex < 0) {
+        firstIndex =
+            (firstIndex = input.length + firstIndex) < 0 ? 0 : firstIndex;
+      }
+      if (lastIndexExcluded == null) {
+        lastIndexExcluded = lastIndexIncluded == null
+            ? input.length
+            : (lastIndexIncluded < 0
+                    ? input.length + lastIndexIncluded
+                    : lastIndexIncluded) +
+                1;
+      }
+      lastIndexExcluded =
+          lastIndexExcluded > input.length ? input.length : lastIndexExcluded;
+      if (startPattern != null) {
+        startRegExp = RegExp(startPattern);
+      }
+      if (endPattern != null) {
+        endRegExp = RegExp(endPattern);
+      }
+      var line;
+      while (firstIndex < lastIndexExcluded) {
+        line = input[firstIndex++];
+        if (!doCopy &&
+            includeStart &&
+            (startString != null && line.contains(startString) ||
+                startRegExp?.firstMatch(line) != null)) {
+          doCopy = true;
+        }
+        if (!includeEnd &&
+            (endString != null && line.contains(endString) ||
+                endRegExp != null && endRegExp.firstMatch(line) != null)) {
+          break;
+        }
+        if (doCopy) {
+          if (converter != null) {
+            line = converter(line);
+          }
+          output.add(line);
+        }
+        if (!doCopy &&
+            (startString != null && line.contains(startString) ||
+                startRegExp?.firstMatch(line) != null)) {
+          doCopy = true;
+        }
+        if (includeEnd &&
+            (endString != null && line.contains(endString) ||
+                endRegExp?.firstMatch(line) != null)) {
+          break;
+        }
+      }
+    }
+    return output;
+  }
+
   /// Convert [value] into a string for storing in a database respecting [dataType].
   static String asDatabaseString(dynamic value, DataType dataType) {
     if (value == null) {
@@ -50,6 +144,12 @@ 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;
+  }
+
   /// Converts a [dateTime] into a string.
   /// If [dateTime] is null the current date and time is used.
   /// If [sortable] is true the result is sortable (year.month.day ...).
@@ -128,28 +228,6 @@ class StringHelper {
     return rc;
   }
 
-  /// Replaces all placeholders defined by [syntaxPlaceholder] or [regExpPlaceholder]
-  /// in [source] and return this result.
-  /// [syntaxPlaceholder] or [regExpPlaceholder] must contain a single group
-  /// identifying the name of the placeholder.
-  /// Example:
-  /// replacePlaceholders('Hi ~user~', { "user": "joe"}, syntaxPlaceholder: r'~(\w+)~')
-  /// returns "Hi Joe".
-  static String replacePlaceholders(
-      String source, Map<String, String> placeholders,
-      {String syntaxPlaceholder, RegExp regExpPlaceholder}) {
-    regExpPlaceholder ??= RegExp(syntaxPlaceholder);
-    final rc =
-        regExpPlaceholder.allMatches(source).fold(source, (string, match) {
-      final name = match.group(1);
-      final rc2 = placeholders.containsKey(name)
-          ? string.replaceFirst(match.group(0), placeholders[name])
-          : string;
-      return rc2;
-    });
-    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.
@@ -184,6 +262,28 @@ class StringHelper {
     return rc;
   }
 
+  /// Replaces all placeholders defined by [syntaxPlaceholder] or [regExpPlaceholder]
+  /// in [source] and return this result.
+  /// [syntaxPlaceholder] or [regExpPlaceholder] must contain a single group
+  /// identifying the name of the placeholder.
+  /// Example:
+  /// replacePlaceholders('Hi ~user~', { "user": "joe"}, syntaxPlaceholder: r'~(\w+)~')
+  /// returns "Hi Joe".
+  static String replacePlaceholders(
+      String source, Map<String, String> placeholders,
+      {String syntaxPlaceholder, RegExp regExpPlaceholder}) {
+    regExpPlaceholder ??= RegExp(syntaxPlaceholder);
+    final rc =
+        regExpPlaceholder.allMatches(source).fold(source, (string, match) {
+      final name = match.group(1);
+      final rc2 = placeholders.containsKey(name)
+          ? string.replaceFirst(match.group(0), placeholders[name])
+          : string;
+      return rc2;
+    });
+    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
@@ -199,10 +299,4 @@ 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;
-  }
 }
diff --git a/lib/src/helper/url_helper.dart b/lib/src/helper/url_helper.dart
new file mode 100644 (file)
index 0000000..4f5bd22
--- /dev/null
@@ -0,0 +1,58 @@
+import 'package:dart_bones/dart_bones.dart';
+
+class UrlHelper {
+  static final sep = '/';
+  static final currentDirSep = '.' + sep;
+
+  /// Joins parts to a combined path.
+  /// [first]: first part
+  /// [second]: second part
+  /// [third]: third part
+  static String joinPaths(String first, String second, [String third]) {
+    final rc = StringBuffer(first);
+    var last = first;
+    if (second.isNotEmpty) {
+      if (!first.endsWith(sep)) {
+        rc.write(sep);
+      }
+      if (second.startsWith(currentDirSep)) {
+        rc.write(second.substring(2));
+      } else if (second.startsWith(sep)) {
+        rc.write(second.substring(1));
+      } else {
+        rc.write(second);
+      }
+      last = second;
+    }
+    if (third != null && third.isNotEmpty) {
+      if (!last.endsWith(sep)) {
+        rc.write(sep);
+      }
+      if (third.startsWith(currentDirSep)) {
+        rc.write(third.substring(2));
+      } else if (third.startsWith(sep)) {
+        rc.write(third.substring(1));
+      } else {
+        rc.write(third);
+      }
+    }
+    return rc.toString();
+  }
+
+  /// Returns the filename of the [path] without path.
+  /// Example: base('abc/def.txt') == 'def.txt'
+  static String nodeOf(String path) {
+    final ix = path.lastIndexOf(sep);
+    final rc = ix < 0 ? path : path.substring(ix + 1);
+    return rc;
+  }
+
+  /// Returns the parent directory of the [path].
+  /// Example: dirname('abc/def.txt') == 'abc/'
+  /// [trailingSlash]: if false the trailing slash will not be part of the result
+  static String parentOf(String path, {bool trailingSlash: true}) {
+    final ix = path.lastIndexOf(sep);
+    final rc = ix < 0 ? '' : path.substring(0, ix + (trailingSlash ? 1 : 0));
+    return rc;
+  }
+}
index 9e8506afb40ba1c7e47d1535fe1272df83c44a4b..4848fa4b98fd6d5d1f4bc089d7ac0aa6095d7209 100644 (file)
@@ -16,7 +16,7 @@ class AllDbFieldsModel extends WidgetModel {
   AllDbFieldsModel(SectionModel section, PageModel page, BaseLogger logger)
       : super(section, page, ModelTypeBones.allDbFields, null, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${section.name}.$name';
 
index 55bf4d6ffe7c080ed0d73b62dfe4cff1005a6360..f4aa3d12a1b5d5506a8b4ff0026176587a627d2a 100644 (file)
@@ -41,7 +41,7 @@ class ButtonModel extends WidgetModel {
     return stringBuffer;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${section?.name}.$name';
 
index 6c73f4b8b2af9921398cdf462b8e2f2ab3628349..6c5a33f9f672806138b60fcde67f822d9b1b7b41 100644 (file)
@@ -58,7 +58,7 @@ class ColumnModel extends ComboBaseModel {
     return stringBuffer;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${table.name}.$name';
 
index 9bfa2c48bf33c2e9161703211e725e44ff3196ec..5bc1b0f0cac6913bbfedd4d5303c3271b862fc87 100644 (file)
@@ -19,7 +19,7 @@ class EmptyLineModel extends WidgetModel {
     return stringBuffer;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${section.name}.$name';
 
index af820d0f482a532e540d1bd25cd56145da001377..3070ca30c32cff67d218bbbe429c1af041ef031e 100644 (file)
@@ -173,7 +173,7 @@ abstract class FieldModel extends WidgetModel {
     }
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${page.name}.$name';
 
index 9a4efbd9c7a1a12ce0448945083adac7b10fe7ba..2b3de51a03390b9359d3577575cc23161000e630 100644 (file)
@@ -43,7 +43,7 @@ abstract class ModelBase {
     });
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   String fullName();
 
   /// Tests whether a given [option] is part of [options].
index 4ccd3347022b60576ef9482ea122de7d68db230e..85c190e34016fffcaa9409f5d97afa78426e19f4 100644 (file)
@@ -36,5 +36,4 @@ class ModelHelper {
   /// Returns the names of the modules.
   List<String> moduleNames() =>
       ['configuration', 'menu', 'role', 'starter', 'user'];
-
 }
diff --git a/lib/src/model/model_tool.dart b/lib/src/model/model_tool.dart
new file mode 100644 (file)
index 0000000..17271d7
--- /dev/null
@@ -0,0 +1,314 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../helper/string_helper.dart';
+import '../helper/url_helper.dart';
+import 'model_helper.dart';
+import 'module_model.dart';
+import 'page_model.dart';
+
+/// Implements tools for development of flutter_bones applications.
+/// This class is is "web friendly", it does not depend directly on package:io.
+abstract class ModelTool {
+  final BaseLogger logger;
+  final ModelHelper modelHelper;
+  ModelTool(this.modelHelper, this.logger);
+
+  /// Ensures that the [directory] exists. If not it will be created.
+  void ensureDirectory(String directory);
+
+  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 = UrlHelper.joinPaths(directory, 'ddl');
+    final dirREST = UrlHelper.joinPaths(directory, 'rest');
+    ensureDirectory(dirDDL);
+    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 = UrlHelper.joinPaths(dirDDL, '$name.sql');
+        writeFile(filename, module.exportSqlCreateTable());
+        print('exported: $filename');
+        filename = UrlHelper.joinPaths(dirREST, '$name.yaml');
+        writeFile(filename, module.exportSqlBackend());
+        print('exported: $filename');
+      }
+    }
+  }
+
+  void modifyModule(List<String> args, List<String> options) {
+    String baseDirectory;
+    String value;
+    PageModelType handledPageType;
+    bool overwriteConstructor = false;
+    for (var opt in options) {
+      value = StringUtils.stringOption('directory', 'd', opt);
+      if (value != null) {
+        baseDirectory = value;
+        continue;
+      }
+      value = StringUtils.stringOption('directory', 'd', opt);
+      if (value != null) {
+        handledPageType = StringUtils.stringToEnum<PageModelType>(
+            value, PageModelType.values);
+        if (handledPageType == null) {
+          logger.error('unknown page type $value in $opt');
+          logger.error('aborting command');
+          throw FormatException('invalid option $opt');
+          break;
+        }
+        continue;
+      }
+      var boolValue =
+          StringUtils.boolOption('overwrite-constructors', 'c', opt);
+      if (boolValue != Bool.UNDEF) {
+        overwriteConstructor = boolValue == Bool.TRUE;
+        continue;
+      }
+      logger.error('unknown option: $opt');
+    }
+    baseDirectory ??= 'lib/src/model';
+    if (args.isEmpty) {
+      args = modelHelper.moduleNames();
+    }
+    final regExpPageType =
+        RegExp(r'const ([a-z]+)([A-Z][a-z]+)PageType = PageModelType\.(\w+)');
+    for (var name in args) {
+      if (name == 'role') {
+        logger.log('module "role" ignored.');
+      }
+      final sourceDir = 'lib/src/page/$name';
+      final files = pathOfDirectory(sourceDir);
+      final pathSafe = safeDirectory(name);
+      for (var file in files) {
+        if (file.endsWith('_controller.dart')) {
+          continue;
+        }
+        final lines = readFile(file);
+        PageModelType pageType;
+        RegExpMatch matcher;
+        final ix =
+            StringHelper.listIndexOf(lines, string: 'PageType = PageModelType');
+        if (ix == null) {
+          logger.log('missing pageType hint in $file');
+          continue;
+        } else {
+          matcher = regExpPageType.firstMatch(lines[ix]);
+          if (matcher == null) {
+            logger.error('cannot recognize page type: ${lines[ix]} in $file');
+            continue;
+          }
+        }
+        final moduleCapital = StringHelper.capitalize(matcher.group(1));
+        final pageNameCapital = matcher.group(2);
+        final pageTypeLower = matcher.group(3);
+        pageType = StringUtils.stringToEnum<PageModelType>(
+            pageTypeLower, PageModelType.values);
+        if (pageType == null) {
+          logger.error('cannot recognize page type: ${lines[ix]} in $file');
+          continue;
+        }
+        if (handledPageType != null && pageType != handledPageType) {
+          continue;
+        }
+        final fnTemplate = UrlHelper.joinPaths(
+            UrlHelper.parentOf(file).replaceAll(name, 'role'),
+            'role_${StringUtils.enumToString(pageType)}_page.dart');
+        final templateLines = readFile(fnTemplate);
+        if (templateLines.isEmpty) {
+          logger.log('ignoring ${file}: no template found');
+          continue;
+        }
+        final pageTypeCapital = StringHelper.capitalize(pageTypeLower);
+        final classFragment = 'Role$pageTypeCapital';
+        final converter = (String line) => line
+            .replaceAll('Role${pageTypeCapital}Page',
+                '$moduleCapital${pageNameCapital}Page')
+            .replaceAll('RoleController', '${moduleCapital}Controller')
+            .replaceAll(
+                'role$pageNameCapital', '$name$pageNameCapital');
+        modifyPage(
+            name,
+            pageType,
+            lines,
+            templateLines,
+            converter,
+            UrlHelper.joinPaths(baseDirectory, name, UrlHelper.nodeOf(file)),
+            overwriteConstructor,
+            pathSafe);
+      }
+    }
+  }
+
+  void modifyPage(
+      String module,
+      PageModelType type,
+      List<String> lines,
+      List<String> templateLines,
+      Function(String) converter,
+      String filename,
+      bool overwriteConstructors,
+      String pathSafe) {
+    final moduleCapital = StringHelper.capitalize(module);
+    ensureDirectory(UrlHelper.parentOf(filename, trailingSlash: false));
+    final output = <String>[];
+    int ix;
+    if (overwriteConstructors) {
+      if (StringHelper.listIndexOf(lines, string: 'EndOfGeneratedCode') !=
+          null) {
+        StringHelper.addRangeToList(lines, output,
+            endString: 'BeginOfGeneratedCode', includeEnd: false);
+      } else {
+        StringHelper.addRangeToList(templateLines, output,
+            endString: 'BeginOfGeneratedCode',
+            includeEnd: false,
+            converter: converter);
+      }
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'BeginOfGeneratedCode',
+          endString: 'EndOfGeneratedCode',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'EndOfGeneratedCode', includeStart: false);
+      ix = StringHelper.listIndexOf(lines, string: 'EndOfGeneratedCode');
+      // -3: possible empty lines at the end
+      if (ix == null || ix > lines.length - 3) {
+        StringHelper.addRangeToList(templateLines, output,
+            startString: 'EndOfGeneratedCode',
+            includeStart: false,
+            converter: converter);
+      }
+    } else {
+      StringHelper.addRangeToList(lines, output,
+          endString: 'BeginOfGeneratedCode', includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'BeginOfGeneratedCode',
+          endString: 'BeginOfCustomizedVars1',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'BeginOfCustomizedVars1',
+          includeStart: false,
+          endString: 'EndOfCustomizedVars1',
+          includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'EndOfCustomizedVars1',
+          endString: 'BeginOfConstructor1',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'BeginOfConstructor1',
+          includeStart: false,
+          endString: 'EndOfConstructor1',
+          includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'EndOfConstructor1',
+          endString: 'BeginOfCall',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'BeginOfCall',
+          includeStart: false,
+          endString: 'EndOfCall',
+          includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'EndOfCall',
+          endString: 'BeginOfCustomizedVars2',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'BeginOfCustomizedVars2',
+          includeStart: false,
+          endString: 'EndOfCustomizedVars2',
+          includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'EndOfCustomizedVars2',
+          endString: 'BeginOfConstructor2',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'BeginOfConstructor2',
+          includeStart: false,
+          endString: 'EndOfConstructor2',
+          includeEnd: false);
+      StringHelper.addRangeToList(templateLines, output,
+          startString: 'EndOfConstructor2',
+          endString: 'EndOfGeneratedCode',
+          converter: converter);
+      StringHelper.addRangeToList(lines, output,
+          startString: 'EndOfGeneratedCode', includeStart: false);
+    }
+    ensureDirectory(pathSafe);
+    final fnSafe = UrlHelper.joinPaths(pathSafe, UrlHelper.nodeOf(filename));
+    logger.log('saving to $fnSafe');
+    writeFile(fnSafe, lines.join('\n'));
+    logger.log('writing $filename lines: ${output.length}');
+    writeFile(filename, output.join('\n'));
+  }
+
+  List<String> pathOfDirectory(String path);
+
+  /// Reads a [file] into a list of lines.
+  List<String> readFile(String file);
+
+  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;
+    }
+  }
+
+  /// Returns the unique name derived from [name] of a directory to save files.
+  String safeDirectory(String name);
+
+  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>...] [<opts>]
+    Modifies the files of the given modules to renew the generated code.
+  <opt>:
+    --page-type=<type>
+      Modify only files with this type: 'create', 'change', 'list', 'custom'
+    --override-constructors
+      Parts of the constructors will be taken from the template.
+      Can destroy customized code! Needed if there are changed customized areas.
+Examples:
+main create-sql role user -d/tmp
+main create-sql --directory=/opt/sql-data
+main modify-pages role --customize-constructors
+main modify-pages --page-type=list
+''');
+  }
+
+  /// Writes a [content] to a file named [filename].
+  void writeFile(String filename, String content);
+}
index ac8d7b80143aa0b8699122bd891a71b24c462ee6..6f28bea7f075035bdd4b2a6a4e66513046128e99 100644 (file)
@@ -297,7 +297,7 @@ modules:
     buffer.write('\n          WHERE ${table.name}_id=:${table.name}_id;"\n');
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index 01bb896076783f0e3532134660eabddd4e9937ad..2c93ec1d3c2b37ef558b9e43063e0fe9ccf7d1a6 100644 (file)
@@ -73,7 +73,7 @@ class PageModel extends ModelBase {
     return rc;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${module.name}.$name';
 
@@ -97,7 +97,7 @@ class PageModel extends ModelBase {
     return rc;
   }
 
-  /// Parses the [map]and stores the data in the instance.
+  /// Parses the [map] and stores the data in the instance.
   void parse(Map map) {
     super.parseBase(map, nameLabel: 'page', required: true);
     if (name == null) {
index e44c4c7878826cb6149f959c105fbc60c170ca9f..45f020ebb6508848532dfe1afe6f38462666e368 100644 (file)
@@ -35,7 +35,7 @@ class SectionModel extends WidgetModel {
     return stringBuffer;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() =>
       section == null ? '${page.name}.$name' : '${section.name}.$name';
@@ -84,7 +84,7 @@ class SectionModel extends WidgetModel {
                 'Section: unknown "modelType" ${child["modelType"]} in ${fullName()}');
             break;
         }
-        if (model != null){
+        if (model != null) {
           model.parse(child);
         }
       }
@@ -102,7 +102,7 @@ class SectionModel extends WidgetModel {
         'sectionType', map, SectionModelType.values);
     checkSuperfluousAttributes(
         map,
-        'buttons children fields modelType section options sectionType'
+        'buttonBar children fields modelType section options sectionType'
             .split(' '));
 
     checkOptionsByRegExpr(regExprOptions);
index 537727031fbc2589b1fa0759e050c4782a3b8289..eb20373f495574f830b0b20b11da825eecdea38c 100644 (file)
@@ -119,7 +119,16 @@ class ConfigurationModel extends ModuleModel {
                 'modelType': 'textField',
                 'filterType': 'pattern',
               },
-            ]
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neue Konfiguration',
+                'toolTip': 'Erzeugen einer neuen Konfiguration'
+              },
+            ],
           }
         ]
       },
@@ -128,7 +137,7 @@ class ConfigurationModel extends ModuleModel {
 
   ConfigurationModel(BaseLogger logger) : super(yamlMap, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index e3c768798519ae1361ca49d7f5e66c68c63a7050..10c6054846bc1099267baee3b4e8205367833f14 100644 (file)
@@ -61,7 +61,16 @@ class MenuModel extends ModuleModel {
               {
                 'modelType': 'allDbFields',
               },
-            ]
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neues Startmenü',
+                'toolTip': 'Erzeugen eines neuen Startmenüs'
+              },
+            ],
           }
         ]
       },
@@ -92,7 +101,7 @@ class MenuModel extends ModuleModel {
 
   MenuModel(BaseLogger logger) : super(mapMenu, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index 6cb2a396fabefc26c42e5f4a494de390ab06b04e..1001014e3517710149c1aa69879e113e1ab7498e 100644 (file)
@@ -103,7 +103,7 @@ class RoleModel extends ModuleModel {
     ]
   };
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index eb090c36e2e85cbe34f2fa7af9491bd09265db74..b8a8efd4937946be31b9dbeff391e2c91a37e652 100644 (file)
@@ -83,7 +83,16 @@ class StarterModel extends ModuleModel {
                 'toolTip':
                     'Filter bezüglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
               },
-            ]
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neuer Programmpunkt',
+                'toolTip': 'Erzeugen eines neuen Programmpunkts'
+              },
+            ],
           }
         ]
       },
@@ -92,7 +101,7 @@ class StarterModel extends ModuleModel {
 
   StarterModel(BaseLogger logger) : super(mapStarter, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index 23c64236da77b53f305b886ff2d0686325a51d9a..14ffdaa4e1c8bdd6bcd2adbe4200e84ab5a69058 100644 (file)
@@ -153,7 +153,16 @@ class UserModel extends ModuleModel {
                 'toolTip':
                     'Filter bezüglich der Rolle der anzuzeigenden Einträge. "-" bedeutet keine Einschränkung',
               }
-            ]
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neuer Benutzer',
+                'toolTip': 'Erzeugen eines neuen Benutzers'
+              },
+            ],
           }
         ]
       },
@@ -179,6 +188,8 @@ class UserModel extends ModuleModel {
                 'options': 'password',
                 'validators': 'required',
               },
+            ],
+            'buttonBar': [
               {
                 'modelType': 'button',
                 'buttonType': 'custom',
@@ -194,7 +205,7 @@ class UserModel extends ModuleModel {
 
   UserModel(BaseLogger logger) : super(mapUser, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 
index a77cabbc13fbbd3360486fb40845c4cbea6a8e21..c8dc063fe24edc20deb94e5c1d862844f87b12dc 100644 (file)
@@ -67,7 +67,7 @@ class TableModel extends ModelBase {
     return stringBuffer;
   }
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${module.name}.$name';
 
index bc5104cb21ece04b2bd5610d7fc2f8f679f2c38c..5d8925f68fa35135f35107f6eeeccc9875b41b6c 100644 (file)
@@ -13,7 +13,7 @@ class TextModel extends WidgetModel {
   TextModel(SectionModel section, PageModel page, BaseLogger logger)
       : super(section, page, ModelTypeBones.text, null, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => '${section.name}.$name';
 
index be00740a7a723029fea5df101f056856100099d5..ecc97aff8240e52b254713faf797470bce224132 100644 (file)
@@ -6,7 +6,7 @@ import '../persistence/persistence_cache.dart';
 import '../widget/page_controller_bones.dart';
 
 abstract class FooterBones {
-  List<Widget> widgets(PageControllerBones controller);
+  Widget widget(PageControllerBones controller);
 }
 
 /// Data class for storing parameter to build a page.
index e0867d05f095a2d2f9b919d64f57ce108d91e7b8..d8848cf57477bc44079a9a58b471d1afeff4bd88 100644 (file)
@@ -3,25 +3,36 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'configuration_controller.dart';
 
+const configurationChangePageModule = 'configuration';
+const configurationChangePageName = 'change';
+const configurationChangePageType = PageModelType.change;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class ConfigurationChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
 
-  //ConfigurationChangePageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   ConfigurationChangePage(this.primaryId, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   ConfigurationChangePageState createState() {
+    //! === BeginOfCall ===
     final rc =
-        ConfigurationChangePageState(primaryId, applicationData, initialRow);
+        ConfigurationChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -29,25 +40,31 @@ class ConfigurationChangePage extends StatefulWidget {
   }
 }
 
-class ConfigurationChangePageState extends State<ConfigurationChangePage>
+abstract class ConfigurationChangePageState extends State<ConfigurationChangePage>
     implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'configuration_change');
+      GlobalKey<FormState>(debugLabel: '$configurationChangePageModule-$configurationChangePageName');
 
   ConfigurationController controller;
 
-  ConfigurationChangePageState(
-      this.primaryId, this.applicationData, this.initialRow);
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  ConfigurationChangePageState(this.primaryId, this.applicationData, this.initialRow);
+
+  //! === EndOfConstructor2 ===
 
   @override
   Widget build(BuildContext context) {
-    // controller.buildWidgetList() is called in editForm
+    controller.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Konfiguration Ã¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -55,21 +72,27 @@ class ConfigurationChangePageState extends State<ConfigurationChangePage>
           primaryId: primaryId,
           initialRow: initialRow,
         ));
+    //! === EndOfScaffold ===
   }
 
-  @override
-  void initState() {
-    super.initState();
-    controller = ConfigurationController(
-        _formKey, this, 'change', context, applicationData);
-    controller.initialize();
-  }
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
 
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
   }
 
+  @override
+  void initState() {
+    super.initState();
+    controller =
+        ConfigurationController(_formKey, this, 'change', context, applicationData);
+    controller.initialize();
+    customize();
+  }
+
   @override
   void redraw(RedrawReason reason,
       {String customString, RedrawCallbackFunctionSimple callback}) {
@@ -79,3 +102,17 @@ class ConfigurationChangePageState extends State<ConfigurationChangePage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class ConfigurationChangePageStateCustomized
+    extends ConfigurationChangePageState {
+  ConfigurationChangePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 9d74df8af1ee34e768f19a7e9125577119a66e67..939a5293575d933922d2bef980a73ec5a4003f6a 100644 (file)
@@ -2,18 +2,31 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'configuration_controller.dart';
 
+const configurationCreatePageModule = 'configuration';
+const configurationCreatePageName = 'create';
+const configurationCreatePageType = PageModelType.create;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class ConfigurationCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   ConfigurationCreatePage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   ConfigurationCreatePageState createState() {
-    final rc = ConfigurationCreatePageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = ConfigurationCreatePageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -21,36 +34,54 @@ class ConfigurationCreatePage extends StatefulWidget {
   }
 }
 
-class ConfigurationCreatePageState extends State<ConfigurationCreatePage>
+abstract class ConfigurationCreatePageState extends State<ConfigurationCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'configuration_create');
+      GlobalKey<FormState>(debugLabel: '$configurationCreatePageModule-$configurationCreatePageName');
 
   ConfigurationController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   ConfigurationCreatePageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
-    controller.beginOfBuild(context);
+    controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Neue Konfiguration'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(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 dispose() {
+    controller.dispose();
+    super.dispose();
   }
 
   @override
   void initState() {
     super.initState();
-    controller = ConfigurationController(
-        _formKey, this, 'create', context, applicationData);
+    controller =
+        ConfigurationController(_formKey, this, configurationCreatePageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -62,3 +93,16 @@ class ConfigurationCreatePageState extends State<ConfigurationCreatePage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class ConfigurationCreatePageStateCustomized
+    extends ConfigurationCreatePageState {
+  ConfigurationCreatePageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 64913f8fbefc2c142ca1a9e6c9b9ad63939ec134..b2ab89836bcc63987cc6bd5c6b206829ae323458 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/widget/view.dart';
 
 import '../../model/page_model.dart';
 import '../../widget/list_form.dart';
@@ -6,15 +7,26 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'configuration_controller.dart';
 
+const configurationListPageModule = 'configuration';
+const configurationListPageName = 'list';
+const configurationListPageType = PageModelType.list;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class ConfigurationListPage extends StatefulWidget {
   final ApplicationData applicationData;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   ConfigurationListPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   ConfigurationListPageState createState() {
-    // ConfigurationListPageState.setPageData(pageData);
-    final rc = ConfigurationListPageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = ConfigurationListPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,23 +34,31 @@ class ConfigurationListPage extends StatefulWidget {
   }
 }
 
-class ConfigurationListPageState extends State<ConfigurationListPage>
+abstract class ConfigurationListPageState extends State<ConfigurationListPage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'configuration_list');
+      GlobalKey<FormState>(debugLabel: '$configurationListPageModule-$configurationListPageName');
   Iterable<dynamic> rowsDeprecated;
   ConfigurationController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   ConfigurationListPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+
+    //! === BeginOfScaffold ===
     return Scaffold(
-      appBar: applicationData.appBarBuilder('Konfigurationen'),
+      appBar: applicationData.appBarBuilder(controller.page.title),
       drawer: applicationData.drawerBuilder(context),
+      bottomNavigationBar: applicationData.footerBuilder().widget(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -48,44 +68,30 @@ class ConfigurationListPageState extends State<ConfigurationListPage>
         showEditIcon: true,
         pageController: controller,
         buttons: <Widget>[
-          ButtonBar(alignment: MainAxisAlignment.center, children: [
-            controller.searchButton(),
-            RaisedButton(
-              child: Text('Neue Konfiguration'),
-              onPressed: () {
-                controller.goTo(pageType: PageModelType.create);
-              },
-            ),
-          ]),
+          ButtonBar(
+            alignment: MainAxisAlignment.spaceBetween,
+            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 = ConfigurationController(
-        _formKey, this, 'list', context, applicationData, redrawCallback:
-            (RedrawReason reason,
-                {String customString, RedrawCallbackFunctionSimple callback}) {
-      switch (reason) {
-        case RedrawReason.fetchList:
-          controller.buildRows();
-          break;
-        case RedrawReason.callback:
-          callback(RedrawReason.custom, customString: customString);
-          setState(() {});
-          break;
-        default:
-          setState(() {});
-          break;
-      }
-    });
+    controller =
+        ConfigurationController(_formKey, this, configurationListPageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -97,3 +103,18 @@ class ConfigurationListPageState extends State<ConfigurationListPage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class ConfigurationListPageStateCustomized extends ConfigurationListPageState {
+  ConfigurationListPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    final button = controller.page.buttonByName('new');
+    button.onPressed = () {
+      controller.goTo(pageType: PageModelType.create);
+    };
+  }
+}
index b25913785b17c87001d290812d72023609fad322..6eeebab95525d0c0556ef5bd43e993becc7d454e 100644 (file)
@@ -3,24 +3,36 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'menu_controller.dart';
 
+const menuChangePageModule = 'menu';
+const menuChangePageName = 'change';
+const menuChangePageType = PageModelType.change;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class MenuChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
 
-  //MenuChangePageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   MenuChangePage(this.primaryId, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   MenuChangePageState createState() {
-    final rc = MenuChangePageState(primaryId, applicationData, initialRow);
+    //! === BeginOfCall ===
+    final rc =
+        MenuChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -28,24 +40,31 @@ class MenuChangePage extends StatefulWidget {
   }
 }
 
-class MenuChangePageState extends State<MenuChangePage> implements RedrawPage {
+abstract class MenuChangePageState extends State<MenuChangePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'menu_change');
+      GlobalKey<FormState>(debugLabel: '$menuChangePageModule-$menuChangePageName');
 
   MenuController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   MenuChangePageState(this.primaryId, this.applicationData, this.initialRow);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
-    // controller.buildWidgetList() is called in editForm
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Startmenü Ã¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -53,8 +72,13 @@ class MenuChangePageState extends State<MenuChangePage> implements RedrawPage {
           primaryId: primaryId,
           initialRow: initialRow,
         ));
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
@@ -63,14 +87,10 @@ class MenuChangePageState extends State<MenuChangePage> implements RedrawPage {
   @override
   void initState() {
     super.initState();
-    controller = MenuController(
-      _formKey,
-      this,
-      'change',
-      context,
-      applicationData,
-    );
+    controller =
+        MenuController(_formKey, this, 'change', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -82,3 +102,16 @@ class MenuChangePageState extends State<MenuChangePage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class MenuChangePageStateCustomized extends MenuChangePageState {
+  MenuChangePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index f9cbcd0cbe4d3f6668255e0d0bb94ab0019dc8d4..9f8fcadeac15895ab4d0926fbe0e81aeca712742 100644 (file)
@@ -3,18 +3,31 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'menu_controller.dart';
 
+const menuCreatePageModule = 'menu';
+const menuCreatePageName = 'create';
+const menuCreatePageType = PageModelType.create;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class MenuCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   MenuCreatePage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   MenuCreatePageState createState() {
-    final rc = MenuCreatePageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = MenuCreatePageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,35 +35,54 @@ class MenuCreatePage extends StatefulWidget {
   }
 }
 
-class MenuCreatePageState extends State<MenuCreatePage> implements RedrawPage {
+abstract class MenuCreatePageState extends State<MenuCreatePage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'menu_create');
+      GlobalKey<FormState>(debugLabel: '$menuCreatePageModule-$menuCreatePageName');
 
   MenuController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   MenuCreatePageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
-    controller.beginOfBuild(context);
+    controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Neues Startmenü'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(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 dispose() {
+    controller.dispose();
+    super.dispose();
   }
 
   @override
   void initState() {
     super.initState();
     controller =
-        MenuController(_formKey, this, 'create', context, applicationData);
+        MenuController(_formKey, this, menuCreatePageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -62,3 +94,15 @@ class MenuCreatePageState extends State<MenuCreatePage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class MenuCreatePageStateCustomized extends MenuCreatePageState {
+  MenuCreatePageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index b7bdf00447516a506e56679718c6489366c8a420..6bf709a378d435fc7d4c7bffd8556e402ede1681 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,26 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'menu_controller.dart';
 
+const menuListPageModule = 'menu';
+const menuListPageName = 'list';
+const menuListPageType = PageModelType.list;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class MenuListPage extends StatefulWidget {
   final ApplicationData applicationData;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   MenuListPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   MenuListPageState createState() {
-    // MenuListPageState.setPageData(pageData);
-    final rc = MenuListPageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = MenuListPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,22 +34,31 @@ class MenuListPage extends StatefulWidget {
   }
 }
 
-class MenuListPageState extends State<MenuListPage> implements RedrawPage {
+abstract class MenuListPageState extends State<MenuListPage>
+    implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'menu_list');
+      GlobalKey<FormState>(debugLabel: '$menuListPageModule-$menuListPageName');
   Iterable<dynamic> rowsDeprecated;
   MenuController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   MenuListPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+
+    //! === BeginOfScaffold ===
     return Scaffold(
-      appBar: applicationData.appBarBuilder('Startmenüs'),
+      appBar: applicationData.appBarBuilder(controller.page.title),
       drawer: applicationData.drawerBuilder(context),
+      bottomNavigationBar: applicationData.footerBuilder().widget(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -47,29 +68,30 @@ class MenuListPageState extends State<MenuListPage> implements RedrawPage {
         showEditIcon: true,
         pageController: controller,
         buttons: <Widget>[
-          ButtonBar(alignment: MainAxisAlignment.center, children: [
-            controller.searchButton(),
-            RaisedButton(
-              child: Text('Neues Startmenü'),
-              onPressed: () {
-                controller.goTo(pageType: PageModelType.create);
-              },
-            ),
-          ]),
+          ButtonBar(
+            alignment: MainAxisAlignment.spaceBetween,
+            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 =
-        MenuController(_formKey, this, 'list', context, applicationData);
+        MenuController(_formKey, this, menuListPageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -81,3 +103,18 @@ class MenuListPageState extends State<MenuListPage> implements RedrawPage {
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class MenuListPageStateCustomized extends MenuListPageState {
+  MenuListPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    final button = controller.page.buttonByName('new');
+    button.onPressed = () {
+      controller.goTo(pageType: PageModelType.create);
+    };
+  }
+}
index 73b942a3306bb2d8e034aa47111556a4083cc309..cb75dd9c1bc6727827dc5786f2307d39255ee3c8 100644 (file)
@@ -4,10 +4,13 @@ import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
+import '../../model/page_model.dart';
 import 'role_controller.dart';
 
+const roleChangePageModule = 'role';
+const roleChangePageName = 'create';
+const roleChangePageType = PageModelType.create;
 //! === BeginOfGeneratedCode: Change only in areas marked as customized
-//! pageType: change
 
 class RoleChangePage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -43,7 +46,7 @@ abstract class RoleChangePageState extends State<RoleChangePage>
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-  GlobalKey<FormState>(debugLabel: 'role_change');
+      GlobalKey<FormState>(debugLabel: '$roleChangePageModule-$roleChangePageName');
 
   RoleController controller;
 
@@ -61,8 +64,7 @@ abstract class RoleChangePageState extends State<RoleChangePage>
     return Scaffold(
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
-        persistentFooterButtons:
-            applicationData.footerBuilder().widgets(controller),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -76,6 +78,7 @@ abstract class RoleChangePageState extends State<RoleChangePage>
   /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
@@ -103,8 +106,8 @@ abstract class RoleChangePageState extends State<RoleChangePage>
 //! === EndOfGeneratedCode: Below you can change manually:
 
 class RoleChangePageStateCustomized extends RoleChangePageState {
-  RoleChangePageStateCustomized(int primaryId, ApplicationData applicationData,
-      Map initialRow)
+  RoleChangePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
       : super(primaryId, applicationData, initialRow);
 
   @override
index ed578ac80efa07a2ef755283114dc79a654ccad7..13fe1f2aafa8166caab53993fa88822c464b7020 100644 (file)
@@ -3,11 +3,14 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'role_controller.dart';
 
+const roleCreatePageModule = 'role';
+const roleCreatePageName = 'create';
+const roleCreatePageType = PageModelType.create;
 //! === BeginOfGeneratedCode: Change only in areas marked as customized
-//! pageType: change
 
 class RoleCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -37,7 +40,7 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'role_create');
+      GlobalKey<FormState>(debugLabel: '$roleCreatePageModule-$roleCreatePageName');
 
   RoleController controller;
 
@@ -55,8 +58,7 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
     return Scaffold(
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
-        persistentFooterButtons:
-            applicationData.footerBuilder().widgets(controller),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -68,11 +70,17 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
   /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
   @override
   void initState() {
     super.initState();
     controller =
-        RoleController(_formKey, this, 'create', context, applicationData);
+        RoleController(_formKey, this, roleCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
index 77af1b718e6803bbc37f4dfd54c94817de46b5b3..313f207a9e98efdfadd7b58e1c3b379ba57399af 100644 (file)
@@ -1,14 +1,16 @@
 import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/page_model.dart';
 import '../../widget/list_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../widget/view.dart';
 import '../application_data.dart';
 import 'role_controller.dart';
 
+const roleListPageModule = 'role';
+const roleListPageName = 'list';
+const roleListPageType = PageModelType.list;
 //! === BeginOfGeneratedCode: Change only in areas marked as customized
-//! pageType: change
 
 class RoleListPage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -37,7 +39,7 @@ abstract class RoleListPageState extends State<RoleListPage>
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'role_list');
+      GlobalKey<FormState>(debugLabel: '$roleListPageModule-$roleListPageName');
   Iterable<dynamic> rowsDeprecated;
   RoleController controller;
 
@@ -56,8 +58,7 @@ abstract class RoleListPageState extends State<RoleListPage>
     return Scaffold(
       appBar: applicationData.appBarBuilder(controller.page.title),
       drawer: applicationData.drawerBuilder(context),
-      persistentFooterButtons:
-          applicationData.footerBuilder().widgets(controller),
+      bottomNavigationBar: applicationData.footerBuilder().widget(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -67,9 +68,12 @@ abstract class RoleListPageState extends State<RoleListPage>
         showEditIcon: true,
         pageController: controller,
         buttons: <Widget>[
-          ButtonBar(alignment: MainAxisAlignment.center,
-              children: View().modelsToWidgets(controller.page.sections[0].buttonBar, controller),
-          )],
+          ButtonBar(
+            alignment: MainAxisAlignment.spaceBetween,
+            children: View().modelsToWidgets(
+                controller.page.sections[0].buttonBar, controller),
+          )
+        ],
         filters: controller.modelList,
         errorMessage:
             applicationData.lastErrorMessage(controller.page.fullName()),
@@ -85,7 +89,7 @@ abstract class RoleListPageState extends State<RoleListPage>
   void initState() {
     super.initState();
     controller =
-        RoleController(_formKey, this, 'list', context, applicationData);
+        RoleController(_formKey, this, roleListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -112,6 +116,5 @@ class RoleListPageStateCustomized extends RoleListPageState {
     button.onPressed = () {
       controller.goTo(pageType: PageModelType.create);
     };
-    controller.modelList.addModel(button.name, button);
   }
 }
index 453c516be67849182172dea599fdc0f1e92ed9da..ea8001f658babe0bc5ede0935a7d05f95c5462bb 100644 (file)
@@ -3,24 +3,36 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'starter_controller.dart';
 
+const starterChangePageModule = 'starter';
+const starterChangePageName = 'change';
+const starterChangePageType = PageModelType.change;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class StarterChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
 
-  //StarterChangePageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   StarterChangePage(this.primaryId, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   StarterChangePageState createState() {
-    final rc = StarterChangePageState(primaryId, applicationData, initialRow);
+    //! === BeginOfCall ===
+    final rc =
+        StarterChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -28,7 +40,7 @@ class StarterChangePage extends StatefulWidget {
   }
 }
 
-class StarterChangePageState extends State<StarterChangePage>
+abstract class StarterChangePageState extends State<StarterChangePage>
     implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
@@ -38,15 +50,21 @@ class StarterChangePageState extends State<StarterChangePage>
 
   StarterController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   StarterChangePageState(this.primaryId, this.applicationData, this.initialRow);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
-    // controller.buildWidgetList() is called in editForm
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Startmenü Ã¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -54,8 +72,12 @@ class StarterChangePageState extends State<StarterChangePage>
           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();
@@ -64,14 +86,10 @@ class StarterChangePageState extends State<StarterChangePage>
   @override
   void initState() {
     super.initState();
-    controller = StarterController(
-      _formKey,
-      this,
-      'change',
-      context,
-      applicationData,
-    );
+    controller =
+        StarterController(_formKey, this, 'change', context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -83,3 +101,16 @@ class StarterChangePageState extends State<StarterChangePage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class StarterChangePageStateCustomized extends StarterChangePageState {
+  StarterChangePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index eab2be4b0a6c75826e332571f41dbb63c7b5b3a8..3c47cb388d13ab8948a08b28bde8c449904fbec6 100644 (file)
@@ -3,18 +3,31 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'starter_controller.dart';
 
+const starterCreatePageModule = 'starter';
+const starterCreatePageName = 'create';
+const starterCreatePageType = PageModelType.create;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class StarterCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   StarterCreatePage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   StarterCreatePageState createState() {
-    final rc = StarterCreatePageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = StarterCreatePageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,36 +35,54 @@ class StarterCreatePage extends StatefulWidget {
   }
 }
 
-class StarterCreatePageState extends State<StarterCreatePage>
+abstract class StarterCreatePageState extends State<StarterCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'starter_create');
+      GlobalKey<FormState>(debugLabel: '$starterCreatePageModule-$starterCreatePageName');
 
   StarterController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   StarterCreatePageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
-    controller.beginOfBuild(context);
+    controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Neues Startmenü'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(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 dispose() {
+    controller.dispose();
+    super.dispose();
   }
 
   @override
   void initState() {
     super.initState();
     controller =
-        StarterController(_formKey, this, 'create', context, applicationData);
+        StarterController(_formKey, this, starterCreatePageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -63,3 +94,15 @@ class StarterCreatePageState extends State<StarterCreatePage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class StarterCreatePageStateCustomized extends StarterCreatePageState {
+  StarterCreatePageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 20e0f06a8c7de956fbcb55d472043bc866e9e713..41d00ed0d52a51bad0469f8f3647dd259ec25973 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,26 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'starter_controller.dart';
 
+const starterListPageModule = 'starter';
+const starterListPageName = 'list';
+const starterListPageType = PageModelType.list;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class StarterListPage extends StatefulWidget {
   final ApplicationData applicationData;
 
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   StarterListPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   StarterListPageState createState() {
-    // StarterListPageState.setPageData(pageData);
-    final rc = StarterListPageState(applicationData);
+    //! === BeginOfCall ===
+    final rc = StarterListPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -22,56 +34,64 @@ class StarterListPage extends StatefulWidget {
   }
 }
 
-class StarterListPageState extends State<StarterListPage>
+abstract class StarterListPageState extends State<StarterListPage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'starter_list');
+      GlobalKey<FormState>(debugLabel: '$starterListPageModule-$starterListPageName');
   Iterable<dynamic> rowsDeprecated;
   StarterController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   StarterListPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+
+    //! === BeginOfScaffold ===
     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));
+      appBar: applicationData.appBarBuilder(controller.page.title),
+      drawer: applicationData.drawerBuilder(context),
+      bottomNavigationBar: applicationData.footerBuilder().widget(controller),
+      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.spaceBetween,
+            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 =
-        StarterController(_formKey, this, 'list', context, applicationData);
+        StarterController(_formKey, this, starterListPageName, context, applicationData);
     controller.initialize();
+    customize();
   }
 
   @override
@@ -83,3 +103,18 @@ class StarterListPageState extends State<StarterListPage>
     });
   }
 }
+
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class StarterListPageStateCustomized extends StarterListPageState {
+  StarterListPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  void customize() {
+    final button = controller.page.buttonByName('new');
+    button.onPressed = () {
+      controller.goTo(pageType: PageModelType.create);
+    };
+  }
+}
index e60bea77fe285b74dadd3c3e0e7781eefe5e66b1..984fcb2f5087d82ab43a46d441a9328e67c6b3c3 100644 (file)
@@ -4,12 +4,15 @@ import '../../helper/settings.dart';
 import '../../model/button_model.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
 import 'user_password_page.dart';
 
-//! === BeginOfGeneratedCode: Below you can change manually:
-//! pageType: change
+const userChangePageModule = 'user';
+const userChangePageName = 'change';
+const userChangePageType = PageModelType.change;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
 
 class UserChangePage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -45,7 +48,7 @@ abstract class UserChangePageState extends State<UserChangePage>
   final int primaryId;
   final Map initialRow;
   final GlobalKey<FormState> _formKey =
-  GlobalKey<FormState>(debugLabel: 'user_change');
+      GlobalKey<FormState>(debugLabel: '$userChangePageModule-$userChangePageName');
 
   UserController controller;
 
@@ -63,8 +66,7 @@ abstract class UserChangePageState extends State<UserChangePage>
     return Scaffold(
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
-        persistentFooterButtons:
-            applicationData.footerBuilder().widgets(controller),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -78,6 +80,7 @@ abstract class UserChangePageState extends State<UserChangePage>
   /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
@@ -105,8 +108,8 @@ abstract class UserChangePageState extends State<UserChangePage>
 //! === EndOfGeneratedCode: Below you can change manually:
 
 class UserChangePageStateCustomized extends UserChangePageState {
-  UserChangePageStateCustomized(int primaryId, ApplicationData applicationData,
-      Map initialRow)
+  UserChangePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
       : super(primaryId, applicationData, initialRow);
 
   @override
index 09689a25f00cf14768fc865b2aabd3a06dce9bac..9dcb2ec370b80c2f9623cb15bdc762b93981965a 100644 (file)
@@ -3,11 +3,14 @@ import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
 
-//! === BeginOfGeneratedCode: Below you can change manually:
-//! pageType: change
+const userCreatePageModule = 'user';
+const userCreatePageName = 'create';
+const userCreatePageType = PageModelType.create;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
 
 class UserCreatePage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -37,7 +40,7 @@ abstract class UserCreatePageState extends State<UserCreatePage>
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user_create');
+      GlobalKey<FormState>(debugLabel: '$userCreatePageModule-$userCreatePageName');
 
   UserController controller;
 
@@ -53,10 +56,9 @@ abstract class UserCreatePageState extends State<UserCreatePage>
     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),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
@@ -68,6 +70,12 @@ abstract class UserCreatePageState extends State<UserCreatePage>
   /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
   @override
   void initState() {
     super.initState();
index 16e6939fa7674b62e961d36393a925926076909d..a537dc958c71fd91d7f87dd3fe711fe1f94a1af1 100644 (file)
@@ -7,8 +7,10 @@ import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
 
-//! === BeginOfGeneratedCode: Below you can change manually:
-//! pageType: change
+const userListPageModule = 'user';
+const userListPageName = 'list';
+const userListPageType = PageModelType.list;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
 
 class UserListPage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -37,7 +39,7 @@ abstract class UserListPageState extends State<UserListPage>
   final ApplicationData applicationData;
 
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user_list');
+      GlobalKey<FormState>(debugLabel: '$userListPageModule-$userListPageName');
   Iterable<dynamic> rowsDeprecated;
   UserController controller;
 
@@ -54,10 +56,9 @@ abstract class UserListPageState extends State<UserListPage>
 
     //! === BeginOfScaffold ===
     return Scaffold(
-      appBar: applicationData.appBarBuilder('Rollen'),
+      appBar: applicationData.appBarBuilder(controller.page.title),
       drawer: applicationData.drawerBuilder(context),
-      persistentFooterButtons:
-          applicationData.footerBuilder().widgets(controller),
+      bottomNavigationBar: applicationData.footerBuilder().widget(controller),
       body: ListForm.listForm(
         key: _formKey,
         configuration: applicationData.configuration,
@@ -67,15 +68,11 @@ abstract class UserListPageState extends State<UserListPage>
         showEditIcon: true,
         pageController: controller,
         buttons: <Widget>[
-          ButtonBar(alignment: MainAxisAlignment.center, children: [
-            controller.searchButton(),
-            RaisedButton(
-              child: Text('Neuer Benutzer'),
-              onPressed: () {
-                controller.goTo(pageType: PageModelType.create);
-              },
-            ),
-          ]),
+          ButtonBar(
+            alignment: MainAxisAlignment.spaceBetween,
+            children: View().modelsToWidgets(
+                controller.page.sections[0].buttonBar, controller),
+          )
         ],
         filters: controller.modelList,
         errorMessage:
@@ -92,7 +89,7 @@ abstract class UserListPageState extends State<UserListPage>
   void initState() {
     super.initState();
     controller =
-        UserController(_formKey, this, 'list', context, applicationData);
+        UserController(_formKey, this, userListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -115,6 +112,9 @@ class UserListPageStateCustomized extends UserListPageState {
 
   @override
   void customize() {
-    // ToDo: write code if needed
+    final button = controller.page.buttonByName('new');
+    button.onPressed = () {
+      controller.goTo(pageType: PageModelType.create);
+    };
   }
 }
index abf35653f5a05084246dc64113e1ad68359b538d..fd7d9d96a32df6eb229a283c16f214f83ea7cb61 100644 (file)
@@ -1,26 +1,35 @@
-import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
 import '../../model/button_model.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'hash.dart';
 import 'user_controller.dart';
 
-// === BeginOfGeneratedCode: Do not change manually
+const userLoginPageModule = 'user';
+const userLoginPageName = 'login';
+const userLoginPageType = PageModelType.create;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class UserLoginPage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
 
-  //UserLoginPageState lastState;
-
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   UserLoginPage(this.applicationData, {Key key}) : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   UserLoginPageState createState() {
+    //! === BeginOfCall ===
     final rc = UserLoginPageStateCustomized(applicationData);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -31,29 +40,39 @@ class UserLoginPage extends StatefulWidget {
 abstract class UserLoginPageState extends State<UserLoginPage>
     implements RedrawPage {
   final ApplicationData applicationData;
+
   final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user.login');
+      GlobalKey<FormState>(debugLabel: '$userLoginPageModule-$userLoginPageName');
 
   UserController controller;
 
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
   UserLoginPageState(this.applicationData);
 
+  //! === EndOfConstructor2 ===
+
   @override
   Widget build(BuildContext context) {
-    controller.beginOfBuild(context);
+    controller?.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Anmelden'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
-          primaryId: 0,
         ));
+    //! === EndOfScaffold ===
   }
 
+  /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
@@ -62,8 +81,8 @@ abstract class UserLoginPageState extends State<UserLoginPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, 'login', context, applicationData);
+    controller = UserController(
+        _formKey, this, userLoginPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -77,7 +96,8 @@ abstract class UserLoginPageState extends State<UserLoginPage>
     });
   }
 }
-// === EndOfGeneratedCode: Do not change manually
+
+//! === EndOfGeneratedCode: Below you can change manually:
 
 class UserLoginPageStateCustomized extends UserLoginPageState {
   UserLoginPageStateCustomized(ApplicationData applicationData)
@@ -88,7 +108,6 @@ class UserLoginPageStateCustomized extends UserLoginPageState {
       if (_formKey.currentState.validate()) {
         _formKey.currentState.save();
         final user = controller.page.fieldByName('user').value;
-
         final params = {
           ':user': user,
         };
index cedfec337a4f65386cbdbd2e2aa8f121de79b6c4..950d232254819c464646fdd7718c2011404aa02b 100644 (file)
@@ -4,27 +4,38 @@ import '../../helper/settings.dart';
 import '../../model/button_model.dart';
 import '../../widget/edit_form.dart';
 import '../../widget/page_controller_bones.dart';
+import '../../model/page_model.dart';
 import '../application_data.dart';
 import 'hash.dart';
 import 'user_controller.dart';
 
+const userPasswordPageModule = 'user';
+const userPasswordPageName = 'password';
+const userPasswordPageType = PageModelType.change;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
 class UserPasswordPage extends StatefulWidget {
   final ApplicationData applicationData;
   final Map initialRow;
   final logger = Settings().logger;
   final int primaryId;
-  final String userName;
-
-  //UserPasswordPageState lastState;
 
+  //! === BeginOfCustomizedVars1 ===
+  final String userName;
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
   UserPasswordPage(
       this.primaryId, this.userName, this.applicationData, this.initialRow,
       {Key key})
       : super(key: key);
 
+  //! === EndOfConstructor1 ===
+
   @override
   UserPasswordPageState createState() {
-    final rc = UserPasswordPageState(primaryId, this.userName, applicationData);
+    //! === BeginOfCall ===
+    final rc = UserPasswordPageStateCustomized(primaryId, this.userName, applicationData, initialRow);
+    //! === EndOfCall ===
 
     /// for unittests:
     applicationData.lastModuleState = rc;
@@ -32,32 +43,77 @@ class UserPasswordPage extends StatefulWidget {
   }
 }
 
-class UserPasswordPageState extends State<UserPasswordPage>
+abstract class UserPasswordPageState extends State<UserPasswordPage>
     implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
-  final String userName;
   final GlobalKey<FormState> _formKey =
       GlobalKey<FormState>(debugLabel: 'user.password');
+  final Map initialRow;
 
   UserController controller;
 
-  UserPasswordPageState(this.primaryId, this.userName, this.applicationData);
+  //! === BeginOfCustomizedVars2 ===
+  final String userName;
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  UserPasswordPageState(this.primaryId, this.userName, this.applicationData, this.initialRow);
+  //! === EndOfConstructor2 ===
 
   @override
   Widget build(BuildContext context) {
     controller.beginOfBuild(context);
+    //! === BeginOfScaffold ===
     return Scaffold(
-        appBar: applicationData.appBarBuilder('Passwort Ã¤ndern'),
+        appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
         body: EditForm.editForm(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
           primaryId: primaryId,
+          initialRow: initialRow,
         ));
+    //! === EndOfScaffold ===
+  }
+
+  /// Customize the page, e.g. modify the default widget list given by the model
+  void customize();
+
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    controller =
+        UserController(_formKey, this, userPasswordPageName, context, applicationData);
+    controller.initialize();
+    customize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
   }
+}
 
+//! === EndOfGeneratedCode: Below you can change manually:
+
+class UserPasswordPageStateCustomized extends UserPasswordPageState {
+  UserPasswordPageStateCustomized(
+      int primaryId, String userName, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, userName, applicationData, initialRow);
+
+  @override
   void customize() {
     controller.placeholders['user'] = userName;
     ButtonModel button = controller.page.buttonByName('store');
@@ -85,27 +141,4 @@ class UserPasswordPageState extends State<UserPasswordPage>
       }
     };
   }
-
-  void dispose() {
-    controller.dispose();
-    super.dispose();
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    controller =
-        UserController(_formKey, this, 'password', context, applicationData);
-    controller.initialize();
-    customize();
-  }
-
-  @override
-  void redraw(RedrawReason reason,
-      {String customString, RedrawCallbackFunctionSimple callback}) {
-    setState(() {
-      controller.afterSetState(reason,
-          customString: customString, callback: callback);
-    });
-  }
 }
index c13753be4438453a7878a3358e0686fea95abf81..e21df021e60e5b47a1df9c95b284443d8d4bf88c 100644 (file)
@@ -4,26 +4,24 @@ 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}'),
-      ])
-    ];
+  Widget widget(PageControllerBones controller) {
+    final rc = ButtonBar(alignment: MainAxisAlignment.spaceBetween, children: [
+      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'),
index 292b72c5ec434517c39d2272254b3b06d8ef7741..ec505213b677aed033131f38c1aa4355ed0843c4 100644 (file)
@@ -24,8 +24,8 @@ class EditForm {
     pageController.buildModelList(initialRow);
     final widgets = pageController.getWidgets();
     final view = View();
-    final buttons =
-        view.modelsToWidgets(pageController.page.sections[0].buttonBar, pageController);
+    final buttons = view.modelsToWidgets(
+        pageController.page.sections[0].buttonBar, pageController);
     return Form(
       key: key,
       child: Card(
index 446b36ac6ffd00999538c08ca0eee3dc4868eb29..f76f40880315cd3fd7bde282536cac05d1c2060d 100644 (file)
@@ -279,7 +279,9 @@ class View {
   /// Returns a form with the properties given by the [sectionModel]
   /// [formKey] identifies the form. Used for form validation and saving.
   Form simpleForm(
-      {SectionModel sectionModel, PageControllerBones controller, Key formKey}) {
+      {SectionModel sectionModel,
+      PageControllerBones controller,
+      Key formKey}) {
     assert(formKey != null);
     final padding =
         widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
index c67d095ea0513e5169c3ce1565efe62f882b3644..b3331a521100326d8f92505e0ac1ccf167728ade 100644 (file)
@@ -1,11 +1,12 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter_bones_tool/src/model/model_helper.dart';
-import 'package:flutter_bones_tool/src/model_tools.dart';
+import 'package:flutter_bones_tool/src/model/model_tool.dart';
+import 'package:flutter_bones_tool/src/model_tool_io.dart';
 
 void main(List<String> argv) async {
   final logger = MemoryLogger(LEVEL_FINE);
   final ModelHelper modelHelper = ModelHelper();
-  final ModelTools modelTools = ModelTools(modelHelper, logger);
+  final ModelTool modelTools = ModelToolIo(modelHelper, logger);
   if (argv.isEmpty) {
     argv = ['export-sql'];
   }
diff --git a/model_tool/lib/src/model_tool_io.dart b/model_tool/lib/src/model_tool_io.dart
new file mode 100644 (file)
index 0000000..cbc744d
--- /dev/null
@@ -0,0 +1,45 @@
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones_tool/src/model/model_helper.dart';
+import 'package:flutter_bones_tool/src/model/model_tool.dart';
+
+/// Completes the ModelTool with io specific functionality.
+class ModelToolIo extends ModelTool {
+  ModelToolIo(ModelHelper modelHelper, BaseLogger logger)
+      : super(modelHelper, logger);
+
+  @override
+  void ensureDirectory(String path) {
+    FileSync.ensureDirectory(path);
+  }
+
+  @override
+  List<String> pathOfDirectory(String path) {
+    final rc = Directory(path).listSync().map((entry) => entry.path).toList();
+    return rc;
+  }
+
+  @override
+  readFile(String file) {
+    final rc = FileSync.fileAsList(file);
+    return rc;
+  }
+
+  @override
+  String safeDirectory(String name) {
+    final rc = FileSync.tempDirectory(
+        name +
+            '.' +
+            (DateTime.now().millisecondsSinceEpoch / 1000 % 86400)
+                .round()
+                .toString(),
+        subDirs: 'model_tools');
+    return rc;
+  }
+
+  @override
+  void writeFile(String filename, String content) {
+    FileSync.toFile(filename, content);
+  }
+}
diff --git a/model_tool/lib/src/model_tools.dart b/model_tool/lib/src/model_tools.dart
deleted file mode 100644 (file)
index 8fb97f6..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-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
-''');
-  }
-}
index 25684ba5f78ab83670c22757cbd5c9614db0b3e9..c4dacc3a4aa259be7d218f244176e74e54094c95 100644 (file)
@@ -29,6 +29,12 @@ dependencies:
   # cupertino_icons: ^1.0.0
 
 dev_dependencies:
+  pedantic: ^1.8.0
+  test: ^1.6.0
+  mockito: ^4.1.1
+  test_coverage: ^0.4.2
+  flutter_test:
+    sdk: flutter
 
 #  flutter_test:
 #    sdk: flutter
diff --git a/model_tool/test/model_tool_test.dart b/model_tool/test/model_tool_test.dart
new file mode 100644 (file)
index 0000000..3e554a2
--- /dev/null
@@ -0,0 +1,17 @@
+import 'package:flutter_bones_tool/src/model/model_helper.dart';
+import 'package:flutter_bones_tool/src/model_tool_io.dart';
+import 'package:test/test.dart';
+import 'package:dart_bones/dart_bones.dart';
+
+void main() {
+  final logger = MemoryLogger(LEVEL_FINE);
+  group('modify-modules', () {
+    test('modify-modules-std', () {
+      logger.clear();
+      final helper = ModelHelper();
+      final tool = ModelToolIo(helper, logger);
+      String target = FileSync.tempDirectory('module_tool_test');
+      tool.modifyModule(['user'], ['--directory=$target']);
+    });
+  });
+}
index fd62faab9384ed5d47093284049f4e7c299548fe..be6d207a8163d20852443c279f5bc0b70f304944 100644 (file)
@@ -35,13 +35,12 @@ dependencies:
   # cupertino_icons: ^1.0.0
 
 dev_dependencies:
-  flutter_test:
-    sdk: flutter
   pedantic: ^1.8.0
   test: ^1.6.0
   mockito: ^4.1.1
   test_coverage: ^0.4.2
-
+  flutter_test:
+    sdk: flutter
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 
index e33d2848fee17d5f2406cc952d32dccf26934ac7..41d8e1b48f21341ee6ec6120790270dc2f751682 100644 (file)
@@ -49,7 +49,7 @@ void main() {
       expect(
           StringHelper.replacePlaceholders(
               'a: ~aa~ b: ~b c: ~c~ ~d~', {'aa': 'x', 'b': 'y', 'c': 'zz'},
-          syntaxPlaceholder: r'~(\w+)~'),
+              syntaxPlaceholder: r'~(\w+)~'),
           equals('a: x b: ~b c: zz ~d~'));
     });
   });
@@ -142,8 +142,8 @@ void main() {
       expect(options, equals([]));
     });
   });
-  group('StringList', (){
-    test('listIndexOf-string', (){
+  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));
@@ -151,16 +151,147 @@ void main() {
       expect(StringHelper.listIndexOf(list, string: 'x'), isNull);
       expect(StringHelper.listIndexOf(null, string: 'x'), isNull);
     });
-    test('listIndexOf-regEx', (){
+    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'a', caseSensitive: false)),
+          equals(0));
       expect(StringHelper.listIndexOf(list, regExp: RegExp(r'd')), isNull);
       expect(StringHelper.listIndexOf(null, regExp: RegExp(r'd')), isNull);
     });
   });
+  group('addRangeToList', () {
+    test('addRangeToList-String', () {
+      List<String> output;
+      final list = <String>['a', '-- bab', 'cAC', '123'];
+      expect(StringHelper.addRangeToList(list, null, endString: 'b').join('\n'),
+          equals('''a
+-- bab'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  endString: 'b', includeEnd: false)
+              .join('\n'),
+          equals('''a'''));
+      expect(
+          StringHelper.addRangeToList(list, output, startString: 'C')
+              .join('\n'),
+          equals('''cAC
+123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startString: 'C', includeStart: false)
+              .join('\n'),
+          equals('''123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startString: 'b', endString: '2')
+              .join('\n'),
+          equals('''-- bab
+cAC
+123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startString: 'b',
+                  includeStart: false,
+                  endString: '2',
+                  includeEnd: false)
+              .join('\n'),
+          equals('''cAC'''));
+    });
+    test('addRangeToList-regExp', () {
+      List<String> output;
+      final list = <String>['a', '-- bab', 'cAC', '123'];
+      expect(
+          StringHelper.addRangeToList(list, null, endPattern: 'b').join('\n'),
+          equals('''a
+-- bab'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  endPattern: 'b', includeEnd: false)
+              .join('\n'),
+          equals('''a'''));
+      expect(
+          StringHelper.addRangeToList(list, output, startPattern: 'C')
+              .join('\n'),
+          equals('''cAC
+123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startPattern: '[C]', includeStart: false)
+              .join('\n'),
+          equals('''123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startPattern: 'b', endPattern: '2')
+              .join('\n'),
+          equals('''-- bab
+cAC
+123'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  startPattern: '[b]',
+                  includeStart: false,
+                  endPattern: r'\d',
+                  includeEnd: false)
+              .join('\n'),
+          equals('''cAC'''));
+    });
+    test('addRangeToList-firstIndex', () {
+      List<String> output;
+      final list = <String>['a', '-- bab', 'cAC', '123AC'];
+      expect(
+          StringHelper.addRangeToList(list, null, endString: 'a', firstIndex: 1)
+              .join('\n'),
+          equals('''-- bab'''));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  firstIndex: -1, startString: 'A')
+              .join('\n'),
+          equals('''123AC'''));
+    });
+    test('addRangeToList-lastIndexIncluded', () {
+      List<String> output;
+      final list = <String>['a1', 'a2', 'a3', 'a4'];
+      expect(
+          StringHelper.addRangeToList(list, null,
+                  startString: 'a2', lastIndexIncluded: 2)
+              .join('\n'),
+          equals('a2\na3'));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  lastIndexIncluded: -2, startString: 'a3')
+              .join('\n'),
+          equals('a3'));
+    });
+    test('addRangeToList-lastIndexExcluded', () {
+      List<String> output;
+      final list = <String>['a1', 'a2', 'a3', 'a4'];
+      expect(
+          StringHelper.addRangeToList(list, null,
+                  startString: 'a2', lastIndexExcluded: 2)
+              .join('\n'),
+          equals('a2'));
+      expect(
+          StringHelper.addRangeToList(list, output,
+                  lastIndexExcluded: -2, startString: 'a3')
+              .join('\n'),
+          equals(''));
+    });
+    test('addRangeToList-converter', () {
+      List<String> output;
+      final list = <String>['a1', 'a2', 'a3', 'a4'];
+      expect(
+          StringHelper.addRangeToList(list, null,
+              startRegExp: RegExp('[2-3]'),
+              endRegExp: RegExp(r'[3-4]'),
+              converter: (input) => input.toUpperCase()).join('\n'),
+          equals('A2\nA3'));
+    });
+  });
 }
index 8331bb01fa7d271cfdc62f486a759ce41a0edab1..0950f96f1693db824ab729d76809a873c8ed4138 100644 (file)
@@ -273,7 +273,7 @@ Map<String, dynamic> cloneOfMap(Map<String, dynamic> map) {
 class Demo1 extends ModuleModel {
   Demo1(Map<String, dynamic> map, BaseLogger logger) : super(map, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 }
index 2d630ec5af7e38c4b0278fec8b44a08cbc5086ed..5f14f5119a1833fe945b2880ffa8706c43bd18bf 100644 (file)
@@ -677,7 +677,7 @@ Map<String, dynamic> cloneOfMap(Map<String, dynamic> map) {
 class Demo1 extends ModuleModel {
   Demo1(Map<String, dynamic> map, BaseLogger logger) : super(map, logger);
 
-  /// Returns the name including the names of the parent
+  /// Returns the name including the names of the parents.
   @override
   String fullName() => name;
 }
index 881d0d36d6d4c92ce25a0eb39eb6dddafec87fd0..cc616e4436af092e17a008c6b432deb962dd539f 100644 (file)
@@ -1,8 +1,13 @@
+import 'dart:io';
+
 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';
 
+import '../../lib/src/model/model_helper.dart' as mh;
+import '../../lib/src/model/model_tool.dart';
+
 void main() {
   final logger = MemoryLogger(LEVEL_FINE);
   group('sql', () {
@@ -67,7 +72,7 @@ modules:
 '''));
     });
     test('standard_modules', () {
-      ModelHelper helper = ModelHelper();
+      final helper = mh.ModelHelper();
       logger.clear();
       final dir = FileSync.tempDirectory('data', subDirs: 'unittest');
       for (var name in helper.moduleNames()) {
@@ -80,10 +85,228 @@ modules:
     });
     test('standard_modules-errors', () {
       logger.clear();
-      final helper = ModelHelper();
+      final helper = mh.ModelHelper();
       final module = helper.moduleByName('not-exists', logger);
       expect(module, isNull);
       expect(logger.contains('unknown standard module: not-exists'), isTrue);
     });
   });
+  group('modify-modules', () {
+    test('modify-modules-std', () {
+      logger.clear();
+      final helper = mh.ModelHelper();
+      final tool = ModelToolIo(helper, logger);
+      String target = FileSync.tempDirectory('model_tool_test');
+      tool.modifyModule([], ['--directory=$target']);
+      expect(logger.errors.length, equals(0));
+      expect(tool.fileCache['/tmp/model_tool_test/user/user_change_page.dart'],
+          equals(bodyUserChangePage));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/user/user_create_page.dart']
+              .length,
+          equals(2756));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/user/user_list_page.dart']
+              .length,
+          equals(3455));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/configuration/configuration_create_page.dart']
+              .length,
+          equals(2885));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/configuration/configuration_change_page.dart']
+              .length,
+          equals(3285));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/configuration/configuration_list_page.dart']
+              .length,
+          equals(3583));
+    });
+    test('modify-modules-overwrite-constructors', () {
+      logger.clear();
+      final helper = mh.ModelHelper();
+      final tool = ModelToolIo(helper, logger);
+      String target = FileSync.tempDirectory('model_tool_test');
+      tool.modifyModule(
+          ['user'], ['--directory=$target', '--overwrite-constructors']);
+      expect(logger.errors.length, equals(0));
+      expect(tool.fileCache['/tmp/model_tool_test/user/user_change_page.dart'],
+          equals(bodyUserChangePage));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/user/user_create_page.dart']
+              .length,
+          equals(2756));
+      expect(
+          tool.fileCache['/tmp/model_tool_test/user/user_list_page.dart']
+              .length,
+          equals(3455));
+    });
+  });
+}
+
+final bodyUserChangePage = '''import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/button_model.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'user_controller.dart';
+import 'user_password_page.dart';
+
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+//! pageType: change
+
+class UserChangePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  UserChangePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  UserChangePageState createState() {
+    //! === BeginOfCall ===
+    final rc =
+        UserChangePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+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');
+
+  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(controller.page.title),
+        drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
+        body: EditForm.editForm(
+          key: _formKey,
+          pageController: controller,
+          configuration: applicationData.configuration,
+          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
+  void initState() {
+    super.initState();
+    controller =
+        UserController(_formKey, this, 'change', context, applicationData);
+    controller.initialize();
+    customize();
+  }
+
+  @override
+  void redraw(RedrawReason reason,
+      {String customString, RedrawCallbackFunctionSimple callback}) {
+    setState(() {
+      controller.afterSetState(reason,
+          customString: customString, callback: callback);
+    });
+  }
+}
+
+//! === 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)));
+    };
+  }
+}''';
+
+class ModelToolIo extends ModelTool {
+  ModelToolIo(mh.ModelHelper modelHelper, BaseLogger logger)
+      : super(modelHelper, logger);
+
+  final fileCache = <String, String>{};
+  @override
+  void ensureDirectory(String path) {
+    FileSync.ensureDirectory(path);
+  }
+
+  @override
+  List<String> pathOfDirectory(String path) {
+    final rc = Directory(path).listSync().map((entry) => entry.path).toList();
+    return rc;
+  }
+
+  @override
+  readFile(String file) {
+    final rc = FileSync.fileAsList(file);
+    return rc;
+  }
+
+  @override
+  String safeDirectory(String name) {
+    final rc = FileSync.tempDirectory(
+        name +
+            '.' +
+            12345.0
+                // (DateTime.now().millisecondsSinceEpoch / 1000 % 86400)
+                .round()
+                .toString(),
+        subDirs: 'model_tools');
+    return rc;
+  }
+
+  @override
+  void writeFile(String filename, String content) {
+    fileCache[filename] = content;
+    FileSync.toFile(filename, content);
+  }
 }