]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work:
authorHamatoma <author@hamatoma.de>
Fri, 9 Oct 2020 22:30:27 +0000 (00:30 +0200)
committerHamatoma <author@hamatoma.de>
Fri, 9 Oct 2020 22:30:27 +0000 (00:30 +0200)
* +filters, +role_model, +all_db_fields_model
* +role_list

20 files changed:
Doc.sh [new file with mode: 0755]
lib/app.dart
lib/flutter_bones.dart
lib/src/helper/filters.dart [new file with mode: 0644]
lib/src/helper/settings.dart
lib/src/model/all_db_fields_model.dart [new file with mode: 0644]
lib/src/model/column_model.dart
lib/src/model/module/user_model.dart [deleted file]
lib/src/model/module_model.dart
lib/src/model/section_model.dart
lib/src/model/standard/role_model.dart [new file with mode: 0644]
lib/src/model/standard/user_model.dart [new file with mode: 0644]
lib/src/model/widget_model.dart
lib/src/page/role_create_page.dart [new file with mode: 0644]
lib/src/page/role_list_page.dart [new file with mode: 0644]
lib/src/page/role_page.dart [deleted file]
lib/src/tool/db_tool.dart [new file with mode: 0644]
lib/src/widget/list_form.dart [new file with mode: 0644]
lib/src/widget/view.dart
test/tool/tool_test.dart [new file with mode: 0644]

diff --git a/Doc.sh b/Doc.sh
new file mode 100755 (executable)
index 0000000..8424cf4
--- /dev/null
+++ b/Doc.sh
@@ -0,0 +1,2 @@
+#! /bin/bash
+dartdoc --sdk-dir=/opt/dart-sdk
index d947f36906667e1cadfe08435d4a94b100339435..f345958686f29d029934678ee35edad0e43ca7bd 100644 (file)
@@ -1,10 +1,22 @@
+import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/page/role_list_page.dart';
 import 'package:flutter_bones/src/private/bsettings.dart';
 
 class BoneApp extends StatefulWidget {
   @override
-  BoneAppState createState() => BoneAppState();
+  BoneAppState createState() {
+    final mapWidgetData = <String, dynamic>{
+      'form.card.padding': '16.0',
+      'form.gap.field_button.height': '16.0',
+    };
+    final logger = MemoryLogger();
+    Settings(
+        logger: logger,
+        widgetConfiguration: BaseConfiguration(mapWidgetData, logger));
+    return BoneAppState();
+  }
 }
 
 class BoneAppState extends State<BoneApp> {
@@ -12,12 +24,12 @@ class BoneAppState extends State<BoneApp> {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
-      title: 'Virtuelle Fragestunde',
+      title: 'Test Standardseiten',
       theme: ThemeData(
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
-      initialRoute: '/login',
+      initialRoute: '/role/list',
       onGenerateRoute: _getRoute,
     );
   }
@@ -26,17 +38,18 @@ class BoneAppState extends State<BoneApp> {
 
 Route<dynamic> _getRoute(RouteSettings settings) {
   MaterialPageRoute route;
-  if (settings.name == '/login') {
+  if (settings.name == '/role/list') {
     route = MaterialPageRoute<void>(
       settings: settings,
-      builder: (BuildContext context) => LoginPage(BSettings.instance.pageData),
+      builder: (BuildContext context) =>
+          RoleListPage(BSettings.instance.pageData),
       fullscreenDialog: false,
     );
   } else {
     route = MaterialPageRoute<void>(
-  settings: settings,
-  builder: (BuildContext context) => LoginPage(BSettings.instance.pageData),
-  fullscreenDialog: false,
+      settings: settings,
+      builder: (BuildContext context) => LoginPage(BSettings.instance.pageData),
+      fullscreenDialog: false,
     );
   }
   return route;
index 8d645ba22908389b6fa6d79824e04c4cfcf18189..cf34b7d7a28e40e540b9b4550ef7294921924852 100644 (file)
@@ -15,12 +15,14 @@ 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/role_model.dart';
+export 'src/model/standard/user_model.dart';
 export 'src/model/text_field_model.dart';
 export 'src/model/text_model.dart';
 export 'src/model/widget_model.dart';
 export 'src/page/login_page.dart';
 export 'src/page/page_data.dart';
-export 'src/page/role_page.dart';
+export 'src/page/role_create_page.dart';
 export 'src/page/user_page.dart';
 export 'src/widget/raised_button_bone.dart';
 export 'src/widget/simple_form.dart';
diff --git a/lib/src/helper/filters.dart b/lib/src/helper/filters.dart
new file mode 100644 (file)
index 0000000..c31d21a
--- /dev/null
@@ -0,0 +1,103 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+typedef FilterPredicate = bool Function(Map<String, dynamic> row);
+
+class FilterItem {
+  final String name;
+  final String label;
+  final FilterType filterType;
+  final String toolTip;
+  final FilterPredicate filterPredicate;
+  var value;
+
+  FilterItem(
+      {this.label,
+      this.filterType,
+      this.toolTip,
+      this.name,
+      this.filterPredicate});
+
+  bool isValid(Map<String, dynamic> row) {
+    bool rc = true;
+    if (filterPredicate != null) {
+      rc = filterPredicate(row);
+    } else {
+      final current = row[name].toString();
+      final value2 = value ?? '';
+      switch (filterType) {
+        case FilterType.pattern:
+          rc = value2 == ''
+              ? true
+              : (value2.startsWidth('*')
+                  ? current.contains(value2.substring(1))
+                  : current.startsWith(value2));
+          break;
+        case FilterType.dateFrom:
+          // TODO: Handle this case.
+          break;
+        case FilterType.dateTil:
+          // TODO: Handle this case.
+          break;
+        case FilterType.dateTimeFrom:
+          // TODO: Handle this case.
+          break;
+        case FilterType.dateTimeTil:
+          // TODO: Handle this case.
+          break;
+        default:
+          rc = true;
+          break;
+      }
+    }
+    return rc;
+  }
+}
+
+class Filters {
+  var filters = <FilterItem>[];
+  final BaseLogger logger;
+
+  Filters(this.logger);
+
+  Filters.ready(this.filters, this.logger);
+
+  void add(FilterItem item) => filters.add(item);
+
+  FilterItem byName(String name) =>
+      filters.firstWhere((element) => element.name == name);
+
+  /// Returns a list of widgets for the filter fields.
+  List<Widget> getWidgets() {
+    final rc = filters.map((filter) {
+      Widget rc = TextFormField(
+        decoration: InputDecoration(labelText: filter.label),
+      );
+      if (filter.toolTip != null) {
+        rc = Tooltip(message: filter.toolTip, child: rc);
+      }
+      return rc;
+    }).toList();
+    return rc;
+  }
+
+  /// Tests whether the [row] belongs to the result.
+  bool isValid(Map<String, dynamic> row) {
+    var rc = true;
+    for (var filter in filters) {
+      if (!filter.isValid(row)) {
+        rc = false;
+        break;
+      }
+    }
+    return rc;
+  }
+}
+
+enum FilterType {
+  pattern,
+  dateFrom,
+  dateTil,
+  dateTimeFrom,
+  dateTimeTil,
+}
index 67bb01b17c97e8001db510b35b32813077f6c0c2..2836e36395cb04cac6066900784c0891da677ab5 100644 (file)
@@ -14,7 +14,7 @@ class Settings {
 
   final BaseLogger logger;
 
-  factory Settings(BaseLogger logger, {BaseConfiguration widgetConfiguration}) {
+  factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) {
     if (_instance == null) {
       _instance = Settings.internal(
           widgetConfiguration == null
diff --git a/lib/src/model/all_db_fields_model.dart b/lib/src/model/all_db_fields_model.dart
new file mode 100644 (file)
index 0000000..72128b3
--- /dev/null
@@ -0,0 +1,39 @@
+import 'package:dart_bones/dart_bones.dart';
+
+import 'page_model.dart';
+import 'section_model.dart';
+import 'widget_model.dart';
+
+/// Describes a text widget without user interaction.
+class AllDbFieldsModel extends WidgetModel {
+  static final regExprOptions = RegExp(r'^(unknown)$');
+  List<String> options;
+  String text;
+  bool isRichText;
+  final Map<String, dynamic> map;
+
+  AllDbFieldsModel(
+      SectionModel section, PageModel page, this.map, BaseLogger logger)
+      : super(section, page, WidgetModelType.text, logger);
+
+  /// Returns the name including the names of the parent
+  @override
+  String fullName() => '${section.name}.allDbFields$id';
+
+  /// Dumps the internal structure into a [stringBuffer]
+  StringBuffer dump(StringBuffer stringBuffer) {
+    stringBuffer
+        .write('    allDbFields $id text: options: ${options.join(' ')}\n');
+    return stringBuffer;
+  }
+
+  /// Parses the map and stores the data in the instance.
+  void parse() {
+    checkSuperfluousAttributes(map, 'options widgetType'.split(' '));
+    options = parseOptions('options', map);
+    checkOptionsByRegExpr(options, regExprOptions);
+  }
+
+  @override
+  String widgetName() => 'allDbFields$id';
+}
index f3763a4faf4606cfe07d2edea910dda647b98c90..92f521f204302b4d7382c2275d303e8aebd87e71 100644 (file)
@@ -7,8 +7,8 @@ import 'widget_model.dart';
 
 /// Describes a column of a database table.
 class ColumnModel extends WidgetModel {
-  static final regExprOptions =
-      RegExp(r'^(undef|readonly|disabled|primary|required|unique)$');
+  static final regExprOptions = RegExp(
+      r'^(undef|readonly|disabled|hidden|null|notnull|primary|required|unique)$');
   String name;
   String label;
   String toolTip;
diff --git a/lib/src/model/module/user_model.dart b/lib/src/model/module/user_model.dart
deleted file mode 100644 (file)
index 898592c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-
-class UserModel extends ModuleModel {
-  static final model = <String, dynamic>{
-    "module": "user",
-    "pages": [
-      {
-        "name": "create",
-        "pageType": "create",
-        "sections": [
-          {
-            "sectionType": "simpleForm",
-            "children": [
-              {
-                "widgetType": "text",
-                "text": "*_Erfassung eines neuen Benutzers:_*",
-                "options": "rich",
-              },
-              {
-                "widgetType": "emptyLine",
-              },
-              {
-                "widgetType": "textField",
-                "name": "user",
-                "label": "Benutzer",
-                "options": "required;unique",
-              },
-              {
-                "widgetType": "textField",
-                "name": "displayname",
-                "label": "Anzeigename",
-                "fieldType": "text",
-                "options": "required",
-              },
-              {
-                "widgetType": "combobox",
-                "name": "role",
-                "label": "Rolle",
-                "dataType": "reference",
-                "options": "required;undef",
-              },
-            ]
-          }
-        ]
-      },
-    ],
-  };
-
-  UserModel(BaseLogger logger) : super(model, logger);
-
-  /// Returns the name including the names of the parent
-  @override
-  String fullName() => name;
-
-  @override
-  String widgetName() => name;
-}
index 85a40ac76a01a79a1a31d422e8f7e507d604dc74..d5fbd23c3f8e6bbdd108327d925e6ee3c008ddd4 100644 (file)
@@ -48,6 +48,62 @@ class ModuleModel extends ModelBase {
     return stringBuffer;
   }
 
+  /// Exports the SQL statement to create the module tables.
+  String exportSql() {
+    StringBuffer buffer = StringBuffer();
+    for (var table in tables) {
+      buffer.write('drop table if exists ${table.name};\n');
+      buffer.write('create table ${table.name} (\n');
+      for (var column in table.columns) {
+        String type;
+        switch (column.dataType) {
+          case DataType.bool:
+            type = 'char(1)';
+            break;
+          case DataType.currency:
+            type = 'int(10)';
+            break;
+          case DataType.date:
+            type = 'date';
+            break;
+          case DataType.dateTime:
+            type = 'timestamp';
+            break;
+          case DataType.float:
+            type = 'double';
+            break;
+          case DataType.int:
+            type = 'int(10)';
+            break;
+          case DataType.reference:
+            type = 'int(10)';
+            break;
+          case DataType.string:
+            if (column.size == null) {
+              type = 'varchar(255)';
+            } else if (column.size <= 255) {
+              type = 'varchar($column.size})';
+            } else if (column.size <= 0xffff) {
+              type = 'text';
+            } else {
+              type = 'mediumtext';
+            }
+            break;
+        }
+        String options = '';
+        for (var option in column.options) {
+          if ('notnull null primary unique'.contains(option)) {
+            options += ' ' + option;
+          }
+        }
+        buffer.write('  ${column.name} $type$options;\n');
+      }
+      buffer.write(');\n');
+    }
+    final rc = buffer.toString();
+    return rc;
+  }
+
   /// Returns the name including the names of the parent
   @override
   String fullName() => name;
index a771ba4b1ae959bdc6fb226bc89b48e8133ef73d..b35f1c58b4bcc5ffb7fab7248ee987c2b4b79b77 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/src/model/all_db_fields_model.dart';
 import 'package:flutter_bones/src/model/db_reference_model.dart';
 
 import 'button_model.dart';
@@ -78,6 +79,9 @@ class SectionModel extends WidgetModel {
                   child['widgetType'].toString(), WidgetModelType.values);
               WidgetModel widget;
               switch (widgetType) {
+                case WidgetModelType.allDbFields:
+                  widget = AllDbFieldsModel(this, page, child, logger);
+                  break;
                 case WidgetModelType.checkbox:
                   widget = CheckboxModel(this, page, child, logger);
                   break;
diff --git a/lib/src/model/standard/role_model.dart b/lib/src/model/standard/role_model.dart
new file mode 100644 (file)
index 0000000..a1ded34
--- /dev/null
@@ -0,0 +1,99 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+class RoleModel extends ModuleModel {
+  static final model = <String, dynamic>{
+    "module": "role",
+    "tables": [
+      {
+        'name': 'role',
+        'columns': [
+          {
+            'name': 'role_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'name': 'role_name',
+            'dataType': 'string',
+            'label': 'Rolle',
+            'size': 32,
+            'options': 'unique;notnull',
+          },
+          {
+            'name': 'role_priority',
+            'dataType': 'int',
+            'label': 'Priorität',
+          },
+          {
+            'name': 'role_created',
+            'dataType': 'dateTime',
+            'label': 'Erzeugt',
+            'options': 'hidden;null',
+          },
+          {
+            'name': 'role_changed',
+            'dataType': 'dateTime',
+            'label': 'Geändert',
+            'options': 'hidden;null',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        "name": "create",
+        "pageType": "create",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
+              {
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "change",
+        "pageType": "change",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
+              {
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "list",
+        "pageType": "list",
+        "sections": [
+          {
+            "sectionType": "filterPanel",
+            "children": [
+              {
+                "widgetType": "textfield",
+                "searchMode": "pattern",
+              }
+            ]
+          }
+        ]
+      },
+    ],
+  };
+
+  RoleModel(BaseLogger logger) : super(model, 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/user_model.dart b/lib/src/model/standard/user_model.dart
new file mode 100644 (file)
index 0000000..3411bff
--- /dev/null
@@ -0,0 +1,106 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+class UserModel extends ModuleModel {
+  static final model = <String, dynamic>{
+    "module": "user",
+    "tables": [
+      {
+        'name': 'users',
+        'columns': [
+          {
+            'name': 'user_id',
+            'dataType': 'int',
+            'label': 'Id',
+            'options': 'primary',
+          },
+          {
+            'name': 'user_name',
+            'dataType': 'string',
+            'label': 'User',
+            'size': 64,
+            'options': 'unique',
+          },
+          {
+            'name': 'user_displayname',
+            'dataType': 'string',
+            'label': 'Anzeigename',
+            'size': 32,
+            'options': 'unique',
+          },
+          {
+            'name': 'user_email',
+            'dataType': 'string',
+            'label': 'EMail',
+            'size': 128,
+            'options': 'unique',
+          },
+          {
+            'name': 'user_password',
+            'dataType': 'string',
+            'label': 'User',
+            'size': 128,
+            'options': 'password',
+          },
+          {
+            'name': 'user_role',
+            'dataType': 'reference',
+            'label': 'Role',
+            'foreignKey': 'role.role_id',
+            'widgetType': 'combobox',
+          },
+        ]
+      },
+    ],
+    'pages': [
+      {
+        "name": "create",
+        "pageType": "create",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
+              {
+                "widgetType": "text",
+                "text": "*_Erfassung eines neuen Benutzers:_*",
+                "options": "rich",
+              },
+              {
+                "widgetType": "emptyLine",
+              },
+              {
+                "widgetType": "textField",
+                "name": "user",
+                "label": "Benutzer",
+                "options": "required;unique",
+              },
+              {
+                "widgetType": "textField",
+                "name": "displayname",
+                "label": "Anzeigename",
+                "fieldType": "text",
+                "options": "required",
+              },
+              {
+                "widgetType": "combobox",
+                "name": "role",
+                "label": "Rolle",
+                "dataType": "reference",
+                "options": "required;undef",
+              },
+            ]
+          }
+        ]
+      },
+    ],
+  };
+
+  UserModel(BaseLogger logger) : super(model, logger);
+
+  /// Returns the name including the names of the parent
+  @override
+  String fullName() => name;
+
+  @override
+  String widgetName() => name;
+}
index f04d3888ee6fecb1e243de83d2891d01d39239ba..269964b068607dec5e1ef0f64e58cb1cf11315c4 100644 (file)
@@ -26,6 +26,7 @@ abstract class WidgetModel extends ModelBase {
 }
 
 enum WidgetModelType {
+  allDbFields,
   button,
   checkbox,
   column,
diff --git a/lib/src/page/role_create_page.dart b/lib/src/page/role_create_page.dart
new file mode 100644 (file)
index 0000000..cde36c7
--- /dev/null
@@ -0,0 +1,67 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+class RoleCreatePage extends StatefulWidget {
+  final PageData pageData;
+
+  RoleCreatePage(this.pageData, {Key key}) : super(key: key);
+
+  @override
+  RoleCreatePageState createState() {
+    // RoleCreatePageState.setPageData(pageData);
+    final rc = RoleCreatePageState(pageData);
+
+    return rc;
+  }
+}
+
+class RoleCreatePageState extends State<RoleCreatePage> {
+  RoleCreatePageState(this.pageData);
+
+  final PageData pageData;
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+  static Role currentRole = Role();
+
+  @override
+  Widget build(BuildContext context) {
+    final role = Role();
+    return Scaffold(
+        appBar: pageData.appBarBuilder('Rollen'),
+        drawer: pageData.drawerBuilder(context),
+        body: SimpleForm.simpleForm(
+          key: _formKey,
+          configuration: pageData.configuration,
+          fields: <Widget>[
+            TextFormField(
+              validator: checkNotEmpty,
+              decoration: InputDecoration(labelText: 'Name'),
+              onSaved: (input) => role.name = input,
+            ),
+            TextFormField(
+              validator: checkNat,
+              decoration: InputDecoration(labelText: 'Priorität'),
+              onSaved: (input) => role.priority = input,
+            ),
+          ],
+          buttons: <Widget>[
+            RaisedButton(
+              onPressed: () => login(context),
+              child: Text('Anmelden'),
+            ),
+          ],
+        ));
+  }
+
+  void login(context) async {
+    if (_formKey.currentState.validate()) {
+      _formKey.currentState.save();
+      //@ToDo: store in database
+    }
+  }
+}
+
+class Role {
+  String priority;
+  String name;
+}
\ No newline at end of file
diff --git a/lib/src/page/role_list_page.dart b/lib/src/page/role_list_page.dart
new file mode 100644 (file)
index 0000000..bbbb26d
--- /dev/null
@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+
+import '../helper/filters.dart';
+import '../helper/settings.dart';
+import '../page/page_data.dart';
+import '../widget/list_form.dart';
+
+class RoleListPage extends StatefulWidget {
+  final PageData pageData;
+
+  RoleListPage(this.pageData, {Key key}) : super(key: key);
+
+  @override
+  RoleListPageState createState() {
+    // RoleListPageState.setPageData(pageData);
+    final rc = RoleListPageState(pageData);
+
+    return rc;
+  }
+}
+
+class RoleListPageState extends State<RoleListPage> {
+  RoleListPageState(this.pageData);
+
+  final PageData pageData;
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+  static Role currentRole = Role();
+
+  List<Map<String, dynamic>> getRows(Filters filters) {
+    final rows = <Map<String, dynamic>>[
+      {
+        'role_id': '1',
+        'role_name': 'Administrator',
+        'role_priority': '10',
+      },
+      {
+        'role_id': '2',
+        'role_name': 'Verwalter',
+        'role_priority': '20',
+      },
+      {
+        'role_id': '3',
+        'role_name': 'Benutzer',
+        'role_priority': '30',
+      },
+      {
+        'role_id': '4',
+        'role_name': 'Gast',
+        'role_priority': '40',
+      },
+    ];
+    final rc = rows.where((row) => filters.isValid(row)).toList();
+    return rc;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final logger = Settings().logger;
+    final filters = Filters.ready([
+      FilterItem(filterType: FilterType.pattern, label: 'Name'),
+    ], logger);
+    return Scaffold(
+      appBar: pageData.appBarBuilder('Rollen'),
+      drawer: pageData.drawerBuilder(context),
+      body: ListForm.listForm(
+        key: _formKey,
+        configuration: pageData.configuration,
+        titles: ListForm.stringsToTitles(';Id;Name;Priorität'),
+        columnNames: 'role_id role_name role_priority'.split(' '),
+        rows: getRows(filters),
+        showEditIcon: true,
+        buttons: <Widget>[
+          RaisedButton(
+            onPressed: () => search(context),
+            child: Text('Suchen'),
+          ),
+        ],
+        filters: filters,
+      ),
+    );
+  }
+
+  void search(context) async {
+    if (_formKey.currentState.validate()) {
+      _formKey.currentState.save();
+    }
+  }
+}
+
+class Role {
+  int id;
+  String priority;
+  String name;
+}
diff --git a/lib/src/page/role_page.dart b/lib/src/page/role_page.dart
deleted file mode 100644 (file)
index d37fea1..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-
-class RolePage extends StatefulWidget {
-  final PageData pageData;
-  RolePage(this.pageData, { Key key }) : super(key: key);
-  @override
-  RolePageState createState() {
-    // RolePageState.setPageData(pageData);
-    final rc = RolePageState(pageData);
-
-    return rc;
-  }
-}
-
-class RolePageState extends State<RolePage>{
-  RolePageState(this.pageData);
-  final PageData pageData;
-
-  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
-  static Role currentRole = Role();
-
-  @override
-  Widget build(BuildContext context) {
-    final role = Role();
-    return Scaffold(
-      appBar: pageData.appBarBuilder('Rollen'),
-        drawer: pageData.drawerBuilder(context),
-        body: SimpleForm.simpleForm(
-          key: _formKey,
-          configuration: pageData.configuration,
-          fields: <Widget>[
-            TextFormField(
-              validator: checkNotEmpty,
-              decoration: InputDecoration(labelText: 'Name'),
-              onSaved: (input) => role.name = input,
-            ),
-            TextFormField(
-              validator: checkNat,
-              decoration: InputDecoration(labelText: 'Priorität'),
-              onSaved: (input) => role.priority = input,
-            ),
-          ],
-          buttons: <Widget>[
-            RaisedButton(
-              onPressed: () => login(context),
-              child: Text('Anmelden'),
-            ),
-          ],
-        ));
-  }
-
-  void login(context) async {
-    if (_formKey.currentState.validate()) {
-      _formKey.currentState.save();
-      //@ToDo: store in database
-    }
-  }
-}
-
-class Role {
-  String priority;
-  String name;
-}
\ No newline at end of file
diff --git a/lib/src/tool/db_tool.dart b/lib/src/tool/db_tool.dart
new file mode 100644 (file)
index 0000000..984ac4b
--- /dev/null
@@ -0,0 +1,79 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:test/test.dart';
+
+import '../model/standard/role_model.dart';
+import '../model/standard/user_model.dart';
+
+void dummy() {
+  group('xxx', () {
+    test('y', () {
+      Widget text = Text('');
+      expect(text, isNotNull);
+    });
+  });
+}
+
+void main(List<String> argv) async {
+  final logger = MemoryLogger(LEVEL_FINE);
+  if (argv.length > 0xffff) {
+    dummy();
+  }
+  final dbHelper = DbHelper(logger);
+  if (argv.length == 0) {
+    logger.error("missing arguments");
+  } else {
+    final mode = argv[0];
+    argv.removeAt(0);
+    switch (mode) {
+      case 'create-sql':
+        dbHelper.exportSql(argv);
+        break;
+      default:
+        logger.error('unknown mode: ${argv[0]}');
+        break;
+    }
+  }
+  //await tool.main();
+}
+
+class DbHelper {
+  final BaseLogger logger;
+
+  DbHelper(this.logger);
+
+  void exportSql(List<String> argv) {
+    if (argv.length == 0) {
+      logger.error('missing table');
+    } else {
+      final table = argv[0];
+      argv.removeAt(0);
+      ModuleModel module;
+      switch (table) {
+        case 'user':
+          module = UserModel(logger);
+          break;
+        case 'role':
+          module = RoleModel(logger);
+          break;
+        default:
+          logger.error('unknown table');
+          break;
+      }
+      final filename = argv.length == 0 ? '$table.sql' : argv[0];
+      FileSync.toFile(filename, module.exportSql());
+      print('exported: $filename');
+    }
+  }
+
+  void usage(String error) {
+    print('''usage: dbhelper <mode> [<args>]
+<mode>:
+  create-sql <module> [<output-file>]
+    <module>: 'role', 'user' ...
+Examples:
+dbhelper create-sql role role.sql
+''');
+  }
+}
diff --git a/lib/src/widget/list_form.dart b/lib/src/widget/list_form.dart
new file mode 100644 (file)
index 0000000..424868f
--- /dev/null
@@ -0,0 +1,99 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+import '../helper/filters.dart';
+
+typedef Function OnEditTap(Map<String, dynamic> row, int index);
+
+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(String titles) {
+    final rc = titles
+        .substring(1)
+        .split(titles[0])
+        .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 first column.
+  static Widget table({
+    @required List<Widget> titles,
+    @required List<String> columnNames,
+    @required List<Map<String, dynamic>> rows,
+    bool showEditIcon = false,
+    OnEditTap onEditTap,
+  }) {
+    Widget rc = Container(
+        margin: EdgeInsets.all(2),
+        child: DataTable(
+            showCheckboxColumn: true,
+            showBottomBorder: true,
+            sortColumnIndex: 1,
+            columns: titles.map((item) => DataColumn(label: item)).toList(),
+            rows: rows.map((row) {
+              final cells = <DataCell>[];
+              int ix = -1;
+              for (var key in columnNames) {
+                ix++;
+                cells.add(DataCell(
+                  Text(row[key]),
+                  showEditIcon: showEditIcon && ix == 0,
+                  onTap: () => onEditTap == null ? null : onEditTap(row, ix),
+                ));
+              }
+              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.
+  static Form listForm(
+      {@required Key key,
+      @required Filters filters,
+      @required List<Widget> buttons,
+      @required List<Widget> titles,
+      @required List<String> columnNames,
+      @required List<Map<String, dynamic>> rows,
+      bool showEditIcon = false,
+      OnEditTap onEditTap,
+      @required BaseConfiguration configuration}) {
+    final padding =
+        configuration.asFloat('form.card.padding', defaultValue: 16.0);
+    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>[
+                ...filters.getWidgets(),
+                SizedBox(
+                    height: configuration.asFloat(
+                        'form.gap.field_button.height',
+                        defaultValue: 16.0)),
+                ...buttons,
+                table(
+                    titles: titles,
+                    columnNames: columnNames,
+                    rows: rows,
+                    showEditIcon: showEditIcon,
+                    onEditTap: onEditTap),
+              ])),
+        ));
+  }
+}
index ec23fb6ffd95032207177b741abc62914145a9bd..b57b2a968da74021d241f254843fee5de47dd1e8 100644 (file)
@@ -21,7 +21,7 @@ class View {
   factory View(BaseLogger logger) {
     if (_instance == null) {
       _instance = View.internal(logger);
-      _instance.settings = Settings(logger);
+      _instance.settings = Settings(logger: logger);
       _instance.widgetConfiguration = _instance.settings.widgetConfiguration;
     }
     return _instance;
@@ -166,6 +166,7 @@ class View {
           break;
         case WidgetModelType.table:
         case WidgetModelType.column:
+        case WidgetModelType.allDbFields:
           logger.error('not allowed in section: ${child.fullName()}');
           break;
       }
diff --git a/test/tool/tool_test.dart b/test/tool/tool_test.dart
new file mode 100644 (file)
index 0000000..506f74a
--- /dev/null
@@ -0,0 +1,26 @@
+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('sql', () {
+    test('export-sql', () {
+      logger.clear();
+      final module = RoleModel(logger);
+      module.parse();
+      final content = module.exportSql();
+      final errors = logger.errors;
+      expect(errors.length, equals(0));
+      expect(content, equals('''drop table if exists role;
+create table role (
+  role_id int(10) primary;
+  role_name varchar(255) unique notnull;
+  role_priority int(10);
+  role_created timestamp null;
+  role_changed timestamp null;
+);
+'''));
+    });
+  });
+}