]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work master
authorHamatoma <author@hamatoma.de>
Mon, 23 Nov 2020 10:07:00 +0000 (11:07 +0100)
committerHamatoma <author@hamatoma.de>
Wed, 25 Nov 2020 19:01:35 +0000 (20:01 +0100)
61 files changed:
Coverage.sh
CreateModule
data/rest/custom.user.yaml
lib/app.dart
lib/flutter_bones.dart
lib/src/helper/string_helper.dart
lib/src/helper/url_helper.dart
lib/src/model/model_helper.dart
lib/src/model/model_tool.dart
lib/src/model/module/configuration_model.dart [new file with mode: 0644]
lib/src/model/module/menu_model.dart [new file with mode: 0644]
lib/src/model/module/role_model.dart [new file with mode: 0644]
lib/src/model/module/starter_model.dart [new file with mode: 0644]
lib/src/model/module/user_model.dart [new file with mode: 0644]
lib/src/model/page_model.dart
lib/src/model/standard/configuration_model.dart [deleted file]
lib/src/model/standard/menu_model.dart [deleted file]
lib/src/model/standard/role_model.dart [deleted file]
lib/src/model/standard/starter_model.dart [deleted file]
lib/src/model/standard/user_model.dart [deleted file]
lib/src/page/configuration/configuration_change_page.dart
lib/src/page/configuration/configuration_controller.dart
lib/src/page/configuration/configuration_create_page.dart
lib/src/page/configuration/configuration_delete_page.dart [new file with mode: 0644]
lib/src/page/configuration/configuration_list_page.dart
lib/src/page/menu/menu_change_page.dart
lib/src/page/menu/menu_controller.dart
lib/src/page/menu/menu_create_page.dart
lib/src/page/menu/menu_delete_page.dart [new file with mode: 0644]
lib/src/page/menu/menu_list_page.dart
lib/src/page/role/role_change_page.dart
lib/src/page/role/role_controller.dart
lib/src/page/role/role_create_page.dart
lib/src/page/role/role_custom_page.dart [new file with mode: 0644]
lib/src/page/role/role_delete_page.dart [new file with mode: 0644]
lib/src/page/role/role_list_page.dart
lib/src/page/starter/starter_change_page.dart
lib/src/page/starter/starter_controller.dart
lib/src/page/starter/starter_create_page.dart
lib/src/page/starter/starter_delete_page.dart [new file with mode: 0644]
lib/src/page/starter/starter_list_page.dart
lib/src/page/user/user_change_page.dart
lib/src/page/user/user_controller.dart
lib/src/page/user/user_create_page.dart
lib/src/page/user/user_delete_page.dart [new file with mode: 0644]
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/widget/dialog_bones.dart [new file with mode: 0644]
lib/src/widget/edit_form.dart [deleted file]
lib/src/widget/list_form.dart [deleted file]
lib/src/widget/page_controller_bones.dart
lib/src/widget/utilities.dart [new file with mode: 0644]
lib/src/widget/view.dart
model_tool/lib/src/model_tool_io.dart
test/helpers/string_helper_test.dart
test/helpers/url_helper_test.dart [new file with mode: 0644]
test/model/db_model_test.dart
test/model/model_test.dart
test/model/standard_test.dart
test/tool/tool_test.dart

index 0f927f2496c6735231b50f6a64f6d657a7ceb42b..016065ce61c65dedc682558158d7ce93aaad07c8 100755 (executable)
@@ -2,7 +2,14 @@
 if [ $(id -u) = 0 ]; then
   echo "+++ not working as root"
 else
-flutter test --coverage
-rm -Rf coverage/html/*
-genhtml --show-details --output-directory=coverage/html coverage/lcov.info
+  file=test/coverage_helper_test.dart
+  PACKAGE=flutter_bones
+  echo "// Helper file to make coverage work for all dart files\n" > $file
+  echo "// ignore_for_file: unused_import" >> $file
+  find lib -not -name '*.g.dart' -name '*.dart' | grep -v 'generated_plugin_registrant' | cut -c4- \
+    | awk -v package=$PACKAGE '{printf "import '\''package:%s%s'\'';\n", package, $1}' >> $file
+  echo "\nvoid main(){}" >> $file
+  flutter test --coverage
+  rm -Rf coverage/html/*
+  genhtml --show-details --output-directory=coverage/html coverage/lcov.info
 fi
index d513e73ba439b8b5619ad28302beb0ccac114b1f..dd4ac592d50e6a11cc17a83941398888dda8769e 100755 (executable)
@@ -1,22 +1,3 @@
 #! /bin/bash
-MODULE=$1
-if [ -z "$MODULE" ]; then
-  echo "Missing module name"
-  echo "example: $0 menu"
-else
-  cd lib/src/page
-  if [ -d $MODULE ]; then
-    FN_NEW=/tmp/$MODULE.$(date "+%s")
-    echo "$MODULE already exists, moving to $FN_NEW"
-    mv $MODULE $FN_NEW
-  fi
-  mkdir $MODULE
-  cp -av role/* $MODULE/
-  FN_REPL=/tmp/create_module.repl
-  MODULE3=$(echo $MODULE | tr a-z A-Z)
-  MODULE2=${MODULE3:0:1}${MODULE:1}
-  # echo -e "role\t$MODULE\nRole\t$MODULE2" >$FN_REPL
-  cd $MODULE
-  perl -pi -e"s/role/$MODULE/g;s/Role/$MODULE2/g;" *.dart
-  rename -v "s/role(.*)/${MODULE}\$1/;" *.dart
-fi
+dart run model_tool/lib/main.dart create-module $*
+
index 37243eb98c32ab5bc283eef33a5efb9284518f77..0cb5223609c4aa53f6c73163f459939940865c0e 100644 (file)
@@ -16,3 +16,9 @@ modules:
         sql: "SELECT u.*, r.* FROM user u
           LEFT JOIN role r ON u.user_role=r.role_id
           WHERE (user_name=:user or user_email=:user);"
+      - name: list
+        type: list
+        options: override
+        sql: "SELECT t0.*,t1.role_name as user_role, role_priority FROM user t0
+          LEFT JOIN role t1 on t1.role_id=t0.user_role
+          WHERE user_name like :user_name AND (:user_role IS NULL OR user_role=:user_role)"
index 5e4670dfccdc6ba34cd9393a786fdd85316dd534..2407c42dda4a58d3631aa765817358663cfe7681 100644 (file)
@@ -16,12 +16,14 @@ import 'src/page/user/user_login_page.dart';
 import 'src/page/starter/starter_list_page.dart';
 import 'src/page/starter/starter_create_page.dart';
 import 'src/private/bsettings.dart';
+import 'src/widget/utilities.dart';
 
 class BoneApp extends StatefulWidget {
   @override
   BoneAppState createState() {
     final logger = MemoryLogger(LEVEL_FINE);
     BSettings.create(logger);
+    utilities = Utilities(logger);
     final mapWidgetData = <String, dynamic>{
       'form.card.padding': '16.0',
       'form.gap.field_button.height': '16.0',
index ad289448dde01bf11741c94a3250a1c6c3d4f932..a5731ae4bde2fedc97fbf91b24093104d06022be 100644 (file)
@@ -17,10 +17,11 @@ export 'src/model/model_types.dart';
 export 'src/model/module_model.dart';
 export 'src/model/page_model.dart';
 export 'src/model/section_model.dart';
-export 'src/model/standard/configuration_model.dart';
-export 'src/model/standard/menu_model.dart';
-export 'src/model/standard/role_model.dart';
-export 'src/model/standard/user_model.dart';
+export 'src/model/module/configuration_model.dart';
+export 'src/model/module/menu_model.dart';
+export 'src/model/module/role_model.dart';
+export 'src/model/module/starter_model.dart';
+export 'src/model/module/user_model.dart';
 export 'src/model/text_field_model.dart';
 export 'src/model/text_model.dart';
 export 'src/model/widget_model.dart';
index 1473e0446527a86d36faa982da86029fe79e4bac..b132b7a26bd6f307287ecb79ea7bc70526eba445 100644 (file)
@@ -115,7 +115,7 @@ class StringHelper {
           value = DateFormat('yyyy-MM-dd').format(value);
           break;
         case DataType.bool:
-          value = value ? 'T' : 'F';
+          value = value is String ? value : (value ? 'T' : 'F');
           break;
         default:
           value = value.toString();
index 4f5bd2294ff8dddd74d3205492e5ae8e98b2aedf..94a351ec24cba16a8e8ce93c0d7c35a983bd7e04 100644 (file)
@@ -1,9 +1,43 @@
-import 'package:dart_bones/dart_bones.dart';
-
 class UrlHelper {
   static final sep = '/';
   static final currentDirSep = '.' + sep;
 
+  /// Returns the extension of the [path].
+  /// The extension is the part behind the last '.'.
+  /// If the only '.' is at the top, the result is '' otherwise the the last part with '.'.
+  static String extensionOf(String path) {
+    var rc = '';
+    final ix = path.lastIndexOf('.');
+    final ixSlash = path.lastIndexOf(sep);
+    if (ix > 0 && (ixSlash < 0 || ix > ixSlash + 1)) {
+      rc = path.substring(ix);
+    }
+    return rc;
+  }
+
+  /// Returns the extension of the [path].
+  /// The extension is the part behind the last '.'.
+  /// If the only '.' is at the top, the result is '' otherwise the the last part with '.'.
+  static String filenameOf(String path) {
+    var rc = '';
+    final ix = path.lastIndexOf('.');
+    final ixSlash = path.lastIndexOf(sep);
+    if (ixSlash < 0) {
+      if (ix <= 0) {
+        rc = path;
+      } else {
+        rc = path.substring(0, ix);
+      }
+    } else {
+      if (ix <= ixSlash + 1) {
+        rc = path.substring(ixSlash + 1);
+      } else if (ix > ixSlash + 1) {
+        rc = path.substring(ixSlash + 1, ix);
+      }
+    }
+    return rc;
+  }
+
   /// Joins parts to a combined path.
   /// [first]: first part
   /// [second]: second part
index 85c190e34016fffcaa9409f5d97afa78426e19f4..6a89a60cfbcffae5660189a01c46cb4ef719a4b4 100644 (file)
@@ -1,10 +1,10 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'module_model.dart';
-import 'standard/role_model.dart';
-import 'standard/user_model.dart';
-import 'standard/menu_model.dart';
-import 'standard/starter_model.dart';
-import 'standard/configuration_model.dart';
+import 'module/role_model.dart';
+import 'module/user_model.dart';
+import 'module/menu_model.dart';
+import 'module/starter_model.dart';
+import 'module/configuration_model.dart';
 
 class ModelHelper {
   /// Returns an instance of a module given by [name].
@@ -34,6 +34,11 @@ class ModelHelper {
   }
 
   /// Returns the names of the modules.
-  List<String> moduleNames() =>
-      ['configuration', 'menu', 'role', 'starter', 'user'];
+  List<String> moduleNames() => [
+        'configuration',
+        'menu',
+        'role',
+        'starter',
+        'user',
+      ];
 }
index 17271d734e6b062362013a0490051725eb08fac8..cce16026cae45220a74f1cbcc6ff85989389bef6 100644 (file)
@@ -13,6 +13,130 @@ abstract class ModelTool {
   final ModelHelper modelHelper;
   ModelTool(this.modelHelper, this.logger);
 
+  void createModule(List<String> args, List<String> options) {
+    try {
+      String baseDirectory;
+      String value;
+      PageModelType handledPageType;
+      for (var opt in options) {
+        value = StringUtils.stringOption('directory', 'd', opt);
+        if (value != null) {
+          baseDirectory = value;
+          continue;
+        }
+        value = StringUtils.stringOption('page-type', 't', opt);
+        if (value != null) {
+          handledPageType = StringUtils.stringToEnum<PageModelType>(
+              value, PageModelType.values);
+          if (handledPageType == null) {
+            throw FormatException('unknown page type $value in $opt');
+          }
+          continue;
+        }
+        logger.error('unknown option: $opt');
+      }
+      baseDirectory ??= 'lib/src';
+      if (args.isEmpty) {
+        args = modelHelper.moduleNames();
+      }
+      var fnTemplate = 'lib/src/model/module/role_model.dart';
+      var templateLines = readFile(fnTemplate);
+      if (templateLines.isEmpty) {
+        logger.error('missing $fnTemplate. Wrong current directory?');
+      } else if (args.length < 1) {
+        logger.error('too few arguments');
+        throw FormatException('too few arguments');
+      } else {
+        final moduleName = args[0];
+        final pageName = args.length > 1 ? args[1] : null;
+        if (RegExp(r'^\w+$').firstMatch(moduleName) == null) {
+          throw FormatException(
+              'invalid character(s) in module name: $moduleName');
+        }
+        if (pageName != null && RegExp(r'^\w+$').firstMatch(pageName) == null) {
+          throw FormatException(
+              'invalid character(s) in page name: $moduleName');
+        }
+        if (pageName != null && handledPageType == null) {
+          throw FormatException('having page name but no page type');
+        }
+        final moduleCapital = StringHelper.capitalize(moduleName);
+        final converter = (String line) => line
+            .replaceAll('Role', moduleCapital)
+            .replaceAll('role', moduleName)
+            .replaceAll('Rollen', '!!!${moduleCapital}s')
+            .replaceAll('Rolle', '!!$moduleCapital');
+        List<String> output = [];
+        String fnOutput;
+        // Create the module model:
+        StringHelper.addRangeToList(templateLines, output,
+            converter: converter);
+        fnOutput = UrlHelper.joinPaths(
+            baseDirectory, 'module/standard', '${moduleName}_model.dart');
+        writeFile(fnOutput, output.join('\n'),
+            mayExist: false, ensureDirectory: true);
+        // Create the controller:
+        fnTemplate = 'lib/src/model/module/role_controller.dart';
+        templateLines = readFile(fnTemplate);
+        StringHelper.addRangeToList(templateLines, output,
+            converter: converter);
+        fnOutput = UrlHelper.joinPaths(
+            baseDirectory, 'module/standard', '${moduleName}_model.dart');
+        writeFile(fnOutput, output.join('\n'),
+            mayExist: false, ensureDirectory: true);
+        // Create the pages:
+        List<String> names;
+        List<PageModelType> types;
+        if (pageName != null) {
+          names = [pageName];
+          types = [handledPageType];
+        } else {
+          names = ['create', 'change', 'delete', 'list'];
+          types = [
+            PageModelType.create,
+            PageModelType.change,
+            PageModelType.delete,
+            PageModelType.list
+          ];
+        }
+        for (var ix = 0; ix < names.length; ix++) {
+          createPage(baseDirectory, moduleName, names[ix], types[ix]);
+        }
+      }
+    } on FormatException catch (exc) {
+      logger.error(exc.toString());
+    }
+  }
+
+  /// Creates a page named [pageName] of [pageType] in the [baseDirectory].
+  void createPage(String baseDirectory, String moduleName, String pageName,
+      PageModelType pageType) {
+    final typeString = StringUtils.enumToString(pageType);
+    final fnTemplate = 'lib/src/page/role/role_${typeString}_page.dart';
+    final templateLines = readFile(fnTemplate);
+    if (templateLines.isEmpty) {
+      logger.error('missing $fnTemplate');
+    } else {
+      final output = <String>[];
+      final pageTypeCapital = StringHelper.capitalize(typeString);
+      final moduleCapital = StringHelper.capitalize(moduleName);
+      final pageNameCapital = StringHelper.capitalize(pageName);
+      final converter = (String line) => line
+          .replaceAll('Role${pageTypeCapital}Page',
+              '$moduleCapital${pageNameCapital}Page')
+          .replaceAll('RoleController', '${moduleCapital}Controller')
+          .replaceAll('role$pageNameCapital', '$moduleName$pageNameCapital')
+          .replaceAll('role$pageTypeCapital', '$moduleName$pageNameCapital')
+          .replaceAll('Role', moduleCapital)
+          .replaceAll('role', moduleName);
+      StringHelper.addRangeToList(templateLines, output, converter: converter);
+      final fnOutput = UrlHelper.joinPaths(
+          baseDirectory, moduleName, '${moduleName}_${pageName}_page.dart');
+      writeFile(fnOutput, output.join('\n'),
+          ensureDirectory: true, mayExist: false);
+    }
+  }
+
   /// Ensures that the [directory] exists. If not it will be created.
   void ensureDirectory(String directory);
 
@@ -62,15 +186,12 @@ abstract class ModelTool {
         baseDirectory = value;
         continue;
       }
-      value = StringUtils.stringOption('directory', 'd', opt);
+      value = StringUtils.stringOption('page-type', 't', 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;
+          throw FormatException('unknown page type $value in $opt');
         }
         continue;
       }
@@ -91,6 +212,7 @@ abstract class ModelTool {
     for (var name in args) {
       if (name == 'role') {
         logger.log('module "role" ignored.');
+        continue;
       }
       final sourceDir = 'lib/src/page/$name';
       final files = pathOfDirectory(sourceDir);
@@ -131,17 +253,16 @@ abstract class ModelTool {
             'role_${StringUtils.enumToString(pageType)}_page.dart');
         final templateLines = readFile(fnTemplate);
         if (templateLines.isEmpty) {
-          logger.log('ignoring ${file}: no template found');
+          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');
+            .replaceAll('role$pageNameCapital', '$name$pageNameCapital')
+            .replaceAll('role$pageTypeCapital', '$name$pageNameCapital');
         modifyPage(
             name,
             pageType,
@@ -164,7 +285,6 @@ abstract class ModelTool {
       String filename,
       bool overwriteConstructors,
       String pathSafe) {
-    final moduleCapital = StringHelper.capitalize(module);
     ensureDirectory(UrlHelper.parentOf(filename, trailingSlash: false));
     final output = <String>[];
     int ix;
@@ -272,6 +392,9 @@ abstract class ModelTool {
       case 'modify-module':
         modifyModule(args, options);
         break;
+      case 'create-module':
+        createModule(args, options);
+        break;
       default:
         logger.error('unknown mode: $mode');
         break;
@@ -284,24 +407,35 @@ abstract class ModelTool {
   void usage(String error) {
     print('''usage: main <mode> [<args>]
 <mode>:
+  create-module <moduleName> [<opts>]
+    Modifies the files of the given modules to renew the generated code.
+    <opt>:
+      -d<dir> or --directory=<dir>:
+        The base directory used for the created files.
+      --page-type=<type>
+        Creates only the file with this type: 'create', 'change', 'list', 'custom'
   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.
+        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.
+    <opt>:
+      -d<dir> or --directory=<dir>:
+        The base directory used for the modified files.
+      --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-module address
+main create-module address compare --page-type=custom
 main create-sql role user -d/tmp
 main create-sql --directory=/opt/sql-data
 main modify-pages role --customize-constructors
@@ -310,5 +444,6 @@ main modify-pages --page-type=list
   }
 
   /// Writes a [content] to a file named [filename].
-  void writeFile(String filename, String content);
+  void writeFile(String filename, String content,
+      {bool mayExist, bool ensureDirectory});
 }
diff --git a/lib/src/model/module/configuration_model.dart b/lib/src/model/module/configuration_model.dart
new file mode 100644 (file)
index 0000000..396e908
--- /dev/null
@@ -0,0 +1,161 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+///
+class ConfigurationModel extends ModuleModel {
+  static final yamlMap = <String, dynamic>{
+    //
+    'module': 'configuration',
+    'tables': [
+      {
+        // configuration_id | configuration_scope | configuration_property | configuration_order | configuration_type
+        // | configuration_value | configuration_description
+        'table': 'configuration',
+        'columns': [
+          {
+            'column': 'configuration_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'configuration_scope',
+            'dataType': 'string',
+            'label': 'Bereich',
+            'size': 64,
+            'options': 'notnull',
+            'validators': r'required regExpr=i/^\w+$',
+            'validatorsText':
+                'regExpr=Nur Buchstaben, Ziffern und Unterstrich erlaubt',
+          },
+          {
+            'column': 'configuration_property',
+            'dataType': 'string',
+            'size': 64,
+            'label': 'Eigenschaft',
+          },
+          {
+            'column': 'configuration_order',
+            'dataType': 'int',
+            'label': 'Reihe',
+          },
+          {
+            'column': 'configuration_type',
+            'dataType': 'string',
+            'size': 16,
+            'label': 'Datentyp',
+            'listType': 'explicite',
+            'texts': ';bool;date;float;int;string',
+            'validators': 'required',
+          },
+          {
+            'column': 'configuration_value',
+            'dataType': 'string',
+            'size': 255,
+            'label': 'Wert',
+          },
+          {
+            'column': 'configuration_description',
+            'dataType': 'string',
+            'size': 255,
+            'label': 'Beschreibung',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Konfiguration hinzufügen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Konfiguration ändern',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'delete',
+        'title': 'Konfiguration löschen',
+        'pageType': 'delete',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Konfiguration',
+        'pageType': 'list',
+        'tableColumns':
+            'configuration_id configuration_scope configuration_property configuration_order configuration_value configuration_type',
+        'tableTitles': ';Id;Bereich;Eigenschaft;Reihe;Wert;Typ',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'name': 'configuration_scope',
+                'label': 'Bereich',
+                'modelType': 'textField',
+                'filterType': 'pattern',
+                //'options': 'undef',
+              },
+              {
+                'name': 'configuration_property',
+                'label': 'Eigenschaft',
+                'modelType': 'textField',
+                'filterType': 'pattern',
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neue Konfiguration',
+                'toolTip': 'Erzeugen einer neuen Konfiguration'
+              },
+            ],
+          }
+        ]
+      },
+    ],
+  };
+
+  ConfigurationModel(BaseLogger logger) : super(yamlMap, logger);
+
+  /// Returns the name including the names of the parents.
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
diff --git a/lib/src/model/module/menu_model.dart b/lib/src/model/module/menu_model.dart
new file mode 100644 (file)
index 0000000..499c253
--- /dev/null
@@ -0,0 +1,134 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class MenuModel extends ModuleModel {
+  static final mapMenu = <String, dynamic>{
+    'module': 'menu',
+    'tables': [
+      {
+        'table': 'menu',
+        'columns': [
+          {
+            'column': 'menu_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'menu_role',
+            'dataType': 'reference',
+            'label': 'Rolle',
+            'foreignKey': 'role.role_id role_name',
+            'listType': 'dbColumn',
+            'listOption': 'all.role.list;role_name role_id;',
+          },
+          {
+            'column': 'menu_starter',
+            'dataType': 'reference',
+            'label': 'Programmpunkt',
+            'foreignKey': 'starter.starter_id starter_name',
+            'listType': 'dbColumn',
+            'listOption': 'all.starter.list;starter_name starter_id;',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Startmenü hinzufügen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Startmenü ändern',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neues Startmenü',
+                'toolTip': 'Erzeugen eines neuen Startmenüs'
+              },
+            ],
+          }
+        ]
+      },
+      {
+        'page': 'delete',
+        'title': 'Startmenü löschen',
+        'pageType': 'delete',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Startmenü',
+        'pageType': 'list',
+        'tableColumns': 'menu_id menu_role menu_order menu_name',
+        'tableTitles': ';Id;Rolle;Reihe;Programmpunkt',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'modelType': 'dbReference',
+                'filterType': 'pattern',
+                'name': 'menu_role',
+                'column': 'menu_role',
+                'toolTip':
+                    'Filter bezüglich der Rolle der anzuzeigenden Einträge.'
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neues Startmenü',
+                'toolTip': 'Erzeugen eines Startmenüs'
+              },
+            ],
+          }
+        ]
+      },
+    ]
+  };
+
+  MenuModel(BaseLogger logger) : super(mapMenu, logger);
+
+  /// Returns the name including the names of the parents.
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
diff --git a/lib/src/model/module/role_model.dart b/lib/src/model/module/role_model.dart
new file mode 100644 (file)
index 0000000..04cf3a5
--- /dev/null
@@ -0,0 +1,127 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class RoleModel extends ModuleModel {
+  RoleModel(BaseLogger logger) : super(yamlMap, logger);
+
+  static final yamlMap = <String, dynamic>{
+    'module': 'role',
+    'tables': [
+      {
+        'table': 'role',
+        'columns': [
+          {
+            'column': 'role_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'role_name',
+            'dataType': 'string',
+            'label': 'Rolle',
+            'size': 32,
+            'options': 'unique notnull',
+          },
+          {
+            'column': 'role_priority',
+            'dataType': 'int',
+            'label': 'Priorität',
+            'validators': 'required minInt=1 maxInt=99',
+          },
+          {
+            'column': 'role_active',
+            'dataType': 'bool',
+            'label': 'Aktiv',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Rolle hinzufügen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Rolle ändern',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'delete',
+        'title': 'Rolle löschen',
+        'pageType': 'delete',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Rollen',
+        'pageType': 'list',
+        'tableColumns': 'role_id role_name role_priority',
+        'tableTitles': ';Id;Rolle;Priorität',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'modelType': 'textField',
+                'filterType': 'pattern',
+                'name': 'role_name',
+                'label': 'Name',
+                'toolTip':
+                    'Suchmuster des Rollennamens: Joker: "*" (beliebiger Text), z.B. "*min*"'
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neue Rolle',
+                'toolTip': 'Erzeugen einer neuen Rolle'
+              },
+            ],
+          }
+        ],
+      },
+    ]
+  };
+
+  /// Returns the name including the names of the parents.
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
diff --git a/lib/src/model/module/starter_model.dart b/lib/src/model/module/starter_model.dart
new file mode 100644 (file)
index 0000000..bbad7ad
--- /dev/null
@@ -0,0 +1,125 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class StarterModel extends ModuleModel {
+  static final mapStarter = <String, dynamic>{
+    'module': 'starter',
+    'tables': [
+      {
+        'table': 'starter',
+        'columns': [
+          {
+            'column': 'starter_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'starter_name',
+            'dataType': 'string',
+            'label': 'Name',
+            'size': 64,
+            'options': 'unique notnull',
+          },
+          {
+            'column': 'starter_icon',
+            'dataType': 'reference',
+            'label': 'Bild',
+            'foreignKey': 'configuration.configuration_id configuration_value',
+            'listType': 'configuration',
+            'listOption':
+                'std.scope.icons;configuration_value configuration_id;',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Programmpunkt hinzufügen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Programmpunkt hinzufügen',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              },
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'delete',
+        'title': 'Programmpunkt löschen',
+        'pageType': 'delete',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Programmpunkte',
+        'pageType': 'list',
+        'tableColumns': 'starter_id starter_name starter_icon',
+        'tableTitles': ';Id;Name;Bild',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'modelType': 'dbReference',
+                'filterType': 'pattern',
+                'name': 'starter_name',
+                'column': 'starter_name',
+                'toolTip':
+                    'Filter bezüglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neuer Programmpunkt',
+                'toolTip': 'Erzeugen eines neuen Programmpunkts'
+              },
+            ],
+          }
+        ]
+      },
+    ]
+  };
+
+  StarterModel(BaseLogger logger) : super(mapStarter, logger);
+
+  /// Returns the name including the names of the parents.
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
diff --git a/lib/src/model/module/user_model.dart b/lib/src/model/module/user_model.dart
new file mode 100644 (file)
index 0000000..c6f9be1
--- /dev/null
@@ -0,0 +1,231 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class UserModel extends ModuleModel {
+  static final mapUser = <String, dynamic>{
+    'module': 'user',
+    'tables': [
+      {
+        'table': 'user',
+        'columns': [
+          {
+            'column': 'user_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'column': 'user_name',
+            'dataType': 'string',
+            'label': 'User',
+            'size': 64,
+            'options': 'unique notnull',
+          },
+          {
+            'column': 'user_displayname',
+            'dataType': 'string',
+            'label': 'Anzeigename',
+            'size': 32,
+            'options': 'unique notnull',
+          },
+          {
+            'column': 'user_email',
+            'dataType': 'string',
+            'label': 'EMail',
+            'size': 128,
+            'options': 'unique notnull',
+            'validators': 'email',
+          },
+          {
+            'column': 'user_password',
+            'dataType': 'string',
+            'label': 'Passwort',
+            'size': 128,
+            'options': 'password hidden',
+          },
+          {
+            'column': 'user_role',
+            'dataType': 'reference',
+            'label': 'Rolle',
+            'foreignKey': 'role.role_id role_name',
+            'listType': 'dbColumn',
+            'listOption': 'all.role.list;role_name role_id;:role_name=%',
+            'options': 'undef',
+            'defaultValue': '4',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        'page': 'create',
+        'title': 'Benutzer hinzufügen',
+        'pageType': 'create',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'change',
+        'title': 'Benutzer ändern',
+        'pageType': 'change',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'name': 'set_password',
+                'label': 'Passwort ändern',
+                'buttonType': 'custom',
+              },
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'password',
+        'title': 'Passwort ändern',
+        'pageType': 'change',
+        'sql': 'update_pw',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'text',
+                'text': 'Ändern des Passworts von Benutzer ~user~',
+                'options': 'placeholder h3'
+              },
+              {
+                'modelType': 'textField',
+                'name': 'user_password',
+                'label': 'Passwort',
+                'options': 'password',
+                'validators': 'required',
+              },
+              {
+                'modelType': 'textField',
+                'name': 'repetition',
+                'label': 'Wiederholung',
+                'options': 'password',
+                'validators': 'required equals=user_password'
+              },
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'delete',
+        'title': 'Benutzer löschen',
+        'pageType': 'delete',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'allDbFields',
+              }
+            ]
+          }
+        ]
+      },
+      {
+        'page': 'list',
+        'title': 'Benutzer',
+        'pageType': 'list',
+        'tableColumns':
+            'user_id user_name user_displayname user_email user_role',
+        'tableTitles': ';Id;Name;Anzeigename;EMail;Rolle',
+        'sections': [
+          {
+            'sectionType': 'filterPanel',
+            'children': [
+              {
+                'modelType': 'dbReference',
+                'filterType': 'pattern',
+                'name': 'user_name',
+                'column': 'user_name',
+                'toolTip':
+                    'Filter bezüglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
+              },
+              {
+                'modelType': 'dbReference',
+                'name': 'user_role',
+                'filterType': 'equals',
+                'column': 'user_role',
+                'toolTip':
+                    'Filter bezüglich der Rolle der anzuzeigenden Einträge. "-" bedeutet keine Einschränkung',
+              }
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'new',
+                'label': 'Neuer Benutzer',
+                'toolTip': 'Erzeugen eines neuen Benutzers'
+              },
+            ],
+          }
+        ]
+      },
+      {
+        'page': 'login',
+        'title': 'Anmelden',
+        'pageType': 'change',
+        'options': 'noAutoButton',
+        'sections': [
+          {
+            'sectionType': 'simpleForm',
+            'children': [
+              {
+                'modelType': 'textField',
+                'name': 'user',
+                'label': 'Benutzer oder EMail',
+                'validators': 'required',
+              },
+              {
+                'modelType': 'textField',
+                'name': 'password',
+                'label': 'Password',
+                'options': 'password',
+                'validators': 'required',
+              },
+            ],
+            'buttonBar': [
+              {
+                'modelType': 'button',
+                'buttonType': 'custom',
+                'name': 'login',
+                'label': 'Anmelden',
+              },
+            ]
+          }
+        ]
+      },
+    ]
+  };
+
+  UserModel(BaseLogger logger) : super(mapUser, logger);
+
+  /// Returns the name including the names of the parents.
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
index 2c93ec1d3c2b37ef558b9e43063e0fe9ccf7d1a6..b4991ac18196a1ec31d61fb2bfb5b507870042f9 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
 
 import '../helper/name_builder.dart';
 import 'button_model.dart';
@@ -10,6 +11,13 @@ import 'widget_model.dart';
 
 typedef FilterWidget = bool Function(WidgetModel item);
 
+/// Returns whether the task specified by [task] and [parameter] can be
+/// granted.
+/// The type of [parameter] depends of the task:
+/// Example: in list pages: TaskRight.edit/delete: parameter is the record of
+/// the table row.
+typedef TaskRightCallback = bool Function(TaskRight task, dynamic parameter);
+
 /// Represents one screen of the module.
 class PageModel extends ModelBase {
   static final regExprOptions = RegExp(r'^(noAutoButton)$');
@@ -20,6 +28,7 @@ class PageModel extends ModelBase {
   final buttonsDeprecated = <ButtonModel>[];
   final widgetsDeprecated = <WidgetModel>[];
   final models = <String, WidgetModel>{};
+  TaskRightCallback taskRightCallback;
   String sql;
   String title;
   List<String> tableTitles;
@@ -37,7 +46,7 @@ class PageModel extends ModelBase {
     final name = model.name;
     if (models.containsKey(name)) {
       logger.error('model ${model.fullName()} already defined: ' +
-          fieldByName(name).fullName());
+          models[name].fullName());
     } else {
       models[model.name] = model;
     }
@@ -139,29 +148,47 @@ class PageModel extends ModelBase {
     }
 
     if (!options.contains('noAutoButton')) {
-      final section = sections[0];
+      final section = sections.isEmpty ? null : sections[0];
       switch (pageModelType) {
         case PageModelType.list:
           if (buttonByName('search', required: false) == null) {
             final model = ButtonModel.direct(section, this, 'search', 'Suchen',
                 ButtonModelType.search, [], logger);
             addModel(model);
-            sections[0].buttonBar.insert(0, model);
+            if (sections.isEmpty) {
+              logger.error('missing section in ${fullName()}');
+            } else {
+              sections[0].buttonBar.insert(0, model);
+            }
           }
           break;
         case PageModelType.create:
         case PageModelType.change:
+        case PageModelType.delete:
           if (buttonByName('cancel', required: false) == null) {
             final model = ButtonModel.direct(section, this, 'cancel', 'Abbruch',
                 ButtonModelType.cancel, [], logger);
             addModel(model);
-            sections[0].buttonBar.insert(0, model);
+            if (sections.isEmpty) {
+              logger.error('missing sections in ${fullName()}');
+            } else {
+              sections[0].buttonBar.insert(0, model);
+            }
           }
-          if (buttonByName('store', required: false) == null) {
-            final model = ButtonModel.direct(section, this, 'store',
-                'Speichern', ButtonModelType.store, [], logger);
+          final buttonName =
+              pageModelType == PageModelType.delete ? 'delete' : 'store';
+          final buttonText =
+              pageModelType == PageModelType.delete ? 'Löschen' : 'Speichern';
+
+          if (buttonByName(buttonName, required: false) == null) {
+            final model = ButtonModel.direct(section, this, buttonName,
+                buttonText, ButtonModelType.store, [], logger);
             addModel(model);
-            sections[0].buttonBar.insert(0, model);
+            if (sections.isEmpty) {
+              logger.error('missing sections in ${fullName()}');
+            } else {
+              sections[0].buttonBar.insert(0, model);
+            }
           }
           break;
         default:
@@ -192,7 +219,7 @@ class PageModel extends ModelBase {
 enum PageModelType {
   change,
   create,
+  custom,
   delete,
   list,
-  overview,
 }
diff --git a/lib/src/model/standard/configuration_model.dart b/lib/src/model/standard/configuration_model.dart
deleted file mode 100644 (file)
index eb20373..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-
-import '../module_model.dart';
-
-///
-class ConfigurationModel extends ModuleModel {
-  static final yamlMap = <String, dynamic>{
-    //
-    'module': 'configuration',
-    'tables': [
-      {
-        // configuration_id | configuration_scope | configuration_property | configuration_order | configuration_type
-        // | configuration_value | configuration_description
-        'table': 'configuration',
-        'columns': [
-          {
-            'column': 'configuration_id',
-            'dataType': 'int',
-            'label': 'Id',
-            'options': 'primary',
-          },
-          {
-            'column': 'configuration_scope',
-            'dataType': 'string',
-            'label': 'Bereich',
-            'size': 64,
-            'options': 'notnull',
-            'validators': r'required regExpr=i/^\w+$',
-            'validatorsText':
-                'regExpr=Nur Buchstaben, Ziffern und Unterstrich erlaubt',
-          },
-          {
-            'column': 'configuration_property',
-            'dataType': 'string',
-            'size': 64,
-            'label': 'Eigenschaft',
-          },
-          {
-            'column': 'configuration_order',
-            'dataType': 'int',
-            'label': 'Reihe',
-          },
-          {
-            'column': 'configuration_type',
-            'dataType': 'string',
-            'size': 16,
-            'label': 'Datentyp',
-            'listType': 'explicite',
-            'texts': ';bool;date;float;int;string',
-            'validators': 'required',
-          },
-          {
-            'column': 'configuration_value',
-            'dataType': 'string',
-            'size': 255,
-            'label': 'Wert',
-          },
-          {
-            'column': 'configuration_description',
-            'dataType': 'string',
-            'size': 255,
-            'label': 'Beschreibung',
-          },
-        ]
-      },
-    ],
-    'pages': [
-      {
-        'page': 'create',
-        'title': 'Konfiguration hinzufügen',
-        'pageType': 'create',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'change',
-        'title': 'Konfiguration ändern',
-        'pageType': 'change',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'list',
-        'title': 'Konfiguration',
-        'pageType': 'list',
-        'tableColumns':
-            'configuration_id configuration_scope configuration_property configuration_order configuration_value configuration_type',
-        'tableTitles': ';Id;Bereich;Eigenschaft;Reihe;Wert;Typ',
-        'sections': [
-          {
-            'sectionType': 'filterPanel',
-            'children': [
-              {
-                'name': 'configuration_scope',
-                'label': 'Bereich',
-                'modelType': 'textField',
-                'filterType': 'pattern',
-                //'options': 'undef',
-              },
-              {
-                'name': 'configuration_property',
-                'label': 'Eigenschaft',
-                'modelType': 'textField',
-                'filterType': 'pattern',
-              },
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'new',
-                'label': 'Neue Konfiguration',
-                'toolTip': 'Erzeugen einer neuen Konfiguration'
-              },
-            ],
-          }
-        ]
-      },
-    ],
-  };
-
-  ConfigurationModel(BaseLogger logger) : super(yamlMap, logger);
-
-  /// Returns the name including the names of the parents.
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
diff --git a/lib/src/model/standard/menu_model.dart b/lib/src/model/standard/menu_model.dart
deleted file mode 100644 (file)
index 10c6054..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-
-import '../module_model.dart';
-
-class MenuModel extends ModuleModel {
-  static final mapMenu = <String, dynamic>{
-    'module': 'menu',
-    'tables': [
-      {
-        'table': 'menu',
-        'columns': [
-          {
-            'column': 'menu_id',
-            'dataType': 'int',
-            'label': 'Id',
-            'options': 'primary',
-          },
-          {
-            'column': 'menu_role',
-            'dataType': 'reference',
-            'label': 'Rolle',
-            'foreignKey': 'role.role_id role_name',
-            'listType': 'dbColumn',
-            'listOption': 'all.role.list;role_name role_id;',
-          },
-          {
-            'column': 'menu_starter',
-            'dataType': 'reference',
-            'label': 'Programmpunkt',
-            'foreignKey': 'starter.starter_id starter_name',
-            'listType': 'dbColumn',
-            'listOption': 'all.starter.list;starter_name starter_id;',
-          },
-        ]
-      },
-    ],
-    'pages': [
-      {
-        'page': 'create',
-        'title': 'Startmenü hinzufügen',
-        'pageType': 'create',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'change',
-        'title': 'Startmenü ändern',
-        'pageType': 'change',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              },
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'new',
-                'label': 'Neues Startmenü',
-                'toolTip': 'Erzeugen eines neuen Startmenüs'
-              },
-            ],
-          }
-        ]
-      },
-      {
-        'page': 'list',
-        'title': 'Startmenü',
-        'pageType': 'list',
-        'tableColumns': 'menu_id menu_role menu_order menu_name',
-        'tableTitles': ';Id;Rolle;Reihe;Programmpunkt',
-        'sections': [
-          {
-            'sectionType': 'filterPanel',
-            'children': [
-              {
-                'modelType': 'dbReference',
-                'filterType': 'pattern',
-                'name': 'menu_role',
-                'column': 'menu_role',
-                'toolTip':
-                    'Filter bezüglich der Rolle der anzuzeigenden Einträge.'
-              },
-            ]
-          }
-        ]
-      },
-    ]
-  };
-
-  MenuModel(BaseLogger logger) : super(mapMenu, logger);
-
-  /// Returns the name including the names of the parents.
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
diff --git a/lib/src/model/standard/role_model.dart b/lib/src/model/standard/role_model.dart
deleted file mode 100644 (file)
index 1001014..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-
-import '../module_model.dart';
-
-class RoleModel extends ModuleModel {
-  RoleModel(BaseLogger logger) : super(yamlMap, logger);
-
-  static final yamlMap = <String, dynamic>{
-    'module': 'role',
-    'tables': [
-      {
-        'table': 'role',
-        'columns': [
-          {
-            'column': 'role_id',
-            'dataType': 'int',
-            'label': 'Id',
-            'options': 'primary',
-          },
-          {
-            'column': 'role_name',
-            'dataType': 'string',
-            'label': 'Rolle',
-            'size': 32,
-            'options': 'unique notnull',
-          },
-          {
-            'column': 'role_priority',
-            'dataType': 'int',
-            'label': 'Priorität',
-            'validators': 'required minInt=1 maxInt=99',
-          },
-          {
-            'column': 'role_active',
-            'dataType': 'bool',
-            'label': 'Aktiv',
-          },
-        ]
-      },
-    ],
-    'pages': [
-      {
-        'page': 'create',
-        'title': 'Rolle hinzufügen',
-        'pageType': 'create',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'change',
-        'title': 'Rolle ändern',
-        'pageType': 'change',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'list',
-        'title': 'Rollen',
-        'pageType': 'list',
-        'tableColumns': 'role_id role_name role_priority',
-        'tableTitles': ';Id;Rolle;Priorität',
-        'sections': [
-          {
-            'sectionType': 'filterPanel',
-            'children': [
-              {
-                'modelType': 'textField',
-                'filterType': 'pattern',
-                'name': 'role_name',
-                'label': 'Name',
-                'toolTip':
-                    'Suchmuster des Rollennamens: Joker: "*" (beliebiger Text), z.B. "*min*"'
-              },
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'new',
-                'label': 'Neue Rolle',
-                'toolTip': 'Erzeugen einer neuen Rolle'
-              },
-            ],
-          }
-        ],
-      }
-    ]
-  };
-
-  /// Returns the name including the names of the parents.
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
diff --git a/lib/src/model/standard/starter_model.dart b/lib/src/model/standard/starter_model.dart
deleted file mode 100644 (file)
index b8a8efd..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-
-import '../module_model.dart';
-
-class StarterModel extends ModuleModel {
-  static final mapStarter = <String, dynamic>{
-    'module': 'starter',
-    'tables': [
-      {
-        'table': 'starter',
-        'columns': [
-          {
-            'column': 'starter_id',
-            'dataType': 'int',
-            'label': 'Id',
-            'options': 'primary',
-          },
-          {
-            'column': 'starter_name',
-            'dataType': 'string',
-            'label': 'Name',
-            'size': 64,
-            'options': 'unique notnull',
-          },
-          {
-            'column': 'starter_icon',
-            'dataType': 'reference',
-            'label': 'Bild',
-            'foreignKey': 'configuration.configuration_id configuration_value',
-            'listType': 'configuration',
-            'listOption':
-                'std.scope.icons;configuration_value configuration_id;',
-          },
-        ]
-      },
-    ],
-    'pages': [
-      {
-        'page': 'create',
-        'title': 'Programmpunkt hinzufügen',
-        'pageType': 'create',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'change',
-        'title': 'Programmpunkt hinzufügen',
-        'pageType': 'change',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              },
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'list',
-        'title': 'Programmpunkte',
-        'pageType': 'list',
-        'tableColumns': 'starter_id starter_name starter_icon',
-        'tableTitles': ';Id;Name;Bild',
-        'sections': [
-          {
-            'sectionType': 'filterPanel',
-            'children': [
-              {
-                'modelType': 'dbReference',
-                'filterType': 'pattern',
-                'name': 'starter_name',
-                'column': 'starter_name',
-                'toolTip':
-                    'Filter bezüglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
-              },
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'new',
-                'label': 'Neuer Programmpunkt',
-                'toolTip': 'Erzeugen eines neuen Programmpunkts'
-              },
-            ],
-          }
-        ]
-      },
-    ]
-  };
-
-  StarterModel(BaseLogger logger) : super(mapStarter, logger);
-
-  /// Returns the name including the names of the parents.
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
diff --git a/lib/src/model/standard/user_model.dart b/lib/src/model/standard/user_model.dart
deleted file mode 100644 (file)
index 14ffdaa..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-
-import '../module_model.dart';
-
-class UserModel extends ModuleModel {
-  static final mapUser = <String, dynamic>{
-    'module': 'user',
-    'tables': [
-      {
-        'table': 'user',
-        'columns': [
-          {
-            'column': 'user_id',
-            'dataType': 'int',
-            'label': 'Id',
-            'options': 'primary',
-          },
-          {
-            'column': 'user_name',
-            'dataType': 'string',
-            'label': 'User',
-            'size': 64,
-            'options': 'unique notnull',
-          },
-          {
-            'column': 'user_displayname',
-            'dataType': 'string',
-            'label': 'Anzeigename',
-            'size': 32,
-            'options': 'unique notnull',
-          },
-          {
-            'column': 'user_email',
-            'dataType': 'string',
-            'label': 'EMail',
-            'size': 128,
-            'options': 'unique notnull',
-            'validators': 'email',
-          },
-          {
-            'column': 'user_password',
-            'dataType': 'string',
-            'label': 'Passwort',
-            'size': 128,
-            'options': 'password hidden',
-          },
-          {
-            'column': 'user_role',
-            'dataType': 'reference',
-            'label': 'Rolle',
-            'foreignKey': 'role.role_id role_name',
-            'listType': 'dbColumn',
-            'listOption': 'all.role.list;role_name role_id;:role_name=%',
-            'options': 'undef',
-            'defaultValue': '4',
-          },
-        ]
-      },
-    ],
-    'pages': [
-      {
-        'page': 'create',
-        'title': 'Benutzer hinzufügen',
-        'pageType': 'create',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              }
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'change',
-        'title': 'Benutzer ändern',
-        'pageType': 'change',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'allDbFields',
-              },
-              {
-                'modelType': 'button',
-                'name': 'set_password',
-                'label': 'Passwort ändern',
-                'buttonType': 'custom',
-              },
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'password',
-        'title': 'Passwort ändern',
-        'pageType': 'change',
-        'sql': 'update_pw',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'text',
-                'text': 'Ändern des Passworts von Benutzer ~user~',
-                'options': 'placeholder h3'
-              },
-              {
-                'modelType': 'textField',
-                'name': 'user_password',
-                'label': 'Passwort',
-                'options': 'password',
-                'validators': 'required',
-              },
-              {
-                'modelType': 'textField',
-                'name': 'repetition',
-                'label': 'Wiederholung',
-                'options': 'password',
-                'validators': 'required equals=user_password'
-              },
-            ]
-          }
-        ]
-      },
-      {
-        'page': 'list',
-        'title': 'Benutzer',
-        'pageType': 'list',
-        'tableColumns':
-            'user_id user_name user_displayname user_email user_role',
-        'tableTitles': ';Id;Name;Anzeigename;EMail;Rolle',
-        'sections': [
-          {
-            'sectionType': 'filterPanel',
-            'children': [
-              {
-                'modelType': 'dbReference',
-                'filterType': 'pattern',
-                'name': 'user_name',
-                'column': 'user_name',
-                'toolTip':
-                    'Filter bezüglich des Namens der anzuzeigenden Einträge: Joker "*" (beliebiger String) ist erlaubt.'
-              },
-              {
-                'modelType': 'dbReference',
-                'name': 'user_role',
-                'filterType': 'equals',
-                'column': 'user_role',
-                'toolTip':
-                    'Filter bezüglich der Rolle der anzuzeigenden Einträge. "-" bedeutet keine Einschränkung',
-              }
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'new',
-                'label': 'Neuer Benutzer',
-                'toolTip': 'Erzeugen eines neuen Benutzers'
-              },
-            ],
-          }
-        ]
-      },
-      {
-        'page': 'login',
-        'title': 'Anmelden',
-        'pageType': 'change',
-        'options': 'noAutoButton',
-        'sections': [
-          {
-            'sectionType': 'simpleForm',
-            'children': [
-              {
-                'modelType': 'textField',
-                'name': 'user',
-                'label': 'Benutzer oder EMail',
-                'validators': 'required',
-              },
-              {
-                'modelType': 'textField',
-                'name': 'password',
-                'label': 'Password',
-                'options': 'password',
-                'validators': 'required',
-              },
-            ],
-            'buttonBar': [
-              {
-                'modelType': 'button',
-                'buttonType': 'custom',
-                'name': 'login',
-                'label': 'Anmelden',
-              },
-            ]
-          }
-        ]
-      },
-    ]
-  };
-
-  UserModel(BaseLogger logger) : super(mapUser, logger);
-
-  /// Returns the name including the names of the parents.
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
index d8848cf57477bc44079a9a58b471d1afeff4bd88..18afa3d56782ff9f47a90aff3e96480a49fd7fb7 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -30,8 +30,8 @@ class ConfigurationChangePage extends StatefulWidget {
   @override
   ConfigurationChangePageState createState() {
     //! === BeginOfCall ===
-    final rc =
-        ConfigurationChangePageStateCustomized(primaryId, applicationData, initialRow);
+    final rc = ConfigurationChangePageStateCustomized(
+        primaryId, applicationData, initialRow);
     //! === EndOfCall ===
 
     /// for unittests:
@@ -40,20 +40,22 @@ class ConfigurationChangePage extends StatefulWidget {
   }
 }
 
-abstract class ConfigurationChangePageState extends State<ConfigurationChangePage>
-    implements RedrawPage {
+abstract class ConfigurationChangePageState
+    extends State<ConfigurationChangePage> implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$configurationChangePageModule-$configurationChangePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel:
+          '$configurationChangePageModule-$configurationChangePageName');
 
   ConfigurationController controller;
 
   //! === BeginOfCustomizedVars2 ===
   //! === EndOfCustomizedVars2 ===
   //! === BeginOfConstructor2 ===
-  ConfigurationChangePageState(this.primaryId, this.applicationData, this.initialRow);
+  ConfigurationChangePageState(
+      this.primaryId, this.applicationData, this.initialRow);
 
   //! === EndOfConstructor2 ===
 
@@ -65,7 +67,7 @@ abstract class ConfigurationChangePageState extends State<ConfigurationChangePag
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -87,8 +89,8 @@ abstract class ConfigurationChangePageState extends State<ConfigurationChangePag
   @override
   void initState() {
     super.initState();
-    controller =
-        ConfigurationController(_formKey, this, 'change', context, applicationData);
+    controller = ConfigurationController(
+        _formKey, this, configurationChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
index f0af0606399afde608b738cf0c0c116a4961b25a..a8f0fb664bf25df245cf31d86ebadfcff43a29fd 100644 (file)
@@ -1,10 +1,11 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../model/standard/configuration_model.dart';
+import '../../model/module/configuration_model.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'configuration_change_page.dart';
+import 'configuration_delete_page.dart';
 
 class ConfigurationController extends PageControllerBones {
   /// Controller for a page named [pageName].
@@ -25,4 +26,14 @@ class ConfigurationController extends PageControllerBones {
             builder: (context) =>
                 ConfigurationChangePage(id, applicationData, row)));
   }
+
+  @override
+  void startDelete(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) =>
+                ConfigurationDeletePage(id, applicationData, row)));
+  }
 }
index 939a5293575d933922d2bef980a73ec5a4003f6a..d0202882c0be14733f54be8ccd54d3eaaa67c5b3 100644 (file)
@@ -1,6 +1,6 @@
 import 'package:flutter/material.dart';
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -34,12 +34,13 @@ class ConfigurationCreatePage extends StatefulWidget {
   }
 }
 
-abstract class ConfigurationCreatePageState extends State<ConfigurationCreatePage>
-    implements RedrawPage {
+abstract class ConfigurationCreatePageState
+    extends State<ConfigurationCreatePage> implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$configurationCreatePageModule-$configurationCreatePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel:
+          '$configurationCreatePageModule-$configurationCreatePageName');
 
   ConfigurationController controller;
 
@@ -58,7 +59,7 @@ abstract class ConfigurationCreatePageState extends State<ConfigurationCreatePag
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -78,8 +79,8 @@ abstract class ConfigurationCreatePageState extends State<ConfigurationCreatePag
   @override
   void initState() {
     super.initState();
-    controller =
-        ConfigurationController(_formKey, this, configurationCreatePageName, context, applicationData);
+    controller = ConfigurationController(
+        _formKey, this, configurationCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
diff --git a/lib/src/page/configuration/configuration_delete_page.dart b/lib/src/page/configuration/configuration_delete_page.dart
new file mode 100644 (file)
index 0000000..cdb4a49
--- /dev/null
@@ -0,0 +1,120 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/dialog_bones.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import '../../model/page_model.dart';
+import 'configuration_controller.dart';
+
+const configurationDeletePageModule = 'configuration';
+const configurationDeletePageName = 'delete';
+const configurationDeletePageType = PageModelType.delete;
+//! === BeginOfGeneratedCode: Delete only in areas marked as customized
+
+class ConfigurationDeletePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  ConfigurationDeletePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  ConfigurationDeletePageState createState() {
+    //! === BeginOfCall ===
+    final rc = ConfigurationDeletePageStateCustomized(
+        primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class ConfigurationDeletePageState
+    extends State<ConfigurationDeletePage> implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel:
+          '$configurationDeletePageModule-$configurationDeletePageName');
+
+  ConfigurationController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  ConfigurationDeletePageState(
+      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: formDialog(
+          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 = ConfigurationController(
+        _formKey, this, configurationDeletePageName, 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 ConfigurationDeletePageStateCustomized
+    extends ConfigurationDeletePageState {
+  ConfigurationDeletePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index b2ab89836bcc63987cc6bd5c6b206829ae323458..b3141cbbb49e537f941d6999363a7c6ab94671ac 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/src/widget/view.dart';
 
 import '../../model/page_model.dart';
-import '../../widget/list_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'configuration_controller.dart';
@@ -38,8 +38,8 @@ abstract class ConfigurationListPageState extends State<ConfigurationListPage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$configurationListPageModule-$configurationListPageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$configurationListPageModule-$configurationListPageName');
   Iterable<dynamic> rowsDeprecated;
   ConfigurationController controller;
 
@@ -65,7 +65,16 @@ abstract class ConfigurationListPageState extends State<ConfigurationListPage>
         titles: ListForm.stringsToTitles(controller.page.tableTitles),
         columnNames: controller.page.tableColumns ?? [],
         rows: controller.listRows ?? [],
-        showEditIcon: true,
+        showDeleteIcon: controller.rightOf(
+            configurationListPageModule,
+            configurationListPageName,
+            configurationListPageType,
+            TaskRight.listEdit),
+        showEditIcon: controller.rightOf(
+            configurationListPageModule,
+            configurationListPageName,
+            configurationListPageType,
+            TaskRight.listDelete),
         pageController: controller,
         buttons: <Widget>[
           ButtonBar(
@@ -88,8 +97,8 @@ abstract class ConfigurationListPageState extends State<ConfigurationListPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        ConfigurationController(_formKey, this, configurationListPageName, context, applicationData);
+    controller = ConfigurationController(
+        _formKey, this, configurationListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
index 6eeebab95525d0c0556ef5bd43e993becc7d454e..e115ed8c5d598cc49af93dcd18887a66fc1e85cb 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -45,8 +45,8 @@ abstract class MenuChangePageState extends State<MenuChangePage>
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$menuChangePageModule-$menuChangePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$menuChangePageModule-$menuChangePageName');
 
   MenuController controller;
 
@@ -65,7 +65,7 @@ abstract class MenuChangePageState extends State<MenuChangePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -87,8 +87,8 @@ abstract class MenuChangePageState extends State<MenuChangePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        MenuController(_formKey, this, 'change', context, applicationData);
+    controller = MenuController(
+        _formKey, this, menuChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
index 857f0af913dfcccd65ebe4a257d796d65249e9c1..328b54a4bc0753239a1d94e4ff41a7a84444178a 100644 (file)
@@ -1,10 +1,11 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../model/standard/menu_model.dart';
+import '../../model/module/menu_model.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'menu_change_page.dart';
+import 'menu_delete_page.dart';
 
 class MenuController extends PageControllerBones {
   /// Controller for a page named [pageName].
@@ -23,4 +24,13 @@ class MenuController extends PageControllerBones {
         MaterialPageRoute(
             builder: (context) => MenuChangePage(id, applicationData, row)));
   }
+
+  @override
+  void startDelete(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) => MenuDeletePage(id, applicationData, row)));
+  }
 }
index 9f8fcadeac15895ab4d0926fbe0e81aeca712742..b4dca068875fdd243aefb2b9aea2179a1c7cd641 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -39,8 +39,8 @@ abstract class MenuCreatePageState extends State<MenuCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$menuCreatePageModule-$menuCreatePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$menuCreatePageModule-$menuCreatePageName');
 
   MenuController controller;
 
@@ -59,7 +59,7 @@ abstract class MenuCreatePageState extends State<MenuCreatePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -79,8 +79,8 @@ abstract class MenuCreatePageState extends State<MenuCreatePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        MenuController(_formKey, this, menuCreatePageName, context, applicationData);
+    controller = MenuController(
+        _formKey, this, menuCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
diff --git a/lib/src/page/menu/menu_delete_page.dart b/lib/src/page/menu/menu_delete_page.dart
new file mode 100644 (file)
index 0000000..bcb71ef
--- /dev/null
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/dialog_bones.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import '../../model/page_model.dart';
+import 'menu_controller.dart';
+
+const menuDeletePageModule = 'menu';
+const menuDeletePageName = 'delete';
+const menuDeletePageType = PageModelType.delete;
+//! === BeginOfGeneratedCode: Delete only in areas marked as customized
+
+class MenuDeletePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  MenuDeletePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  MenuDeletePageState createState() {
+    //! === BeginOfCall ===
+    final rc =
+        MenuDeletePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class MenuDeletePageState extends State<MenuDeletePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$menuDeletePageModule-$menuDeletePageName');
+
+  MenuController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  MenuDeletePageState(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: formDialog(
+          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 = MenuController(
+        _formKey, this, menuDeletePageName, 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 MenuDeletePageStateCustomized extends MenuDeletePageState {
+  MenuDeletePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 6bf709a378d435fc7d4c7bffd8556e402ede1681..8b6742590d3422785b2481f8d604b1d3f05fcbcd 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/page_model.dart';
-import '../../widget/list_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'menu_controller.dart';
@@ -65,7 +65,10 @@ abstract class MenuListPageState extends State<MenuListPage>
         titles: ListForm.stringsToTitles(controller.page.tableTitles),
         columnNames: controller.page.tableColumns ?? [],
         rows: controller.listRows ?? [],
-        showEditIcon: true,
+        showDeleteIcon: controller.rightOf(menuListPageModule, menuListPageName,
+            menuListPageType, TaskRight.listEdit),
+        showEditIcon: controller.rightOf(menuListPageModule, menuListPageName,
+            menuListPageType, TaskRight.listDelete),
         pageController: controller,
         buttons: <Widget>[
           ButtonBar(
@@ -88,8 +91,8 @@ abstract class MenuListPageState extends State<MenuListPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        MenuController(_formKey, this, menuListPageName, context, applicationData);
+    controller = MenuController(
+        _formKey, this, menuListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
index cb75dd9c1bc6727827dc5786f2307d39255ee3c8..62a5bcc2f5ae99f9bf0283fc0926fcf330e95b3a 100644 (file)
@@ -1,15 +1,15 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../model/page_model.dart';
+import '../../widget/dialog_bones.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;
+const roleChangePageName = 'change';
+const roleChangePageType = PageModelType.change;
 //! === BeginOfGeneratedCode: Change only in areas marked as customized
 
 class RoleChangePage extends StatefulWidget {
@@ -45,8 +45,8 @@ abstract class RoleChangePageState extends State<RoleChangePage>
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$roleChangePageModule-$roleChangePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$roleChangePageModule-$roleChangePageName');
 
   RoleController controller;
 
@@ -65,7 +65,7 @@ abstract class RoleChangePageState extends State<RoleChangePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -87,8 +87,8 @@ abstract class RoleChangePageState extends State<RoleChangePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        RoleController(_formKey, this, 'change', context, applicationData);
+    controller = RoleController(
+        _formKey, this, roleChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -105,7 +105,8 @@ abstract class RoleChangePageState extends State<RoleChangePage>
 
 //! === EndOfGeneratedCode: Below you can change manually:
 
-class RoleChangePageStateCustomized extends RoleChangePageState {
+class RoleChangePageStateCustomized extends RoleChangePageState
+    implements SingleRecordPage {
   RoleChangePageStateCustomized(
       int primaryId, ApplicationData applicationData, Map initialRow)
       : super(primaryId, applicationData, initialRow);
@@ -114,4 +115,9 @@ class RoleChangePageStateCustomized extends RoleChangePageState {
   void customize() {
     // ToDo: write code if needed
   }
+
+  @override
+  int getPrimaryKey() {
+    return primaryId;
+  }
 }
index 0ccf112fdad252b84b8f7aa3a9b0d0e80419648d..af7594a8c835f1a7dd89e70a6cd5212abe47f98e 100644 (file)
@@ -1,10 +1,11 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../model/standard/role_model.dart';
+import '../../model/module/role_model.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'role_change_page.dart';
+import 'role_delete_page.dart';
 
 class RoleController extends PageControllerBones {
   /// Controller for a page named [pageName].
@@ -23,4 +24,13 @@ class RoleController extends PageControllerBones {
         MaterialPageRoute(
             builder: (context) => RoleChangePage(id, applicationData, row)));
   }
+
+  @override
+  void startDelete(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) => RoleDeletePage(id, applicationData, row)));
+  }
 }
index 13fe1f2aafa8166caab53993fa88822c464b7020..86dc814e244d8f7193c73fae6d45a01043d6f47e 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -39,8 +39,8 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$roleCreatePageModule-$roleCreatePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$roleCreatePageModule-$roleCreatePageName');
 
   RoleController controller;
 
@@ -59,7 +59,7 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -79,8 +79,8 @@ abstract class RoleCreatePageState extends State<RoleCreatePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        RoleController(_formKey, this, roleCreatePageName, context, applicationData);
+    controller = RoleController(
+        _formKey, this, roleCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
diff --git a/lib/src/page/role/role_custom_page.dart b/lib/src/page/role/role_custom_page.dart
new file mode 100644 (file)
index 0000000..c83d0c2
--- /dev/null
@@ -0,0 +1,110 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/page_model.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'role_controller.dart';
+
+const roleCustomPageModule = 'role';
+const roleCustomPageName = 'custom';
+const roleCustomPageType = PageModelType.custom;
+//! === BeginOfGeneratedCode: Change only in areas marked as customized
+
+class RoleCustomPage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final logger = Settings().logger;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  RoleCustomPage(this.applicationData, {Key key}) : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  RoleCustomPageState createState() {
+    //! === BeginOfCall ===
+    final rc = RoleCustomPageStateCustomized(applicationData);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class RoleCustomPageState extends State<RoleCustomPage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$roleCustomPageModule-$roleCustomPageName');
+
+  RoleController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  RoleCustomPageState(this.applicationData);
+
+  //! === EndOfConstructor2 ===
+
+  @override
+  Widget build(BuildContext context) {
+    controller?.beginOfBuild(context);
+    return buildScaffold(context);
+  }
+
+  // Builds the widget tree of the page.
+  Widget buildScaffold(BuildContext context);
+
+  /// 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, roleCustomPageName, 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 RoleCustomPageStateCustomized extends RoleCustomPageState {
+  RoleCustomPageStateCustomized(ApplicationData applicationData)
+      : super(applicationData);
+
+  @override
+  Widget buildScaffold(BuildContext context) {
+    // ToDo: write code
+    return Scaffold(
+        appBar: applicationData.appBarBuilder(controller.page.title),
+        drawer: applicationData.drawerBuilder(context),
+        bottomNavigationBar: applicationData.footerBuilder().widget(controller),
+        body: Text('Hello world'));
+  }
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
diff --git a/lib/src/page/role/role_delete_page.dart b/lib/src/page/role/role_delete_page.dart
new file mode 100644 (file)
index 0000000..4b06a6e
--- /dev/null
@@ -0,0 +1,122 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/page_model.dart';
+import '../../widget/dialog_bones.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'role_controller.dart';
+
+const roleDeletePageModule = 'role';
+const roleDeletePageName = 'delete';
+const roleDeletePageType = PageModelType.delete;
+//! === BeginOfGeneratedCode: Delete only in areas marked as customized
+
+class RoleDeletePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  RoleDeletePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  RoleDeletePageState createState() {
+    //! === BeginOfCall ===
+    final rc =
+        RoleDeletePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class RoleDeletePageState extends State<RoleDeletePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$roleDeletePageModule-$roleDeletePageName');
+
+  RoleController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  RoleDeletePageState(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: formDialog(
+          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 = RoleController(
+        _formKey, this, roleDeletePageName, 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 RoleDeletePageStateCustomized extends RoleDeletePageState
+    implements SingleRecordPage {
+  RoleDeletePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+  @override
+  int getPrimaryKey() {
+    return primaryId;
+  }
+}
index 313f207a9e98efdfadd7b58e1c3b379ba57399af..15d9a333b651fbf8b1b05a3a560948132b14385e 100644 (file)
@@ -1,7 +1,8 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/widget/utilities.dart';
 
 import '../../model/page_model.dart';
-import '../../widget/list_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../widget/view.dart';
 import '../application_data.dart';
@@ -65,7 +66,10 @@ abstract class RoleListPageState extends State<RoleListPage>
         titles: ListForm.stringsToTitles(controller.page.tableTitles),
         columnNames: controller.page.tableColumns ?? [],
         rows: controller.listRows ?? [],
-        showEditIcon: true,
+        showDeleteIcon: controller.rightOf(roleListPageModule, roleListPageName,
+            roleListPageType, TaskRight.listEdit),
+        showEditIcon: controller.rightOf(roleListPageModule, roleListPageName,
+            roleListPageType, TaskRight.listDelete),
         pageController: controller,
         buttons: <Widget>[
           ButtonBar(
@@ -88,8 +92,8 @@ abstract class RoleListPageState extends State<RoleListPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        RoleController(_formKey, this, roleListPageName, context, applicationData);
+    controller = RoleController(
+        _formKey, this, roleListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -116,5 +120,7 @@ class RoleListPageStateCustomized extends RoleListPageState {
     button.onPressed = () {
       controller.goTo(pageType: PageModelType.create);
     };
+    controller.page.taskRightCallback =
+        utilities.rightsOfRoleListPage(controller);
   }
 }
index ea8001f658babe0bc5ede0935a7d05f95c5462bb..9a76623293f908f3ba913ebdebdafa2a389a6210 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -30,8 +30,8 @@ class StarterChangePage extends StatefulWidget {
   @override
   StarterChangePageState createState() {
     //! === BeginOfCall ===
-    final rc =
-        StarterChangePageStateCustomized(primaryId, applicationData, initialRow);
+    final rc = StarterChangePageStateCustomized(
+        primaryId, applicationData, initialRow);
     //! === EndOfCall ===
 
     /// for unittests:
@@ -45,8 +45,8 @@ abstract class StarterChangePageState extends State<StarterChangePage>
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'starter_change');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$starterChangePageModule-$starterChangePageName');
 
   StarterController controller;
 
@@ -65,7 +65,7 @@ abstract class StarterChangePageState extends State<StarterChangePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -78,6 +78,7 @@ abstract class StarterChangePageState extends State<StarterChangePage>
   /// Customize the page, e.g. modify the default widget list given by the model
   void customize();
 
+  @override
   void dispose() {
     controller.dispose();
     super.dispose();
@@ -86,8 +87,8 @@ abstract class StarterChangePageState extends State<StarterChangePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        StarterController(_formKey, this, 'change', context, applicationData);
+    controller = StarterController(
+        _formKey, this, starterChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
index 44d5426a5558dc0ce944f0c96e582eb7eb18d8ea..af5b455355d6346491bd2446f86ca9915fd2cdac 100644 (file)
@@ -1,10 +1,11 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../model/standard/starter_model.dart';
+import '../../model/module/starter_model.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'starter_change_page.dart';
+import 'starter_delete_page.dart';
 
 class StarterController extends PageControllerBones {
   /// Controller for a page named [pageName].
@@ -23,4 +24,13 @@ class StarterController extends PageControllerBones {
         MaterialPageRoute(
             builder: (context) => StarterChangePage(id, applicationData, row)));
   }
+
+  @override
+  void startDelete(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) => StarterDeletePage(id, applicationData, row)));
+  }
 }
index 3c47cb388d13ab8948a08b28bde8c449904fbec6..b140ef6819b08977abf52bf898aac7578e64bbea 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -39,8 +39,8 @@ abstract class StarterCreatePageState extends State<StarterCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$starterCreatePageModule-$starterCreatePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$starterCreatePageModule-$starterCreatePageName');
 
   StarterController controller;
 
@@ -59,7 +59,7 @@ abstract class StarterCreatePageState extends State<StarterCreatePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -79,8 +79,8 @@ abstract class StarterCreatePageState extends State<StarterCreatePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        StarterController(_formKey, this, starterCreatePageName, context, applicationData);
+    controller = StarterController(
+        _formKey, this, starterCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
diff --git a/lib/src/page/starter/starter_delete_page.dart b/lib/src/page/starter/starter_delete_page.dart
new file mode 100644 (file)
index 0000000..5e94064
--- /dev/null
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/dialog_bones.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import '../../model/page_model.dart';
+import 'starter_controller.dart';
+
+const starterDeletePageModule = 'starter';
+const starterDeletePageName = 'delete';
+const starterDeletePageType = PageModelType.delete;
+//! === BeginOfGeneratedCode: Delete only in areas marked as customized
+
+class StarterDeletePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  StarterDeletePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  StarterDeletePageState createState() {
+    //! === BeginOfCall ===
+    final rc = StarterDeletePageStateCustomized(
+        primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class StarterDeletePageState extends State<StarterDeletePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$starterDeletePageModule-$starterDeletePageName');
+
+  StarterController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  StarterDeletePageState(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: formDialog(
+          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 = StarterController(
+        _formKey, this, starterDeletePageName, 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 StarterDeletePageStateCustomized extends StarterDeletePageState {
+  StarterDeletePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index 41d00ed0d52a51bad0469f8f3647dd259ec25973..093afbd338549e253ec8e6e6e6a7cddd54eb0eb6 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/page_model.dart';
-import '../../widget/list_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'starter_controller.dart';
@@ -38,8 +38,8 @@ abstract class StarterListPageState extends State<StarterListPage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$starterListPageModule-$starterListPageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$starterListPageModule-$starterListPageName');
   Iterable<dynamic> rowsDeprecated;
   StarterController controller;
 
@@ -65,7 +65,10 @@ abstract class StarterListPageState extends State<StarterListPage>
         titles: ListForm.stringsToTitles(controller.page.tableTitles),
         columnNames: controller.page.tableColumns ?? [],
         rows: controller.listRows ?? [],
-        showEditIcon: true,
+        showDeleteIcon: controller.rightOf(starterListPageModule,
+            starterListPageName, starterListPageType, TaskRight.listEdit),
+        showEditIcon: controller.rightOf(starterListPageModule,
+            starterListPageName, starterListPageType, TaskRight.listDelete),
         pageController: controller,
         buttons: <Widget>[
           ButtonBar(
@@ -88,8 +91,8 @@ abstract class StarterListPageState extends State<StarterListPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        StarterController(_formKey, this, starterListPageName, context, applicationData);
+    controller = StarterController(
+        _formKey, this, starterListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
index 984fcb2f5087d82ab43a46d441a9328e67c6b3c3..6804f68349747575399a7b9a9a2a120ea406caa4 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
 import '../../model/button_model.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -47,8 +47,8 @@ abstract class UserChangePageState extends State<UserChangePage>
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$userChangePageModule-$userChangePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userChangePageModule-$userChangePageName');
 
   UserController controller;
 
@@ -67,7 +67,7 @@ abstract class UserChangePageState extends State<UserChangePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -89,8 +89,8 @@ abstract class UserChangePageState extends State<UserChangePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, 'change', context, applicationData);
+    controller = UserController(
+        _formKey, this, userChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -115,14 +115,20 @@ class UserChangePageStateCustomized extends UserChangePageState {
   @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)));
-    };
+    if (applicationData.currentRolePriority > 20) {
+      if (!button.hasOption('disabled')) {
+        button.options.add('disabled');
+      }
+    } else {
+      button?.onPressed = () {
+        String userName = controller.page.fieldByName('user_name').value;
+        applicationData.pushCaller(controller);
+        Navigator.push(
+            context,
+            MaterialPageRoute(
+                builder: (context) => UserPasswordPage(
+                    primaryId, userName, applicationData, null)));
+      };
+    }
   }
 }
index 6519eae28d10d499ad337a4d56d9ab6a2de4bf99..d31d1e6dbe4e43e2dead9c81c74750d7a4b2c50c 100644 (file)
@@ -1,10 +1,11 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../model/standard/user_model.dart';
+import '../../model/module/user_model.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'user_change_page.dart';
+import 'user_delete_page.dart';
 
 class UserController extends PageControllerBones {
   /// Controller for a page named [pageName].
@@ -23,4 +24,13 @@ class UserController extends PageControllerBones {
         MaterialPageRoute(
             builder: (context) => UserChangePage(id, applicationData, row)));
   }
+
+  @override
+  void startDelete(int id, Map row) {
+    applicationData.pushCaller(this);
+    Navigator.push(
+        context,
+        MaterialPageRoute(
+            builder: (context) => UserDeletePage(id, applicationData, row)));
+  }
 }
index 9dcb2ec370b80c2f9623cb15bdc762b93981965a..0be82146d4abd2d463423060e8b7ff2bd10a306d 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -39,8 +39,8 @@ abstract class UserCreatePageState extends State<UserCreatePage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$userCreatePageModule-$userCreatePageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userCreatePageModule-$userCreatePageName');
 
   UserController controller;
 
@@ -59,7 +59,7 @@ abstract class UserCreatePageState extends State<UserCreatePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -79,8 +79,8 @@ abstract class UserCreatePageState extends State<UserCreatePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, 'create', context, applicationData);
+    controller = UserController(
+        _formKey, this, userCreatePageName, context, applicationData);
     controller.initialize();
     customize();
   }
diff --git a/lib/src/page/user/user_delete_page.dart b/lib/src/page/user/user_delete_page.dart
new file mode 100644 (file)
index 0000000..8d04db4
--- /dev/null
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/dialog_bones.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import '../../model/page_model.dart';
+import 'user_controller.dart';
+
+const userDeletePageModule = 'user';
+const userDeletePageName = 'delete';
+const userDeletePageType = PageModelType.delete;
+//! === BeginOfGeneratedCode: Delete only in areas marked as customized
+
+class UserDeletePage extends StatefulWidget {
+  final ApplicationData applicationData;
+  final Map initialRow;
+  final logger = Settings().logger;
+  final int primaryId;
+
+  //! === BeginOfCustomizedVars1 ===
+  //! === EndOfCustomizedVars1 ===
+  //! === BeginOfConstructor1 ===
+  UserDeletePage(this.primaryId, this.applicationData, this.initialRow,
+      {Key key})
+      : super(key: key);
+
+  //! === EndOfConstructor1 ===
+
+  @override
+  UserDeletePageState createState() {
+    //! === BeginOfCall ===
+    final rc =
+        UserDeletePageStateCustomized(primaryId, applicationData, initialRow);
+    //! === EndOfCall ===
+
+    /// for unittests:
+    applicationData.lastModuleState = rc;
+    return rc;
+  }
+}
+
+abstract class UserDeletePageState extends State<UserDeletePage>
+    implements RedrawPage {
+  final ApplicationData applicationData;
+  final int primaryId;
+  final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userDeletePageModule-$userDeletePageName');
+
+  UserController controller;
+
+  //! === BeginOfCustomizedVars2 ===
+  //! === EndOfCustomizedVars2 ===
+  //! === BeginOfConstructor2 ===
+  UserDeletePageState(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: formDialog(
+          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, userDeletePageName, 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 UserDeletePageStateCustomized extends UserDeletePageState {
+  UserDeletePageStateCustomized(
+      int primaryId, ApplicationData applicationData, Map initialRow)
+      : super(primaryId, applicationData, initialRow);
+
+  @override
+  void customize() {
+    // ToDo: write code if needed
+  }
+}
index a537dc958c71fd91d7f87dd3fe711fe1f94a1af1..ce1563f77bcf5dc7b82cbcf7a320290286767c17 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/page_model.dart';
-import '../../widget/list_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../application_data.dart';
 import 'user_controller.dart';
@@ -65,7 +65,10 @@ abstract class UserListPageState extends State<UserListPage>
         titles: ListForm.stringsToTitles(controller.page.tableTitles),
         columnNames: controller.page.tableColumns ?? [],
         rows: controller.listRows ?? [],
-        showEditIcon: true,
+        showDeleteIcon: controller.rightOf(userListPageModule, userListPageName,
+            userListPageType, TaskRight.listEdit),
+        showEditIcon: controller.rightOf(userListPageModule, userListPageName,
+            userListPageType, TaskRight.listDelete),
         pageController: controller,
         buttons: <Widget>[
           ButtonBar(
@@ -88,8 +91,8 @@ abstract class UserListPageState extends State<UserListPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, userListPageName, context, applicationData);
+    controller = UserController(
+        _formKey, this, userListPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -116,5 +119,10 @@ class UserListPageStateCustomized extends UserListPageState {
     button.onPressed = () {
       controller.goTo(pageType: PageModelType.create);
     };
+    controller.page.taskRightCallback = (task, parameter) {
+      final row = parameter as Map;
+      return row['role_priority'] >=
+          controller.applicationData.currentRolePriority;
+    };
   }
 }
index fd7d9d96a32df6eb229a283c16f214f83ea7cb61..758238fe6842a4838803110b839e5d0f99942ac4 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
 import '../../model/button_model.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -41,8 +41,8 @@ abstract class UserLoginPageState extends State<UserLoginPage>
     implements RedrawPage {
   final ApplicationData applicationData;
 
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: '$userLoginPageModule-$userLoginPageName');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userLoginPageModule-$userLoginPageName');
 
   UserController controller;
 
@@ -61,7 +61,7 @@ abstract class UserLoginPageState extends State<UserLoginPage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
index 950d232254819c464646fdd7718c2011404aa02b..e9e94058bdf1c408ce21d89a591bd3bff1951513 100644 (file)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
 import '../../model/button_model.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.dart';
 import '../../widget/page_controller_bones.dart';
 import '../../model/page_model.dart';
 import '../application_data.dart';
@@ -34,7 +34,8 @@ class UserPasswordPage extends StatefulWidget {
   @override
   UserPasswordPageState createState() {
     //! === BeginOfCall ===
-    final rc = UserPasswordPageStateCustomized(primaryId, this.userName, applicationData, initialRow);
+    final rc = UserPasswordPageStateCustomized(
+        primaryId, this.userName, applicationData, initialRow);
     //! === EndOfCall ===
 
     /// for unittests:
@@ -47,9 +48,9 @@ abstract class UserPasswordPageState extends State<UserPasswordPage>
     implements RedrawPage {
   final ApplicationData applicationData;
   final int primaryId;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user.password');
   final Map initialRow;
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userPasswordPageModule-$userPasswordPageName');
 
   UserController controller;
 
@@ -57,7 +58,8 @@ abstract class UserPasswordPageState extends State<UserPasswordPage>
   final String userName;
   //! === EndOfCustomizedVars2 ===
   //! === BeginOfConstructor2 ===
-  UserPasswordPageState(this.primaryId, this.userName, this.applicationData, this.initialRow);
+  UserPasswordPageState(
+      this.primaryId, this.userName, this.applicationData, this.initialRow);
   //! === EndOfConstructor2 ===
 
   @override
@@ -68,7 +70,7 @@ abstract class UserPasswordPageState extends State<UserPasswordPage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -90,8 +92,8 @@ abstract class UserPasswordPageState extends State<UserPasswordPage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, userPasswordPageName, context, applicationData);
+    controller = UserController(
+        _formKey, this, userPasswordPageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -109,8 +111,8 @@ abstract class UserPasswordPageState extends State<UserPasswordPage>
 //! === EndOfGeneratedCode: Below you can change manually:
 
 class UserPasswordPageStateCustomized extends UserPasswordPageState {
-  UserPasswordPageStateCustomized(
-      int primaryId, String userName, ApplicationData applicationData, Map initialRow)
+  UserPasswordPageStateCustomized(int primaryId, String userName,
+      ApplicationData applicationData, Map initialRow)
       : super(primaryId, userName, applicationData, initialRow);
 
   @override
diff --git a/lib/src/widget/dialog_bones.dart b/lib/src/widget/dialog_bones.dart
new file mode 100644 (file)
index 0000000..589b315
--- /dev/null
@@ -0,0 +1,200 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
+
+import '../helper/string_helper.dart';
+import 'model_list.dart';
+import 'page_controller_bones.dart';
+import 'view.dart';
+
+typedef Function OnEditTap(Map<String, dynamic> row, int index);
+
+abstract class TableCallbackController {
+  OnEditTap getOnEditTap(String customString,
+      TableCallbackController controller, Map<String, dynamic> row);
+}
+
+class ListForm {
+  /// Converts a string with [titles] into a list of widgets of type Text().
+  /// Format of [titles]: first character is the delimiter, e.g. ";Id;Name;"
+  /// This allows to use different delimiters when the title text
+  /// contains characters normally used as delimiters.
+  static List<Widget> stringsToTitles(List<String> titles) {
+    final rc = titles
+        .map((String title) =>
+            Text(title, style: TextStyle(fontWeight: FontWeight.bold)))
+        .toList();
+    return rc;
+  }
+
+  /// Returns a widget with a data table.
+  /// [titles] is used for the table header.
+  /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
+  /// [rows] is a list of rows normally delivered from a database query:
+  /// each row is a map with (key, value) pairs.
+  /// If [showEditItems] is true the edit icon is shown in the additional first column.
+  /// If [sortIndex] is not null the rows will be sorted by this index.
+  static Widget table({
+    @required List<Widget> titles,
+    @required List<String> columnNames,
+    @required PageControllerBones controller,
+    @required Iterable<dynamic> rows,
+    bool showEditIcon = false,
+    bool showDeleteIcon = false,
+    int sortIndex,
+    TableCallbackController tableCallbackController,
+    String customString,
+  }) {
+    if (titles.length != columnNames.length) {
+      controller.moduleModel.logger.error(
+          'titles.length != columnNames.length: ${titles.length}/${columnNames.length}');
+    }
+    final titles2 = titles.map((item) => DataColumn(label: item)).toList();
+    if (showEditIcon) {
+      titles2.insert(0, DataColumn(label: SizedBox(width: 1)));
+      sortIndex = sortIndex == null ? null : sortIndex + 1;
+    }
+    if (showDeleteIcon) {
+      titles2.add(DataColumn(label: SizedBox(width: 1)));
+    }
+    final taskRightCallBack = controller.page.taskRightCallback;
+    Widget rc = Container(
+        margin: EdgeInsets.all(2),
+        child: DataTable(
+            showCheckboxColumn: true,
+            showBottomBorder: true,
+            sortColumnIndex: sortIndex,
+            columns: titles2,
+            rows: rows.map((row) {
+              final cells = <DataCell>[];
+              if (showEditIcon) {
+                final canEdit = taskRightCallBack == null
+                    ? true
+                    : taskRightCallBack(TaskRight.listEdit, row);
+                cells.add(DataCell(SizedBox(width: 1), showEditIcon: canEdit,
+                    onTap: () {
+                  if (canEdit) {
+                    controller.onEditTap(customString, controller, row);
+                  }
+                }));
+              }
+              for (var key in columnNames) {
+                cells.add(DataCell(
+                  Text(StringHelper.asString(row[key], nullString: '')),
+                ));
+              }
+              if (showDeleteIcon) {
+                final canDelete = taskRightCallBack == null
+                    ? true
+                    : taskRightCallBack(TaskRight.listDelete, row);
+                cells.add(canDelete
+                    ? DataCell(
+                        Tooltip(
+                            message: 'Endgültiges Löschen des Eintrags',
+                            child: Icon(
+                              Icons.delete_forever_outlined,
+                              semanticLabel: 'Eintrag löschen',
+                            )),
+                        onTap: () => controller.onDeleteTap(
+                            customString, controller, row))
+                    : DataCell(Tooltip(
+                        message: 'Löschen nicht erlaubt',
+                        child: Icon(Icons.delete_rounded))));
+              }
+              return DataRow(cells: cells);
+            }).toList()));
+    return rc;
+  }
+
+  /// Returns a widget with a form containing some [filters] and a data table.
+  /// [titles] is used for the table header.
+  /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
+  /// [rows] is a list of rows normally delivered from a database query:
+  /// each row is a map with (key, value) pairs.
+  /// If [showEditItems] is true the edit icon is shown in the first column.
+  /// If [errorMessage] is not null this message will be shown.
+  static Form listForm(
+      {@required Key key,
+      @required ModelList filters,
+      @required List<Widget> buttons,
+      @required List<Widget> titles,
+      @required List<String> columnNames,
+      @required Iterable<dynamic> rows,
+      bool showDeleteIcon = false,
+      bool showEditIcon = false,
+      String errorMessage,
+      PageControllerBones pageController,
+      @required BaseConfiguration configuration,
+      String customString}) {
+    final padding =
+        configuration.asFloat('form.card.padding', defaultValue: 16.0);
+
+    final view = View(pageController.moduleModel.logger);
+    final widgets = <Widget>[
+      ...view.modelsToWidgets(filters.models, pageController),
+      SizedBox(
+          height: configuration.asFloat('form.gap.field_button.height',
+              defaultValue: 16.0)),
+      ...buttons
+    ];
+    if (errorMessage != null) {
+      widgets.add(View().errorMessage(errorMessage));
+    }
+    widgets.add(table(
+      titles: titles,
+      columnNames: columnNames,
+      rows: rows,
+      showDeleteIcon: showDeleteIcon,
+      showEditIcon: showEditIcon,
+      controller: pageController,
+      customString: customString,
+    ));
+    return Form(
+        key: key,
+        child: Card(
+          margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+          child: Padding(
+              padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
+              child: ListView(children: widgets)),
+        ));
+  }
+}
+
+/// Returns a widget with a form containing at least some input fields
+/// and a save/cancel button.
+/// a change page.
+/// [page] is the name of the page in the model.
+Form formDialog({
+  @required Key key,
+  @required PageControllerBones pageController,
+  @required BaseConfiguration configuration,
+  int primaryId,
+  Map initialRow,
+}) {
+  final padding =
+      configuration.asFloat('form.card.padding', defaultValue: 16.0);
+  pageController.buildModelList(initialRow);
+  final widgets = pageController.getWidgets();
+  final view = View();
+  final buttons = view.modelsToWidgets(
+      pageController.page.sections[0].buttonBar, pageController);
+  return Form(
+    key: key,
+    child: Card(
+        margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+        child: Padding(
+            padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
+            child: ListView(
+              children: <Widget>[
+                ...widgets,
+                SizedBox(
+                    height: configuration.asFloat(
+                        'form.gap.field_button.height',
+                        defaultValue: 16.0)),
+                ButtonBar(
+                  children: buttons,
+                ),
+              ],
+            ))),
+  );
+}
diff --git a/lib/src/widget/edit_form.dart b/lib/src/widget/edit_form.dart
deleted file mode 100644 (file)
index ec50521..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/material.dart';
-
-import 'page_controller_bones.dart';
-import 'view.dart';
-
-typedef Function OnEditTap(Map<String, dynamic> row, int index);
-
-/// Contains helper functions for creating/changing data of a model based module.
-class EditForm {
-  /// Returns a widget with a form containing at least some input fields
-  /// and a save/cancel button.
-  /// a change page.
-  /// [page] is the name of the page in the model.
-  static Form editForm({
-    @required Key key,
-    @required PageControllerBones pageController,
-    @required BaseConfiguration configuration,
-    int primaryId,
-    Map initialRow,
-  }) {
-    final padding =
-        configuration.asFloat('form.card.padding', defaultValue: 16.0);
-    pageController.buildModelList(initialRow);
-    final widgets = pageController.getWidgets();
-    final view = View();
-    final buttons = view.modelsToWidgets(
-        pageController.page.sections[0].buttonBar, pageController);
-    return Form(
-      key: key,
-      child: Card(
-          margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
-          child: Padding(
-              padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
-              child: ListView(
-                children: <Widget>[
-                  ...widgets,
-                  SizedBox(
-                      height: configuration.asFloat(
-                          'form.gap.field_button.height',
-                          defaultValue: 16.0)),
-                  ButtonBar(
-                    children: buttons,
-                  ),
-                ],
-              ))),
-    );
-  }
-}
diff --git a/lib/src/widget/list_form.dart b/lib/src/widget/list_form.dart
deleted file mode 100644 (file)
index 61dfe09..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bones/src/widget/page_controller_bones.dart';
-
-import '../helper/string_helper.dart';
-import 'model_list.dart';
-import 'page_controller_bones.dart';
-import 'view.dart';
-
-typedef Function OnEditTap(Map<String, dynamic> row, int index);
-
-abstract class TableCallbackController {
-  OnEditTap getOnEditTap(String customString,
-      TableCallbackController controller, Map<String, dynamic> row);
-}
-
-class ListForm {
-  /// Converts a string with [titles] into a list of widgets of type Text().
-  /// Format of [titles]: first character is the delimiter, e.g. ";Id;Name;"
-  /// This allows to use different delimiters when the title text
-  /// contains characters normally used as delimiters.
-  static List<Widget> stringsToTitles(List<String> titles) {
-    final rc = titles
-        .map((String title) =>
-            Text(title, style: TextStyle(fontWeight: FontWeight.bold)))
-        .toList();
-    return rc;
-  }
-
-  /// Returns a widget with a data table.
-  /// [titles] is used for the table header.
-  /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
-  /// [rows] is a list of rows normally delivered from a database query:
-  /// each row is a map with (key, value) pairs.
-  /// If [showEditItems] is true the edit icon is shown in the additional first column.
-  /// If [sortIndex] is not null the rows will be sorted by this index.
-  static Widget table({
-    @required List<Widget> titles,
-    @required List<String> columnNames,
-    @required PageControllerBones controller,
-    @required Iterable<dynamic> rows,
-    bool showEditIcon = false,
-    int sortIndex,
-    TableCallbackController tableCallbackController,
-    String customString,
-  }) {
-    if (titles.length != columnNames.length) {
-      controller.moduleModel.logger.error(
-          'titles.length != columnNames.length: ${titles.length}/${columnNames.length}');
-    }
-    final titles2 = titles.map((item) => DataColumn(label: item)).toList();
-    if (showEditIcon) {
-      titles2.insert(0, DataColumn(label: SizedBox(width: 1)));
-      sortIndex = sortIndex == null ? null : sortIndex + 1;
-    }
-    Widget rc = Container(
-        margin: EdgeInsets.all(2),
-        child: DataTable(
-            showCheckboxColumn: true,
-            showBottomBorder: true,
-            sortColumnIndex: sortIndex,
-            columns: titles2,
-            rows: rows.map((row) {
-              final cells = <DataCell>[];
-              if (showEditIcon) {
-                cells.add(
-                    DataCell(SizedBox(width: 1), showEditIcon: true, onTap: () {
-                  controller.onEditTap(customString, controller, row);
-                }));
-              }
-              for (var key in columnNames) {
-                cells.add(DataCell(
-                  Text(StringHelper.asString(row[key], nullString: '')),
-                ));
-              }
-              return DataRow(cells: cells);
-            }).toList()));
-    return rc;
-  }
-
-  /// Returns a widget with a form containing some [filters] and a data table.
-  /// [titles] is used for the table header.
-  /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
-  /// [rows] is a list of rows normally delivered from a database query:
-  /// each row is a map with (key, value) pairs.
-  /// If [showEditItems] is true the edit icon is shown in the first column.
-  /// If [errorMessage] is not null this message will be shown.
-  static Form listForm(
-      {@required Key key,
-      @required ModelList filters,
-      @required List<Widget> buttons,
-      @required List<Widget> titles,
-      @required List<String> columnNames,
-      @required Iterable<dynamic> rows,
-      bool showEditIcon = false,
-      String errorMessage,
-      PageControllerBones pageController,
-      @required BaseConfiguration configuration,
-      String customString}) {
-    final padding =
-        configuration.asFloat('form.card.padding', defaultValue: 16.0);
-
-    final view = View(pageController.moduleModel.logger);
-    final widgets = <Widget>[
-      ...view.modelsToWidgets(filters.models, pageController),
-      SizedBox(
-          height: configuration.asFloat('form.gap.field_button.height',
-              defaultValue: 16.0)),
-      ...buttons
-    ];
-    if (errorMessage != null) {
-      widgets.add(View().errorMessage(errorMessage));
-    }
-    widgets.add(table(
-      titles: titles,
-      columnNames: columnNames,
-      rows: rows,
-      showEditIcon: showEditIcon,
-      controller: pageController,
-      customString: customString,
-    ));
-    return Form(
-        key: key,
-        child: Card(
-          margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
-          child: Padding(
-              padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
-              child: ListView(children: widgets)),
-        ));
-  }
-}
index 3062ed33ca7965740a2f28bfd1dddf9bd95fda11..0f30d34d9ae9cc140284fab8c18e08a1cd342bea 100644 (file)
@@ -40,22 +40,12 @@ class PageControllerBones implements ValidatorController {
   int refreshCounter = 0;
   final placeholders = <String, String>{};
 
-  /// Opens another page.
-  /// Use exactly one of the parameter:
-  /// [pageType]: in this case the route will be constructed
-  /// [route]: the route of the new page, e.g. '/role/change'
-  void goTo({PageModelType pageType, String route}) {
-    applicationData.pushCaller(this);
-    if (pageType != null) {
-      route = '/${moduleModel.name}/${StringUtils.enumToString(pageType)}';
-    }
-    Navigator.pushNamed(context, route);
-  }
-
   PageControllerBones(this.globalKey, this.parent, this.moduleModel,
       this.pageName, this.context, this.applicationData,
       [this.redrawCallback]);
 
+  ThemeData get themeData => Theme.of(context);
+
   void afterSetState(RedrawReason reason,
       {String customString, RedrawCallbackFunctionSimple callback}) {
     if (callback != null) {
@@ -63,14 +53,6 @@ class PageControllerBones implements ValidatorController {
     }
   }
 
-  Future<ComboboxData> buildComboboxDataFromPersistence(
-      ComboBaseModel model) async {
-    final rc = await applicationData.persistenceCache.comboboxAsync(
-        model.listType, model.listOption,
-        hasUndef: model.hasOption('undef'));
-    return rc;
-  }
-
   /// This method should be called in each stateful widget of a page in the
   /// build() method.
   void beginOfBuild(BuildContext context) {
@@ -82,6 +64,39 @@ class PageControllerBones implements ValidatorController {
     }
   }
 
+  Future<ComboboxData> buildComboboxDataFromPersistence(
+      ComboBaseModel model) async {
+    final rc = await applicationData.persistenceCache.comboboxAsync(
+        model.listType, model.listOption,
+        hasUndef: model.hasOption('undef'));
+    return rc;
+  }
+
+  /// Prepares the widgetList: builds the widgets of the page.
+  /// [initialRow] is null or a map with the field values,
+  /// e.g. { 'role_name': 'admin', ...}
+  void buildModelList([Map initialRow]) {
+    modelList.clear();
+    page.sections[0].children.forEach((model) {
+      if (initialRow != null && model is FieldModel) {
+        model.valueFromRow(initialRow);
+      }
+      final name = model is FieldModel ? model.name : null;
+      final value =
+          initialRow == null || name == null ? null : initialRow[name];
+      if (model is FieldModel) {
+        model.value = value;
+      }
+      completeModels(model);
+      modelList.addModel(name, model);
+    });
+    page.sections[0].children.forEach((element) {
+      if (element is FieldModel) {
+        prepareModel(element);
+      }
+    });
+  }
+
   /// Retrieves the rows shown in the list page.
   void buildRows() {
     final persistence = applicationData.persistence;
@@ -120,31 +135,6 @@ class PageControllerBones implements ValidatorController {
     return rc;
   }
 
-  /// Prepares the widgetList: builds the widgets of the page.
-  /// [initialRow] is null or a map with the field values,
-  /// e.g. { 'role_name': 'admin', ...}
-  void buildModelList([Map initialRow]) {
-    modelList.clear();
-    page.sections[0].children.forEach((model) {
-      if (initialRow != null && model is FieldModel) {
-        model.valueFromRow(initialRow);
-      }
-      final name = model is FieldModel ? model.name : null;
-      final value =
-          initialRow == null || name == null ? null : initialRow[name];
-      if (model is FieldModel) {
-        model.value = value;
-      }
-      completeModels(model);
-      modelList.addModel(name, model);
-    });
-    page.sections[0].children.forEach((element) {
-      if (element is FieldModel) {
-        prepareModel(element);
-      }
-    });
-  }
-
   /// Completes the widgets asynchronously if needed.
   void completeAsync() {
     waitForCompletion = completeModelsAsync();
@@ -313,6 +303,20 @@ class PageControllerBones implements ValidatorController {
             }
           };
           break;
+        case 'delete':
+          rc = () {
+            if (globalKey.currentState.validate()) {
+              final id = (parent as SingleRecordPage).getPrimaryKey();
+              applicationData.persistence
+                  .delete(module: moduleModel.name, id: id)
+                  .then((id) {
+                applicationData.callerRedraw(RedrawReason.fetchList);
+                applicationData.popCaller();
+                Navigator.pop(controller.getContext());
+              });
+            }
+          };
+          break;
         case 'cancel':
           rc = () {
             applicationData.popCaller();
@@ -373,7 +377,17 @@ class PageControllerBones implements ValidatorController {
     return rc ?? [];
   }
 
-  ThemeData get themeData => Theme.of(context);
+  /// Opens another page.
+  /// Use exactly one of the parameter:
+  /// [pageType]: in this case the route will be constructed
+  /// [route]: the route of the new page, e.g. '/role/change'
+  void goTo({PageModelType pageType, String route}) {
+    applicationData.pushCaller(this);
+    if (pageType != null) {
+      route = '/${moduleModel.name}/${StringUtils.enumToString(pageType)}';
+    }
+    Navigator.pushNamed(context, route);
+  }
 
   /// Initializes the controller when the instance is complete constructed,
   /// e.g. ModuleModel.parse() is called.
@@ -397,6 +411,14 @@ class PageControllerBones implements ValidatorController {
     });
   }
 
+  onDeleteTap(String customString, PageControllerBones controller, Map row) {
+    ColumnModel primary = moduleModel.mainTable().primary;
+    final id = row[primary.name];
+    applicationData.persistence
+        .recordById(module: moduleModel.name, id: id)
+        .then((row) => startDelete(id, row));
+  }
+
   onEditTap(String customString, PageControllerBones controller, Map row) {
     ColumnModel primary = moduleModel.mainTable().primary;
     final id = row[primary.name];
@@ -425,6 +447,27 @@ class PageControllerBones implements ValidatorController {
     Navigator.pushNamed(context, route);
   }
 
+  /// Returns whether the current user has the right to do the [task].
+  /// [module], [pageName] and [pageType] specify the location of the task.
+  bool rightOf(
+      String module, String pageName, PageModelType pageType, TaskRight task) {
+    bool rc;
+    switch (task) {
+      case TaskRight.listEdit:
+      case TaskRight.listDelete:
+        if (module == 'role' || module == 'user' || module == 'configuration') {
+          rc = applicationData.currentRolePriority <= 20;
+        } else {
+          rc = applicationData.currentRolePriority <= 30;
+        }
+        break;
+      default:
+        rc = false;
+        break;
+    }
+    return rc;
+  }
+
   /// Returns a standard search button for the list page.
   Widget searchButton() {
     final rc = View(moduleModel.logger)
@@ -439,6 +482,13 @@ class PageControllerBones implements ValidatorController {
         'missing override of startChange() in ${moduleModel.fullName()}');
   }
 
+  /// Starts the change page to edit the record with primary key [id].
+  /// [row] contains the db record of [id].
+  void startDelete(int id, Map row) {
+    moduleModel.logger.error(
+        'missing override of startDelete() in ${moduleModel.fullName()}');
+  }
+
   /// Returns the [TextEditingController] instance of a [TextFormField] assigned
   /// to a model named [name].
   TextEditingController textController(String name) {
@@ -471,3 +521,16 @@ enum RedrawReason {
   redraw,
   setError,
 }
+
+abstract class SingleRecordPage {
+  /// Returns the primary key of the record displayed by the page:
+  int getPrimaryKey();
+}
+
+enum TaskRight {
+  custom1,
+  custom2,
+  custom3,
+  listEdit,
+  listDelete,
+}
diff --git a/lib/src/widget/utilities.dart b/lib/src/widget/utilities.dart
new file mode 100644 (file)
index 0000000..c3a7e19
--- /dev/null
@@ -0,0 +1,20 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'page_controller_bones.dart';
+import '../model/page_model.dart';
+
+Utilities utilities;
+
+class Utilities {
+  BaseLogger logger;
+  Utilities(this.logger);
+  TaskRightCallback rightsOfRoleListPage(PageControllerBones controller) {
+    return (TaskRight task, dynamic parameter) {
+      final row = parameter as Map;
+      final rc = (task != TaskRight.listDelete && task != TaskRight.listEdit ||
+              controller.applicationData.currentRolePriority <=
+                  row['role_priority']) &&
+          (task != TaskRight.listDelete || row['role_name'] != 'Administrator');
+      return rc;
+    };
+  }
+}
index f76f40880315cd3fd7bde282536cac05d1c2060d..2694ad11f74f0fbfd6f76cd37709ab1c185f0e68 100644 (file)
@@ -89,6 +89,7 @@ class View {
     if (model != null && model.data?.waitState == WaitState.ready) {
       rc = comboboxRaw(model, controller, initialValue);
     } else {
+      controller.waitForCompletion = null;
       rc = FutureBuilder<bool>(
         future: controller.waitForCompletion,
         builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
index cbc744d0a1d15e2881ee77b77395b13e9137d70b..be4d0a37fdf654ee25b27bb298482422132ee1af 100644 (file)
@@ -1,13 +1,16 @@
 import 'dart:io';
 
 import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones_tool/src/helper/url_helper.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);
+      : super(modelHelper, logger) {
+    FileSync.setLogger(logger);
+  }
 
   @override
   void ensureDirectory(String path) {
@@ -39,7 +42,16 @@ class ModelToolIo extends ModelTool {
   }
 
   @override
-  void writeFile(String filename, String content) {
-    FileSync.toFile(filename, content);
+  void writeFile(String filename, String content,
+      {bool mayExist, bool ensureDirectory}) {
+    if (ensureDirectory == null || ensureDirectory) {
+      FileSync.ensureDirectory(
+          UrlHelper.parentOf(filename, trailingSlash: false));
+    }
+    if (mayExist != null && !mayExist && FileSync.isFile(filename)) {
+      logger.error('$filename already exists');
+    } else {
+      FileSync.toFile(filename, content);
+    }
   }
 }
index 41d8e1b48f21341ee6ec6120790270dc2f751682..c6212586ad2288f6e7000d4e9e2c97b5bdb5a499 100644 (file)
@@ -284,7 +284,6 @@ cAC
           equals(''));
     });
     test('addRangeToList-converter', () {
-      List<String> output;
       final list = <String>['a1', 'a2', 'a3', 'a4'];
       expect(
           StringHelper.addRangeToList(list, null,
diff --git a/test/helpers/url_helper_test.dart b/test/helpers/url_helper_test.dart
new file mode 100644 (file)
index 0000000..4390e62
--- /dev/null
@@ -0,0 +1,48 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/src/helper/url_helper.dart';
+import 'package:test/test.dart';
+
+void main() {
+  final logger = MemoryLogger(LEVEL_FINE);
+  group('filename', () {
+    logger.log('start');
+    test('joinPath', () {
+      expect(UrlHelper.joinPaths('/a', '/b'), equals('/a/b'));
+      expect(UrlHelper.joinPaths('/a/', '/b'), equals('/a/b'));
+      expect(UrlHelper.joinPaths('/a/', 'b'), equals('/a/b'));
+      expect(UrlHelper.joinPaths('/a', ''), equals('/a'));
+      expect(UrlHelper.joinPaths('/a', '/b', '/c'), equals('/a/b/c'));
+      expect(UrlHelper.joinPaths('/a', './b', './c'), equals('/a/b/c'));
+      expect(UrlHelper.joinPaths('/a/', '/b/', '/c'), equals('/a/b/c'));
+      expect(UrlHelper.joinPaths('/a/', '/b/', 'c'), equals('/a/b/c'));
+      expect(UrlHelper.joinPaths('/a/', '', 'c'), equals('/a/c'));
+    });
+  });
+  test('nodeOf', () {
+    expect(UrlHelper.nodeOf('abc.de'), equals('abc.de'));
+    expect(UrlHelper.nodeOf('path/a'), equals('a'));
+    expect(UrlHelper.nodeOf('/base/in/path/abc.de'), equals('abc.de'));
+  });
+  test('parentOf', () {
+    expect(UrlHelper.parentOf('abc.de'), equals(''));
+    expect(UrlHelper.parentOf('path/a'), equals('path/'));
+    expect(UrlHelper.parentOf('path/a', trailingSlash: false), equals('path'));
+    expect(UrlHelper.parentOf('/', trailingSlash: false), equals(''));
+    expect(
+        UrlHelper.parentOf('/base/in/path/abc.de'), equals('/base/in/path/'));
+  });
+  test('extensionOf', () {
+    expect(UrlHelper.extensionOf('abc.blub.de'), equals('.de'));
+    expect(UrlHelper.extensionOf('.de'), equals(''));
+    expect(UrlHelper.extensionOf('path/a'), equals(''));
+    expect(UrlHelper.extensionOf('/base/in.path/abc.de'), equals('.de'));
+    expect(UrlHelper.extensionOf('/base/in.path/.de'), equals(''));
+  });
+  test('filenameOf', () {
+    expect(UrlHelper.filenameOf('abc.blub.de'), equals('abc.blub'));
+    expect(UrlHelper.filenameOf('.de'), equals('.de'));
+    expect(UrlHelper.filenameOf('path/a'), equals('a'));
+    expect(UrlHelper.filenameOf('/base/in.path/abc.de'), equals('abc'));
+    expect(UrlHelper.filenameOf('/base/in.path/.de'), equals('.de'));
+  });
+}
index 0950f96f1693db824ab729d76809a873c8ed4138..03352e02a04bd754341692cc33452f4310e43a08 100644 (file)
@@ -40,7 +40,7 @@ void main() {
     column role_changedby: DataType.string "Geändert von" options: hidden
 == page create: PageModelType.create options: 
     = section section1: SectionModelType.simpleForm options:  [
-    textField user: "User" options: required unique primary notnull unique
+    textField user: "User" options: required unique primary notnull
     textField role: "Id" options: primary notnull unique
     button buttonStore: label: Save options: Save 
     ] # create.section1
@@ -237,6 +237,7 @@ final userModel = <String, dynamic>{
     {
       'page': 'create',
       'pageType': 'create',
+      'title': 'Demo',
       'sections': [
         {
           'sectionType': 'simpleForm',
index 5f14f5119a1833fe945b2880ffa8706c43bd18bf..b3491c561e918ea7836566adaaed6d267f6703be 100644 (file)
@@ -34,6 +34,9 @@ void main() {
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField user_id: "Id" options: primary notnull unique readonly
+    textField user_name: "User" options: unique notnull
+    textField user_role: "Role" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 '''));
@@ -143,19 +146,19 @@ void main() {
       page.buttonByName('unknown');
       page.fieldByName('nothing');
       final errors = logger.errors;
-      expect(errors.length, equals(7));
+      expect(errors.length, equals(8));
       expect(errors.contains('missing column user_name in table demo1.user'),
           isTrue);
       expect(
           errors.contains('different sizes of tableTitles/tableColumns: 2/1'),
           isTrue);
-      expect(errors.contains('button null.a already defined: null.a'),
+      expect(errors.contains('model null.a already defined: null.a'),
           isTrue);
       expect(errors.contains('missing button unknown in page demo1.list'),
           isTrue);
       expect(errors.contains('missing field nothing in page demo1.list'),
           isTrue);
-      expect(errors.contains('field list.n already defined: list.n'),
+      expect(errors.contains('model list.n already defined: list.n'),
           isTrue);
     });
     test('missing-section', () {
@@ -167,6 +170,7 @@ void main() {
         'pages': [
           {
             "page": "list",
+            'title': 'Demo',
             "pageType": "list",
           },
         ],
@@ -176,7 +180,7 @@ void main() {
       final page = module.pageByName('list');
       expect(page, isNotNull);
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('missing sections in page demo1.list'),
           isTrue);
     });
@@ -198,7 +202,7 @@ void main() {
       final page = module.pageByName('list');
       expect(page, isNotNull);
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(3));
       expect(errors.contains('"sections" is not an array in demo1.list: wrong'),
           isTrue);
     });
@@ -219,7 +223,7 @@ void main() {
       module.parse();
       final page = module.pageByName('list');
       final errors = logger.errors;
-      expect(errors.length, equals(2));
+      expect(errors.length, equals(5));
       expect(errors.contains(
           'tableTitles and tableColumns are only meaningful in list pages: demo1.list'),
           isTrue);
@@ -244,7 +248,7 @@ void main() {
       module.parse();
       final page = module.pageByName('list');
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(4));
       expect(errors.contains('curious item in section list of demo1.list: []'),
           isTrue);
       expect(page.fullName() + page.widgetName(), equals('demo1.listlist'));
@@ -365,6 +369,9 @@ void main() {
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField user_id: "Id" options: primary notnull unique readonly
+    textField user_name: "User" options: unique notnull
+    textField user_role: "Role" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 '''));
@@ -409,6 +416,9 @@ void main() {
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField user_id: "Id" options: primary notnull unique readonly
+    textField user_name: "User" options: unique notnull
+    textField user_role: "Role" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 '''));
@@ -425,7 +435,7 @@ void main() {
       expect(errors.length, equals(0));
       final page = module.pageByName('change');
       expect(page, isNotNull);
-      expect(page.models.values.length, equals(3));
+      expect(page.models.values.length, equals(6));
       expect(page.hasField('user_id'), isTrue);
       expect(page.hasField('user_name'), isTrue);
       expect(page.hasField('user_role'), isTrue);
@@ -485,7 +495,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('missing children in list.section1'),
           isTrue);
     });
@@ -510,7 +520,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('"children" is not a list in list.section1: a'),
           isTrue);
     });
@@ -537,7 +547,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('child 1 of "children" is not a map in list.section1: []'),
           isTrue);
     });
@@ -564,7 +574,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('child 1 of "children" does not have "modelType" in list.section1: {}'),
           isTrue);
     });
@@ -593,7 +603,7 @@ void main() {
       final module = ModuleModel(map, logger);
       module.parse();
       final errors = logger.errors;
-      expect(errors.length, equals(1));
+      expect(errors.length, equals(2));
       expect(errors.contains('Section: unknown "modelType"  in list.section1'),
           isTrue);
     });
@@ -632,6 +642,7 @@ final userModel = <String, dynamic>{
   'pages': [
     {
       'page': 'create',
+      'title': 'Demo',
       'pageType': 'create',
       'sections': [
         {
@@ -655,6 +666,7 @@ final userModel = <String, dynamic>{
     {
       'page': 'change',
       'pageType': 'change',
+      'title': 'Demo-Change',
       'sections': [
         {
           'sectionType': 'simpleForm',
index a672919ad661d8a30829953facd8ed4846e5b486..254e8c0ab2be38d6043e4d1a830533ebdb11e3bb 100644 (file)
@@ -27,10 +27,17 @@ void main() {
     column role_changedby: DataType.string "Geändert von" options: hidden
 == page create: PageModelType.create options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField role_name: "Rolle" options: unique notnull
+    textField role_priority: "Priorität" options: 
+    textField role_active: "Aktiv" options: 
     allDbFields allDbFields1 options: 
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField role_id: "Id" options: primary notnull unique readonly
+    textField role_name: "Rolle" options: unique notnull
+    textField role_priority: "Priorität" options: 
+    textField role_active: "Aktiv" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 == page list: PageModelType.list options: 
@@ -63,10 +70,19 @@ void main() {
     column user_changedby: DataType.string "Geändert von" options: hidden
 == page create: PageModelType.create options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField user_name: "User" options: unique notnull
+    textField user_displayname: "Anzeigename" options: unique notnull
+    textField user_email: "EMail" options: unique notnull
+    textField user_role: "Rolle" options: undef
     allDbFields allDbFields1 options: 
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField user_id: "Id" options: primary notnull unique readonly
+    textField user_name: "User" options: unique notnull
+    textField user_displayname: "Anzeigename" options: unique notnull
+    textField user_email: "EMail" options: unique notnull
+    textField user_role: "Rolle" options: undef
     allDbFields allDbFields1 options: 
     button set_password: label: Passwort ändern options: Passwort ändern 
     ] # change.section1
@@ -78,14 +94,13 @@ void main() {
     ] # password.section1
 == page list: PageModelType.list options: 
     = section section1: SectionModelType.filterPanel options:  [
-    textField user_name: "User" options: unique notnull
+    textField user_name: "User" options: 
     textField user_role: "Rolle" options: undef
     ] # list.section1
 == page login: PageModelType.change options: noAutoButton
     = section section1: SectionModelType.simpleForm options:  [
     textField user: options: 
     textField password: options: password
-    button login: label: Anmelden options: Anmelden 
     ] # login.section1
 '''));
     });
@@ -114,10 +129,23 @@ void main() {
     column configuration_changedby: DataType.string "Geändert von" options: hidden
 == page create: PageModelType.create options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField configuration_scope: "Bereich" options: notnull
+    textField configuration_property: "Eigenschaft" options: 
+    textField configuration_order: "Reihe" options: 
+    textField configuration_type: "Datentyp" options: 
+    textField configuration_value: "Wert" options: 
+    textField configuration_description: "Beschreibung" options: 
     allDbFields allDbFields1 options: 
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField configuration_id: "Id" options: primary notnull unique readonly
+    textField configuration_scope: "Bereich" options: notnull
+    textField configuration_property: "Eigenschaft" options: 
+    textField configuration_order: "Reihe" options: 
+    textField configuration_type: "Datentyp" options: 
+    textField configuration_value: "Wert" options: 
+    textField configuration_description: "Beschreibung" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 == page list: PageModelType.list options: 
@@ -140,25 +168,42 @@ void main() {
       expect(dump, equals('''= module menu: options: 
 == table menu: options: 
     column menu_id: DataType.int "Id" options: primary notnull unique
-    column menu_name: DataType.string "Name" options: unique notnull
-    column menu_icon: DataType.reference "Bild" options: 
+    column menu_role: DataType.reference "Rolle" options: 
+    column menu_starter: DataType.reference "Programmpunkt" options: 
     column menu_createdat: DataType.dateTime "Erzeugt" options: hidden null
     column menu_createdby: DataType.string "Erzeugt von" options: hidden
     column menu_changedat: DataType.dateTime "Geändert" options: hidden null
     column menu_changedby: DataType.string "Geändert von" options: hidden
 == page create: PageModelType.create options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField menu_role: "Rolle" options: 
+    textField menu_starter: "Programmpunkt" options: 
     allDbFields allDbFields1 options: 
     ] # create.section1
 == page change: PageModelType.change options: 
     = section section1: SectionModelType.simpleForm options:  [
+    textField menu_id: "Id" options: primary notnull unique readonly
+    textField menu_role: "Rolle" options: 
+    textField menu_starter: "Programmpunkt" options: 
     allDbFields allDbFields1 options: 
     ] # change.section1
 == page list: PageModelType.list options: 
     = section section1: SectionModelType.filterPanel options:  [
-    textField menu_name: "Name" options: unique notnull
+    textField menu_role: "Rolle" options: 
     ] # list.section1
 '''));
     });
+  test('starter', () {
+    ModelBase.lastId = 0;
+    logger.clear();
+    final module = StarterModel(logger);
+    module.parse();
+    expect(module.fullName(), equals('starter'));
+    expect(module.widgetName(), equals('starter'));
+    final errors = logger.errors;
+    expect(errors.length, equals(0));
+    expect(module.dump(StringBuffer()).toString().length, 1377);
+
   });
+});
 }
index cc616e4436af092e17a008c6b432deb962dd539f..895f5c28c8f570f72596a68529d5d3c87643cc81 100644 (file)
@@ -2,7 +2,7 @@ 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:flutter_bones/src/helper/url_helper.dart';
 import 'package:test/test.dart';
 
 import '../../lib/src/model/model_helper.dart' as mh;
@@ -10,6 +10,16 @@ import '../../lib/src/model/model_tool.dart';
 
 void main() {
   final logger = MemoryLogger(LEVEL_FINE);
+  final configuration =
+      Configuration('/etc/flutter_bones', 'flutter_bones', logger);
+  setUpAll(() {
+    final directory = configuration.asString('baseDirectory', section: 'test');
+    if (directory == null) {
+      logger.error('missing baseDirectory in ' + configuration.filename);
+    } else {
+      FileSync.chdir(directory);
+    }
+  });
   group('sql', () {
     test('exportSqlCreateTablel', () {
       logger.clear();
@@ -99,28 +109,38 @@ modules:
       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(
+      final value =
+          tool.fileCache['/tmp/model_tool_test/user/user_change_page.dart'];
+      expect(value, equals(bodyUserChangePage));
+      final expected = 'user-create: ' +
           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));
+              .length
+              .toString() +
+          ' user-list: ' +
+          tool.fileCache['/tmp/model_tool_test/user/user_list_page.dart'].length
+              .toString() +
+          ' config-create: ' +
+          tool
+              .fileCache[
+                  '/tmp/model_tool_test/configuration/configuration_create_page.dart']
+              .length
+              .toString() +
+          ' config-change: ' +
+          tool
+              .fileCache[
+                  '/tmp/model_tool_test/configuration/configuration_change_page.dart']
+              .length
+              .toString() +
+          ' config-list: ' +
+          tool
+              .fileCache[
+                  '/tmp/model_tool_test/configuration/configuration_list_page.dart']
+              .length
+              .toString();
       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));
+          expected,
+          equals(
+              'user-create: 3017 user-list: 4014 config-create: 3200 config-change: 3537 config-list: 4063'));
     });
     test('modify-modules-overwrite-constructors', () {
       logger.clear();
@@ -132,30 +152,69 @@ modules:
       expect(logger.errors.length, equals(0));
       expect(tool.fileCache['/tmp/model_tool_test/user/user_change_page.dart'],
           equals(bodyUserChangePage));
-      expect(
+      final expected = 'user-create: ' +
           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));
+              .length
+              .toString() +
+          ' user-list: ' +
+          tool.fileCache['/tmp/model_tool_test/user/user_list_page.dart'].length
+              .toString();
+      expect(expected, equals('user-create: 3017 user-list: 4014'));
+    });
+  });
+  group('create-module', () {
+    test('create-module-single', () {
+      logger.clear();
+      final helper = mh.ModelHelper();
+      final tool = ModelToolIo(helper, logger);
+      String target = FileSync.tempDirectory('model_tool_test');
+      FileSync.clearDirectory(target);
+      tool.createModule(
+          ['demo', 'admin'], ['--page-type=list', '--directory=$target']);
+      expect(logger.errors.length, equals(1));
+      final expected = tool.fileCache['/tmp/model_tool_test/demo/demo_admin_page.dart']
+              .length.toString();
+      expect(expected, equals('3974'));
+    });
+    test('create-module-all', () {
+      logger.clear();
+      final helper = mh.ModelHelper();
+      final tool = ModelToolIo(helper, logger);
+      String target = FileSync.tempDirectory('model_tool_test');
+      FileSync.clearDirectory(target);
+      tool.createModule(['demo'], ['--directory=$target']);
+      expect(logger.errors.length, equals(1));
+      final expected = 'change: ' +
+          tool.fileCache['/tmp/model_tool_test/demo/demo_change_page.dart']
+              .length
+              .toString() +
+          ' create: ' +
+          tool.fileCache['/tmp/model_tool_test/demo/demo_create_page.dart']
+              .length
+              .toString() +
+          ' list: ' +
+          tool.fileCache['/tmp/model_tool_test/demo/demo_list_page.dart'].length
+              .toString();
+      expect(expected, equals('change: 3440 create: 3017 list: 3951'));
     });
   });
 }
 
-final bodyUserChangePage = '''import 'package:flutter/material.dart';
+final bodyUserChangePage = r'''import 'package:flutter/material.dart';
 
 import '../../helper/settings.dart';
 import '../../model/button_model.dart';
-import '../../widget/edit_form.dart';
+import '../../widget/dialog_bones.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';
 
+const userChangePageModule = 'user';
+const userChangePageName = 'change';
+const userChangePageType = PageModelType.change;
 //! === BeginOfGeneratedCode: Change only in areas marked as customized
-//! pageType: change
 
 class UserChangePage extends StatefulWidget {
   final ApplicationData applicationData;
@@ -190,8 +249,8 @@ abstract class UserChangePageState extends State<UserChangePage>
   final ApplicationData applicationData;
   final int primaryId;
   final Map initialRow;
-  final GlobalKey<FormState> _formKey =
-      GlobalKey<FormState>(debugLabel: 'user_change');
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(
+      debugLabel: '$userChangePageModule-$userChangePageName');
 
   UserController controller;
 
@@ -210,7 +269,7 @@ abstract class UserChangePageState extends State<UserChangePage>
         appBar: applicationData.appBarBuilder(controller.page.title),
         drawer: applicationData.drawerBuilder(context),
         bottomNavigationBar: applicationData.footerBuilder().widget(controller),
-        body: EditForm.editForm(
+        body: formDialog(
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
@@ -223,6 +282,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();
@@ -231,8 +291,8 @@ abstract class UserChangePageState extends State<UserChangePage>
   @override
   void initState() {
     super.initState();
-    controller =
-        UserController(_formKey, this, 'change', context, applicationData);
+    controller = UserController(
+        _formKey, this, userChangePageName, context, applicationData);
     controller.initialize();
     customize();
   }
@@ -257,15 +317,24 @@ class UserChangePageStateCustomized extends UserChangePageState {
   @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)));
-    };
+    if (applicationData.currentRolePriority > 20) {
+      if (! button.hasOption('disabled')) {
+        button.options.add('disabled');
+      }
+    } else {
+      button?.onPressed = () {
+        String userName = controller.page
+            .fieldByName('user_name')
+            .value;
+        applicationData.pushCaller(controller);
+        Navigator.push(
+            context,
+            MaterialPageRoute(
+                builder: (context) =>
+                    UserPasswordPage(
+                        primaryId, userName, applicationData, null)));
+      };
+    }
   }
 }''';
 
@@ -305,8 +374,20 @@ class ModelToolIo extends ModelTool {
   }
 
   @override
-  void writeFile(String filename, String content) {
+  bool writeFile(String filename, String content,
+      {bool mayExist, bool ensureDirectory}) {
+    bool rc = true;
     fileCache[filename] = content;
-    FileSync.toFile(filename, content);
+    if (ensureDirectory == null || ensureDirectory) {
+      FileSync.ensureDirectory(
+          UrlHelper.parentOf(filename, trailingSlash: false));
+    }
+    if (mayExist != null && !mayExist && FileSync.isFile(filename)) {
+      logger.error('$filename already exists');
+      rc = false;
+    } else {
+      FileSync.toFile(filename, content);
+    }
+    return rc;
   }
 }