]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work: pages list+create works,change is called
authorHamatoma <author@hamatoma.de>
Fri, 23 Oct 2020 18:29:13 +0000 (20:29 +0200)
committerHamatoma <author@hamatoma.de>
Fri, 23 Oct 2020 22:43:15 +0000 (00:43 +0200)
25 files changed:
Prepare [new file with mode: 0755]
lib/app.dart
lib/src/model/button_model.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/table_model.dart
lib/src/page/configuration/configuration_controller.dart
lib/src/page/configuration/configuration_list_page.dart
lib/src/page/role/role_change_page.dart
lib/src/page/role/role_controller.dart
lib/src/page/role/role_list_page.dart
lib/src/page/user/user_list_page.dart
lib/src/widget/callback_controller_bones.dart
lib/src/widget/checkbox_form_field.dart [new file with mode: 0644]
lib/src/widget/checkbox_list_tile_bone.dart
lib/src/widget/dropdown_button_form_bone.dart
lib/src/widget/edit_form.dart
lib/src/widget/list_form.dart
lib/src/widget/page_controller_bones.dart
lib/src/widget/text_form_field_bone.dart
lib/src/widget/view.dart
test/helpers/settings_test.dart
test/model/db_model_test.dart
test/model/model_test.dart

diff --git a/Prepare b/Prepare
new file mode 100755 (executable)
index 0000000..532b666
--- /dev/null
+++ b/Prepare
@@ -0,0 +1,6 @@
+#! /bin/bash
+cd lib
+dartfmt -w .
+cd ..
+./Coverage.sh
+
index 6c5f4a31af2732870b7f2c82ff0f18dffa112dd5..0f6d5e88489ae4addc54ff476e7f5267a7224e7e 100644 (file)
@@ -33,7 +33,7 @@ class BoneAppState extends State<BoneApp> {
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
-      initialRoute: '/role/create',
+      initialRoute: '/role/list',
       onGenerateRoute: _getRoute,
     );
   }
@@ -46,9 +46,6 @@ Route<dynamic> _getRoute(RouteSettings settings) {
     case '/role/list':
       page = RoleListPage(BSettings.lastInstance.pageData);
       break;
-    case '/role/change':
-      page = RoleChangePage(BSettings.lastInstance.pageData);
-      break;
     case '/role/create':
       page = RoleCreatePage(BSettings.lastInstance.pageData);
       break;
@@ -56,18 +53,12 @@ Route<dynamic> _getRoute(RouteSettings settings) {
     case '/user/list':
       page = UserListPage(BSettings.lastInstance.pageData);
       break;
-    case '/user/change':
-      page = UserChangePage(BSettings.lastInstance.pageData);
-      break;
     case '/user/create':
       page = UserCreatePage(BSettings.lastInstance.pageData);
       break;
     case '/configuration/list':
       page = ConfigurationListPage(BSettings.lastInstance.pageData);
       break;
-    case '/configuration/change':
-      page = ConfigurationChangePage(BSettings.lastInstance.pageData);
-      break;
     case '/configuration/create':
       page = ConfigurationCreatePage(BSettings.lastInstance.pageData);
       break;
index 9a2e1fa8f5cef83e39a41150a4c592b52e9292de..84bd202d40c933786689bae3d88fbbcb385130d3 100644 (file)
@@ -8,13 +8,33 @@ class ButtonModel extends WidgetModel {
   static final regExprOptions = RegExp(r'^(undef)$');
   String text;
   String name;
+  String toolTip;
   final Map<String, dynamic> map;
   List<String> options;
   ButtonModelType buttonModelType;
 
+  /// Constructs an instance by parsing the map for some properties.
   ButtonModel(SectionModel section, PageModel page, this.map, BaseLogger logger)
       : super(section, page, WidgetModelType.button, logger);
 
+  /// Constructs an instance with all properties given.
+  ButtonModel.direct(
+      SectionModel section,
+      PageModel page,
+      String name,
+      String text,
+      ButtonModelType buttonModelType,
+      List<String> options,
+      this.map,
+      BaseLogger logger)
+      : super(section, page, WidgetModelType.button, logger) {
+    this.name = name;
+    this.text = text;
+    this.toolTip = toolTip;
+    this.options = options;
+    this.buttonModelType = buttonModelType;
+  }
+
   /// Dumps a the instance into a [stringBuffer]
   StringBuffer dump(StringBuffer stringBuffer) {
     stringBuffer.write(
@@ -28,10 +48,11 @@ class ButtonModel extends WidgetModel {
 
   /// Parses the map and stores the data in the instance.
   void parse() {
-    checkSuperfluousAttributes(
-        map, 'buttonType label name options text widgetType'.split(' '));
     name = parseString('name', map, required: true);
+    checkSuperfluousAttributes(map,
+        'buttonType label name options text toolTip widgetType'.split(' '));
     text = parseString('text', map);
+    toolTip = parseString('toolTip', map);
     buttonModelType =
         parseEnum<ButtonModelType>('buttonType', map, ButtonModelType.values);
     buttonModelType ??= ButtonModelType.custom;
index 13acc33ce89de539807f4daf32981780ef016118..b8dde9cda435e37ab6b73b13640da57330668b73 100644 (file)
@@ -296,7 +296,7 @@ modules:
       columnName = parts[1];
     }
     if (table != null) {
-      rc = table.getColumn(columnName);
+      rc = table.columnByName(columnName);
     }
     return rc;
   }
index ccd7475f8c86ea53c7e3716dafb3ed02d74ea5e2..858c0b4abbefdbbbcc1afda0c5a0cddf059472ee 100644 (file)
@@ -1,5 +1,4 @@
 import 'package:dart_bones/dart_bones.dart';
-
 import 'button_model.dart';
 import 'field_model.dart';
 import 'model_base.dart';
@@ -11,7 +10,7 @@ typedef FilterWidget = bool Function(WidgetModel item);
 
 /// Represents one screen of the module.
 class PageModel extends ModelBase {
-  static final regExprOptions = RegExp(r'^(unknown)$');
+  static final regExprOptions = RegExp(r'^(noAutoButton)$');
   final ModuleModel module;
   final Map<String, dynamic> map;
   String name;
@@ -19,7 +18,7 @@ class PageModel extends ModelBase {
   PageModelType pageModelType;
   List<String> options;
   final fields = <FieldModel>[];
-  final buttons = <String, ButtonModel>{};
+  final buttons = <ButtonModel>[];
   final widgets = <WidgetModel>[];
 
   PageModel(this.module, this.map, BaseLogger logger) : super(logger);
@@ -27,15 +26,24 @@ class PageModel extends ModelBase {
   /// Adds a [button] to the [this.buttons].
   /// If already defined and error is logged.
   void addButton(ButtonModel button) {
-    final name = button.name;
-    if (buttons.containsKey(name)) {
+    if (buttonByName(button.name, required: false) != null) {
       logger.error('button ${button.fullName()} already defined: ' +
-          buttons[name].fullName());
+          buttonByName(button.name, required: false).fullName());
     } else {
-      buttons[name] = button;
+      buttons.add(button);
     }
   }
 
+  /// Returns a button named [name] or null if not found.
+  ButtonModel buttonByName(String name, {bool required = true}) {
+    final rc =
+        buttons.firstWhere((item) => item.name == name, orElse: () => null);
+    if (rc == null && required) {
+      logger.error('missing button $name in page ${fullName()}');
+    }
+    return rc;
+  }
+
   /// Tests whether the field with [name] is part of the page.
   bool hasField(String name) {
     final first = fields.firstWhere((element) => element.name == name,
@@ -70,19 +78,6 @@ class PageModel extends ModelBase {
   @override
   String fullName() => '${module.name}.$name';
 
-  /// Returns a field by [name] or null on error.
-  ButtonModel getButton(String name, {bool required = true}) {
-    ButtonModel rc;
-    if (!buttons.containsKey(name)) {
-      if (required) {
-        logger.error('missing button $name in page ${fullName()}');
-      }
-    } else {
-      rc = buttons[name];
-    }
-    return rc;
-  }
-
   /// Returns a field by [name] or null on error.
   FieldModel getField(String name, {bool required = true}) {
     final rc = fields.firstWhere((element) => element.name == name,
@@ -127,6 +122,13 @@ class PageModel extends ModelBase {
         SectionModel.parseSections(this, null, item, logger);
       }
     }
+    if (!options.contains('noAutoButton') &&
+        buttonByName('search', required: false) == null) {
+      final section = null;
+      final button = ButtonModel.direct(section, this, 'search', 'Suchen',
+          ButtonModelType.search, [], null, logger);
+      addButton(button);
+    }
     checkOptionsByRegExpr(options, regExprOptions);
   }
 
index 0e06a8877c209abd79e79a73f809e09b20baea53..cf52506b1bb0e86632d46e482ea04484f59af657 100644 (file)
@@ -73,7 +73,7 @@ class SectionModel extends WidgetModel {
             if (!child.containsKey('widgetType')) {
               logger.error(
                   'child $no of "children" does not have "widgetType" in ${fullName()}: '
-                      '${StringUtils.limitString(child.toString(), 80)}');
+                  '${StringUtils.limitString(child.toString(), 80)}');
             } else {
               final widgetType = StringUtils.stringToEnum<WidgetModelType>(
                   child['widgetType'].toString(), WidgetModelType.values);
@@ -143,8 +143,7 @@ class SectionModel extends WidgetModel {
       no++;
       if (!ModelBase.isMap(item)) {
         page.logger.error(
-            'curious item in section list of ${page.fullName()}: ${StringUtils
-                .limitString("$item", 80)}');
+            'curious item in section list of ${page.fullName()}: ${StringUtils.limitString("$item", 80)}');
       } else {
         final current = SectionModel(no, page, null, item, logger);
         current.parse();
index 37a4f1948661d2b4c2d9b98af9e0c915b4ee8537..e1753d6c80e19e6532a48a043368e7ccc10bd1ee 100644 (file)
@@ -1,5 +1,6 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:meta/meta.dart';
+
 import 'column_model.dart';
 import 'model_base.dart';
 import 'model_types.dart';
@@ -13,22 +14,32 @@ class TableModel extends ModelBase {
   String name;
   List<String> options;
   @protected
-  final columnMap = <String, ColumnModel>{};
   final columns = <ColumnModel>[];
+  ColumnModel primary;
 
   TableModel(this.module, this.map, BaseLogger logger) : super(logger);
 
   /// Adds a [button] to the [this.buttons].
   /// If already defined and error is logged.
   void addColumn(ColumnModel column) {
-    final name = column.name;
-    if (columnMap.containsKey(column)) {
-      logger.error('column ${column.fullName()} already defined: ' +
-          columnMap[name].fullName());
+    if (columnByName(column.name, required: false) != null) {
+      logger.error('column ${column.name} already defined: ' + fullName());
     } else {
-      columnMap[name] = column;
       columns.add(column);
+      if (column.hasOption('primary')) {
+        primary = column;
+      }
+    }
+  }
+
+  /// Returns a column by [name] or null if not found.
+  ColumnModel columnByName(String name, {bool required = true}) {
+    ColumnModel rc = columns.firstWhere((element) => element.name == name,
+        orElse: () => null);
+    if (rc == null && required) {
+      logger.error('missing column $name in table ${fullName()}');
     }
+    return rc;
   }
 
   /// Dumps the internal structure into a [stringBuffer]
@@ -44,19 +55,6 @@ class TableModel extends ModelBase {
   @override
   String fullName() => '${module.name}.$name';
 
-  /// Returns a column by [name] or null on error.
-  ColumnModel getColumn(String name, {bool required = true}) {
-    ColumnModel rc;
-    if (!columnMap.containsKey(name)) {
-      if (required) {
-        logger.error('missing column $name in table ${fullName()}');
-      }
-    } else {
-      rc = columnMap[name];
-    }
-    return rc;
-  }
-
   /// Parses the map and stores the data in the instance.
   void parse() {
     name = parseString('table', map, required: true);
@@ -91,7 +89,7 @@ class TableModel extends ModelBase {
   void _addIfMissing(
       String name, String label, DataType dataType, List<String> options,
       [int size]) {
-    if (getColumn(name, required: false) == null) {
+    if (columnByName(name, required: false) == null) {
       addColumn(ColumnModel.raw(
           name: name,
           table: this,
index 9049089740c48d278d54153d3aefa7ecf5aa76a4..27af1e1f5d5cffea4186d8289fb7dafa860e8fca 100644 (file)
@@ -11,7 +11,8 @@ class ConfigurationController extends PageControllerBones {
       State<StatefulWidget> parent,
       String pageName,
       BuildContext context,
-      ApplicationData applicationData)
+      ApplicationData applicationData,
+      {Function fetchRows})
       : super(
           formKey,
           parent,
index 4cae1cbe0d71fd1542ae6613ac12f446aa733446..57d9ac6c4840c5198c0d6c420ebb65291b0fba32 100644 (file)
@@ -34,7 +34,7 @@ class ConfigurationListPageState extends State<ConfigurationListPage> {
   Widget build(BuildContext context) {
     controller ??= ConfigurationController(
         _formKey, this, 'list', context, applicationData);
-    filters ??= controller.filterSet(page: 'list');
+    filters ??= controller.filterSet(pageName: 'list');
     getRows();
     return Scaffold(
       appBar: applicationData.appBarBuilder('Rollen'),
index e1c56331f851fc56baf24217f316923a23f32de0..5ee94d7ee2f66bc85e9198ca36d3575400ec825c 100644 (file)
@@ -7,14 +7,15 @@ import 'role_controller.dart';
 class RoleChangePage extends StatefulWidget {
   final ApplicationData applicationData;
   final logger = Settings().logger;
-
+  final int primaryId;
   //RoleChangePageState lastState;
 
-  RoleChangePage(this.applicationData, {Key key}) : super(key: key);
+  RoleChangePage(this.primaryId, this.applicationData, {Key key})
+      : super(key: key);
 
   @override
   RoleChangePageState createState() {
-    final rc = RoleChangePageState(applicationData);
+    final rc = RoleChangePageState(primaryId, applicationData);
     // lastState = rc;
     return rc;
   }
@@ -22,18 +23,22 @@ class RoleChangePage extends StatefulWidget {
 
 class RoleChangePageState extends State<RoleChangePage> {
   final ApplicationData applicationData;
-
+  final int primaryId;
   final GlobalKey<FormState> _formKey =
       GlobalKey<FormState>(debugLabel: 'role_change');
 
   RoleController controller;
 
-  RoleChangePageState(this.applicationData);
+  RoleChangePageState(this.primaryId, this.applicationData);
 
   @override
   Widget build(BuildContext context) {
-    controller = controller ??
-        RoleController(_formKey, this, 'change', context, applicationData);
+    if (controller == null) {
+      controller = RoleController(
+          _formKey, this, 'list', context, applicationData,
+          redrawCallback: () => setState(() => null));
+      controller.initialize();
+    }
     return Scaffold(
         appBar: applicationData.appBarBuilder('Rolle ändern'),
         drawer: applicationData.drawerBuilder(context),
@@ -41,6 +46,7 @@ class RoleChangePageState extends State<RoleChangePage> {
           key: _formKey,
           pageController: controller,
           configuration: applicationData.configuration,
+          primaryId: primaryId,
         ));
   }
 }
index 717cd166e0db57f37d20add099770744fa4a6a47..2bfa22c66f4b77f400166e525fa44876ffb7e701 100644 (file)
@@ -2,20 +2,21 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 
 import '../../model/standard/role_model.dart';
+import 'role_change_page.dart';
 import '../../widget/page_controller_bones.dart';
 
 class RoleController extends PageControllerBones {
   /// Controller for a page named [pageName].
   RoleController(GlobalKey<FormState> formKey, State<StatefulWidget> parent,
-      String pageName, BuildContext context, ApplicationData applicationData)
-      : super(
-          formKey,
-          parent,
-          RoleModel(Settings().logger),
-          pageName,
-          context,
-          applicationData,
-        ) {
+      String pageName, BuildContext context, ApplicationData applicationData,
+      {Function redrawCallback})
+      : super(formKey, parent, RoleModel(Settings().logger), pageName, context,
+            applicationData, redrawCallback) {
     moduleModel.parse();
   }
+  @override
+  void startChange(int id) {
+    Navigator.push(context,
+        MaterialPageRoute(builder: (context) => RoleChangePage(id, applicationData)));
+  }
 }
index 7398527e4a67bf37d64c97e43797118b6eee73ef..e27f868bf80b95dfe3edd7354f0f4173721923fc 100644 (file)
@@ -24,7 +24,7 @@ class RoleListPageState extends State<RoleListPage> {
 
   final GlobalKey<FormState> _formKey =
       GlobalKey<FormState>(debugLabel: 'role_list');
-  Iterable<dynamic> rows;
+  Iterable<dynamic> rowsDeprecated;
   RoleController controller;
   FilterSet filters;
 
@@ -32,10 +32,14 @@ class RoleListPageState extends State<RoleListPage> {
 
   @override
   Widget build(BuildContext context) {
-    controller ??=
-        RoleController(_formKey, this, 'list', context, applicationData);
-    filters ??= controller.filterSet(page: 'list');
-    getRows();
+    if (controller == null) {
+      controller = RoleController(
+          _formKey, this, 'list', context, applicationData,
+          redrawCallback: () => setState(() => null));
+      controller.initialize();
+    }
+    filters ??= controller.filterSet(pageName: 'list');
+    controller.buildRows();
     return Scaffold(
       appBar: applicationData.appBarBuilder('Rollen'),
       drawer: applicationData.drawerBuilder(context),
@@ -44,24 +48,19 @@ class RoleListPageState extends State<RoleListPage> {
         configuration: applicationData.configuration,
         titles: ListForm.stringsToTitles(';Id;Name;Priorität'),
         columnNames: 'role_id role_name role_priority'.split(' '),
-        rows: rows ?? [],
+        rows: controller.listRows ?? [],
         showEditIcon: true,
+        pageController: controller,
         buttons: <Widget>[
           ButtonBar(alignment: MainAxisAlignment.center, children: [
-            RaisedButton(
-              child: Text('Suchen'),
-              onPressed: () {
-                rows = null;
-                if (_formKey.currentState.validate()) {
-                  _formKey.currentState.save();
-                  getRows();
-                }
-              },
-            ),
+            controller.searchButton(),
             RaisedButton(
               child: Text('Neue Rolle'),
               onPressed: () {
                 Navigator.pushNamed(context, '/role/create');
+
+                /// Force the redraw on return:
+                controller.listRows = null;
               },
             ),
           ]),
@@ -70,19 +69,4 @@ class RoleListPageState extends State<RoleListPage> {
       ),
     );
   }
-
-  void getRows() {
-    final persistence = applicationData.persistence;
-    final namePattern = filters.valueOf('role_name') ?? '';
-    persistence.list(module: 'role', params: {
-      ':role_name': namePattern.replaceAll('*', '%') + '%'
-    }).then((list) {
-      if (rows == null) {
-        rows = list;
-        setState(() {});
-      }
-    }, onError: (error) {
-      applicationData.logger.error('cannot retrieve role list: $error');
-    });
-  }
 }
index a2d803953eb58e6e598b994075699b572b9e3a73..55ba74ea2f4472160c5acb03c2b2503cf71403ca 100644 (file)
@@ -34,7 +34,7 @@ class UserListPageState extends State<UserListPage> {
   Widget build(BuildContext context) {
     controller ??=
         UserController(_formKey, this, 'list', context, applicationData);
-    filters ??= controller.filterSet(page: 'list');
+    filters ??= controller.filterSet(pageName: 'list');
     getRows();
     return Scaffold(
       appBar: applicationData.appBarBuilder('Rollen'),
index b32910a1cc24cfb5d02dbd58d9b1cf4c0352f96d..8a84fe0f05bd2ddf78f2104ad366b0020b65581e 100644 (file)
@@ -5,6 +5,9 @@ import 'package:flutter_bones/flutter_bones.dart';
 /// flutter_bones specific widgets: [CheckboxListTileBone],
 /// [DropDownButtonFormBone], [RaisedButtonBone], [TextFormFieldBone]
 abstract class CallbackControllerBones {
+  /// Prepares the rows shown in the list page
+  void buildRows();
+
   List<String> comboboxTexts(
       String customString, CallbackControllerBones controller);
 
@@ -57,4 +60,20 @@ abstract class CallbackControllerBones {
 
   FormFieldValidator<String> getValidator(
       String customString, CallbackControllerBones controller);
+
+  /// Handles the tap event in the table of the list page.
+  void onEditTap(
+      String customString, CallbackControllerBones controller, Map row);
+
+  /// Rebuilds the view of the page.
+  void redraw();
+
+  /// Returns a standard search button for the list page.
+  Widget searchButton();
+
+  /// Starts the change page to edit the record with primary key [id].
+  void startChange(int id);
+
+  /// Returns the field value of field named [fieldName] or null if not found.
+  dynamic valueOf(String fieldName);
 }
diff --git a/lib/src/widget/checkbox_form_field.dart b/lib/src/widget/checkbox_form_field.dart
new file mode 100644 (file)
index 0000000..2a54d0e
--- /dev/null
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'page_controller_bones.dart';
+
+class CheckboxFormField extends FormField<bool> {
+  CheckboxFormField({
+    /// properties of FormField:
+    Key keyFormField,
+    FormFieldSetter<bool> onSaved,
+    FormFieldValidator<bool> validator,
+    bool initialValue = false,
+    AutovalidateMode autovalidateMode,
+    // properties of CheckboxListTile:
+    Key keyCheckbox,
+    Widget title,
+  }) : super(
+            key: keyFormField,
+            onSaved: onSaved,
+            validator: validator,
+            initialValue: initialValue,
+            builder: (FormFieldState<bool> state) {
+              return CheckboxListTile(
+                key: keyCheckbox,
+                dense: state.hasError,
+                title: title,
+                value: state.value,
+                onChanged: state.didChange,
+                subtitle: state.hasError
+                    ? Builder(
+                        builder: (BuildContext context) => Text(
+                          state.errorText,
+                          style: TextStyle(color: Theme.of(context).errorColor),
+                        ),
+                      )
+                    : null,
+                controlAffinity: ListTileControlAffinity.leading,
+              );
+            },
+            autovalidateMode: autovalidateMode);
+/**
+ *     Key? key,
+    required this.value,
+    required this.onChanged,
+    this.activeColor,
+    this.checkColor,
+    this.tileColor,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.secondary,
+    this.selected = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.autofocus = false,
+    this.contentPadding,
+    this.tristate = false,
+    this.shape,
+
+ *     Key? key,
+    required this.builder,
+    this.onSaved,
+    this.validator,
+    this.initialValue,
+    @Deprecated(
+    'Use autoValidateMode parameter which provides more specific '
+    'behavior related to auto validation. '
+    'This feature was deprecated after v1.19.0.'
+    )
+    this.autovalidate = false,
+    this.enabled = true,
+    AutovalidateMode? autovalidateMode,
+
+ */
+}
index 51c34a3673bea19622db3da0007b60d4c29af11e..f2722aeaf13f28b5a4ef1c95edc42543952900c3 100644 (file)
@@ -1,6 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
-import 'package:flutter_bones/src/widget/page_controller_bones.dart';
+import 'page_controller_bones.dart';
 
 /// Implements a [Checkbox] with "outsourced" callbacks:
 /// [customString] a string mostly used for a name needed in the [callbackController]
index 5164046a626d33503ee4a1e02fbc11852865b032..54cd8f79bed3a147c0d5f91580bf2678710665f8 100644 (file)
@@ -35,25 +35,25 @@ class DropdownButtonFormBone<T> extends DropdownButtonFormField<T> {
     FormFieldValidator<T> validator,
     AutovalidateMode autovalidateMode,
   }) : super(
-    key: key,
-    items: items,
-    selectedItemBuilder: callbackController.getOnSelectedItemBuilder(
-        customString, callbackController),
-    value: value,
-    hint: hint,
-    disabledHint: disabledHint,
-    onChanged: callbackController.getOnChangedCombobox<T>(
-        customString, callbackController),
-    onTap: callbackController.getOnTap(customString, callbackController),
-    elevation: elevation,
-    style: style,
-    icon: icon,
-    iconDisabledColor: iconDisabledColor,
-    iconEnabledColor: iconEnabledColor,
-    iconSize: iconSize,
-    isDense: isDense,
-    isExpanded: isExpanded,
-    itemHeight: itemHeight,
+          key: key,
+          items: items,
+          selectedItemBuilder: callbackController.getOnSelectedItemBuilder(
+              customString, callbackController),
+          value: value,
+          hint: hint,
+          disabledHint: disabledHint,
+          onChanged: callbackController.getOnChangedCombobox<T>(
+              customString, callbackController),
+          onTap: callbackController.getOnTap(customString, callbackController),
+          elevation: elevation,
+          style: style,
+          icon: icon,
+          iconDisabledColor: iconDisabledColor,
+          iconEnabledColor: iconEnabledColor,
+          iconSize: iconSize,
+          isDense: isDense,
+          isExpanded: isExpanded,
+          itemHeight: itemHeight,
           focusColor: focusColor,
           focusNode: focusNode,
           autofocus: autofocus,
index 4a3f9b6311bf56fe12e74305715377ed4921dda6..7dc0f93e4dfb5c5bffee3fc4d18051083aa2db02 100644 (file)
@@ -16,10 +16,14 @@ class EditForm {
     @required Key key,
     @required PageControllerBones pageController,
     @required BaseConfiguration configuration,
+    int primaryId,
   }) {
     final padding =
         configuration.asFloat('form.card.padding', defaultValue: 16.0);
     final widgets = pageController.getWidgets();
+    if (primaryId != null){
+      pageController.fetchData(primaryId);
+    }
     return Form(
       key: key,
       child: Card(
index 99988891ddfac602856ab3c0f6aa22c15f9a082e..7d16fbf5c306bcb50bab6144aa284cc9e81307ca 100644 (file)
@@ -1,8 +1,10 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
 
 import '../helper/string_helper.dart';
 import 'filter_set.dart';
+import 'page_controller_bones.dart';
 
 typedef Function OnEditTap(Map<String, dynamic> row, int index);
 
@@ -36,6 +38,7 @@ class ListForm {
   static Widget table({
     @required List<Widget> titles,
     @required List<String> columnNames,
+    @required PageControllerBones controller,
     @required Iterable<dynamic> rows,
     bool showEditIcon = false,
     int sortIndex,
@@ -58,13 +61,14 @@ class ListForm {
             rows: rows.map((row) {
               final cells = <DataCell>[];
               if (showEditIcon) {
-                cells.add(DataCell(SizedBox(width: 1), showEditIcon: true));
+                cells.add(
+                    DataCell(SizedBox(width: 1), showEditIcon: true, onTap: () {
+                  controller.onEditTap(customString, controller, row);
+                }));
               }
               for (var key in columnNames) {
                 cells.add(DataCell(
                   Text(StringHelper.asString(row[key])),
-                  onTap: () => tableCallbackController.getOnEditTap(
-                      customString, tableCallbackController, row),
                 ));
               }
               return DataRow(cells: cells);
@@ -86,7 +90,7 @@ class ListForm {
       @required List<String> columnNames,
       @required Iterable<dynamic> rows,
       bool showEditIcon = false,
-      TableCallbackController tableCallbackController,
+      PageControllerBones pageController,
       @required BaseConfiguration configuration,
       String customString}) {
     final padding =
@@ -109,7 +113,7 @@ class ListForm {
                   columnNames: columnNames,
                   rows: rows,
                   showEditIcon: showEditIcon,
-                  tableCallbackController: tableCallbackController,
+                  controller: pageController,
                   customString: customString,
                 )
               ])),
index 8087368d404059fef2b3ec8cf1c0b8914395be61..5d8f6c1914ada0f0b1ffbe0135c99ef3163ff606 100644 (file)
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 import 'package:flutter_bones/src/widget/widget_list.dart';
 
+import '../model/column_model.dart';
 import '../model/module_model.dart';
 import '../page/application_data.dart';
 import 'callback_controller_bones.dart';
@@ -13,7 +14,8 @@ class PageControllerBones implements CallbackControllerBones {
   final ModuleModel moduleModel;
   String primaryColumn;
   WidgetList widgetList;
-
+  final Function redrawCallback;
+  bool needsRedraw;
   //WidgetList changeWidgets;
   final GlobalKey<FormState> globalKey;
   final String pageName;
@@ -21,21 +23,48 @@ class PageControllerBones implements CallbackControllerBones {
   final ApplicationData applicationData;
   State parent;
   final BuildContext context;
+  Iterable listRows;
 
   PageControllerBones(this.globalKey, this.parent, this.moduleModel,
-      this.pageName, this.context, this.applicationData) {}
+      this.pageName, this.context, this.applicationData,
+      [this.redrawCallback]);
+  @override
+  void buildRows() {
+    needsRedraw = true;
+    final persistence = applicationData.persistence;
+    final params = buildSqlParams() ?? {};
+    persistence.list(module: 'role', params: params).then((list) {
+      if (listRows == null) {
+        listRows = list;
+        redraw();
+      }
+    }, onError: (error) {
+      applicationData.logger.error('cannot retrieve role list: $error');
+    });
+  }
 
-  /// Initializes the controller when the instance is complete constructed,
-  /// e.g. ModuleModel.parse() is called.
-  /// This method must be called early after the constructor.
-  void initialize() {
-    widgetList =
-        WidgetList('${moduleModel.fullName()}.widgets', moduleModel.logger);
-    page = moduleModel.pageByName(pageName);
-    page.fields.forEach((model) {
-      widgetList.addWidget(
-          model.name, View(moduleModel.logger).modelToWidget(model, this));
+  /// Builds the SQL parameters of all members of the [widgets].
+  Map<String, String> buildSqlParams() {
+    final rc = <String, String>{};
+    page.fields.forEach((element) {
+      if (element.filterType != null) {
+        dynamic value = element.value;
+        DataType dataType = element.dataType;
+        String name = element.name;
+        String operator;
+        if (element.filterType == FilterType.pattern) {
+          if (value == null || value.isEmpty) {
+            value = '%';
+          } else if (value is String) {
+            value = value.replaceAll('*', '%') + '%';
+          }
+        }
+        if (name != null && dataType != null) {
+          rc[':$name'] = StringHelper.asDatabaseString(value, dataType);
+        }
+      }
     });
+    return rc;
   }
 
   @override
@@ -49,10 +78,23 @@ class PageControllerBones implements CallbackControllerBones {
     return [];
   }
 
-  FilterSet filterSet({@required String page}) {
+  /// Gets the data from the database using the [primaryId].
+  fetchData(int primaryId) async {
+    applicationData.persistence
+        .record(module: moduleModel.name, id: primaryId)
+        .then((row) {
+      page.fields.forEach((model) {
+        model.value = row[model.name];
+      });
+      needsRedraw = true;
+      redraw();
+    });
+  }
+
+  FilterSet filterSet({@required String pageName}) {
     final rc = FilterSet(globalKey, parent, this, moduleModel.logger);
     moduleModel
-        .pageByName(page)
+        .pageByName(pageName)
         .fields
         .where((element) => element.filterType != null)
         .forEach((element) {
@@ -65,6 +107,11 @@ class PageControllerBones implements CallbackControllerBones {
     return rc;
   }
 
+  @override
+  ApplicationData getApplicationData() {
+    return applicationData;
+  }
+
   @override
   BuildContext getContext() {
     return context;
@@ -112,8 +159,8 @@ class PageControllerBones implements CallbackControllerBones {
   }
 
   @override
-  getOnHighlightChanged(String customString,
-      CallbackControllerBones controller) {
+  getOnHighlightChanged(
+      String customString, CallbackControllerBones controller) {
     return null;
   }
 
@@ -125,23 +172,32 @@ class PageControllerBones implements CallbackControllerBones {
   @override
   getOnPressed(String customString, CallbackControllerBones controller) {
     var rc;
-    if (customString == 'store') {
+    if (customString == 'search') {
+      rc = () {
+        if (globalKey.currentState.validate()) {
+          globalKey.currentState.save();
+          listRows = null;
+          buildRows();
+          // controller.fetchListRows();
+        }
+      };
+    } else if (customString == 'store') {
       rc = () {
         if (globalKey.currentState.validate()) {
           globalKey.currentState.save();
           final params = widgetList.buildSqlParams(this);
           PageModelType pageType =
-              moduleModel
-                  .pageByName(pageName)
-                  .pageModelType;
+              moduleModel.pageByName(pageName).pageModelType;
           switch (pageType) {
             case PageModelType.create:
               applicationData.persistence
                   .insert(module: moduleModel.name, data: params);
+              Navigator.pop(controller.getContext());
               break;
             case PageModelType.change:
               applicationData.persistence
                   .update(module: moduleModel.name, data: params);
+              Navigator.pop(controller.getContext());
               break;
             default:
               moduleModel.logger
@@ -159,7 +215,6 @@ class PageControllerBones implements CallbackControllerBones {
   @override
   getOnSaved(String customString, CallbackControllerBones controller) {
     final rc = (input) {
-      final page = moduleModel.pageByName(pageName);
       FieldModel model = page.getField(customString);
       model.value = StringHelper.fromString(input, model.dataType);
     };
@@ -167,8 +222,8 @@ class PageControllerBones implements CallbackControllerBones {
   }
 
   @override
-  getOnSelectedItemBuilder(String customString,
-      CallbackControllerBones controller) {
+  getOnSelectedItemBuilder(
+      String customString, CallbackControllerBones controller) {
     return null;
   }
 
@@ -182,12 +237,6 @@ class PageControllerBones implements CallbackControllerBones {
     return null;
   }
 
-  @override
-  TextEditingController getTextEditingController(String customString,
-      CallbackControllerBones controller) {
-    return null;
-  }
-
   @override
   getValidator(String customString, CallbackControllerBones controller) {
     return null;
@@ -198,13 +247,59 @@ class PageControllerBones implements CallbackControllerBones {
   ///
   List<Widget> getWidgets() {
     List<Widget> rc;
-    final page = moduleModel.pageByName(pageName);
     rc = View(moduleModel.logger).modelsToWidgets(page.fields, this);
     return rc ?? [];
   }
 
+  /// Initializes the controller when the instance is complete constructed,
+  /// e.g. ModuleModel.parse() is called.
+  /// This method must be called early after the constructor.
+  void initialize() {
+    widgetList =
+        WidgetList('${moduleModel.fullName()}.widgets', moduleModel.logger);
+    page = moduleModel.pageByName(pageName);
+    page.fields.forEach((model) {
+      widgetList.addWidget(
+          model.name, View(moduleModel.logger).modelToWidget(model, this));
+    });
+  }
+
   @override
-  ApplicationData getApplicationData() {
-    return applicationData;
+  onEditTap(String customString, CallbackControllerBones controller, Map row) {
+    ColumnModel primary = moduleModel.mainTable().primary;
+    final id = row[primary.name];
+    controller.startChange(id);
+  }
+
+  @override
+  void redraw() {
+    if (redrawCallback == null) {
+      moduleModel.logger.error(
+          'not overridden: fetchListRows() in ${moduleModel.widgetName()}.$pageName');
+    } else {
+      if (needsRedraw) {
+        needsRedraw = false;
+        redrawCallback();
+      }
+    }
+  }
+
+  @override
+  Widget searchButton() {
+    final rc = View(moduleModel.logger)
+        .modelToWidget(page.buttonByName('search'), this);
+    return rc;
+  }
+
+  @override
+  void startChange(int id) {
+    moduleModel.logger.error(
+        'missing override of startChange() in ${moduleModel.fullName()}');
+  }
+
+  @override
+  dynamic valueOf(String fieldName) {
+    final rc = page.getField(fieldName)?.value;
+    return rc;
   }
 }
index 8322fbe7357af64dbe543881e6e002100612ffb5..af89457b3fff8280be19a33f001306bbac1c9076 100644 (file)
@@ -31,34 +31,35 @@ class TextFormFieldBone extends TextFormField {
   final String customString;
   final CallbackControllerBones 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,
+  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,
index 71f7d40dcc7fd0ea0736a229c407ca367d05a873..826e61acef063180771df44f355068a30e804637 100644 (file)
@@ -34,11 +34,15 @@ class View {
 
   /// Creates a button from the [controller].
   Widget button(ButtonModel model, CallbackControllerBones controller) {
-    final rc = RaisedButtonBone(
+    Widget rc;
+    rc = RaisedButtonBone(
       model.name,
       controller,
       child: Text(model.text),
     );
+    if (model.toolTip != null) {
+      rc = Tooltip(message: model.toolTip, child: rc);
+    }
     return rc;
   }
 
@@ -52,6 +56,19 @@ class View {
     return rc;
   }
 
+  /// Creates a checkbox from the [model].
+  Widget checkbox(FieldModel model, CallbackControllerBones controller) {
+    final tristate = model.hasOption('undef');
+    final rc = toolTip(
+        CheckboxListTileBone(model.name, controller,
+            value: tristate ? model.value : model.value ?? false,
+            tristate: tristate,
+            title: Text(model.label),
+            selected: model.value ?? false),
+        model);
+    return rc;
+  }
+
   /// Creates a combobox via the [controller].
   Widget combobox<T>(FieldModel model, CallbackControllerBones controller) {
     final texts = controller.comboboxTexts(model.name, controller);
@@ -67,19 +84,6 @@ class View {
     return rc;
   }
 
-  /// Creates a checkbox from the [model].
-  Widget checkbox(FieldModel model, CallbackControllerBones controller) {
-    final tristate = model.hasOption('undef');
-    final rc = toolTip(
-        CheckboxListTileBone(model.name, controller,
-            value: tristate ? model.value : model.value ?? false,
-            tristate: tristate,
-            title: Text(model.label),
-            selected: model.value ?? false),
-        model);
-    return rc;
-  }
-
   Widget dbReference(
       DbReferenceModel model, CallbackControllerBones controller) {
     var rc;
@@ -105,6 +109,60 @@ class View {
     return rc;
   }
 
+  /// Converts a list of [models] into a list of [Widget]s.
+  List<Widget> modelsToWidgets(
+      List<WidgetModel> models, CallbackControllerBones controller) {
+    final rc = <Widget>[];
+    for (var model in models) {
+      final widget = modelToWidget(model, controller);
+      if (widget != null) {
+        rc.add(widget);
+      }
+    }
+    return rc;
+  }
+
+  /// Converts a [model] into a [Widget].
+  Widget modelToWidget(WidgetModel model, CallbackControllerBones controller) {
+    Widget rc;
+    switch (model?.widgetModelType) {
+      case WidgetModelType.textField:
+        rc = textField(model, controller);
+        break;
+      case WidgetModelType.button:
+        rc = button(model, controller);
+        break;
+      case WidgetModelType.emptyLine:
+        rc = emptyLine(model);
+        break;
+      case WidgetModelType.text:
+        rc = text(model);
+        break;
+      case WidgetModelType.checkbox:
+        rc = checkbox(model, controller);
+        break;
+      case WidgetModelType.combobox:
+        rc = text(model);
+        break;
+      case WidgetModelType.image:
+        rc = image(model);
+        break;
+      case WidgetModelType.section:
+        rc = section(model);
+        break;
+      case WidgetModelType.dbReference:
+        rc = dbReference(model, controller);
+        break;
+      case WidgetModelType.allDbFields:
+        break;
+      case WidgetModelType.table:
+      case WidgetModelType.column:
+        logger.error('not allowed in section: ${model.fullName()}');
+        break;
+    }
+    return rc;
+  }
+
   /// Creates a section from the [model].
   Widget section(WidgetModel model) {
     var rc;
@@ -117,7 +175,7 @@ class View {
       {SectionModel model, CallbackControllerBones controller, Key formKey}) {
     assert(formKey != null);
     final padding =
-    widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
+        widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
     final children = modelsToWidgets(model.children, controller);
     final buttons = buttonList(model.buttons, controller);
     final rc = Form(
@@ -168,58 +226,4 @@ class View {
     }
     return rc;
   }
-
-  /// Converts a list of [models] into a list of [Widget]s.
-  List<Widget> modelsToWidgets(List<WidgetModel> models,
-      CallbackControllerBones controller) {
-    final rc = <Widget>[];
-    for (var model in models) {
-      final widget = modelToWidget(model, controller);
-      if (widget != null) {
-        rc.add(widget);
-      }
-    }
-    return rc;
-  }
-
-  /// Converts a [model] into a [Widget].
-  Widget modelToWidget(WidgetModel model, CallbackControllerBones controller) {
-    Widget rc;
-    switch (model.widgetModelType) {
-      case WidgetModelType.textField:
-        rc = textField(model, controller);
-        break;
-      case WidgetModelType.button:
-        rc = button(model, controller);
-        break;
-      case WidgetModelType.emptyLine:
-        rc = emptyLine(model);
-        break;
-      case WidgetModelType.text:
-        rc = text(model);
-        break;
-      case WidgetModelType.checkbox:
-        rc = checkbox(model, controller);
-        break;
-      case WidgetModelType.combobox:
-        rc = text(model);
-        break;
-      case WidgetModelType.image:
-        rc = image(model);
-        break;
-      case WidgetModelType.section:
-        rc = section(model);
-        break;
-      case WidgetModelType.dbReference:
-        rc = dbReference(model, controller);
-        break;
-      case WidgetModelType.allDbFields:
-        break;
-      case WidgetModelType.table:
-      case WidgetModelType.column:
-        logger.error('not allowed in section: ${model.fullName()}');
-        break;
-    }
-    return rc;
-  }
 }
index efd22531f8f22e704cb1e4cd03addfeb092e2785..a9dbd40f57c907279650400d2ee806acbcd8e893 100644 (file)
@@ -1,5 +1,4 @@
 import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/material.dart';
 import 'package:flutter_bones/flutter_bones.dart';
 import 'package:intl/intl.dart';
 import 'package:test/test.dart';
index ce5d8026ec922ac9464b4f9fb8b9921bfa31fa4f..17d301fe77c6d6a27795f4a951ad322241bdfd21 100644 (file)
@@ -43,7 +43,7 @@ void main() {
     button buttonStore: text: options: null 
     ] # create.simpleForm1
 ''');
-      final userField = table.getColumn('user_id');
+      final userField = table.columnByName('user_id');
       expect(userField, isNotNull);
       expect(userField.widgetName(), 'user_id');
       expect(userField.fullName(), equals('user.user_id'));
index 0301cd1744bd900f39b3684064f30b668a47701b..a4b5c6a030385c74f99679d4556aa1712fb99bda 100644 (file)
@@ -44,7 +44,7 @@ void main() {
       expect(userField.fullName(), equals('create.user'));
       expect(userField.page.fullName(), equals(page.fullName()));
       expect(userField.section, isNotNull);
-      final button = page.getButton('buttonStore');
+      final button = page.buttonByName('buttonStore');
       expect(button, isNotNull);
       expect(button.section, equals(userField.section));
       expect(button.fullName(), 'simpleForm1.buttonStore');