From dd125836a7bbd4f96a71bb41a08566263e22a342 Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Fri, 23 Oct 2020 20:29:13 +0200 Subject: [PATCH] daily work: pages list+create works,change is called --- Prepare | 6 + lib/app.dart | 11 +- lib/src/model/button_model.dart | 25 ++- lib/src/model/module_model.dart | 2 +- lib/src/model/page_model.dart | 42 ++--- lib/src/model/section_model.dart | 5 +- lib/src/model/table_model.dart | 38 ++--- .../configuration_controller.dart | 3 +- .../configuration_list_page.dart | 2 +- lib/src/page/role/role_change_page.dart | 20 ++- lib/src/page/role/role_controller.dart | 19 ++- lib/src/page/role/role_list_page.dart | 46 ++--- lib/src/page/user/user_list_page.dart | 2 +- lib/src/widget/callback_controller_bones.dart | 19 +++ lib/src/widget/checkbox_form_field.dart | 74 ++++++++ lib/src/widget/checkbox_list_tile_bone.dart | 2 +- lib/src/widget/dropdown_button_form_bone.dart | 38 ++--- lib/src/widget/edit_form.dart | 4 + lib/src/widget/list_form.dart | 14 +- lib/src/widget/page_controller_bones.dart | 159 ++++++++++++++---- lib/src/widget/text_form_field_bone.dart | 57 ++++--- lib/src/widget/view.dart | 142 ++++++++-------- test/helpers/settings_test.dart | 1 - test/model/db_model_test.dart | 2 +- test/model/model_test.dart | 2 +- 25 files changed, 472 insertions(+), 263 deletions(-) create mode 100755 Prepare create mode 100644 lib/src/widget/checkbox_form_field.dart diff --git a/Prepare b/Prepare new file mode 100755 index 0000000..532b666 --- /dev/null +++ b/Prepare @@ -0,0 +1,6 @@ +#! /bin/bash +cd lib +dartfmt -w . +cd .. +./Coverage.sh + diff --git a/lib/app.dart b/lib/app.dart index 6c5f4a3..0f6d5e8 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -33,7 +33,7 @@ class BoneAppState extends State { primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), - initialRoute: '/role/create', + initialRoute: '/role/list', onGenerateRoute: _getRoute, ); } @@ -46,9 +46,6 @@ Route _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 _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; diff --git a/lib/src/model/button_model.dart b/lib/src/model/button_model.dart index 9a2e1fa..84bd202 100644 --- a/lib/src/model/button_model.dart +++ b/lib/src/model/button_model.dart @@ -8,13 +8,33 @@ class ButtonModel extends WidgetModel { static final regExprOptions = RegExp(r'^(undef)$'); String text; String name; + String toolTip; final Map map; List 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 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('buttonType', map, ButtonModelType.values); buttonModelType ??= ButtonModelType.custom; diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index 13acc33..b8dde9c 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -296,7 +296,7 @@ modules: columnName = parts[1]; } if (table != null) { - rc = table.getColumn(columnName); + rc = table.columnByName(columnName); } return rc; } diff --git a/lib/src/model/page_model.dart b/lib/src/model/page_model.dart index ccd7475..858c0b4 100644 --- a/lib/src/model/page_model.dart +++ b/lib/src/model/page_model.dart @@ -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 map; String name; @@ -19,7 +18,7 @@ class PageModel extends ModelBase { PageModelType pageModelType; List options; final fields = []; - final buttons = {}; + final buttons = []; final widgets = []; 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); } diff --git a/lib/src/model/section_model.dart b/lib/src/model/section_model.dart index 0e06a88..cf52506 100644 --- a/lib/src/model/section_model.dart +++ b/lib/src/model/section_model.dart @@ -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( 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(); diff --git a/lib/src/model/table_model.dart b/lib/src/model/table_model.dart index 37a4f19..e1753d6 100644 --- a/lib/src/model/table_model.dart +++ b/lib/src/model/table_model.dart @@ -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 options; @protected - final columnMap = {}; final columns = []; + 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 options, [int size]) { - if (getColumn(name, required: false) == null) { + if (columnByName(name, required: false) == null) { addColumn(ColumnModel.raw( name: name, table: this, diff --git a/lib/src/page/configuration/configuration_controller.dart b/lib/src/page/configuration/configuration_controller.dart index 9049089..27af1e1 100644 --- a/lib/src/page/configuration/configuration_controller.dart +++ b/lib/src/page/configuration/configuration_controller.dart @@ -11,7 +11,8 @@ class ConfigurationController extends PageControllerBones { State parent, String pageName, BuildContext context, - ApplicationData applicationData) + ApplicationData applicationData, + {Function fetchRows}) : super( formKey, parent, diff --git a/lib/src/page/configuration/configuration_list_page.dart b/lib/src/page/configuration/configuration_list_page.dart index 4cae1cb..57d9ac6 100644 --- a/lib/src/page/configuration/configuration_list_page.dart +++ b/lib/src/page/configuration/configuration_list_page.dart @@ -34,7 +34,7 @@ class ConfigurationListPageState extends State { 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'), diff --git a/lib/src/page/role/role_change_page.dart b/lib/src/page/role/role_change_page.dart index e1c5633..5ee94d7 100644 --- a/lib/src/page/role/role_change_page.dart +++ b/lib/src/page/role/role_change_page.dart @@ -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 { final ApplicationData applicationData; - + final int primaryId; final GlobalKey _formKey = GlobalKey(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 { key: _formKey, pageController: controller, configuration: applicationData.configuration, + primaryId: primaryId, )); } } diff --git a/lib/src/page/role/role_controller.dart b/lib/src/page/role/role_controller.dart index 717cd16..2bfa22c 100644 --- a/lib/src/page/role/role_controller.dart +++ b/lib/src/page/role/role_controller.dart @@ -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 formKey, State 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))); + } } diff --git a/lib/src/page/role/role_list_page.dart b/lib/src/page/role/role_list_page.dart index 7398527..e27f868 100644 --- a/lib/src/page/role/role_list_page.dart +++ b/lib/src/page/role/role_list_page.dart @@ -24,7 +24,7 @@ class RoleListPageState extends State { final GlobalKey _formKey = GlobalKey(debugLabel: 'role_list'); - Iterable rows; + Iterable rowsDeprecated; RoleController controller; FilterSet filters; @@ -32,10 +32,14 @@ class RoleListPageState extends State { @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 { 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: [ 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 { ), ); } - - 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'); - }); - } } diff --git a/lib/src/page/user/user_list_page.dart b/lib/src/page/user/user_list_page.dart index a2d8039..55ba74e 100644 --- a/lib/src/page/user/user_list_page.dart +++ b/lib/src/page/user/user_list_page.dart @@ -34,7 +34,7 @@ class UserListPageState extends State { 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'), diff --git a/lib/src/widget/callback_controller_bones.dart b/lib/src/widget/callback_controller_bones.dart index b32910a..8a84fe0 100644 --- a/lib/src/widget/callback_controller_bones.dart +++ b/lib/src/widget/callback_controller_bones.dart @@ -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 comboboxTexts( String customString, CallbackControllerBones controller); @@ -57,4 +60,20 @@ abstract class CallbackControllerBones { FormFieldValidator 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 index 0000000..2a54d0e --- /dev/null +++ b/lib/src/widget/checkbox_form_field.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'page_controller_bones.dart'; + +class CheckboxFormField extends FormField { + CheckboxFormField({ + /// properties of FormField: + Key keyFormField, + FormFieldSetter onSaved, + FormFieldValidator validator, + bool initialValue = false, + AutovalidateMode autovalidateMode, + // properties of CheckboxListTile: + Key keyCheckbox, + Widget title, + }) : super( + key: keyFormField, + onSaved: onSaved, + validator: validator, + initialValue: initialValue, + builder: (FormFieldState 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, + + */ +} diff --git a/lib/src/widget/checkbox_list_tile_bone.dart b/lib/src/widget/checkbox_list_tile_bone.dart index 51c34a3..f2722ae 100644 --- a/lib/src/widget/checkbox_list_tile_bone.dart +++ b/lib/src/widget/checkbox_list_tile_bone.dart @@ -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] diff --git a/lib/src/widget/dropdown_button_form_bone.dart b/lib/src/widget/dropdown_button_form_bone.dart index 5164046..54cd8f7 100644 --- a/lib/src/widget/dropdown_button_form_bone.dart +++ b/lib/src/widget/dropdown_button_form_bone.dart @@ -35,25 +35,25 @@ class DropdownButtonFormBone extends DropdownButtonFormField { FormFieldValidator validator, AutovalidateMode autovalidateMode, }) : super( - key: key, - items: items, - selectedItemBuilder: callbackController.getOnSelectedItemBuilder( - customString, callbackController), - value: value, - hint: hint, - disabledHint: disabledHint, - onChanged: callbackController.getOnChangedCombobox( - 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( + 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, diff --git a/lib/src/widget/edit_form.dart b/lib/src/widget/edit_form.dart index 4a3f9b6..7dc0f93 100644 --- a/lib/src/widget/edit_form.dart +++ b/lib/src/widget/edit_form.dart @@ -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( diff --git a/lib/src/widget/list_form.dart b/lib/src/widget/list_form.dart index 9998889..7d16fbf 100644 --- a/lib/src/widget/list_form.dart +++ b/lib/src/widget/list_form.dart @@ -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 row, int index); @@ -36,6 +38,7 @@ class ListForm { static Widget table({ @required List titles, @required List columnNames, + @required PageControllerBones controller, @required Iterable rows, bool showEditIcon = false, int sortIndex, @@ -58,13 +61,14 @@ class ListForm { rows: rows.map((row) { final cells = []; 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 columnNames, @required Iterable 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, ) ])), diff --git a/lib/src/widget/page_controller_bones.dart b/lib/src/widget/page_controller_bones.dart index 8087368..5d8f6c1 100644 --- a/lib/src/widget/page_controller_bones.dart +++ b/lib/src/widget/page_controller_bones.dart @@ -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 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 buildSqlParams() { + final rc = {}; + 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 getWidgets() { List 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; } } diff --git a/lib/src/widget/text_form_field_bone.dart b/lib/src/widget/text_form_field_bone.dart index 8322fbe..af89457 100644 --- a/lib/src/widget/text_form_field_bone.dart +++ b/lib/src/widget/text_form_field_bone.dart @@ -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 inputFormatters, diff --git a/lib/src/widget/view.dart b/lib/src/widget/view.dart index 71f7d40..826e61a 100644 --- a/lib/src/widget/view.dart +++ b/lib/src/widget/view.dart @@ -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(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 modelsToWidgets( + List models, CallbackControllerBones controller) { + final rc = []; + 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 modelsToWidgets(List models, - CallbackControllerBones controller) { - final rc = []; - 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; - } } diff --git a/test/helpers/settings_test.dart b/test/helpers/settings_test.dart index efd2253..a9dbd40 100644 --- a/test/helpers/settings_test.dart +++ b/test/helpers/settings_test.dart @@ -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'; diff --git a/test/model/db_model_test.dart b/test/model/db_model_test.dart index ce5d802..17d301f 100644 --- a/test/model/db_model_test.dart +++ b/test/model/db_model_test.dart @@ -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')); diff --git a/test/model/model_test.dart b/test/model/model_test.dart index 0301cd1..a4b5c6a 100644 --- a/test/model/model_test.dart +++ b/test/model/model_test.dart @@ -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'); -- 2.39.5