]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work: persistence works
authorHamatoma <author@hamatoma.de>
Mon, 19 Oct 2020 06:05:46 +0000 (08:05 +0200)
committerHamatoma <author@hamatoma.de>
Tue, 20 Oct 2020 15:50:57 +0000 (17:50 +0200)
45 files changed:
data/ddl/configuration.sql [new file with mode: 0644]
data/ddl/role.sql [new file with mode: 0644]
data/ddl/user.sql [new file with mode: 0644]
data/rest/role.yaml [new file with mode: 0644]
data/rest/user.yaml [new file with mode: 0644]
lib/db_tool.dart
lib/flutter_bones.dart
lib/src/controller/base_controller.dart [new file with mode: 0644]
lib/src/controller/button_controller.dart [new file with mode: 0644]
lib/src/controller/combobox_controller.dart [new file with mode: 0644]
lib/src/controller/text_form_controller.dart [new file with mode: 0644]
lib/src/helper/settings.dart
lib/src/helper/validators.dart
lib/src/model/button_model.dart
lib/src/model/checkbox_model.dart
lib/src/model/combobox_model.dart
lib/src/model/field_model.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/standard/configuration_model.dart [new file with mode: 0644]
lib/src/model/standard/standard_modules.dart [new file with mode: 0644]
lib/src/model/table_model.dart
lib/src/model/widget_model.dart
lib/src/page/login_page.dart
lib/src/page/page_data.dart
lib/src/page/role/role_list_page.dart
lib/src/page/user_page.dart
lib/src/persistence/persistence.dart [new file with mode: 0644]
lib/src/persistence/rest_persistence.dart [new file with mode: 0644]
lib/src/private/bdrawer.dart
lib/src/widget/dropdown_button_form_bone.dart [new file with mode: 0644]
lib/src/widget/filters.dart
lib/src/widget/list_form.dart
lib/src/widget/module_controller.dart
lib/src/widget/raised_button_bone.dart
lib/src/widget/text_form_field_bone.dart
lib/src/widget/view.dart
pubspec.yaml
test/helpers/settings_test.dart
test/model/model_test.dart
test/model/standard_test.dart [new file with mode: 0644]
test/rest_persistence_test.dart [new file with mode: 0644]
test/tool/tool_test.dart
test/widget/widget_test.dart

diff --git a/data/ddl/configuration.sql b/data/ddl/configuration.sql
new file mode 100644 (file)
index 0000000..dd4958d
--- /dev/null
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS configuration;
+CREATE TABLE configuration (
+  configuration_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+  configuration_scope VARCHAR(64) UNIQUE NOT NULL,
+  configuration_property VARCHAR(64),
+  configuration_order INT(10),
+  configuration_type VARCHAR(16),
+  configuration_value VARCHAR(255),
+  configuration_description VARCHAR(255),
+  configuration_createdat TIMESTAMP NULL,
+  configuration_createdby VARCHAR(16),
+  configuration_changedat TIMESTAMP NULL,
+  configuration_changedby VARCHAR(16),
+  PRIMARY KEY(configuration_id)
+);
diff --git a/data/ddl/role.sql b/data/ddl/role.sql
new file mode 100644 (file)
index 0000000..cba827e
--- /dev/null
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS role;
+CREATE TABLE role (
+  role_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+  role_name VARCHAR(32) UNIQUE NOT NULL,
+  role_priority INT(10),
+  role_active CHAR(1),
+  role_createdat TIMESTAMP NULL,
+  role_createdby VARCHAR(16),
+  role_changedat TIMESTAMP NULL,
+  role_changedby VARCHAR(16),
+  PRIMARY KEY(role_id)
+);
diff --git a/data/ddl/user.sql b/data/ddl/user.sql
new file mode 100644 (file)
index 0000000..84441b6
--- /dev/null
@@ -0,0 +1,14 @@
+DROP TABLE IF EXISTS user;
+CREATE TABLE user (
+  user_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+  user_name VARCHAR(64) UNIQUE NOT NULL,
+  user_displayname VARCHAR(32) UNIQUE,
+  user_email VARCHAR(128) UNIQUE,
+  user_password VARCHAR(128),
+  user_role INT(10) UNSIGNED,
+  user_createdat TIMESTAMP NULL,
+  user_createdby VARCHAR(16),
+  user_changedat TIMESTAMP NULL,
+  user_changedby VARCHAR(16),
+  PRIMARY KEY(user_id)
+);
diff --git a/data/rest/role.yaml b/data/rest/role.yaml
new file mode 100644 (file)
index 0000000..9d7cb3c
--- /dev/null
@@ -0,0 +1,30 @@
+---
+# configuration of the bones backend for role:
+created: 2020.10.20 15:25:01
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+  - module: role
+    list:
+      - name: insert
+        type: insert
+        sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby)
+          VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby);"
+      - name: update
+        type: update
+        sql: "UPDATE role SET
+          role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_changedat=NOW(),role_changedby=:role_changedby
+          WHERE role_id=:role_id;"
+      - name: delete
+        type: delete
+        sql: "DELETE from role WHERE role_id=:role_id;"
+      - name: record
+        type: record
+        sql: "SELECT * from role WHERE role_id=:role_id;"
+      - name: by_role_name
+        type: record
+        sql: "SELECT * from role WHERE role_name=:role_name&&role_id!=:excluded;"
+      - name: list
+        type: list
+        sql: "SELECT * from role
+          WHERE role_name like :role_name;"
diff --git a/data/rest/user.yaml b/data/rest/user.yaml
new file mode 100644 (file)
index 0000000..3a995c6
--- /dev/null
@@ -0,0 +1,36 @@
+---
+# configuration of the bones backend for user:
+created: 2020.10.20 15:25:01
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+  - module: user
+    list:
+      - name: insert
+        type: insert
+        sql: "INSERT INTO user(user_name,user_displayname,user_email,user_password,user_role,user_createdat,user_createdby)
+          VALUES(:user_name,:user_displayname,:user_email,:user_password,:user_role,NOW(),:user_createdby);"
+      - name: update
+        type: update
+        sql: "UPDATE user SET
+          user_name=:user_name,user_displayname=:user_displayname,user_email=:user_email,user_password=:user_password,user_role=:user_role,user_changedat=NOW(),user_changedby=:user_changedby
+          WHERE user_id=:user_id;"
+      - name: delete
+        type: delete
+        sql: "DELETE from user WHERE user_id=:user_id;"
+      - name: record
+        type: record
+        sql: "SELECT * from user WHERE user_id=:user_id;"
+      - name: by_user_name
+        type: record
+        sql: "SELECT * from user WHERE user_name=:user_name&&user_id!=:excluded;"
+      - name: by_user_displayname
+        type: record
+        sql: "SELECT * from user WHERE user_displayname=:user_displayname&&user_id!=:excluded;"
+      - name: by_user_email
+        type: record
+        sql: "SELECT * from user WHERE user_email=:user_email&&user_id!=:excluded;"
+      - name: list
+        type: list
+        sql: "SELECT * from user
+          WHERE user_name like :user_name AND user_role like :user_role AND user_role like :user_role;"
index 7c4c0808000e37fc12856fd13ecd713f5096f3cd..48299f425b68ece3e1746239eab014ab6930c706 100644 (file)
@@ -44,7 +44,7 @@ class DbHelper {
     FileSync.ensureDirectory(dirDDL);
     FileSync.ensureDirectory(dirREST);
     if (args.isEmpty) {
-      args = ['user', 'role'];
+      args = ['user', 'role', 'configuration'];
     }
     while (args.isNotEmpty) {
       final name = args[0];
@@ -57,6 +57,9 @@ class DbHelper {
         case 'role':
           module = RoleModel(logger);
           break;
+        case 'configuration':
+          module = ConfigurationModel(logger);
+          break;
         default:
           logger.error('unknown table');
           break;
index 0d8fa51acfc7625634985437dbb183e42d4678af..3dd8821a0a32f4859926577b9c2fcf3a9546aad0 100644 (file)
@@ -15,7 +15,9 @@ 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/role_model.dart';
+export 'src/model/standard/standard_modules.dart';
 export 'src/model/standard/user_model.dart';
 export 'src/model/text_field_model.dart';
 export 'src/model/text_model.dart';
@@ -24,6 +26,8 @@ export 'src/page/login_page.dart';
 export 'src/page/page_data.dart';
 export 'src/page/role/role_create_page.dart';
 export 'src/page/user_page.dart';
+export 'src/persistence/persistence.dart';
+export 'src/persistence/rest_persistence.dart';
 export 'src/widget/raised_button_bone.dart';
 export 'src/widget/simple_form.dart';
 export 'src/widget/text_form_field_bone.dart';
diff --git a/lib/src/controller/base_controller.dart b/lib/src/controller/base_controller.dart
new file mode 100644 (file)
index 0000000..46b9533
--- /dev/null
@@ -0,0 +1,11 @@
+import 'package:flutter_bones/flutter_bones.dart';
+
+/// Base class of all controller classes in this package.
+abstract class BaseController {
+  /// Returns the assigned model.
+  WidgetModel getModel();
+
+  /// Returns a unique name of the controller inside the module, mostly the
+  /// widget name of the assigned model.
+  String getName();
+}
diff --git a/lib/src/controller/button_controller.dart b/lib/src/controller/button_controller.dart
new file mode 100644 (file)
index 0000000..7d5347c
--- /dev/null
@@ -0,0 +1,48 @@
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/controller/base_controller.dart';
+
+import '../widget/raised_button_bone.dart';
+
+class ButtonController extends BaseController
+    implements ButtonCallbackController {
+  ButtonModel model;
+
+  ButtonController(this.model);
+
+  @override
+  WidgetModel getModel() {
+    return model;
+  }
+
+  @override
+  String getName() {
+    return model.widgetName();
+  }
+
+  @override
+  getOnHighlightChanged(
+      String customString, ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnLongPressed(String customString, ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnPressed(String customString, ButtonCallbackController controller) {
+    var rc;
+    switch (model.buttonModelType) {
+      case ButtonModelType.cancel:
+        break;
+      case ButtonModelType.custom:
+        break;
+      case ButtonModelType.search:
+        break;
+      case ButtonModelType.store:
+        break;
+    }
+    return rc;
+  }
+}
diff --git a/lib/src/controller/combobox_controller.dart b/lib/src/controller/combobox_controller.dart
new file mode 100644 (file)
index 0000000..322f642
--- /dev/null
@@ -0,0 +1,57 @@
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../widget/dropdown_button_form_bone.dart';
+import 'base_controller.dart';
+
+class ComboboxController<T> extends BaseController
+    implements ComboboxCallbackController {
+  ComboboxModel model;
+
+  ComboboxController(this.model);
+
+  @override
+  WidgetModel getModel() {
+    return model;
+  }
+
+  @override
+  String getName() {
+    return model.widgetName();
+  }
+
+  @override
+  getOnChanged(String customString, ComboboxCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnSaved(String customString, ComboboxCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnSelectedItemBuilder(
+      String customString, ComboboxCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnTap(String customString, ComboboxCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnValidator(String customString, ComboboxCallbackController controller) {
+    return null;
+  }
+
+  @override
+  List<String> texts() {
+    return model.texts;
+  }
+
+  @override
+  List<dynamic> values() {
+    return model.values;
+  }
+}
diff --git a/lib/src/controller/text_form_controller.dart b/lib/src/controller/text_form_controller.dart
new file mode 100644 (file)
index 0000000..d7cd775
--- /dev/null
@@ -0,0 +1,53 @@
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../widget/text_form_field_bone.dart';
+import 'base_controller.dart';
+
+class TextFormController extends BaseController
+    implements TextFormCallbackController {
+  ComboboxModel model;
+
+  TextFormController(this.model);
+
+  @override
+  WidgetModel getModel() {
+    return model;
+  }
+
+  @override
+  String getName() {
+    return model.widgetName();
+  }
+
+  @override
+  getOnChanged(String customString, TextFormCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnEditingComplete(
+      String customString, TextFormCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnFieldSubmitted(
+      String customString, TextFormCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnSaved(String customString, TextFormCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnTap(String customString, TextFormCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getValidator(String customString, TextFormCallbackController controller) {
+    return null;
+  }
+}
index 2836e36395cb04cac6066900784c0891da677ab5..a3dfb5afce22809f32f8f16514a39807e7602b7c 100644 (file)
@@ -1,39 +1,36 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
+import 'package:intl/date_symbol_data_local.dart';
+import 'package:intl/intl.dart';
 
-class Settings {
+class BaseSettings {
   static Locale locale = Locale('US', 'en');
 
-  static final mapWidgetData = <String, dynamic>{
-    'form.card.padding': '16.0',
-    'form.gap.field_button.height': '16.0',
-  };
-  static Settings _instance;
+  static get language => locale.languageCode;
 
-  final BaseConfiguration widgetConfiguration;
+  final BaseConfiguration configuration;
 
   final BaseLogger logger;
 
-  factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) {
-    if (_instance == null) {
-      _instance = Settings.internal(
-          widgetConfiguration == null
-              ? BaseConfiguration(mapWidgetData, logger)
-              : widgetConfiguration,
-          logger);
-    }
-    return _instance;
-  }
-
-  Settings.internal(this.widgetConfiguration, this.logger);
+  BaseSettings(this.configuration, this.logger);
 
   /// Sets the locale code.
   /// [locale] the info about localisation
   /// Finding [locale] of an app:  @see https://github.com/flutter/website/blob/master/examples/internationalization/minimal/lib/main.dart
-  static setLocale(locale) => Settings.locale = locale;
+  static setLocale(locale) {
+    BaseSettings.locale = locale;
+    Intl.defaultLocale = "${locale.languageCode}_$locale.countryCode";
+    initializeDateFormatting();
+  }
 
-  static setLocaleByNames({String country = 'US', String language = 'en'}) =>
-      Settings.locale = Locale(country, language);
+  /// Sets the locales with [language] and [country].
+  static setLocaleByNames({String country, @required String language}) {
+    country ??= language;
+    country = country.toUpperCase();
+    country = country == 'EN' ? 'US' : country;
+    language = language.toLowerCase();
+    setLocale(Locale(language, country));
+  }
 
   /// Translates a [text] with a given translation [map].
   /// Structure of the [map]: { <English text> : { <language code> : translation } }
@@ -52,3 +49,25 @@ class Settings {
     return rc;
   }
 }
+
+class Settings extends BaseSettings {
+  static final mapWidgetData = <String, dynamic>{
+    'form.card.padding': '16.0',
+    'form.gap.field_button.height': '16.0',
+  };
+  static Settings _instance;
+
+  factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) {
+    if (_instance == null) {
+      _instance = Settings.internal(
+          widgetConfiguration == null
+              ? BaseConfiguration(mapWidgetData, logger)
+              : widgetConfiguration,
+          logger);
+    }
+    return _instance;
+  }
+
+  Settings.internal(BaseConfiguration configuration, BaseLogger logger)
+      : super(configuration, logger);
+}
index 7bb567b19f7c4a0508852921544756529012f0dd..351bf22e7176e77bbc03b559e26830407f474e93 100644 (file)
@@ -46,7 +46,7 @@ String checkPhoneNumber(String input) => Validation.isPhoneNumber(input)
         {'0': input});
 
 String _vt(String key, [Map<String, String> placeholders]) =>
-    Settings.translate(key, ValidatorTranslations.translations,
+    BaseSettings.translate(key, ValidatorTranslations.translations,
         placeholders: placeholders);
 
 @protected
@@ -63,7 +63,7 @@ class ValidatorTranslations {
     },
     'Not a phone number: %{0} Examples: "089-123452 "+49-89-12345"': {
       'de':
-      'Keine gültige Telefonnummer: %{0} Beispiele: "089-123452 "+49-89-12345"',
+          'Keine gültige Telefonnummer: %{0} Beispiele: "089-123452 "+49-89-12345"',
     },
   };
 }
index eb5721c9a700b971d9546b82068fbf12da70b415..3fba73b8e2e486211eae53cc21e4a2a09228aa9e 100644 (file)
@@ -5,7 +5,7 @@ import 'package:flutter_bones/flutter_bones.dart';
 typedef ButtonOnPressed = void Function(String name);
 
 /// Describes a button widget.
-class ButtonModel extends WidgetModel implements ButtonCallbackController {
+class ButtonModel extends WidgetModel {
   static final regExprOptions = RegExp(r'^(undef)$');
   String text;
   String name;
@@ -48,22 +48,6 @@ class ButtonModel extends WidgetModel implements ButtonCallbackController {
 
   @override
   String widgetName() => name;
-
-  @override
-  getOnHighlightChanged(
-      String customString, ButtonCallbackController controller) {
-    return null;
-  }
-
-  @override
-  getOnLongPressed(String customString, ButtonCallbackController controller) {
-    return null;
-  }
-
-  @override
-  getOnPressed(String customString, ButtonCallbackController controller) {
-    return onPressed;
-  }
 }
 
 enum ButtonModelType {
index 78990d15f942a7a02ec82d9bddbdc579cd771dee..79c4c16bd564d4ce0c0b712a6ffae0cc935e83b5 100644 (file)
@@ -26,8 +26,8 @@ class CheckboxModel extends FieldModel {
 
   /// Dumps the instance into a [StringBuffer]
   StringBuffer dump(StringBuffer stringBuffer) {
-    stringBuffer.write(
-        '    checkbox $name: text: options: ${listToString(options)}\n');
+    stringBuffer
+        .write('    checkbox $name: text: options: ${listToString(options)}\n');
     return stringBuffer;
   }
 }
index 395c660974b528d7ba17840b0d130bb0d552622e..695a47dab396e49b1da13e294c112da968763a6f 100644 (file)
@@ -32,7 +32,7 @@ class ComboboxModel extends FieldModel {
     super.parse();
     checkSuperfluousAttributes(
         map,
-        'name label dataType options texts toolTip widgetType values'
+        'name label dataType filterType options texts toolTip widgetType values'
             .split(' '));
     texts = parseStringList('texts', map);
     values = parseValueList('values', map, dataType);
index d9185fbf4dcfeef4e084e8f027968b3924cfdf6f..5d5b0c823dd8bda0e901c8d3625e80c10af214ea 100644 (file)
@@ -9,8 +9,6 @@ import 'widget_model.dart';
 
 /// Base class of widgets with user interaction like text field, combobox, checkbox.
 abstract class FieldModel extends WidgetModel {
-  static final regExprOptions =
-      RegExp(r'^(undef|readonly|disabled|password|required)$');
   String name;
   String label;
   String toolTip;
index 2d781e2dd9f386879c3842ac95410b6283f85cd3..039affe8478fb9424307dcde4254654b9ea4c291 100644 (file)
@@ -68,8 +68,8 @@ class ModuleModel extends ModelBase {
     buffer.write('''        sql: "INSERT INTO ${table.name}(''');
     final columns = table.columns
         .where((col) =>
-    col.hasOption('doStore') ||
-        (!col.hasOption('primary') && (!col.hasOption('hidden'))))
+            col.hasOption('doStore') ||
+            (!col.hasOption('primary') && (!col.hasOption('hidden'))))
         .toList();
     addColumnIfMissing(columns, table.columns, '${table.name}_createdat');
     addColumnIfMissing(columns, table.columns, '${table.name}_createdby');
@@ -91,8 +91,8 @@ class ModuleModel extends ModelBase {
 
   /// Writes the list SQL part into the [buffer].
   void exportList(StringBuffer buffer, TableModel table) {
-    final page = pages
-        .firstWhere((element) => element.pageModelType == PageModelType.list,
+    final page = pages.firstWhere(
+            (element) => element.pageModelType == PageModelType.list,
         orElse: () => null);
     if (page != null) {
       buffer.write('''      - name: list
@@ -119,6 +119,9 @@ class ModuleModel extends ModelBase {
             case FilterType.dateTimeTil:
               buffer.write('${field.name}<=:${field.name}');
               break;
+            case FilterType.equals:
+              buffer.write('${field.name} like :${field.name}');
+              break;
             case FilterType.pattern:
               buffer.write('${field.name} like :${field.name}');
               break;
@@ -130,6 +133,29 @@ class ModuleModel extends ModelBase {
     }
   }
 
+  /// Writes the record SQL part into the [buffer].
+  void exportRecord(StringBuffer buffer, TableModel table) {
+    exportRecordOne(
+        'record', '${table.name}_id=:${table.name}_id', buffer, table);
+    table.columns
+        .where((col) => col.hasOption('unique') && !col.hasOption('primary'))
+        .forEach((col) {
+      exportRecordOne(
+          'by_${col.name}',
+          '${col.name}=:${col.name}&&${table.name}_id!=:excluded', buffer,
+          table);
+    });
+  }
+
+  /// Writes the record SQL part into the [buffer].
+  void exportRecordOne(String sqlName, String condition, StringBuffer buffer,
+      TableModel table) {
+    buffer.write('      - name: $sqlName\n');
+    buffer.write('        type: record\n');
+    buffer.write(
+        '        sql: "SELECT * from ${table.name} WHERE $condition;"\n');
+  }
+
   /// Exports a YAML file (as String) describing the SQL statements for insert, update ...
   /// testDate: the fix date 2020.01.01 will be used (for unit tests)
   String exportSqlBackend({bool testDate: false}) {
@@ -158,10 +184,8 @@ modules:
       buffer.write('''      - name: delete
         type: delete
         sql: "DELETE from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
-      - name: record
-        type: record
-        sql: "SELECT * from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
 ''');
+      exportRecord(buffer, table);
       exportList(buffer, table);
     }
     return buffer.toString();
@@ -192,7 +216,7 @@ modules:
             type = 'DOUBLE';
             break;
           case DataType.int:
-            type = 'INT(10)';
+            type = column.hasOption('primary') ? 'INT(10) UNSIGNED' : 'INT(10)';
             break;
           case DataType.reference:
             type = 'INT(10) UNSIGNED';
@@ -211,10 +235,15 @@ modules:
         }
         String options = '';
         for (var option in column.options) {
-          if ('notnull null unique'.contains(option)) {
-            options += ' ' + option;
+          if (option == 'notnull') {
+            options += ' NOT NULL';
+          } else if ('null unique'.contains(option)) {
+            options += ' ' + option.toUpperCase();
           }
         }
+        if (column.hasOption('primary')) {
+          options += ' AUTO_INCREMENT';
+        }
         buffer.write('  ${column.name} $type$options,\n');
       }
       buffer.write('  PRIMARY KEY(${table.name}_id)\n');
@@ -236,13 +265,13 @@ modules:
     addColumnIfMissing(columns, table.columns, '${table.name}_changedby');
     buffer.write(columns.fold(
         '',
-            (prev, col) =>
-        (prev as String) +
+        (prev, col) =>
+            (prev as String) +
             ((prev as String).isEmpty ? '' : ',') +
             col.name +
             '=' +
             ((col.name.endsWith('changedat') ? 'NOW()' : ':' + col.name))));
-    buffer.write('\n          WHERE ${table.name}_id=:${table.name}_id";\n');
+    buffer.write('\n          WHERE ${table.name}_id=:${table.name}_id;"\n');
   }
 
   /// Returns the name including the names of the parent
@@ -279,8 +308,8 @@ modules:
 
   /// Returns a child page given by [name], null otherwise.
   PageModel pageByName(String name) {
-    final found = pages.firstWhere((element) => element.name == name,
-        orElse: () => null);
+    final found =
+    pages.firstWhere((element) => element.name == name, orElse: () => null);
     return found;
   }
 
index eca7fdb185ce0da0e8cfe4842de37f48fa019c24..cc1fc91624dbb8cd990e4ccec901eaef6469631b 100644 (file)
@@ -1,5 +1,4 @@
 import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/foundation.dart';
 
 import 'button_model.dart';
 import 'field_model.dart';
@@ -19,9 +18,7 @@ class PageModel extends ModelBase {
   final List<SectionModel> sections = [];
   PageModelType pageModelType;
   List<String> options;
-  @protected
   final fields = <String, FieldModel>{};
-  @protected
   final buttons = <String, ButtonModel>{};
   final widgets = <WidgetModel>[];
 
index cce657ee605302f31ca75275410831557bbe62c2..c1955be7cbde817cda45b4d97c34eab9c5a3216b 100644 (file)
@@ -30,8 +30,7 @@ class SectionModel extends WidgetModel {
   /// Dumps the internal structure into a [stringBuffer]
   StringBuffer dump(StringBuffer stringBuffer) {
     stringBuffer.write(
-        '    = section $name: $sectionModelType options: ${options.join(
-            ' ')} [\n');
+        '    = section $name: $sectionModelType options: ${options.join(' ')} [\n');
     for (var child in children) {
       child.dump(stringBuffer);
     }
diff --git a/lib/src/model/standard/configuration_model.dart b/lib/src/model/standard/configuration_model.dart
new file mode 100644 (file)
index 0000000..fcc66a6
--- /dev/null
@@ -0,0 +1,122 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.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': 'unique;notnull',
+          },
+          {
+            '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',
+          },
+          {
+            'column': 'configuration_value',
+            'dataType': 'string',
+            'size': 255,
+            'label': 'Wert',
+          },
+          {
+            'column': 'configuration_description',
+            'dataType': 'string',
+            'size': 255,
+            'label': 'Beschreibung',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        "page": "create",
+        "pageType": "create",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
+              {
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "page": "change",
+        "pageType": "change",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
+              {
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "page": "list",
+        "pageType": "list",
+        "sections": [
+          {
+            "sectionType": "filterPanel",
+            "children": [
+              {
+                "name": "configuration_name",
+                "widgetType": "textField",
+                "filterType": "pattern",
+              },
+              {
+                "name": "configuration_scope",
+                "widgetType": "combobox",
+                "filterType": "equals",
+                "options": 'undef',
+              },
+            ]
+          }
+        ]
+      },
+    ],
+  };
+
+  ConfigurationModel(BaseLogger logger) : super(yamlMap, logger);
+
+  /// Returns the name including the names of the parent
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
diff --git a/lib/src/model/standard/standard_modules.dart b/lib/src/model/standard/standard_modules.dart
new file mode 100644 (file)
index 0000000..7eda53f
--- /dev/null
@@ -0,0 +1,25 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+/// Returns an instance of a standard module given by [name].
+ModuleModel standardModule(String name, BaseLogger logger) {
+  var rc;
+  switch (name) {
+    case 'role':
+      rc = RoleModel(logger);
+      break;
+    case 'user':
+      rc = UserModel(logger);
+      break;
+    case 'configuration':
+      rc = ConfigurationModel(logger);
+      break;
+    default:
+      logger.error('unknown standard module: $name');
+      break;
+  }
+  return rc;
+}
+
+/// Returns the names of the standard modules.
+List<String> standardModules() => ['configuration', 'role', 'user'];
index 3deafec37edbef8c59af2f78be8dc9e6a954e002..3be6eff7defa676be4d2a0e26713a1cfd7128cca 100644 (file)
@@ -75,14 +75,14 @@ class TableModel extends ModelBase {
       }
     }
     checkOptionsByRegExpr(options, regExprOptions);
-    _addIfMissing('${name}_createdat', 'Erzeugt', DataType.dateTime,
-        ['hidden', 'null', 'doStore']);
-    _addIfMissing('${name}_createdby', 'Erzeugt von', DataType.string,
-        ['hidden', 'doStore'], 16);
-    _addIfMissing('${name}_changedat', 'Geändert', DataType.dateTime,
-        ['hidden', 'null', 'doStore']);
-    _addIfMissing('${name}_changedby', 'Geändert von', DataType.string,
-        ['hidden', 'doStore'], 16);
+    _addIfMissing(
+        '${name}_createdat', 'Erzeugt', DataType.dateTime, ['hidden', 'null']);
+    _addIfMissing(
+        '${name}_createdby', 'Erzeugt von', DataType.string, ['hidden'], 16);
+    _addIfMissing(
+        '${name}_changedat', 'Geändert', DataType.dateTime, ['hidden', 'null']);
+    _addIfMissing(
+        '${name}_changedby', 'Geändert von', DataType.string, ['hidden'], 16);
   }
 
   @override
index e7f75cc1ca412f8c369661a5e087ffa11e543cf7..269964b068607dec5e1ef0f64e58cb1cf11315c4 100644 (file)
@@ -16,6 +16,7 @@ abstract class WidgetModel extends ModelBase {
       : super(logger) {
     this.id = ++lastId;
   }
+
   /// Dumps the internal structure into a [stringBuffer]
   StringBuffer dump(StringBuffer stringBuffer);
 
index d03a477d267c6c9a14fe8c64bccfa6f728c0f265..a8282dff57e118b6459b3ceb62927cd83f9e2cb9 100644 (file)
@@ -4,7 +4,8 @@ import 'package:flutter_bones/flutter_bones.dart';
 
 class LoginPage extends StatefulWidget {
   final PageData pageData;
-  LoginPage(this.pageData, { Key key }) : super(key: key);
+  LoginPage(this.pageData, {Key key}) : super(key: key);
+
   @override
   LoginPageState createState() {
     // LoginPageState.setPageData(pageData);
@@ -14,8 +15,9 @@ class LoginPage extends StatefulWidget {
   }
 }
 
-class LoginPageState extends State<LoginPage>{
+class LoginPageState extends State<LoginPage> {
   LoginPageState(this.pageData);
+
   final PageData pageData;
 
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@@ -25,7 +27,7 @@ class LoginPageState extends State<LoginPage>{
   Widget build(BuildContext context) {
     final user = LoginUser();
     return Scaffold(
-      appBar: pageData.appBarBuilder('login'),
+        appBar: pageData.appBarBuilder('login'),
         drawer: pageData.drawerBuilder(context),
         body: SimpleForm.simpleForm(
           key: _formKey,
@@ -38,7 +40,10 @@ class LoginPageState extends State<LoginPage>{
               onSaved: (input) => user.name = input,
             ),
             TextFormField(
-              validator: (input) => Validation.isEmail(input) || Validation.isPhoneNumber(input) ? null : 'keine Emailadresse und keine Telefonnummer: $input',
+              validator: (input) =>
+                  Validation.isEmail(input) || Validation.isPhoneNumber(input)
+                      ? null
+                      : 'keine Emailadresse und keine Telefonnummer: $input',
               decoration: InputDecoration(labelText: 'Passwort'),
               onSaved: (input) => user.password = input,
               obscureText: true,
@@ -64,4 +69,4 @@ class LoginPageState extends State<LoginPage>{
 class LoginUser {
   String name;
   String password;
-}
\ No newline at end of file
+}
index 70ad597a49f222666de21913aeab9f24a3cf30c1..646e317c4f8470168308b3a76ceb3d45ebb79db3 100644 (file)
@@ -4,12 +4,14 @@ import 'package:flutter/material.dart';
 /// Data class for storing parameter to build a page.
 class PageData {
   BaseConfiguration configuration;
+
   /// Signature: AppBar func(String title)
   AppBar Function(String title) appBarBuilder;
   Drawer Function(dynamic context) drawerBuilder;
+
   /// Constructor.
   /// [configuration] is a map with the widget data (e.g. padding)
   /// [appBarBuilder] is a factory of a function returning a outside designed AppBar
   /// [drawerBuilder] is a factory of a function returning a Drawer which handles the "Hamburger menu"
   PageData(this.configuration, this.appBarBuilder, this.drawerBuilder);
-}
\ No newline at end of file
+}
index f8aa52280652f6d2e277d59a4a6ee515a0dad30b..5d26db03dc2dbc951caf9943b464fa1aeaa57da9 100644 (file)
@@ -85,7 +85,8 @@ class RoleListPageState extends State<RoleListPage> {
         showEditIcon: true,
         buttons: <Widget>[
           RaisedButtonBone(
-            'search', filters,
+            'search',
+            filters,
             child: Text('Suchen'),
           ),
         ],
index 85166d4e34e1d078ad9717732f17360e8196ae13..127a3cd05335e8246b4021a2bb3b1aaa6502f14c 100644 (file)
@@ -4,7 +4,8 @@ import 'package:flutter_bones/flutter_bones.dart';
 
 class UserPage extends StatefulWidget {
   final PageData pageData;
-  UserPage(this.pageData, { Key key }) : super(key: key);
+  UserPage(this.pageData, {Key key}) : super(key: key);
+
   @override
   UserPageState createState() {
     // UserPageState.setPageData(pageData);
@@ -14,8 +15,9 @@ class UserPage extends StatefulWidget {
   }
 }
 
-class UserPageState extends State<UserPage>{
+class UserPageState extends State<UserPage> {
   UserPageState(this.pageData);
+
   final PageData pageData;
 
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@@ -25,7 +27,7 @@ class UserPageState extends State<UserPage>{
   Widget build(BuildContext context) {
     final user = User();
     return Scaffold(
-      appBar: pageData.appBarBuilder('Benutzer'),
+        appBar: pageData.appBarBuilder('Benutzer'),
         drawer: pageData.drawerBuilder(context),
         body: SimpleForm.simpleForm(
           key: _formKey,
@@ -37,7 +39,10 @@ class UserPageState extends State<UserPage>{
               onSaved: (input) => user.name = input,
             ),
             TextFormField(
-              validator: (input) => Validation.isEmail(input) || Validation.isPhoneNumber(input) ? null : 'keine Emailadresse und keine Telefonnummer: $input',
+              validator: (input) =>
+                  Validation.isEmail(input) || Validation.isPhoneNumber(input)
+                      ? null
+                      : 'keine Emailadresse und keine Telefonnummer: $input',
               decoration: InputDecoration(labelText: 'Passwort'),
               onSaved: (input) => user.password = input,
               obscureText: true,
@@ -66,4 +71,4 @@ class User {
   String displayName;
   String password;
   int role;
-}
\ No newline at end of file
+}
diff --git a/lib/src/persistence/persistence.dart b/lib/src/persistence/persistence.dart
new file mode 100644 (file)
index 0000000..f548a49
--- /dev/null
@@ -0,0 +1,38 @@
+import 'package:meta/meta.dart';
+
+abstract class Persistence {
+  /// Makes a customized query for [module] with a SQL statement named [sqlName]
+  /// and the [sqlType] using some [params].
+  /// Returns null or a record (as Map) (if sqlType == 'record')
+  /// or a list of records (List<Map>).
+  Future<dynamic> customQuery(
+      {@required String module,
+      @required String sqlName,
+      @required String sqlType,
+      Map<String, dynamic> params});
+
+  /// Deletes a record with primary key [id] of the [module] with the
+  /// SQL statement [sqlName].
+  Future delete({@required String module, String sqlName, @required int id});
+
+  /// Inserts a record of the [module] with the SQL statement [sqlName]
+  /// and returns the primary key.
+  Future<int> insert(
+      {@required String module,
+      String sqlName,
+      @required Map<String, dynamic> data});
+
+  /// Returns all records of the [module] specified with [params] using
+  /// a SQL statement named [sqlName].
+  Future<dynamic> list(
+      {@required String module, String sqlName, Map<String, dynamic> params});
+
+  /// Returns a record with primary key [id] of the [module] with the
+  /// SQL statement [sqlName].
+  Future<Map<String, dynamic>> record({@required String module, String sqlName, @required int id});
+
+  /// Updates a record of [module] with the [data] using the SQL statement [sqlName].
+  Future update({@required String module,
+    String sqlName,
+    @required Map<String, dynamic> data});
+}
diff --git a/lib/src/persistence/rest_persistence.dart b/lib/src/persistence/rest_persistence.dart
new file mode 100644 (file)
index 0000000..68896af
--- /dev/null
@@ -0,0 +1,160 @@
+import 'dart:convert' as convert;
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/src/persistence/persistence.dart';
+import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
+
+/// A persistence layer using a REST interface to store/fetch the data.
+class RestPersistence extends Persistence {
+  static RestPersistence _instance;
+  final String application;
+  final String version;
+  final BaseLogger logger;
+  final String host;
+  int sqlTraceLimit = 80;
+
+  final String scheme;
+  int port;
+  int sessionTimeout;
+  String uriPrefix;
+
+  Map<String, String> headers;
+
+  factory RestPersistence(
+      {String application,
+      String version,
+      int port,
+      String host,
+      BaseLogger logger}) {
+    _instance ??= RestPersistence.internal(
+        application: application,
+        host: host,
+        port: port,
+        version: version,
+        logger: logger);
+    return _instance;
+  }
+
+  factory RestPersistence.fromConfig(
+      BaseConfiguration configuration, BaseLogger logger) {
+    final section = 'client';
+    _instance = RestPersistence.internal(
+        application: configuration.asString('application', section: section),
+        version: configuration.asString('version', section: section),
+        host: configuration.asString('host', section: section),
+        port: configuration.asInt('port', section: section),
+        scheme: configuration.asString('schema',
+            section: section, defaultValue: 'https'),
+        logger: logger);
+
+    return _instance;
+  }
+
+  RestPersistence.internal({this.application,
+    @required this.version,
+    @required this.host,
+    @required this.port,
+    this.scheme = 'http',
+    @required this.logger}) {
+    uriPrefix = '$scheme://$host:$port';
+  }
+
+  @override
+  Future customQuery({String module,
+    String sqlName,
+    String sqlType,
+    Map<String, dynamic> params}) async {
+    var rc;
+    assert(['list', 'record', 'update'].contains(sqlType));
+    final params2 = params == null ? '{}' : convert.jsonEncode(params);
+    final answer = await runRequest(module, sqlName, sqlType, body: params2);
+    if (answer.isNotEmpty) {
+      rc = convert.jsonDecode(answer);
+    }
+    if (logger.logLevel >= LEVEL_FINE) {
+      logger.log('answer: ${StringUtils.limitString(answer, sqlTraceLimit)}');
+    }
+    return rc;
+  }
+
+  @override
+  Future delete({String module, String sqlName, int id}) async {
+    sqlName ??= 'delete';
+    await runRequest(module, sqlName, 'delete', body: '{":${module}_id":$id}');
+  }
+
+  @override
+  Future<int> insert(
+      {String module, String sqlName, Map<String, dynamic> data}) async {
+    sqlName ??= 'insert';
+    var rc = 0;
+    final data2 = data == null ? '{}' : convert.jsonEncode(data);
+    final answer = await runRequest(module, sqlName, 'insert', body: data2);
+    if (answer.isNotEmpty) {
+      final map = convert.jsonDecode(answer);
+      rc = map['id'];
+    }
+    return rc;
+  }
+
+  @override
+  Future<dynamic> list(
+      {String module, String sqlName, Map<String, dynamic> params}) async {
+    sqlName ??= 'list';
+    List<dynamic> rc;
+    final answer = await runRequest(module, sqlName, 'list',
+        body: params == null ? '{}' : convert.jsonEncode(params));
+    if (answer.isNotEmpty) {
+      rc = convert.jsonDecode(answer);
+    }
+    if (logger.logLevel >= LEVEL_FINE) {
+      logger.log('answer: ${StringUtils.limitString(answer, sqlTraceLimit)}');
+    }
+    return rc;
+  }
+
+  @override
+  Future<Map<String, dynamic>> record(
+      {String module, String sqlName, int id}) async {
+    sqlName ??= 'record';
+    Map rc;
+    final answer = await runRequest(module, sqlName, 'record',
+        body: '{":${module}_id":$id}');
+    if (answer.isNotEmpty) {
+      rc = convert.jsonDecode(answer);
+    }
+    return rc;
+  }
+
+  /// Handles a HTTP request with a single HTTP connection.
+  /// [module]: the module which implies the requested table.
+  /// [sqlName]: the name of the SQL statement.
+  /// [sqlType]: 'insert', 'record', 'update', 'list', 'delete'
+  /// [body]: null or the body of the POST request, e.g. '{ 'role_id'
+  /// [headers]: the optional HTTP headers.
+  /// [result]: the body of the response or ''
+  Future<String> runRequest(String module, String sqlName, String sqlType,
+      {String body, Map<String, String> headers}) async {
+    var rc = '';
+    final uri = '$uriPrefix/$application/$module/$sqlType/$sqlName/$version';
+    http.Response response;
+    logger.log('request: POST $module $sqlName', LEVEL_LOOP);
+    response = await http.post(uri, body: body, headers: headers);
+    logger.log('status: ${response.statusCode}', LEVEL_LOOP);
+    if (response.statusCode != 200) {
+      logger.error('$uri: status: ${response.statusCode}');
+    } else {
+      rc = response.body ?? '';
+    }
+    return rc;
+  }
+
+  @override
+  Future update(
+      {String module, String sqlName, Map<String, dynamic> data}) async {
+    sqlName ??= 'update';
+    final data2 = data == null ? '{}' : convert.jsonEncode(data);
+    await runRequest(module, sqlName, 'update', body: data2);
+  }
+}
index 544dd48bbbcdb891d374cfff804cede1901913fd..58444f1163992061fe4e86c1728085f14da5eb4d 100644 (file)
@@ -53,6 +53,7 @@ class VFrageDrawer extends Drawer {
     ));
     return rc;
   }
+
   static Widget buildListView(context) {
     final list = MenuItem.menuItems();
     final rc = Card(
diff --git a/lib/src/widget/dropdown_button_form_bone.dart b/lib/src/widget/dropdown_button_form_bone.dart
new file mode 100644 (file)
index 0000000..3bbedf6
--- /dev/null
@@ -0,0 +1,86 @@
+import 'package:flutter/material.dart';
+
+/// Implements a [DropdownButtonFormField] with "outsourced" callbacks:
+/// [customString] a string mostly used for a name needed in the [customController]
+/// [callbackController] handles the callback methods.
+class DropdownButtonFormBone<T> extends DropdownButtonFormField<T> {
+  final String customString;
+  final ComboboxCallbackController callbackController;
+
+  DropdownButtonFormBone(
+    this.customString,
+    this.callbackController, {
+    Key key,
+    @required List<DropdownMenuItem<T>> items,
+    T value,
+    Widget hint,
+    Widget disabledHint,
+    int elevation: 8,
+    TextStyle style,
+    Widget icon,
+    Color iconDisabledColor,
+    Color iconEnabledColor,
+    double iconSize: 24.0,
+    bool isDense: true,
+    bool isExpanded: false,
+    double itemHeight,
+    Color focusColor,
+    FocusNode focusNode,
+    bool autofocus: false,
+    Color dropdownColor,
+    InputDecoration decoration,
+    FormFieldSetter<T> onSaved,
+    FormFieldValidator<T> validator,
+    AutovalidateMode autovalidateMode,
+  }) : super(
+          key: key,
+          items: items,
+          selectedItemBuilder: callbackController.getOnSelectedItemBuilder(
+              customString, callbackController),
+          value: value,
+          hint: hint,
+          disabledHint: disabledHint,
+          onChanged:
+              callbackController.getOnChanged(customString, callbackController),
+          onTap: callbackController.getOnTap(customString, callbackController),
+          elevation: elevation,
+          style: style,
+          icon: icon,
+          iconDisabledColor: iconDisabledColor,
+          iconEnabledColor: iconEnabledColor,
+          iconSize: iconSize,
+          isDense: isDense,
+          isExpanded: isExpanded,
+          itemHeight: itemHeight,
+          focusColor: focusColor,
+          focusNode: focusNode,
+          autofocus: autofocus,
+          dropdownColor: dropdownColor,
+          decoration: decoration,
+          autovalidateMode: autovalidateMode,
+        );
+}
+
+/// Interface for a [DropdownButtonFormBone] specific callback controller.
+abstract class ComboboxCallbackController<T> {
+  ValueChanged<T> getOnChanged(
+      String customString, ComboboxCallbackController controller);
+
+  FormFieldSetter<T> getOnSaved(
+      String customString, ComboboxCallbackController controller);
+
+  DropdownButtonBuilder getOnSelectedItemBuilder(
+      String customString, ComboboxCallbackController controller);
+
+  VoidCallback getOnTap(
+      String customString, ComboboxCallbackController controller);
+
+  FormFieldValidator<T> getOnValidator(
+      String customString, ComboboxCallbackController controller);
+
+  String getName();
+
+  List<String> texts();
+
+  List<dynamic> values();
+}
index 99e290fb1cc9e9a0349ae2fee6b17a0c5209211b..1c779bbf890fd3d3fcb16a65ca162706ad0232f8 100644 (file)
@@ -61,7 +61,7 @@ class FilterItem {
 
 class Filters
     implements
-        TextCallbackController,
+        TextFormCallbackController,
         ButtonCallbackController,
         TableCallbackController {
   var filters = <FilterItem>[];
@@ -80,12 +80,13 @@ class Filters
       filters.firstWhere((element) => element.name == name, orElse: () => null);
 
   @override
-  getOnChanged(String customString, TextCallbackController controller) {
+  getOnChanged(String customString, TextFormCallbackController controller) {
     return null;
   }
 
   @override
-  getOnEditingComplete(String customString, TextCallbackController controller) {
+  getOnEditingComplete(
+      String customString, TextFormCallbackController controller) {
     return null;
   }
 
@@ -96,7 +97,8 @@ class Filters
   }
 
   @override
-  getOnFieldSubmitted(String customString, TextCallbackController controller) {
+  getOnFieldSubmitted(
+      String customString, TextFormCallbackController controller) {
     return null;
   }
 
@@ -126,19 +128,19 @@ class Filters
   }
 
   @override
-  getOnSaved(String customString, TextCallbackController controller) {
+  getOnSaved(String customString, TextFormCallbackController controller) {
     return (input) {
       byName(customString).value = input;
     };
   }
 
   @override
-  getOnTap(String customString, TextCallbackController controller) {
+  getOnTap(String customString, TextFormCallbackController controller) {
     return null;
   }
 
   @override
-  getValidator(String customString, TextCallbackController controller) {
+  getValidator(String customString, TextFormCallbackController controller) {
     return null;
   }
 
index d3db052ba1d6830113773905679c38cf32cfbfa6..994a8cdd81bb6c0801b966f8b0c1b7380f06e005 100644 (file)
@@ -62,9 +62,8 @@ class ListForm {
               for (var key in columnNames) {
                 cells.add(DataCell(
                   Text(row[key]),
-                  onTap: () =>
-                      tableCallbackController.getOnEditTap(
-                          customString, tableCallbackController, row),
+                  onTap: () => tableCallbackController.getOnEditTap(
+                      customString, tableCallbackController, row),
                 ));
               }
               return DataRow(cells: cells);
index 49c10c8e3cd9451cbec2efa5ba30c929a7761046..b27c6396525e370189305936eee36c93240301f0 100644 (file)
@@ -11,7 +11,7 @@ import 'widget_helper.dart';
 
 // This interface allows the generic handling of the edit form by a model driven module.
 class ModuleController
-    implements TextCallbackController, ButtonCallbackController {
+    implements TextFormCallbackController, ButtonCallbackController {
   final ModuleModel moduleModel;
   String primaryColumn;
   WidgetList createWidgets;
@@ -33,17 +33,19 @@ class ModuleController
   ModuleModel getModuleModel() => moduleModel;
 
   @override
-  getOnChanged(String customString, TextCallbackController controller) {
+  getOnChanged(String customString, TextFormCallbackController controller) {
     return null;
   }
 
   @override
-  getOnEditingComplete(String customString, TextCallbackController controller) {
+  getOnEditingComplete(
+      String customString, TextFormCallbackController controller) {
     return null;
   }
 
   @override
-  getOnFieldSubmitted(String customString, TextCallbackController controller) {
+  getOnFieldSubmitted(
+      String customString, TextFormCallbackController controller) {
     return null;
   }
 
@@ -73,7 +75,7 @@ class ModuleController
   }
 
   @override
-  getOnSaved(String customString, TextCallbackController controller) {
+  getOnSaved(String customString, TextFormCallbackController controller) {
     final rc = (input) {
       values[customString] =
           StringHelper.fromString(input, dataTypes[customString]);
@@ -82,12 +84,12 @@ class ModuleController
   }
 
   @override
-  getOnTap(String customString, TextCallbackController controller) {
+  getOnTap(String customString, TextFormCallbackController controller) {
     return null;
   }
 
   @override
-  getValidator(String customString, TextCallbackController controller) {
+  getValidator(String customString, TextFormCallbackController controller) {
     return null;
   }
 
index 402c61174e51c2be4cbc382c7c150840473f056b..64bd8e8ecf7b7cd5dc7a11e9a43e673a781d9284 100644 (file)
@@ -45,22 +45,22 @@ class RaisedButtonBone extends RaisedButton {
       Duration animationDuration,
       Widget child})
       : super(
-      onPressed: callbackController.getOnPressed(
-          customString, callbackController),
-      onLongPress: callbackController.getOnLongPressed(
-          customString, callbackController),
-      onHighlightChanged: callbackController.getOnHighlightChanged(
-          customString, callbackController),
-      textTheme: textTheme,
-      textColor: textColor,
-      disabledTextColor: disabledTextColor,
-      color: color,
-      disabledColor: disabledColor,
-      focusColor: focusColor,
-      hoverColor: hoverColor,
-      highlightColor: highlightColor,
-      splashColor: splashColor,
-      colorBrightness: colorBrightness,
+            onPressed: callbackController.getOnPressed(
+                customString, callbackController),
+            onLongPress: callbackController.getOnLongPressed(
+                customString, callbackController),
+            onHighlightChanged: callbackController.getOnHighlightChanged(
+                customString, callbackController),
+            textTheme: textTheme,
+            textColor: textColor,
+            disabledTextColor: disabledTextColor,
+            color: color,
+            disabledColor: disabledColor,
+            focusColor: focusColor,
+            hoverColor: hoverColor,
+            highlightColor: highlightColor,
+            splashColor: splashColor,
+            colorBrightness: colorBrightness,
             elevation: elevation,
             focusElevation: focusElevation,
             hoverElevation: hoverElevation,
index e8c01ded21f50098d5ef7570bfeb03a92fba5521..09b228994c77235fccdf3162d70a5b303b95ec49 100644 (file)
@@ -2,24 +2,24 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 /// Interface for a [TextFormField] specific callback controller.
-abstract class TextCallbackController {
+abstract class TextFormCallbackController {
   ValueChanged<String> getOnChanged(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 
   VoidCallback getOnEditingComplete(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 
   ValueChanged<String> getOnFieldSubmitted(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 
   FormFieldSetter<String> getOnSaved(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 
   GestureTapCallback getOnTap(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 
   FormFieldValidator<String> getValidator(
-      String customString, TextCallbackController controller);
+      String customString, TextFormCallbackController controller);
 }
 
 /// Implements a [TextFormField] with "outsourced" callbacks:
@@ -27,27 +27,26 @@ abstract class TextCallbackController {
 /// [callbackController] handles the callback methods.
 class TextFormFieldBone extends TextFormField {
   final String customString;
-  final TextCallbackController callbackController;
+  final TextFormCallbackController callbackController;
 
-  TextFormFieldBone(
-    this.customString,
-    this.callbackController, {
-    Key key,
-    TextEditingController controller,
-    String initialValue,
-    FocusNode focusNode,
-    InputDecoration decoration = const InputDecoration(),
-    TextInputType keyboardType,
-    TextCapitalization textCapitalization = TextCapitalization.none,
-    TextInputAction textInputAction,
-    TextStyle style,
-    StrutStyle strutStyle,
-    TextDirection textDirection,
-    TextAlign textAlign = TextAlign.start,
-    TextAlignVertical textAlignVertical,
-    bool autofocus = false,
-    bool readOnly = false,
-    ToolbarOptions toolbarOptions,
+  TextFormFieldBone(this.customString,
+      this.callbackController, {
+        Key key,
+        TextEditingController controller,
+        String initialValue,
+        FocusNode focusNode,
+        InputDecoration decoration = const InputDecoration(),
+        TextInputType keyboardType,
+        TextCapitalization textCapitalization = TextCapitalization.none,
+        TextInputAction textInputAction,
+        TextStyle style,
+        StrutStyle strutStyle,
+        TextDirection textDirection,
+        TextAlign textAlign = TextAlign.start,
+        TextAlignVertical textAlignVertical,
+        bool autofocus = false,
+        bool readOnly = false,
+        ToolbarOptions toolbarOptions,
     bool showCursor,
     String obscuringCharacter = '•',
     bool obscureText = false,
index d820d8fe2aa57b762578c0069bd81c62e5984356..989279cef1f4f81cd084dce0bf3b34f9a8c0105a 100644 (file)
@@ -1,14 +1,16 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/controller/button_controller.dart';
 
+import '../controller/combobox_controller.dart';
 import '../helper/settings.dart';
-import '../model/button_model.dart';
 import '../model/empty_line_model.dart';
 import '../model/field_model.dart';
 import '../model/section_model.dart';
 import '../model/text_field_model.dart';
 import '../model/text_model.dart';
 import '../model/widget_model.dart';
+import 'dropdown_button_form_bone.dart';
 import 'raised_button_bone.dart';
 
 class View {
@@ -22,32 +24,46 @@ class View {
     if (_instance == null) {
       _instance = View.internal(logger);
       _instance.settings = Settings(logger: logger);
-      _instance.widgetConfiguration = _instance.settings.widgetConfiguration;
+      _instance.widgetConfiguration = _instance.settings.configuration;
     }
     return _instance;
   }
 
   View.internal(this.logger);
 
-  /// Creates a button from the [model].
-  Widget button(ButtonModel model) {
+  /// Creates a button from the [controller].
+  Widget button(ButtonController controller) {
     final rc = RaisedButtonBone(
-      model.widgetName(),
-      model,
-      child: Text(model.text),
+      controller.getName(),
+      controller,
+      child: Text(controller.model.text),
     );
     return rc;
   }
 
-  /// Creates a list of buttons from a list of [models].
-  List<Widget> buttonList(List<WidgetModel> models) {
+  /// Creates a list of buttons from a list of [controllers].
+  List<Widget> buttonList(List<ButtonController> controllers) {
     final rc = <Widget>[];
-    for (var item in models) {
+    for (var item in controllers) {
       rc.add(button(item));
     }
     return rc;
   }
 
+  /// Creates a combobox via the [controller].
+  Widget combobox<T>(ComboboxController controller, onTap) {
+    final texts = controller.texts();
+    final values = controller.values() ?? texts;
+    final items = <DropdownMenuItem<T>>[];
+    for (var ix = 0; ix < texts.length; ix++) {
+      items.add(DropdownMenuItem(
+          onTap: onTap, value: values[ix], child: Text(texts[ix])));
+    }
+    final rc =
+        DropdownButtonFormBone(controller.getName(), controller, items: items);
+    return rc;
+  }
+
   /// Creates a checkbox from the [model].
   Widget checkbox(WidgetModel model) {
     var rc;
@@ -84,7 +100,8 @@ class View {
     final padding =
         widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
     final children = widgetsOfSection(model.children);
-    final buttons = buttonList(model.buttons);
+    final controllers = model.buttons.map((button) => ButtonController(button));
+    final buttons = buttonList(controllers);
     final rc = Form(
         key: formKey,
         child: Card(
@@ -142,7 +159,7 @@ class View {
           rc.add(textField(child));
           break;
         case WidgetModelType.button:
-          rc.add(button(child));
+          rc.add(button(ButtonController(child)));
           break;
         case WidgetModelType.emptyLine:
           rc.add(emptyLine(child));
index ffc1db6bb71db12b04f685ff2e72219e1b411b40..0cd45acd35dd7110fb580b060ec495d188667904 100644 (file)
@@ -25,7 +25,7 @@ dependencies:
     sdk: flutter
   flutter_localizations:
     sdk: flutter
-  dart_bones: "^0.4.3"
+  dart_bones: "^0.4.5"
 
 
   # The following adds the Cupertino Icons font to your application.
index 5413fd67a31128d549d4faeb288325c2f3531c7b..dda85c856ab6e2004cab12bb04263c9e7acf8013 100644 (file)
@@ -1,37 +1,47 @@
+import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:intl/intl.dart';
 import 'package:test/test.dart';
 
 void main() {
+  Intl.defaultLocale = "de_DE";
+  final logger = MemoryLogger(LEVEL_FINE);
   final map = {
     'help': {'de': 'Hilfe'},
-    'wrong name %{0}': {'de': 'falscher name %{0}'}
+    'wrong name %{0}': {'de': 'falscher Name %{0}'}
   };
-  setUpAll(() => Settings.setLocaleByNames(language: 'en'));
+  setUpAll(() => BaseSettings.setLocaleByNames(language: 'en'));
   group('Settings', () {
     test('basic', () {
-      Settings.setLocale(Locale('de', 'de'));
-      expect(Settings.translate('abc', map), equals('abc'));
-      expect(Settings.translate('help', map), equals('help'));
+      BaseSettings.setLocale(Locale('de', 'DE'));
+      expect(BaseSettings.language, 'de');
+      expect(BaseSettings.translate('abc', map), equals('abc'));
+      expect(BaseSettings.translate('help', map), equals('Hilfe'));
       expect(
-          Settings.translate('wrong name %{0}', map,
+          BaseSettings.translate('wrong name %{0}', map,
               placeholders: {'0': 'Joe'}),
-          equals('wrong name Joe'));
+          equals('falscher Name Joe'));
+      expect(Settings(logger: logger), isNotNull);
     });
     test('translate-en', () {
-      Settings.setLocaleByNames(language: 'en');
-      expect(Settings.translate('abc', map), equals('abc'));
-      expect(Settings.translate('help', map), equals('help'));
+      BaseSettings.setLocaleByNames(language: 'en');
+      expect(BaseSettings.language, 'en');
+      expect(BaseSettings.translate('abc', map), equals('abc'));
+      expect(BaseSettings.translate('help', map), equals('help'));
       expect(
-          Settings.translate('wrong name %{0}', map,
+          BaseSettings.translate('wrong name %{0}', map,
               placeholders: {'0': 'Joe'}),
           equals('wrong name Joe'));
     });
     test('translate-de', () {
-      Settings.setLocaleByNames(language: 'de');
-      expect(Settings.translate('abc', map), equals('abc'));
-      expect(Settings.translate('help', map), equals('Hilfe'));
-      expect(Settings.translate('wrong name %{0}', map, placeholders: {'0' : 'Joe'}), equals('falscher name Joe'));
+      BaseSettings.setLocaleByNames(language: 'de');
+      expect(BaseSettings.translate('abc', map), equals('abc'));
+      expect(BaseSettings.translate('help', map), equals('Hilfe'));
+      expect(
+          BaseSettings.translate('wrong name %{0}', map,
+              placeholders: {'0': 'Joe'}),
+          equals('falscher Name Joe'));
     });
 });
 }
index 9e85c68a20dad909ffe65ea9142090f8772d5e11..a9bc06792051b2ea30a01691be5b57f5ab87c49f 100644 (file)
@@ -40,7 +40,7 @@ void main() {
     });
   });
   group('ModelBase', () {
-    test('errors', () {
+    test('basic', () {
       logger.clear();
       final map = cloneOfMap(userModel);
       final field = <String, dynamic>{
@@ -94,8 +94,31 @@ void main() {
     });
   });
   group('CheckboxModel', () {
-    logger.clear();
+    test('basic', () {
+      logger.clear();
+      final map = cloneOfMap(userModel);
+      final field = <String, dynamic>{
+        'widgetType': 'checkbox',
+        'name': 'hidden',
+        'label': 'Hidden',
+        'options': 'required',
+      };
+      map['pages'][0]['sections'][0]['children'][0] = field;
+      var module = Demo1(map, logger);
+      module.parse();
+      var errors = logger.errors;
+      expect(errors.length, equals(0));
+      final checkbox = module.pageByName('create').getField('hidden');
+      expect(checkbox.hasOption('required'), isTrue);
+      checkbox.value = true;
+      expect(checkbox.value, isTrue);
+      checkbox.value = false;
+      expect(checkbox.value, isFalse);
+      checkbox.value = 'true';
+      expect(checkbox.value, isTrue);
+    });
     test('errors', () {
+      logger.clear();
       final map = cloneOfMap(userModel);
       final field = <String, dynamic>{
         'widgetType': 'checkbox',
@@ -107,7 +130,7 @@ void main() {
       var module = Demo1(map, logger);
       module.parse();
       var errors = logger.errors;
-      expect(errors.length, equals(2));
+      expect(errors.length, equals(0));
       final checkbox = module.pageByName('create').getField('hidden');
       expect(checkbox, isNotNull);
       expect(checkbox.dataType, DataType.bool);
@@ -138,7 +161,7 @@ void main() {
       var module = Demo1(map, logger);
       module.parse();
       var errors = logger.errors;
-      expect(errors.length, equals(2));
+      expect(errors.length, equals(0));
       final combobox = module.pageByName('create').getField('class');
       expect(combobox, isNotNull);
       expect(combobox.dataType, DataType.int);
@@ -153,8 +176,8 @@ void main() {
     });
   });
   group('Non field widgets', () {
-    logger.clear();
     test('basic', () {
+      logger.clear();
       final map = cloneOfMap(userModel);
       final list = [
         {
@@ -166,15 +189,9 @@ void main() {
       var module = Demo1(map, logger);
       module.parse();
       var errors = logger.errors;
-      expect(errors.length, equals(2));
+      expect(errors.length, equals(0));
       final dump = module.dump(StringBuffer()).toString();
-      expect(dump, equals('''= module demo1: options: 
-== page create: PageModelType.create options: 
-    = section simpleForm1: SectionModelType.simpleForm options:  [
-    emptyLine: options: 
-    text 24 text: *Hi world*: options: rich
-    ] # create.simpleForm1
-'''));
+      expect(dump.contains('text: *Hi world*: options: rich'), isTrue);
       final page = module.pageByName('create');
       final allWidgets = page.getWidgets(null);
       expect(allWidgets.length, equals(2));
@@ -190,12 +207,6 @@ void main() {
       expect(nonFieldWidgets.length, equals(2));
     });
   });
-  group('standard-models', () {
-    test('user', () {
-      final model = UserModel(logger);
-      model.parse();
-    });
-  });
 }
 
 final userModel = <String, dynamic>{
diff --git a/test/model/standard_test.dart b/test/model/standard_test.dart
new file mode 100644 (file)
index 0000000..27dd8b1
--- /dev/null
@@ -0,0 +1,114 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:test/test.dart';
+
+void main() {
+  final logger = MemoryLogger(LEVEL_FINE);
+  group('module', () {
+    test('role', () {
+      logger.clear();
+      final module = RoleModel(logger);
+      module.parse();
+      expect(module.fullName(), equals('role'));
+      expect(module.widgetName(), equals('role'));
+      final errors = logger.errors;
+      expect(errors.length, equals(0));
+      final dump = module.dump(StringBuffer()).toString();
+      expect(dump, '''= module role: options: 
+== table role: options: 
+    column role_id: DataType.int "Id" options: primary notnull unique
+    column role_name: DataType.string "Rolle" options: unique notnull
+    column role_priority: DataType.int "Priorität" options: 
+    column role_active: DataType.bool "Aktiv" options: 
+    column role_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+    column role_createdby: DataType.string "Erzeugt von" options: hidden doStore
+    column role_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+    column role_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields 10 text: options: 
+    ] # create.simpleForm1
+== page change: PageModelType.change options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields 12 text: options: 
+    ] # change.simpleForm1
+== page list: PageModelType.list options: 
+    = section filterPanel1: SectionModelType.filterPanel options:  [
+    textField role_name: options: 
+    ] # list.filterPanel1
+''');
+    });
+    test('user', () {
+      logger.clear();
+      final module = UserModel(logger);
+      module.parse();
+      expect(module.fullName(), equals('user'));
+      expect(module.widgetName(), equals('user'));
+      final errors = logger.errors;
+      expect(errors.length, equals(0));
+      final dump = module.dump(StringBuffer()).toString();
+      expect(dump, '''= module user: options: 
+== table user: options: 
+    column user_id: DataType.int "Id" options: primary notnull unique
+    column user_name: DataType.string "User" options: unique notnull
+    column user_displayname: DataType.string "Anzeigename" options: unique
+    column user_email: DataType.string "EMail" options: unique
+    column user_password: DataType.string "User" options: password
+    column user_role: DataType.reference "Role" options: 
+    column user_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+    column user_createdby: DataType.string "Erzeugt von" options: hidden doStore
+    column user_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+    column user_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields 26 text: options: 
+    ] # create.simpleForm1
+== page change: PageModelType.change options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields 28 text: options: 
+    ] # change.simpleForm1
+== page list: PageModelType.list options: 
+    = section filterPanel1: SectionModelType.filterPanel options:  [
+    textField user_name: options: 
+    combobox user_role: texts:  options: 
+    ] # list.filterPanel1
+''');
+    });
+    test('configuration', () {
+      logger.clear();
+      final module = ConfigurationModel(logger);
+      module.parse();
+      expect(module.fullName(), equals('configuration'));
+      expect(module.widgetName(), equals('configuration'));
+      final errors = logger.errors;
+      expect(errors.length, equals(0));
+      final dump = module.dump(StringBuffer()).toString();
+      expect(dump, startsWith('''= module configuration: options: 
+== table configuration: options: 
+    column configuration_id: DataType.int "Id" options: primary notnull unique
+    column configuration_scope: DataType.string "Bereich" options: unique notnull
+    column configuration_property: DataType.string "Eigenschaft" options: 
+    column configuration_order: DataType.int "Reihe" options: 
+    column configuration_type: DataType.string "Datentyp" options: 
+    column configuration_value: DataType.string "Wert" options: 
+    column configuration_description: DataType.string "Beschreibung" options: 
+    column configuration_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+    column configuration_createdby: DataType.string "Erzeugt von" options: hidden doStore
+    column configuration_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+    column configuration_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields '''));
+      /*
+      expect(dump.contains('''text: options:
+    ] # create.simpleForm1
+== page change: PageModelType.change options: 
+    = section simpleForm1: SectionModelType.simpleForm options:  [
+    allDbFields '''), isTrue);
+      */
+      expect(dump,
+          contains('combobox configuration_scope: texts:  options: undef'));
+      expect(dump, contains('textField configuration_name: options:'));
+    });
+  });
+}
diff --git a/test/rest_persistence_test.dart b/test/rest_persistence_test.dart
new file mode 100644 (file)
index 0000000..bc32ff6
--- /dev/null
@@ -0,0 +1,74 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility that Flutter provides. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() async {
+  final logger = MemoryLogger(LEVEL_FINE);
+  setUpAll(() {
+    final configuration = BaseConfiguration({
+      'client': {
+        'host': 'localhost',
+        'port': 58011,
+        'schema': 'http',
+        'application': 'unittest',
+        'version': '1.0.0',
+      }
+    }, logger);
+    RestPersistence.fromConfig(configuration, logger);
+  });
+  group('queries', () {
+    test('list', () async {
+      final rest = RestPersistence();
+      final list = await rest.list(
+          module: 'role', sqlName: 'list', params: {'":role_name"': '"A%"'});
+      expect(list, isNotNull);
+      expect(list.length, equals(1));
+      expect(list[0].containsKey('role_id'), isTrue);
+    });
+    test('record', () async {
+      final rest = RestPersistence();
+      final record = await rest.record(module: 'role', id: 2);
+      expect(record, isNotNull);
+      expect(record.length, greaterThan(4));
+      expect(record.containsKey('role_id'), isTrue);
+    });
+  });
+  group('modify', () {
+    test('insert+update+delete', () async {
+      final rest = RestPersistence();
+      final rec = await rest.customQuery(
+          module: 'role',
+          sqlName: 'by_role_name',
+          sqlType: 'record',
+          params: {':excluded': 0, ':role_name': 'dummy'});
+      if (rec != 0 && rec.containsKey('role_id')) {
+        await rest.delete(module: 'role', id: rec['role_id']);
+      }
+      final id = await rest.insert(module: 'role', sqlName: 'insert', data: {
+        ':role_name': 'dummy',
+        ':role_priority': 111,
+        ':role_active': 'F',
+        ':role_createdby': 'joe'
+      });
+      expect(id is int, isTrue);
+      final answer2 =
+          await rest.update(module: 'role', sqlName: 'update', data: {
+        ':role_id': id,
+        ':role_name': 'dummy2',
+        ':role_priority': 112,
+        ':role_active': 'T',
+        ':role_changedby': 'eve'
+      });
+      final answer = rest.record(module: 'role', id: id);
+      logger.log('log: $answer');
+      await rest.delete(module: 'role', id: id);
+    });
+  });
+}
index 023c4664090e4d2e7e8a1aa5af504d6118fcb821..55254ba8acab3b38a54d3a35f1d65feaa566b5b2 100644 (file)
@@ -14,13 +14,13 @@ void main() {
       expect(errors.length, equals(0));
       expect(content, equals('''DROP TABLE IF EXISTS role;
 CREATE TABLE role (
-  role_id INT(10) notnull unique,
-  role_name VARCHAR(32) unique notnull,
+  role_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+  role_name VARCHAR(32) UNIQUE NOT NULL,
   role_priority INT(10),
   role_active CHAR(1),
-  role_createdat TIMESTAMP null,
+  role_createdat TIMESTAMP NULL,
   role_createdby VARCHAR(16),
-  role_changedat TIMESTAMP null,
+  role_changedat TIMESTAMP NULL,
   role_changedby VARCHAR(16),
   PRIMARY KEY(role_id)
 );
@@ -43,24 +43,44 @@ modules:
     list:
       - name: insert
         type: insert
-        sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby,role_changedat,role_changedby)
-          VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby,:role_changedat,:role_changedby);"
+        sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby)
+          VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby);"
       - name: update
         type: update
         sql: "UPDATE role SET
-          role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_createdat=:role_createdat,role_createdby=:role_createdby,role_changedat=NOW(),role_changedby=:role_changedby
-          WHERE role_id=:role_id";
+          role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_changedat=NOW(),role_changedby=:role_changedby
+          WHERE role_id=:role_id;"
       - name: delete
         type: delete
         sql: "DELETE from role WHERE role_id=:role_id;"
       - name: record
         type: record
         sql: "SELECT * from role WHERE role_id=:role_id;"
+      - name: by_role_name
+        type: record
+        sql: "SELECT * from role WHERE role_name=:role_name&&role_id!=:excluded;"
       - name: list
         type: list
         sql: "SELECT * from role
           WHERE role_name like :role_name;"
 '''));
     });
+    test('standard_modules', () {
+      logger.clear();
+      final dir = FileSync.tempDirectory('data', subDirs: 'unittest');
+      for (var name in standardModules()) {
+        final module = standardModule(name, logger);
+        module.parse();
+        expect(logger.errors.isEmpty, isTrue);
+        final content = module.exportSqlBackend();
+        FileSync.toFile(FileSync.joinPaths(dir, '$name.yaml'), content);
+      }
+    });
+    test('standard_modules-errors', () {
+      logger.clear();
+      final module = standardModule('not-exists', logger);
+      expect(module, isNull);
+      expect(logger.contains('unknown standard module: not-exists'), isTrue);
+    });
   });
 }
index c6628127ece393a32ac006bf979b231e86e3ea2b..f713f73bf9fa9422b387605c1797574013f30593 100644 (file)
@@ -29,8 +29,8 @@ void main() {
   });
   group('ModuleController', () {
     test('basic', () {
-      PageData pageData =
-          PageData(BaseConfiguration({}, logger), (title) {}, (context) {});
+      PageData pageData = PageData(
+          BaseConfiguration({}, logger), (title) => null, (context) => null);
       final role = RoleCreatePage(pageData);
       if (role.lastState == null) {
         role.createState();