]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
listForm() and createForm()
authorHamatoma <author@hamatoma.de>
Sat, 10 Oct 2020 10:58:53 +0000 (12:58 +0200)
committerHamatoma <author@hamatoma.de>
Sat, 10 Oct 2020 22:17:05 +0000 (00:17 +0200)
* listForm() works in in role_list_page
* createForm() works in role_create_page
* changeForm() works in role_change_page

31 files changed:
.gitignore
lib/app.dart
lib/flutter_bones.dart
lib/src/helper/filters.dart [deleted file]
lib/src/model/button_model.dart
lib/src/model/column_model.dart
lib/src/model/field_model.dart
lib/src/model/model_types.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/standard/role_model.dart
lib/src/model/text_field_model.dart
lib/src/page/role/role_change_page.dart [new file with mode: 0644]
lib/src/page/role/role_controller.dart [new file with mode: 0644]
lib/src/page/role/role_create_page.dart [new file with mode: 0644]
lib/src/page/role/role_list_page.dart [new file with mode: 0644]
lib/src/page/role_create_page.dart [deleted file]
lib/src/page/role_list_page.dart [deleted file]
lib/src/widget/edit_form.dart [new file with mode: 0644]
lib/src/widget/filters.dart [new file with mode: 0644]
lib/src/widget/list_form.dart
lib/src/widget/module_controller.dart [new file with mode: 0644]
lib/src/widget/raised_button_bone.dart
lib/src/widget/simple_form.dart
lib/src/widget/text_form_field_bone.dart [new file with mode: 0644]
lib/src/widget/view.dart
lib/src/widget/widget_helper.dart [new file with mode: 0644]
lib/src/widget/widget_list.dart [new file with mode: 0644]
test/widget/widget_test.dart [new file with mode: 0644]
test/widget_test.dart

index 77fcaacafac68cf6861fd06a00389ce4a629954e..49bef3d199b8fd97a96a9e04410b306490270985 100644 (file)
@@ -15,6 +15,7 @@ pubspec.lock
 android/
 ios/
 web/
+linux/
 
 # IntelliJ related
 *.iml
@@ -46,3 +47,4 @@ app.*.symbols
 
 # Obfuscation related
 app.*.map.json
+/linux/
index f345958686f29d029934678ee35edad0e43ca7bd..3f40034d107ad30b75df471704a2ac90670e364e 100644 (file)
@@ -1,8 +1,12 @@
 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';
+
+import 'src/helper/settings.dart';
+import 'src/page/login_page.dart';
+import 'src/page/role/role_change_page.dart';
+import 'src/page/role/role_create_page.dart';
+import 'src/page/role/role_list_page.dart';
+import 'src/private/bsettings.dart';
 
 class BoneApp extends StatefulWidget {
   @override
@@ -20,7 +24,6 @@ class BoneApp extends StatefulWidget {
 }
 
 class BoneAppState extends State<BoneApp> {
-
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
@@ -29,26 +32,33 @@ class BoneAppState extends State<BoneApp> {
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
-      initialRoute: '/role/list',
+      initialRoute: '/role/change',
       onGenerateRoute: _getRoute,
     );
   }
-
 }
 
 Route<dynamic> _getRoute(RouteSettings settings) {
   MaterialPageRoute route;
-  if (settings.name == '/role/list') {
-    route = MaterialPageRoute<void>(
-      settings: settings,
-      builder: (BuildContext context) =>
-          RoleListPage(BSettings.instance.pageData),
-      fullscreenDialog: false,
-    );
-  } else {
+  StatefulWidget page;
+  switch (settings.name) {
+    case '/role/list':
+      page = RoleListPage(BSettings.instance.pageData);
+      break;
+    case '/role/change':
+      page = RoleChangePage(BSettings.instance.pageData);
+      break;
+    case '/role/create':
+      page = RoleCreatePage(BSettings.instance.pageData);
+      break;
+    default:
+      page = LoginPage(BSettings.instance.pageData);
+      break;
+  }
+  if (page != null) {
     route = MaterialPageRoute<void>(
       settings: settings,
-      builder: (BuildContext context) => LoginPage(BSettings.instance.pageData),
+      builder: (BuildContext context) => page,
       fullscreenDialog: false,
     );
   }
index cf34b7d7a28e40e540b9b4550ef7294921924852..0d8fa51acfc7625634985437dbb183e42d4678af 100644 (file)
@@ -22,8 +22,9 @@ 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_create_page.dart';
+export 'src/page/role/role_create_page.dart';
 export 'src/page/user_page.dart';
 export 'src/widget/raised_button_bone.dart';
 export 'src/widget/simple_form.dart';
+export 'src/widget/text_form_field_bone.dart';
 export 'src/widget/view.dart';
diff --git a/lib/src/helper/filters.dart b/lib/src/helper/filters.dart
deleted file mode 100644 (file)
index c31d21a..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-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 3fba73b8e2e486211eae53cc21e4a2a09228aa9e..eb5721c9a700b971d9546b82068fbf12da70b415 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 {
+class ButtonModel extends WidgetModel implements ButtonCallbackController {
   static final regExprOptions = RegExp(r'^(undef)$');
   String text;
   String name;
@@ -48,6 +48,22 @@ class ButtonModel extends WidgetModel {
 
   @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 92f521f204302b4d7382c2275d303e8aebd87e71..f9a3467bed01bfcaf6ae30e199d9d9e6f2c5c4c8 100644 (file)
@@ -34,6 +34,12 @@ class ColumnModel extends WidgetModel {
   @override
   String fullName() => '${table.name}.$name';
 
+  /// Tests whether a given [option] is part of [options].
+  bool hasOption(String option) {
+    bool rc = options.contains(option);
+    return rc;
+  }
+
   /// Parses the map and stores the data in the instance.
   void parse() {
     name = parseString('name', map, required: true);
index 1d205f5ceef297d458f1d4d83af4858143bf3bb9..4ddc9e0dc069704636fc9fbb44270e57f45890ac 100644 (file)
@@ -41,6 +41,12 @@ abstract class FieldModel extends WidgetModel {
   @override
   String fullName() => '${page.name}.$name';
 
+  /// Tests whether a given [option] is part of [options].
+  bool hasOption(String option) {
+    bool rc = options.contains(option);
+    return rc;
+  }
+
   /// Parses the map and stores the data in the instance.
   void parse() {
     name = parseString('name', map, required: true);
index 16667a98f8576cf40c67273e19d53bd652a8d27d..292a9ecdc9451291ee69aa826533a8ea641947b5 100644 (file)
@@ -1,4 +1,18 @@
 // on change: adapt StringHelper.fromString()
 enum DataType {
-  bool, currency, date, dateTime, float, int, reference, string,
+  bool,
+  currency,
+  date,
+  dateTime,
+  float,
+  int,
+  reference,
+  string,
+}
+enum FilterType {
+  dateFrom,
+  dateTil,
+  dateTimeFrom,
+  dateTimeTil,
+  pattern,
 }
index d5fbd23c3f8e6bbdd108327d925e6ee3c008ddd4..7763206ea2c22c4ccdd6ce557c32010a0fd79d37 100644 (file)
@@ -128,6 +128,13 @@ class ModuleModel extends ModelBase {
     return rc;
   }
 
+  /// Returns the main table of the module.
+  /// This is the first defined table.
+  TableModel mainTable() {
+    TableModel rc = tables.length == 0 ? null : tables[0];
+    return rc;
+  }
+
   /// Returns a child page given by [name], null otherwise.
   PageModel pageByName(String name) {
     final found = pages.firstWhere((element) => element.name == name);
index 3a8d65898cb44a707747e4e88af59b72ff3d444e..01d5f6ecbe8afde5b3646a9c4bce84184629fb0a 100644 (file)
@@ -152,5 +152,6 @@ enum PageModelType {
   change,
   create,
   delete,
+  list,
   overview,
 }
index b35f1c58b4bcc5ffb7fab7248ee987c2b4b79b77..cce657ee605302f31ca75275410831557bbe62c2 100644 (file)
@@ -53,6 +53,7 @@ class SectionModel extends WidgetModel {
     checkSuperfluousAttributes(
         map, 'children fields name options sectionType widgetType'.split(' '));
     options = parseOptions('options', map);
+
     checkOptionsByRegExpr(options, regExprOptions);
     if (!map.containsKey('children')) {
       logger.error('missing children in ${fullName()}');
@@ -162,6 +163,8 @@ class SectionModel extends WidgetModel {
 }
 
 enum SectionModelType {
+  changeForm,
+  createForm,
   simpleForm,
-  query,
+  filterPanel,
 }
index a1ded3404fde203d60f3eb277a04cc4e57d00822..bc6fec5499467327934b3b55ad94e8526af0fba2 100644 (file)
@@ -2,7 +2,7 @@ import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 
 class RoleModel extends ModuleModel {
-  static final model = <String, dynamic>{
+  static final yamlMap = <String, dynamic>{
     "module": "role",
     "tables": [
       {
@@ -78,8 +78,9 @@ class RoleModel extends ModuleModel {
             "sectionType": "filterPanel",
             "children": [
               {
-                "widgetType": "textfield",
-                "searchMode": "pattern",
+                "widgetType": "textField",
+                "filterType": "pattern",
+                "name": "role_name",
               }
             ]
           }
@@ -88,7 +89,7 @@ class RoleModel extends ModuleModel {
     ],
   };
 
-  RoleModel(BaseLogger logger) : super(model, logger);
+  RoleModel(BaseLogger logger) : super(yamlMap, logger);
 
   /// Returns the name including the names of the parent
   @override
index 0cae1dde31ed03a4a0936aa24942ce83e3a975d2..2360c7958bd429c557fde17fdb325565d9ddcf20 100644 (file)
@@ -20,6 +20,7 @@ class TextFieldModel extends FieldModel {
   FormFieldSetter onSaved;
 
   final Map<String, dynamic> map;
+  FilterType filterType;
 
   TextFieldModel(
       SectionModel section, PageModel page, this.map, BaseLogger logger)
@@ -37,10 +38,11 @@ class TextFieldModel extends FieldModel {
     super.parse();
     checkSuperfluousAttributes(
         map,
-        'dataType label maxSize name options rows toolTip value widgetType'
+        'dataType filterType label maxSize name options rows toolTip value widgetType'
             .split(' '));
     maxSize = parseInt('maxSize', map, required: false);
     rows = parseInt('rows', map, required: false);
+    filterType = parseEnum<FilterType>('filterType', map, FilterType.values);
     switch (dataType) {
       case DataType.int:
       case DataType.reference:
diff --git a/lib/src/page/role/role_change_page.dart b/lib/src/page/role/role_change_page.dart
new file mode 100644 (file)
index 0000000..4aa6b23
--- /dev/null
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../../widget/edit_form.dart';
+import 'role_controller.dart';
+
+class RoleChangePage extends StatefulWidget {
+  final PageData pageData;
+  final logger = Settings().logger;
+  RoleChangePageState lastState;
+
+  RoleChangePage(this.pageData, {Key key}) : super(key: key);
+
+  @override
+  RoleChangePageState createState() {
+    final rc = lastState = RoleChangePageState(pageData);
+    return rc;
+  }
+}
+
+class RoleChangePageState extends State<RoleChangePage> {
+  final PageData pageData;
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+
+  RoleController controller;
+
+  RoleChangePageState(this.pageData);
+
+  @override
+  Widget build(BuildContext context) {
+    controller = controller ?? RoleController(_formKey, this);
+    return Scaffold(
+        appBar: pageData.appBarBuilder('Rolle ändern'),
+        drawer: pageData.drawerBuilder(context),
+        body: EditForm.editForm(
+          key: _formKey,
+          isCreateForm: false,
+          moduleController: controller,
+          configuration: pageData.configuration,
+        ));
+  }
+}
diff --git a/lib/src/page/role/role_controller.dart b/lib/src/page/role/role_controller.dart
new file mode 100644 (file)
index 0000000..76b27c3
--- /dev/null
@@ -0,0 +1,12 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../../model/standard/role_model.dart';
+import '../../widget/module_controller.dart';
+
+class RoleController extends ModuleController {
+  RoleController(GlobalKey<FormState> formKey, State<StatefulWidget> parent)
+      : super(formKey, parent, RoleModel(Settings().logger)) {
+    moduleModel.parse();
+  }
+}
diff --git a/lib/src/page/role/role_create_page.dart b/lib/src/page/role/role_create_page.dart
new file mode 100644 (file)
index 0000000..85902e8
--- /dev/null
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../../widget/edit_form.dart';
+import 'role_controller.dart';
+
+class RoleCreatePage extends StatefulWidget {
+  final PageData pageData;
+  final logger = Settings().logger;
+  RoleCreatePageState lastState;
+
+  RoleCreatePage(this.pageData, {Key key}) : super(key: key);
+
+  @override
+  RoleCreatePageState createState() {
+    final rc = lastState = RoleCreatePageState(pageData);
+    return rc;
+  }
+}
+
+class RoleCreatePageState extends State<RoleCreatePage> {
+  final PageData pageData;
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+
+  RoleController controller;
+
+  RoleCreatePageState(this.pageData);
+
+  @override
+  Widget build(BuildContext context) {
+    controller = controller ?? RoleController(_formKey, this);
+    return Scaffold(
+        appBar: pageData.appBarBuilder('Neue Rolle'),
+        drawer: pageData.drawerBuilder(context),
+        body: EditForm.editForm(
+          key: _formKey,
+          isCreateForm: true,
+          moduleController: controller,
+          configuration: pageData.configuration,
+        ));
+  }
+}
diff --git a/lib/src/page/role/role_list_page.dart b/lib/src/page/role/role_list_page.dart
new file mode 100644 (file)
index 0000000..f8aa522
--- /dev/null
@@ -0,0 +1,102 @@
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/model_types.dart';
+import '../../widget/filters.dart';
+import '../../widget/list_form.dart';
+import '../../widget/raised_button_bone.dart';
+import '../page_data.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;
+  }
+
+  Filters filters;
+
+  @override
+  Widget build(BuildContext context) {
+    final logger = Settings().logger;
+    if (filters == null) {
+      filters = Filters.ready(
+          _formKey,
+          this,
+          [
+            FilterItem(
+                name: 'role_name',
+                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>[
+          RaisedButtonBone(
+            'search', filters,
+            child: Text('Suchen'),
+          ),
+        ],
+        filters: filters,
+      ),
+    );
+  }
+}
+
+class Role {
+  int id;
+  String priority;
+  String name;
+}
diff --git a/lib/src/page/role_create_page.dart b/lib/src/page/role_create_page.dart
deleted file mode 100644 (file)
index cde36c7..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-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
deleted file mode 100644 (file)
index bbbb26d..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-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/widget/edit_form.dart b/lib/src/widget/edit_form.dart
new file mode 100644 (file)
index 0000000..d33ddbd
--- /dev/null
@@ -0,0 +1,57 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+import 'module_controller.dart';
+import 'raised_button_bone.dart';
+
+typedef Function OnEditTap(Map<String, dynamic> row, int index);
+
+/// Contains helper functions for creating/changing data of a model based module.
+class EditForm {
+  /// Returns a widget with a form containing at least some input fields
+  /// and a save/cancel button.
+  /// [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 editForm({
+    @required Key key,
+    @required bool isCreateForm,
+    @required ModuleController moduleController,
+    @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>[
+                  ...moduleController.getWidgets(isCreateForm),
+                  SizedBox(
+                      height: configuration.asFloat(
+                          'form.gap.field_button.height',
+                          defaultValue: 16.0)),
+                  ButtonBar(
+                    children: <Widget>[
+                      FlatButton(
+                        child: Text('Abbruch'),
+                        onPressed: moduleController.getOnPressed(
+                            'cancel', moduleController),
+                      ),
+                      RaisedButtonBone(
+                        'store',
+                        moduleController,
+                        child: Text('Speichern'),
+                      ),
+                    ],
+                  ),
+                ],
+              ))),
+    );
+  }
+}
diff --git a/lib/src/widget/filters.dart b/lib/src/widget/filters.dart
new file mode 100644 (file)
index 0000000..fbdb7dd
--- /dev/null
@@ -0,0 +1,172 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/widget/list_form.dart';
+
+import 'text_form_field_bone.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.startsWith('*')
+                  ? current.contains(value2.replaceAll('*', ''))
+                  : 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
+    implements
+        TextCallbackController,
+        ButtonCallbackController,
+        TableCallbackController {
+  var filters = <FilterItem>[];
+  final BaseLogger logger;
+  final GlobalKey<FormState> globalKey;
+
+  State<StatefulWidget> parent;
+
+  Filters(this.globalKey, this.parent, this.logger);
+
+  Filters.ready(this.globalKey, this.parent, this.filters, this.logger);
+
+  void add(FilterItem item) => filters.add(item);
+
+  FilterItem byName(String name) =>
+      filters.firstWhere((element) => element.name == name);
+
+  @override
+  getOnChanged(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnEditingComplete(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnEditTap(String customString, TableCallbackController controller,
+      Map<String, dynamic> row) {
+    return null;
+  }
+
+  @override
+  getOnFieldSubmitted(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnHighlightChanged(String customString,
+      ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnLongPressed(String customString, ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnPressed(String customString, ButtonCallbackController controller) {
+    var rc;
+    if (customString == 'search') {
+      rc = () {
+        if (globalKey.currentState.validate()) {
+          globalKey.currentState.save();
+          parent.setState(() => null);
+        }
+      };
+    }
+    return rc;
+  }
+
+  @override
+  getOnSaved(String customString, TextCallbackController controller) {
+    return (input) {
+      byName(customString).value = input;
+    };
+  }
+
+  @override
+  getOnTap(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getValidator(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  /// Returns a list of widgets for the filter fields.
+  List<Widget> getWidgets() {
+    final rc = filters.map((filter) {
+      Widget rc = TextFormFieldBone(
+        filter.name,
+        this,
+        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;
+  }
+}
index 424868f2df1c614c1cd3770446c7acd47425ba10..d3db052ba1d6830113773905679c38cf32cfbfa6 100644 (file)
@@ -1,10 +1,15 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
 
-import '../helper/filters.dart';
+import 'filters.dart';
 
 typedef Function OnEditTap(Map<String, dynamic> row, int index);
 
+abstract class TableCallbackController {
+  OnEditTap getOnEditTap(String customString,
+      TableCallbackController controller, Map<String, dynamic> row);
+}
+
 class ListForm {
   /// Converts a string with [titles] into a list of widgets of type Text().
   /// Format of [titles]: first character is the delimiter, e.g. ";Id;Name;"
@@ -25,30 +30,41 @@ class ListForm {
   /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
   /// [rows] is a list of rows normally delivered from a database query:
   /// each row is a map with (key, value) pairs.
-  /// If [showEditItems] is true the edit icon is shown in the first column.
+  /// If [showEditItems] is true the edit icon is shown in the additional first column.
+  /// If [sortIndex] is not null the rows will be sorted by this index.
   static Widget table({
     @required List<Widget> titles,
     @required List<String> columnNames,
     @required List<Map<String, dynamic>> rows,
     bool showEditIcon = false,
-    OnEditTap onEditTap,
+    int sortIndex,
+    TableCallbackController tableCallbackController,
+    String customString,
   }) {
+    assert(titles.length == columnNames.length);
+    final titles2 = titles.map((item) => DataColumn(label: item)).toList();
+    if (showEditIcon) {
+      titles2.insert(0, DataColumn(label: SizedBox(width: 1)));
+      sortIndex = sortIndex == null ? null : sortIndex + 1;
+    }
     Widget rc = Container(
         margin: EdgeInsets.all(2),
         child: DataTable(
             showCheckboxColumn: true,
             showBottomBorder: true,
-            sortColumnIndex: 1,
-            columns: titles.map((item) => DataColumn(label: item)).toList(),
+            sortColumnIndex: sortIndex,
+            columns: titles2,
             rows: rows.map((row) {
               final cells = <DataCell>[];
-              int ix = -1;
+              if (showEditIcon) {
+                cells.add(DataCell(SizedBox(width: 1), showEditIcon: true));
+              }
               for (var key in columnNames) {
-                ix++;
                 cells.add(DataCell(
                   Text(row[key]),
-                  showEditIcon: showEditIcon && ix == 0,
-                  onTap: () => onEditTap == null ? null : onEditTap(row, ix),
+                  onTap: () =>
+                      tableCallbackController.getOnEditTap(
+                          customString, tableCallbackController, row),
                 ));
               }
               return DataRow(cells: cells);
@@ -62,18 +78,18 @@ class ListForm {
   /// [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}) {
+  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,
+    TableCallbackController tableCallbackController,
+    @required BaseConfiguration configuration,
+    String customString}) {
     final padding =
-        configuration.asFloat('form.card.padding', defaultValue: 16.0);
+    configuration.asFloat('form.card.padding', defaultValue: 16.0);
     return Form(
         key: key,
         child: Card(
@@ -88,11 +104,13 @@ class ListForm {
                         defaultValue: 16.0)),
                 ...buttons,
                 table(
-                    titles: titles,
-                    columnNames: columnNames,
-                    rows: rows,
-                    showEditIcon: showEditIcon,
-                    onEditTap: onEditTap),
+                  titles: titles,
+                  columnNames: columnNames,
+                  rows: rows,
+                  showEditIcon: showEditIcon,
+                  tableCallbackController: tableCallbackController,
+                  customString: customString,
+                )
               ])),
         ));
   }
diff --git a/lib/src/widget/module_controller.dart b/lib/src/widget/module_controller.dart
new file mode 100644 (file)
index 0000000..49c10c8
--- /dev/null
@@ -0,0 +1,144 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/widget/widget_list.dart';
+
+import '../model/model_types.dart';
+import '../model/module_model.dart';
+import '../model/table_model.dart';
+import 'raised_button_bone.dart';
+import 'text_form_field_bone.dart';
+import 'widget_helper.dart';
+
+// This interface allows the generic handling of the edit form by a model driven module.
+class ModuleController
+    implements TextCallbackController, ButtonCallbackController {
+  final ModuleModel moduleModel;
+  String primaryColumn;
+  WidgetList createWidgets;
+  WidgetList changeWidgets;
+  final modelsMap = <String, ModelBase>{};
+  final dataTypes = <String, DataType>{};
+  final Map values = <String, dynamic>{};
+  final GlobalKey<FormState> globalKey;
+
+  State parent;
+
+  ModuleController(this.globalKey, this.parent, this.moduleModel) {
+    createWidgets = WidgetList(
+        '${moduleModel.fullName()}.createWidgets', moduleModel.logger);
+    changeWidgets = WidgetList(
+        '${moduleModel.fullName()}.changeWidgets', moduleModel.logger);
+  }
+
+  ModuleModel getModuleModel() => moduleModel;
+
+  @override
+  getOnChanged(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnEditingComplete(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnFieldSubmitted(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnHighlightChanged(
+      String customString, ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnLongPressed(String customString, ButtonCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getOnPressed(String customString, ButtonCallbackController controller) {
+    var rc;
+    if (customString == 'store') {
+      rc = () {
+        if (globalKey.currentState.validate()) {
+          globalKey.currentState.save();
+          moduleModel.logger.log('missing storage in onPressed');
+        }
+      };
+    }
+    return rc;
+  }
+
+  @override
+  getOnSaved(String customString, TextCallbackController controller) {
+    final rc = (input) {
+      values[customString] =
+          StringHelper.fromString(input, dataTypes[customString]);
+    };
+    return rc;
+  }
+
+  @override
+  getOnTap(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  @override
+  getValidator(String customString, TextCallbackController controller) {
+    return null;
+  }
+
+  /// Returns the widgets with at least the input fields of the form
+  ///
+  List<Widget> getWidgets(bool isCreateForm) {
+    List<Widget> rc;
+    if (isCreateForm) {
+      if (createWidgets == null || createWidgets.widgets.length == 0) {
+        modelToWidgets(moduleModel.mainTable(), this, createWidgets);
+      }
+      rc = createWidgets.widgets;
+    } else {
+      if (changeWidgets.widgets.length == 0) {
+        modelToWidgets(moduleModel.mainTable(), this, changeWidgets);
+      }
+      rc = changeWidgets.widgets;
+    }
+    return rc;
+  }
+
+  /// Reads the [tableModel] and creates the [widgetList] with all relevant
+  /// input fields.
+  void modelToWidgets(TableModel tableModel, ModuleController controller,
+      WidgetList widgetList) {
+    for (var column in tableModel.columns) {
+      if (!column.hasOption('hidden')) {
+        if (column.hasOption('primary')) {
+          primaryColumn = column.name;
+        }
+        Widget widget;
+        modelsMap[column.name] = column;
+        dataTypes[column.name] = column.dataType;
+        switch (column.dataType) {
+          case DataType.bool:
+            widget = null;
+            break;
+          default:
+            widget = WidgetHelper.toolTip(
+                TextFormFieldBone(
+                  column.name,
+                  controller,
+                  decoration: InputDecoration(labelText: column.label),
+                ),
+                column.toolTip);
+            break;
+        }
+        if (widget != null) {
+          widgetList.addWidget(column.name, widget);
+        }
+      }
+    }
+  }
+}
index 1506fb41aa213fe581ff1fa475069b47bf6b5b75..402c61174e51c2be4cbc382c7c150840473f056b 100644 (file)
@@ -1,12 +1,26 @@
 import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
 
+/// Interface for a button specific callback controller.
+abstract class ButtonCallbackController {
+  ValueChanged<bool> getOnHighlightChanged(
+      String customString, ButtonCallbackController controller);
+
+  VoidCallback getOnLongPressed(
+      String customString, ButtonCallbackController controller);
+
+  VoidCallback getOnPressed(
+      String customString, ButtonCallbackController controller);
+}
+
+/// Implements a raised button with two additional properties:
+/// [customString] a string often used for a name needed in a callback method
+/// [customObject] an object known by the controller, often used in callback methods like onPressed
 class RaisedButtonBone extends RaisedButton {
-  final ButtonModel model;
+  final String customString;
+  final ButtonCallbackController callbackController;
 
-  RaisedButtonBone(this.model,
-      {ValueChanged<bool> onHighlightChanged,
-      ButtonTextTheme textTheme,
+  RaisedButtonBone(this.customString, this.callbackController,
+      {ButtonTextTheme textTheme,
       Color textColor,
       Color disabledTextColor,
       Color color,
@@ -31,19 +45,22 @@ class RaisedButtonBone extends RaisedButton {
       Duration animationDuration,
       Widget child})
       : super(
-            onPressed: model.onPressed,
-            onLongPress: model.onLongPressed,
-            onHighlightChanged: model.onHighlightChanged,
-            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 0b6a712658b0c2d6031c8ff750b9a6e7793ddc7a..e2d6252f802fa9126ab616cafdd368ec573f3ac5 100644 (file)
@@ -2,22 +2,27 @@ import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
 
 class SimpleForm {
-  static Form simpleForm({@required Key key, @required List<Widget> fields,
-    @required List<Widget> buttons, @required BaseConfiguration configuration} ) {
-    final padding = configuration.asFloat('form.card.padding', defaultValue: 16.0);
+  static Form simpleForm(
+      {@required Key key,
+      @required List<Widget> fields,
+      @required List<Widget> buttons,
+      @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>[...fields,
-        SizedBox(height: configuration.asFloat('form.gap.field_button.height', defaultValue: 16.0)),
-        ...buttons]
-    )),
-    ));
+          margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+          child: Padding(
+              padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
+              child: ListView(children: <Widget>[
+                ...fields,
+                SizedBox(
+                    height: configuration.asFloat(
+                        'form.gap.field_button.height',
+                        defaultValue: 16.0)),
+                ...buttons
+              ])),
+        ));
   }
-}
\ No newline at end of file
+}
diff --git a/lib/src/widget/text_form_field_bone.dart b/lib/src/widget/text_form_field_bone.dart
new file mode 100644 (file)
index 0000000..e8c01de
--- /dev/null
@@ -0,0 +1,129 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+/// Interface for a [TextFormField] specific callback controller.
+abstract class TextCallbackController {
+  ValueChanged<String> getOnChanged(
+      String customString, TextCallbackController controller);
+
+  VoidCallback getOnEditingComplete(
+      String customString, TextCallbackController controller);
+
+  ValueChanged<String> getOnFieldSubmitted(
+      String customString, TextCallbackController controller);
+
+  FormFieldSetter<String> getOnSaved(
+      String customString, TextCallbackController controller);
+
+  GestureTapCallback getOnTap(
+      String customString, TextCallbackController controller);
+
+  FormFieldValidator<String> getValidator(
+      String customString, TextCallbackController controller);
+}
+
+/// Implements a [TextFormField] with "outsourced" callbacks:
+/// [customString] a string mostly used for a name needed in the [customController]
+/// [callbackController] handles the callback methods.
+class TextFormFieldBone extends TextFormField {
+  final String customString;
+  final TextCallbackController 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,
+    bool showCursor,
+    String obscuringCharacter = '•',
+    bool obscureText = false,
+    bool autocorrect = true,
+    SmartDashesType smartDashesType,
+    SmartQuotesType smartQuotesType,
+    bool enableSuggestions = true,
+    bool maxLengthEnforced = true,
+    int maxLines = 1,
+    int minLines,
+    bool expands = false,
+    int maxLength,
+    List<TextInputFormatter> inputFormatters,
+    bool enabled,
+    double cursorWidth = 2.0,
+    double cursorHeight,
+    Radius cursorRadius,
+    Color cursorColor,
+    Brightness keyboardAppearance,
+    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
+    bool enableInteractiveSelection = true,
+    InputCounterWidgetBuilder buildCounter,
+    ScrollPhysics scrollPhysics,
+    Iterable<String> autofillHints,
+    AutovalidateMode autovalidateMode,
+  }) : super(
+          controller: controller,
+          initialValue: initialValue,
+          focusNode: focusNode,
+          decoration: decoration,
+          keyboardType: keyboardType,
+          textCapitalization: textCapitalization,
+          textInputAction: textInputAction,
+          style: style,
+          strutStyle: strutStyle,
+          textDirection: textDirection,
+          textAlign: textAlign,
+          textAlignVertical: textAlignVertical,
+          autofocus: autofocus,
+          readOnly: readOnly,
+          toolbarOptions: toolbarOptions,
+          showCursor: showCursor,
+          obscuringCharacter: obscuringCharacter,
+          obscureText: obscureText,
+          autocorrect: autocorrect,
+          smartDashesType: smartDashesType,
+          smartQuotesType: smartQuotesType,
+          enableSuggestions: enableSuggestions,
+          maxLengthEnforced: maxLengthEnforced,
+          maxLines: maxLines,
+          minLines: minLines,
+          expands: expands,
+          maxLength: maxLength,
+          onChanged:
+              callbackController.getOnChanged(customString, callbackController),
+          onTap: callbackController.getOnTap(customString, callbackController),
+          onEditingComplete: callbackController.getOnEditingComplete(
+              customString, callbackController),
+          onFieldSubmitted: callbackController.getOnFieldSubmitted(
+              customString, callbackController),
+          onSaved:
+              callbackController.getOnSaved(customString, callbackController),
+          validator:
+              callbackController.getValidator(customString, callbackController),
+          inputFormatters: inputFormatters,
+          enabled: enabled,
+          cursorWidth: cursorWidth,
+          cursorHeight: cursorHeight,
+          cursorRadius: cursorRadius,
+          cursorColor: cursorColor,
+          keyboardAppearance: keyboardAppearance,
+          scrollPadding: scrollPadding,
+          enableInteractiveSelection: enableInteractiveSelection,
+          buildCounter: buildCounter,
+          scrollPhysics: scrollPhysics,
+          autofillHints: autofillHints,
+          autovalidateMode: autovalidateMode,
+        );
+}
index b57b2a968da74021d241f254843fee5de47dd1e8..d820d8fe2aa57b762578c0069bd81c62e5984356 100644 (file)
@@ -32,6 +32,7 @@ class View {
   /// Creates a button from the [model].
   Widget button(ButtonModel model) {
     final rc = RaisedButtonBone(
+      model.widgetName(),
       model,
       child: Text(model.text),
     );
diff --git a/lib/src/widget/widget_helper.dart b/lib/src/widget/widget_helper.dart
new file mode 100644 (file)
index 0000000..64e1aac
--- /dev/null
@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+
+/// Some little static helper methods round about [Widgets].
+class WidgetHelper {
+  /// If a [toolTip] is not empty the [widget] is completed with that.
+  /// Otherwise [widget] is returned.
+  static Widget toolTip(Widget widget, String toolTip) {
+    Widget rc;
+    if (toolTip == null || toolTip.isEmpty) {
+      rc = widget;
+    } else {
+      rc = Tooltip(message: toolTip, child: widget);
+    }
+    return rc;
+  }
+}
diff --git a/lib/src/widget/widget_list.dart b/lib/src/widget/widget_list.dart
new file mode 100644 (file)
index 0000000..9ba2514
--- /dev/null
@@ -0,0 +1,48 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+/// Manages a list of named widgets.
+class WidgetList {
+  final String name;
+  final BaseLogger logger;
+  final widgetMap = <String, Widget>{};
+  final widgets = <Widget>[];
+
+  WidgetList(this.name, this.logger);
+
+  /// Adds a [widget] to the [widgets] behind the position of [predecessor].
+  /// If [predecessor] is null the widget will be the first in [widgets]
+  void addSuccessorOf(String predecessor, String name, Widget widget) {
+    if (predecessor == null) {
+      widgets.insert(0, widget);
+    } else {
+      if (!widgetMap.containsKey(predecessor)) {
+        logger
+            .error('$name.addSuccessorOf(): missing predecessor $predecessor');
+      } else {
+        final ix = widgets.indexOf(widgetMap[predecessor]);
+        widgets.insert(ix, widget);
+      }
+    }
+    addWidget(name, widget);
+  }
+
+  /// Adds a widget to the widget list.
+  void addWidget(String name, Widget widget) {
+    if (widgetMap.containsKey(name)) {
+      logger.error('$name.addWidget(): widget $name already defined');
+    } else {
+      widgetMap[name] = widget;
+      widgets.add(widget);
+    }
+  }
+
+  /// Returns the widget given by [name] or null if not found.
+  Widget byName(String name) {
+    final rc = widgetMap.containsKey(name) ? widgetMap[name] : null;
+    if (rc == null) {
+      logger.error('$name.byName(): missing widget $name');
+    }
+    return rc;
+  }
+}
diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart
new file mode 100644 (file)
index 0000000..c662812
--- /dev/null
@@ -0,0 +1,195 @@
+// 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/material.dart';
+import 'package:flutter/src/foundation/diagnostics.dart';
+import 'package:flutter/src/widgets/framework.dart' as x;
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/widget/widget_helper.dart';
+import 'package:test/test.dart';
+
+void main() {
+  final logger = MemoryLogger();
+  final widgetConfiguration = BaseConfiguration({}, logger);
+  Settings(logger: logger, widgetConfiguration: widgetConfiguration);
+
+  group('WidgetHelper', () {
+    test('toolTip', () {
+      final widget = Text('Hi');
+      expect(WidgetHelper.toolTip(widget, null), equals(widget));
+      expect(WidgetHelper.toolTip(widget, ''), equals(widget));
+      Tooltip toolTip = WidgetHelper.toolTip(widget, 'Hi');
+      expect(toolTip.child, equals(widget));
+    });
+  });
+  group('ModuleController', () {
+    test('basic', () {
+      PageData pageData =
+          PageData(BaseConfiguration({}, logger), (title) {}, (context) {});
+      final role = RoleCreatePage(pageData);
+      if (role.lastState == null) {
+        role.createState();
+      }
+      expect(role, isNotNull);
+      BuildContext context = MyContext();
+      role.lastState.build(context);
+      final widgets = role.lastState.controller.getWidgets(true);
+      expect(widgets.length, equals(3));
+    });
+  });
+}
+
+class MyContext extends BuildContext {
+  @override
+  InheritedElement ancestorInheritedElementForWidgetOfExactType(
+      Type targetType) {
+    // TODO: implement ancestorInheritedElementForWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  RenderObject ancestorRenderObjectOfType(x.TypeMatcher matcher) {
+    // TODO: implement ancestorRenderObjectOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  State<StatefulWidget> ancestorStateOfType(x.TypeMatcher matcher) {
+    // TODO: implement ancestorStateOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  Widget ancestorWidgetOfExactType(Type targetType) {
+    // TODO: implement ancestorWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  // TODO: implement debugDoingBuild
+  bool get debugDoingBuild => throw UnimplementedError();
+
+  @override
+  InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
+      {Object aspect}) {
+    // TODO: implement dependOnInheritedElement
+    throw UnimplementedError();
+  }
+
+  @override
+  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(
+      {Object aspect}) {
+    // TODO: implement dependOnInheritedWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  DiagnosticsNode describeElement(String name,
+      {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) {
+    // TODO: implement describeElement
+    throw UnimplementedError();
+  }
+
+  @override
+  List<DiagnosticsNode> describeMissingAncestor({Type expectedAncestorType}) {
+    // TODO: implement describeMissingAncestor
+    throw UnimplementedError();
+  }
+
+  @override
+  DiagnosticsNode describeOwnershipChain(String name) {
+    // TODO: implement describeOwnershipChain
+    throw UnimplementedError();
+  }
+
+  @override
+  DiagnosticsNode describeWidget(String name,
+      {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) {
+    // TODO: implement describeWidget
+    throw UnimplementedError();
+  }
+
+  @override
+  T findAncestorRenderObjectOfType<T extends RenderObject>() {
+    // TODO: implement findAncestorRenderObjectOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  T findAncestorStateOfType<T extends State<StatefulWidget>>() {
+    // TODO: implement findAncestorStateOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  T findAncestorWidgetOfExactType<T extends Widget>() {
+    // TODO: implement findAncestorWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  RenderObject findRenderObject() {
+    // TODO: implement findRenderObject
+    throw UnimplementedError();
+  }
+
+  @override
+  T findRootAncestorStateOfType<T extends State<StatefulWidget>>() {
+    // TODO: implement findRootAncestorStateOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  InheritedElement
+      getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
+    // TODO: implement getElementForInheritedWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  InheritedWidget inheritFromElement(InheritedElement ancestor,
+      {Object aspect}) {
+    // TODO: implement inheritFromElement
+    throw UnimplementedError();
+  }
+
+  @override
+  InheritedWidget inheritFromWidgetOfExactType(Type targetType,
+      {Object aspect}) {
+    // TODO: implement inheritFromWidgetOfExactType
+    throw UnimplementedError();
+  }
+
+  @override
+  // TODO: implement owner
+  BuildOwner get owner => throw UnimplementedError();
+
+  @override
+  State<StatefulWidget> rootAncestorStateOfType(x.TypeMatcher matcher) {
+    // TODO: implement rootAncestorStateOfType
+    throw UnimplementedError();
+  }
+
+  @override
+  // TODO: implement size
+  Size get size => throw UnimplementedError();
+
+  @override
+  void visitAncestorElements(bool Function(Element element) visitor) {
+    // TODO: implement visitAncestorElements
+  }
+
+  @override
+  void visitChildElements(visitor) {
+    // TODO: implement visitChildElements
+  }
+
+  @override
+  // TODO: implement widget
+  Widget get widget => throw UnimplementedError();
+}
index 49ab1a71c5f2a4fe00dbaba1b32a9cdf091afb9b..57ec2b7575b2884e9c20faa471b78f5676635c4d 100644 (file)
@@ -5,29 +5,25 @@
 // 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:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
+//import 'package:flutter/material.dart';
+//import 'package:flutter_bones/main.dart';
+//import 'package:flutter_test/flutter_test.dart';
 
-import 'package:flutter_bones/flutter_bones.dart';
-*/
 void main() {
-  /*
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(MyApp());
-
-    // Verify that our counter starts at 0.
-    expect(find.text('0'), findsOneWidget);
-    expect(find.text('1'), findsNothing);
-
-    // Tap the '+' icon and trigger a frame.
-    await tester.tap(find.byIcon(Icons.add));
-    await tester.pump();
-
-    // Verify that our counter has incremented.
-    expect(find.text('0'), findsNothing);
-    expect(find.text('1'), findsOneWidget);
-  });
-   */
+  // testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+  //   // Build our app and trigger a frame.
+  //   await tester.pumpWidget(MyApp());
+  //
+  //   // Verify that our counter starts at 0.
+  //   expect(find.text('0'), findsOneWidget);
+  //   expect(find.text('1'), findsNothing);
+  //
+  //   // Tap the '+' icon and trigger a frame.
+  //   await tester.tap(find.byIcon(Icons.add));
+  //   await tester.pump();
+  //
+  //   // Verify that our counter has incremented.
+  //   expect(find.text('0'), findsNothing);
+  //   expect(find.text('1'), findsOneWidget);
+  // });
 }