From 15e3639a7d023e0eb689ebccd9ae6c7ff6e839a9 Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Sat, 23 Oct 2021 22:47:07 +0200 Subject: [PATCH] Refactoring, new module rolestarter * Refactoring: ** usage of the dart feature "late": dummy constructors removed * new module rolestarter: ** new: PageType.mapping ** new: MappingPageMetaData ** new: ReferenceProperty --- lib/meta/module_meta_data.dart | 68 ++++++++++--- lib/meta/modules.dart | 5 + lib/meta/rolestarter_meta.dart | 51 ++++++++++ lib/page/page_manager.dart | 4 + lib/setting/global_data.dart | 17 +--- metatool/bin/page_generator.dart | 169 ++++++++++++++++++++++++++++++- 6 files changed, 282 insertions(+), 32 deletions(-) create mode 100644 lib/meta/rolestarter_meta.dart diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart index 659d488..2634f52 100644 --- a/lib/meta/module_meta_data.dart +++ b/lib/meta/module_meta_data.dart @@ -34,10 +34,6 @@ enum DisplayType { text, } -class DummyModule extends ModuleMetaData { - DummyModule() : super('', [], []); -} - /// Describes a field of the page. class FieldMetaData extends WidgetMetaData { DataType dataType; @@ -48,6 +44,8 @@ class FieldMetaData extends WidgetMetaData { : super(name, WidgetType.field); } +/// Describes the list page, that displays an overview over all records of the +/// related table. class ListPageMetaData extends PageMetaData { final String tableHeaders; final String tableColumns; @@ -105,6 +103,38 @@ class ListPageMetaData extends PageMetaData { name: name, fields: fields, globalComboBoxes: globalComboBoxes); } +/// Describes a mapping page, that allows assignments of many table entries +/// of the member table to one entry of a common table. +/// +/// Example: Common table is roles, member table is starters. Than the page +/// allows to assign some starters (menu items) to a role. +class MappingPageMetaData extends PageMetaData { + ModuleMetaData? commonModule; + ModuleMetaData? memberModule; + PropertyMetaData? commonProperty; + PropertyMetaData? memberProperty; + + /// Constructor. + /// + /// [name] is the page name. It must be unique over all pages in the module. + /// Default is a name derived from the page type. + /// + /// [commonModuleName]: the name of the module containing the common entries + /// of the relation. + /// + /// [memberModuleName]: the name of the module containing the member entries + /// that belongs to exactly one entry in the common module. + /// + /// [commonPropertyName]: the name of the property representing the common module. + /// + /// [memberPropertyName]: the name of the property representing the member module. + /// + /// Don't forget to initialize the [commonModule], [memberModule], + /// [commonProperty] and [memberProperty] in the overridden method [onInitialized]. + MappingPageMetaData(String label, {String name = ''}) + : super(label, PageType.mapping, name: name, fields: []); +} + class MetaException extends FormatException {} /// Stores the meta data of a module. @@ -290,13 +320,6 @@ class ModuleMetaData { return rc; } - /// Returns the meta data of a property given by its [name] or null if missing. - PropertyMetaData? propertyByName(String name) { - final rc = propertyList.singleWhere((element) => element.name == name, - orElse: null); - return rc; - } - /// Returns a property given by the [columnName]. PropertyMetaData? propertyByColumnName(String columnName) { PropertyMetaData? rc; @@ -309,6 +332,13 @@ class ModuleMetaData { return rc; } + /// Returns the meta data of a property given by its [name] or null if missing. + PropertyMetaData? propertyByName(String name) { + final rc = propertyList.singleWhere((element) => element.name == name, + orElse: null); + return rc; + } + /// Returns the properties that are not in [metaColumns]. /// : if the name of the property is [included] the property is always part of /// the result. @@ -323,7 +353,7 @@ class PageMetaData { String name = ''; final String label; final PageType pageType; - ModuleMetaData module = DummyModule(); + late ModuleMetaData module; final List fields; final String globalComboBoxes; PageMetaData(this.label, this.pageType, @@ -369,11 +399,11 @@ class PageMetaData { } } -enum PageType { create, custom, delete, edit, list } +enum PageType { create, custom, delete, edit, list, mapping } /// Stores the meta data of a module property stored as column in a database. class PropertyMetaData extends WidgetMetaData { - ModuleMetaData module = DummyModule(); + late ModuleMetaData module; final String label; /// Relative width of the field in a 12 column form: 1 <= width <= 12 @@ -414,6 +444,14 @@ class PropertyMetaData extends WidgetMetaData { } } +class ReferenceProperty extends WidgetMetaData { + /// This attribute gets is real value after the construcctor of the module. + PropertyMetaData child = PropertyMetaData('dummy', '', DataType.bool, ''); + final String nameChild; + ReferenceProperty({required this.nameChild}) + : super(nameChild + 'Ref', WidgetType.referenceProperty); +} + /// Describes a widget used in the page. /// Base class of all other widgets. class WidgetMetaData { @@ -422,4 +460,4 @@ class WidgetMetaData { WidgetMetaData(this.name, this.widgetType); } -enum WidgetType { button, field, dbField, property } +enum WidgetType { button, field, dbField, property, referenceProperty } diff --git a/lib/meta/modules.dart b/lib/meta/modules.dart index 13cd8da..9334a33 100644 --- a/lib/meta/modules.dart +++ b/lib/meta/modules.dart @@ -2,6 +2,7 @@ import 'module_meta_data.dart'; import 'benchmarks_meta.dart'; import 'roles_meta.dart'; +import 'rolestarter_meta.dart'; import 'starters_meta.dart'; import 'structures_meta.dart'; import 'users_meta.dart'; @@ -17,6 +18,9 @@ ModuleMetaData? moduleByName(String name) { case 'Roles': rc = RolesMeta(); break; + case 'Rolestarter': + rc = RoleStarterMeta(); + break; case 'Starters': rc = StartersMeta(); break; @@ -37,6 +41,7 @@ List moduleNames() { return [ 'Benchmarks', 'Roles', + 'Rolestarter', 'Starters', 'Structures', 'Users', diff --git a/lib/meta/rolestarter_meta.dart b/lib/meta/rolestarter_meta.dart new file mode 100644 index 0000000..35c7144 --- /dev/null +++ b/lib/meta/rolestarter_meta.dart @@ -0,0 +1,51 @@ +import '../base/defines.dart'; +import '../base/i18n.dart'; +import 'module_meta_data.dart'; +import 'roles_meta.dart'; +import 'starters_meta.dart'; + +final i18n = I18N(); +final M = i18n.module("RoleStarter"); + +class RoleStarterMeta extends ModuleMetaData { + static RoleStarterMeta instance = RoleStarterMeta.internal(); + factory RoleStarterMeta() { + return instance; + } + RoleStarterMeta.internal() + : super('RoleStarter', [ + PropertyMetaData('id', i18n.tr('Id'), DataType.reference, ':primary:', + displayType: DisplayType.combobox), + PropertyMetaData( + 'role', i18n.tr('Role'), DataType.reference, ':notnull:', + displayType: DisplayType.combobox, + foreignKey: 'roles.role_id;role_name;role'), + PropertyMetaData( + 'starter', i18n.tr('Starter'), DataType.reference, ':notnull:', + displayType: DisplayType.combobox, + foreignKey: 'starters.starter_id;starter_name;starter'), + PropertyMetaData('createdAt', i18n.tr('Created at'), + DataType.datetime, ':hidden:'), + PropertyMetaData( + 'createdBy', i18n.tr('Created by'), DataType.string, ':hidden:', + size: 32), + PropertyMetaData('changedAt', i18n.tr('Changed at'), + DataType.datetime, ':hidden:'), + PropertyMetaData( + 'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:', + size: 32), + ], [ + MappingPageMetaData( + i18n.tr('Role-Starter Assignment'), + ), + ]); + @override + void onInitialized() { + super.onInitialized(); + final page = pageByName('mapping') as MappingPageMetaData; + page.commonModule = RolesMeta(); + page.memberModule = StartersMeta(); + page.commonProperty = propertyByName('role'); + page.memberProperty = propertyByName('starter'); + } +} diff --git a/lib/page/page_manager.dart b/lib/page/page_manager.dart index 7eb7266..581a5e4 100644 --- a/lib/page/page_manager.dart +++ b/lib/page/page_manager.dart @@ -9,6 +9,7 @@ import 'benchmarks/list_benchmark_page.dart'; import 'roles/create_role_page.dart'; import 'roles/edit_role_page.dart'; import 'roles/list_role_page.dart'; +import 'rolestarter/mapping_rolestarter_page.dart'; import 'starters/create_starter_page.dart'; import 'starters/edit_starter_page.dart'; import 'starters/delete_starter_page.dart'; @@ -59,6 +60,9 @@ class PageManager { case '/Roles/list': rc = ListRolePage(); break; + case '/RoleStarter/mapping': + rc = MappingRoleStarterPage(); + break; case '/Starters/create': rc = CreateStarterPage(); break; diff --git a/lib/setting/global_data.dart b/lib/setting/global_data.dart index 618cfef..9c1215c 100644 --- a/lib/setting/global_data.dart +++ b/lib/setting/global_data.dart @@ -17,16 +17,6 @@ typedef DrawerBuilder = Function(dynamic); typedef FooterBuilder = FooterInterface? Function(); -class DummyFooter implements FooterInterface { - @override - Widget widget(PageControllerExhibition controller) { - return const Text(''); - } - - /// Returns a method creating a footer. - static DummyFooter? builder() => DummyFooter(); -} - abstract class FooterInterface { Widget widget(PageControllerExhibition controller); } @@ -47,15 +37,11 @@ class GlobalData { final FooterBuilder footerBuilder; final BaseConfiguration configuration; final RestPersistence? restPersistence; - HomeDirectories homeDirectories = HomeDirectories.dummy(); + late HomeDirectories homeDirectories; final navigatorStack = NavigatorStack(); factory GlobalData() => _instance ?? GlobalData(); - GlobalData.dummy() - : this.internal(BaseConfiguration({}, globalLogger), (input) => '', - (input) => '', DummyFooter.builder, null, globalLogger); - /// [configuration]: general settings. /// [appBarBuilder]: a factory to create the Hamburger menu. /// [footerBuilder]: a factory to create a footer area. @@ -115,7 +101,6 @@ class HomeDirectories { throw FormatException('HomeDirectory.getHomePath(): unknown platform'); } } - HomeDirectories.dummy(); } /// Manages the call stack of the navigator. diff --git a/metatool/bin/page_generator.dart b/metatool/bin/page_generator.dart index 80e4a37..37af3dc 100644 --- a/metatool/bin/page_generator.dart +++ b/metatool/bin/page_generator.dart @@ -247,6 +247,160 @@ class ListUserCustom extends State { } } +class _FieldData { + int thePageSize = 10; + int theOffset = 0; +#DEF_FIELDS} +'''; + + static final templateMappingCustom = + '''// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; + +import '../../base/defines.dart'; +import '../../base/helper.dart'; +import '../../base/i18n.dart'; +import '../../services/global_widget.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import '../../widget/widget_form.dart'; +import '../../persistence/persistence.dart'; +import 'list_user_page.dart'; + +final i18n = I18N(); + +class MappingUserCustom extends State { + final globalData = GlobalData(); + late Future _futureDbData; + AttendedPage? attendedPage; + final _fieldData = _FieldData(); + final GlobalKey _formKey = + GlobalKey(debugLabel: 'CreateUser'); +#DEF_CONTROLLERS MappingUserCustom(); + @override + Widget build(BuildContext context) { + final rc = Scaffold( + appBar: globalData.appBarBuilder(i18n.tr('Overview users')), + drawer: globalData.drawerBuilder(context), + floatingActionButton: FloatingActionButton( + onPressed: () { + globalData.navigate(context, '/Users/create'); + }, + child: const Icon(Icons.add), + tooltip: #TIP_ADD + ), + body: SafeArea( + child: FutureBuilder( + future: _futureDbData, + builder: (context, snapshot) { + Widget rc; + if (snapshot.connectionState != ConnectionState.done) { + rc = const CircularProgressIndicator(); + } else { + if (snapshot.hasData) { + final rows = attendedPage!.getRows(dbData: snapshot.data!, + columnList: '#ROW_COLUMNS', + onDone: () => setState(() => 1), + route#EDIT1: '/Users/#EDIT2', + context: context); + rc = buildFrame(totalCount: snapshot.data?.count ?? rows.length, + rows: rows); + } else if (snapshot.hasError) { + rc = Text('Backend problem: \${snapshot.error}'); + } else { + rc = const CircularProgressIndicator(); + } + } + return rc; + }, + )), + ); + return rc; + } + + Widget buildFrame({required JsonList rows, required int totalCount}){ + final padding = GlobalThemeData.padding; +#INIT_COMBOS final formItems = [ +#FORM_ITEMS FormItem( + ElevatedButton( + onPressed: () => search(), + child: Text(i18n.tr('Search'))), + weight: 12, + gapAbove: padding), + ]; + final form = Form( + key: _formKey, + child: Card( + color: GlobalThemeData.formBackgroundColor, + elevation: GlobalThemeData.formElevation, + margin: + EdgeInsets.symmetric(vertical: padding, horizontal: padding), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: padding, horizontal: padding), + child: WidgetForm.flexibleGrid(formItems, + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding)))); + final table = DataTable( + columns: [ +#TABLE_HEADER ], + rows: rows as List, + ); + Widget? tabBar = attendedPage!.buildChipBar( + totalCount: totalCount, + offset: _fieldData.theOffset, + pageSize: _fieldData.thePageSize, + onTap: (offset) { + _fieldData.theOffset = offset; + requestRecords(); + setState(() => 1); + }); + final frameWidget = ListView(children: [ + form, + if (tabBar != null) tabBar, + SizedBox(height: padding), + SizedBox(width: double.infinity, child: table), + ]); + return frameWidget; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + requestRecords(); + } + + @override + void dispose() { + helperDummyUsage(DataType.string); + globalWidgetDummyUsage(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + } + + void requestRecords() => _futureDbData = globalData.restPersistence!.query( + what: 'query', data: { + 'module': 'Users', + 'sql': 'list', + 'offset': _fieldData.theOffset, + 'size': _fieldData.thePageSize, +#PARAM_DEF }); + + void search() { + attendedPage!.pageStates.dbDataState.clear(); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + requestRecords(); + setState(() => 1); + } + } +} + class _FieldData { int thePageSize = 10; int theOffset = 0; @@ -803,6 +957,9 @@ StatefulWidget? customPageByRoute(String route) { case PageType.custom: rc = createRecordCustomized(page); break; + case PageType.mapping: + rc = createMappingCustomized(page as MappingPageMetaData); + break; } return rc; } @@ -826,6 +983,16 @@ StatefulWidget? customPageByRoute(String route) { return rc; } + /// Creates the customozed part of the [page] that is PageType.mapping. + String createMappingCustomized(MappingPageMetaData page) { + var rc = replaceVariables(templateMappingCustom, page) + .replaceFirst('#DEF_CONTROLLERS', buildDefinitionControllers(page)) + .replaceFirst('#INIT_COMBOS', buildInitializeComboBoxes(page)) + .replaceFirst('#FORM_ITEMS', buildFormItems(page, withController: true)) + .replaceFirst('#PARAM_DEF', buildParamDefinitions(page)); + return rc; + } + /// Returns a Dart class definition of the [page] that should not be modified. String createPage(PageMetaData page) { var rc = replaceVariables(templatePage, page); @@ -896,8 +1063,8 @@ StatefulWidget? customPageByRoute(String route) { hasPrimary = true; break; case PageType.custom: - break; case PageType.list: + case PageType.mapping: break; } var rc = replaceVariables(templateRecordCustom, page) -- 2.39.5