From: Hamatoma Date: Mon, 19 Oct 2020 06:05:46 +0000 (+0200) Subject: daily work: X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=077347096d37bdc084716d9aac6c754f85f92aa6;p=flutter_bones.git daily work: --- diff --git a/data/ddl/role.sql b/data/ddl/role.sql new file mode 100644 index 0000000..73b38db --- /dev/null +++ b/data/ddl/role.sql @@ -0,0 +1,12 @@ +drop table if exists role; +create table role ( + role_id int(10) notnull unique, + role_name varchar(32) unique notnull, + role_priority int(10), + role_active char(1), + role_createdat timestamp null, + role_createdby varchar(16), + role_changedat timestamp null, + role_changedby varchar(16), + primary role_id +); diff --git a/data/ddl/user.sql b/data/ddl/user.sql new file mode 100644 index 0000000..1b4d6b7 --- /dev/null +++ b/data/ddl/user.sql @@ -0,0 +1,14 @@ +drop table if exists user; +create table user ( + user_id int(10) notnull unique, + user_name varchar(64) unique notnull, + user_displayname varchar(32) unique, + user_email varchar(128) unique, + user_password varchar(128), + user_role int(10), + user_createdat timestamp null, + user_createdby varchar(16), + user_changedat timestamp null, + user_changedby varchar(16), + primary user_id +); diff --git a/data/rest/role.yaml b/data/rest/role.yaml new file mode 100644 index 0000000..cd080f3 --- /dev/null +++ b/data/rest/role.yaml @@ -0,0 +1,23 @@ +--- +# configuration of the bones backend for role: +created: 2020.10.17 01:05:34 +author: flutter_bones.module_model.exportSqlBackend() +version: 1.0.0 +modules: + - module: role + list: + - name: insert + type: insert + sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby,role_changedat,role_changedby) + VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby,:role_changedat,:role_changedby);" + - name: update + type: update + sql: "UPDATE role SET + role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_createdat=:role_createdat,role_createdby=:role_createdby,role_changedat=NOW(),role_changedby=:role_changedby + WHERE role_id=:role_id"; + - name: delete + type: delete + sql: "DELETE from role WHERE role_id=:role_id;" + - name: record + type: record + sql: "SELECT * from role WHERE role_id=:role_id;" diff --git a/data/rest/user.yaml b/data/rest/user.yaml new file mode 100644 index 0000000..4145774 --- /dev/null +++ b/data/rest/user.yaml @@ -0,0 +1,23 @@ +--- +# configuration of the bones backend for user: +created: 2020.10.17 01:05:34 +author: flutter_bones.module_model.exportSqlBackend() +version: 1.0.0 +modules: + - module: user + list: + - name: insert + type: insert + sql: "INSERT INTO user(user_name,user_displayname,user_email,user_password,user_role,user_createdat,user_createdby,user_changedat,user_changedby) + VALUES(:user_name,:user_displayname,:user_email,:user_password,:user_role,NOW(),:user_createdby,:user_changedat,:user_changedby);" + - name: update + type: update + sql: "UPDATE user SET + user_name=:user_name,user_displayname=:user_displayname,user_email=:user_email,user_password=:user_password,user_role=:user_role,user_createdat=:user_createdat,user_createdby=:user_createdby,user_changedat=NOW(),user_changedby=:user_changedby + WHERE user_id=:user_id"; + - name: delete + type: delete + sql: "DELETE from user WHERE user_id=:user_id;" + - name: record + type: record + sql: "SELECT * from user WHERE user_id=:user_id;" diff --git a/lib/flutter_bones.dart b/lib/flutter_bones.dart index 0d8fa51..da5cb5e 100644 --- a/lib/flutter_bones.dart +++ b/lib/flutter_bones.dart @@ -15,6 +15,7 @@ export 'src/model/model_types.dart'; export 'src/model/module_model.dart'; export 'src/model/page_model.dart'; export 'src/model/section_model.dart'; +export 'src/model/standard/configuration_model.dart'; export 'src/model/standard/role_model.dart'; export 'src/model/standard/user_model.dart'; export 'src/model/text_field_model.dart'; @@ -24,6 +25,8 @@ export 'src/page/login_page.dart'; export 'src/page/page_data.dart'; export 'src/page/role/role_create_page.dart'; export 'src/page/user_page.dart'; +export 'src/persistence/persistence.dart'; +export 'src/persistence/rest_persistence.dart'; export 'src/widget/raised_button_bone.dart'; export 'src/widget/simple_form.dart'; export 'src/widget/text_form_field_bone.dart'; diff --git a/lib/src/controller/base_controller.dart b/lib/src/controller/base_controller.dart new file mode 100644 index 0000000..46b9533 --- /dev/null +++ b/lib/src/controller/base_controller.dart @@ -0,0 +1,11 @@ +import 'package:flutter_bones/flutter_bones.dart'; + +/// Base class of all controller classes in this package. +abstract class BaseController { + /// Returns the assigned model. + WidgetModel getModel(); + + /// Returns a unique name of the controller inside the module, mostly the + /// widget name of the assigned model. + String getName(); +} diff --git a/lib/src/controller/button_controller.dart b/lib/src/controller/button_controller.dart new file mode 100644 index 0000000..7d5347c --- /dev/null +++ b/lib/src/controller/button_controller.dart @@ -0,0 +1,48 @@ +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/controller/base_controller.dart'; + +import '../widget/raised_button_bone.dart'; + +class ButtonController extends BaseController + implements ButtonCallbackController { + ButtonModel model; + + ButtonController(this.model); + + @override + WidgetModel getModel() { + return model; + } + + @override + String getName() { + return model.widgetName(); + } + + @override + getOnHighlightChanged( + String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnLongPressed(String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnPressed(String customString, ButtonCallbackController controller) { + var rc; + switch (model.buttonModelType) { + case ButtonModelType.cancel: + break; + case ButtonModelType.custom: + break; + case ButtonModelType.search: + break; + case ButtonModelType.store: + break; + } + return rc; + } +} diff --git a/lib/src/controller/combobox_controller.dart b/lib/src/controller/combobox_controller.dart new file mode 100644 index 0000000..322f642 --- /dev/null +++ b/lib/src/controller/combobox_controller.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bones/flutter_bones.dart'; + +import '../widget/dropdown_button_form_bone.dart'; +import 'base_controller.dart'; + +class ComboboxController extends BaseController + implements ComboboxCallbackController { + ComboboxModel model; + + ComboboxController(this.model); + + @override + WidgetModel getModel() { + return model; + } + + @override + String getName() { + return model.widgetName(); + } + + @override + getOnChanged(String customString, ComboboxCallbackController controller) { + return null; + } + + @override + getOnSaved(String customString, ComboboxCallbackController controller) { + return null; + } + + @override + getOnSelectedItemBuilder( + String customString, ComboboxCallbackController controller) { + return null; + } + + @override + getOnTap(String customString, ComboboxCallbackController controller) { + return null; + } + + @override + getOnValidator(String customString, ComboboxCallbackController controller) { + return null; + } + + @override + List texts() { + return model.texts; + } + + @override + List values() { + return model.values; + } +} diff --git a/lib/src/controller/text_form_controller.dart b/lib/src/controller/text_form_controller.dart new file mode 100644 index 0000000..d7cd775 --- /dev/null +++ b/lib/src/controller/text_form_controller.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bones/flutter_bones.dart'; + +import '../widget/text_form_field_bone.dart'; +import 'base_controller.dart'; + +class TextFormController extends BaseController + implements TextFormCallbackController { + ComboboxModel model; + + TextFormController(this.model); + + @override + WidgetModel getModel() { + return model; + } + + @override + String getName() { + return model.widgetName(); + } + + @override + getOnChanged(String customString, TextFormCallbackController controller) { + return null; + } + + @override + getOnEditingComplete( + String customString, TextFormCallbackController controller) { + return null; + } + + @override + getOnFieldSubmitted( + String customString, TextFormCallbackController controller) { + return null; + } + + @override + getOnSaved(String customString, TextFormCallbackController controller) { + return null; + } + + @override + getOnTap(String customString, TextFormCallbackController controller) { + return null; + } + + @override + getValidator(String customString, TextFormCallbackController controller) { + return null; + } +} diff --git a/lib/src/helper/settings.dart b/lib/src/helper/settings.dart index 2836e36..a3dfb5a 100644 --- a/lib/src/helper/settings.dart +++ b/lib/src/helper/settings.dart @@ -1,39 +1,36 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart'; -class Settings { +class BaseSettings { static Locale locale = Locale('US', 'en'); - static final mapWidgetData = { - 'form.card.padding': '16.0', - 'form.gap.field_button.height': '16.0', - }; - static Settings _instance; + static get language => locale.languageCode; - final BaseConfiguration widgetConfiguration; + final BaseConfiguration configuration; final BaseLogger logger; - factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) { - if (_instance == null) { - _instance = Settings.internal( - widgetConfiguration == null - ? BaseConfiguration(mapWidgetData, logger) - : widgetConfiguration, - logger); - } - return _instance; - } - - Settings.internal(this.widgetConfiguration, this.logger); + BaseSettings(this.configuration, this.logger); /// Sets the locale code. /// [locale] the info about localisation /// Finding [locale] of an app: @see https://github.com/flutter/website/blob/master/examples/internationalization/minimal/lib/main.dart - static setLocale(locale) => Settings.locale = locale; + static setLocale(locale) { + BaseSettings.locale = locale; + Intl.defaultLocale = "${locale.languageCode}_$locale.countryCode"; + initializeDateFormatting(); + } - static setLocaleByNames({String country = 'US', String language = 'en'}) => - Settings.locale = Locale(country, language); + /// Sets the locales with [language] and [country]. + static setLocaleByNames({String country, @required String language}) { + country ??= language; + country = country.toUpperCase(); + country = country == 'EN' ? 'US' : country; + language = language.toLowerCase(); + setLocale(Locale(language, country)); + } /// Translates a [text] with a given translation [map]. /// Structure of the [map]: { : { : translation } } @@ -52,3 +49,25 @@ class Settings { return rc; } } + +class Settings extends BaseSettings { + static final mapWidgetData = { + 'form.card.padding': '16.0', + 'form.gap.field_button.height': '16.0', + }; + static Settings _instance; + + factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) { + if (_instance == null) { + _instance = Settings.internal( + widgetConfiguration == null + ? BaseConfiguration(mapWidgetData, logger) + : widgetConfiguration, + logger); + } + return _instance; + } + + Settings.internal(BaseConfiguration configuration, BaseLogger logger) + : super(configuration, logger); +} diff --git a/lib/src/helper/validators.dart b/lib/src/helper/validators.dart index 7bb567b..369f054 100644 --- a/lib/src/helper/validators.dart +++ b/lib/src/helper/validators.dart @@ -46,7 +46,7 @@ String checkPhoneNumber(String input) => Validation.isPhoneNumber(input) {'0': input}); String _vt(String key, [Map placeholders]) => - Settings.translate(key, ValidatorTranslations.translations, + BaseSettings.translate(key, ValidatorTranslations.translations, placeholders: placeholders); @protected diff --git a/lib/src/model/button_model.dart b/lib/src/model/button_model.dart index eb5721c..3fba73b 100644 --- a/lib/src/model/button_model.dart +++ b/lib/src/model/button_model.dart @@ -5,7 +5,7 @@ import 'package:flutter_bones/flutter_bones.dart'; typedef ButtonOnPressed = void Function(String name); /// Describes a button widget. -class ButtonModel extends WidgetModel implements ButtonCallbackController { +class ButtonModel extends WidgetModel { static final regExprOptions = RegExp(r'^(undef)$'); String text; String name; @@ -48,22 +48,6 @@ class ButtonModel extends WidgetModel implements ButtonCallbackController { @override String widgetName() => name; - - @override - getOnHighlightChanged( - String customString, ButtonCallbackController controller) { - return null; - } - - @override - getOnLongPressed(String customString, ButtonCallbackController controller) { - return null; - } - - @override - getOnPressed(String customString, ButtonCallbackController controller) { - return onPressed; - } } enum ButtonModelType { diff --git a/lib/src/model/combobox_model.dart b/lib/src/model/combobox_model.dart index 395c660..695a47d 100644 --- a/lib/src/model/combobox_model.dart +++ b/lib/src/model/combobox_model.dart @@ -32,7 +32,7 @@ class ComboboxModel extends FieldModel { super.parse(); checkSuperfluousAttributes( map, - 'name label dataType options texts toolTip widgetType values' + 'name label dataType filterType options texts toolTip widgetType values' .split(' ')); texts = parseStringList('texts', map); values = parseValueList('values', map, dataType); diff --git a/lib/src/model/field_model.dart b/lib/src/model/field_model.dart index d9185fb..5d5b0c8 100644 --- a/lib/src/model/field_model.dart +++ b/lib/src/model/field_model.dart @@ -9,8 +9,6 @@ import 'widget_model.dart'; /// Base class of widgets with user interaction like text field, combobox, checkbox. abstract class FieldModel extends WidgetModel { - static final regExprOptions = - RegExp(r'^(undef|readonly|disabled|password|required)$'); String name; String label; String toolTip; diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index 2d781e2..6e6bb38 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -119,6 +119,9 @@ class ModuleModel extends ModelBase { case FilterType.dateTimeTil: buffer.write('${field.name}<=:${field.name}'); break; + case FilterType.equals: + buffer.write('${field.name} like :${field.name}'); + break; case FilterType.pattern: buffer.write('${field.name} like :${field.name}'); break; @@ -192,7 +195,7 @@ modules: type = 'DOUBLE'; break; case DataType.int: - type = 'INT(10)'; + type = column.hasOption('primary') ? 'INT(10) UNSIGNED' : 'INT(10)'; break; case DataType.reference: type = 'INT(10) UNSIGNED'; @@ -211,10 +214,15 @@ modules: } String options = ''; for (var option in column.options) { - if ('notnull null unique'.contains(option)) { - options += ' ' + option; + if (option == 'notnull') { + options += ' NOT NULL'; + } else if ('null unique'.contains(option)) { + options += ' ' + option.toUpperCase(); } } + if (column.hasOption('primary')) { + options += ' AUTO_INCREMENT'; + } buffer.write(' ${column.name} $type$options,\n'); } buffer.write(' PRIMARY KEY(${table.name}_id)\n'); diff --git a/lib/src/model/standard/configuration_model.dart b/lib/src/model/standard/configuration_model.dart new file mode 100644 index 0000000..fcc66a6 --- /dev/null +++ b/lib/src/model/standard/configuration_model.dart @@ -0,0 +1,122 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter_bones/flutter_bones.dart'; + +/// +class ConfigurationModel extends ModuleModel { + static final yamlMap = { + // + "module": "configuration", + "tables": [ + { + // configuration_id | configuration_scope | configuration_property | configuration_order | configuration_type + // | configuration_value | configuration_description + 'table': 'configuration', + 'columns': [ + { + 'column': 'configuration_id', + 'dataType': 'int', + 'label': 'Id', + 'options': 'primary', + }, + { + 'column': 'configuration_scope', + 'dataType': 'string', + 'label': 'Bereich', + 'size': 64, + 'options': 'unique;notnull', + }, + { + 'column': 'configuration_property', + 'dataType': 'string', + 'size': 64, + 'label': 'Eigenschaft', + }, + { + 'column': 'configuration_order', + 'dataType': 'int', + 'label': 'Reihe', + }, + { + 'column': 'configuration_type', + 'dataType': 'string', + 'size': 16, + 'label': 'Datentyp', + }, + { + 'column': 'configuration_value', + 'dataType': 'string', + 'size': 255, + 'label': 'Wert', + }, + { + 'column': 'configuration_description', + 'dataType': 'string', + 'size': 255, + 'label': 'Beschreibung', + }, + ] + }, + ], + 'pages': [ + { + "page": "create", + "pageType": "create", + "sections": [ + { + "sectionType": "simpleForm", + "children": [ + { + "widgetType": "allDbFields", + } + ] + } + ] + }, + { + "page": "change", + "pageType": "change", + "sections": [ + { + "sectionType": "simpleForm", + "children": [ + { + "widgetType": "allDbFields", + } + ] + } + ] + }, + { + "page": "list", + "pageType": "list", + "sections": [ + { + "sectionType": "filterPanel", + "children": [ + { + "name": "configuration_name", + "widgetType": "textField", + "filterType": "pattern", + }, + { + "name": "configuration_scope", + "widgetType": "combobox", + "filterType": "equals", + "options": 'undef', + }, + ] + } + ] + }, + ], + }; + + ConfigurationModel(BaseLogger logger) : super(yamlMap, logger); + + /// Returns the name including the names of the parent + @override + String fullName() => name; + + @override + String widgetName() => name; +} diff --git a/lib/src/persistence/persistence.dart b/lib/src/persistence/persistence.dart new file mode 100644 index 0000000..c50ce3d --- /dev/null +++ b/lib/src/persistence/persistence.dart @@ -0,0 +1,30 @@ +import 'package:meta/meta.dart'; + +abstract class Persistence { + /// Deletes a record with primary key [id] of the [module] with the + /// SQL statement [sqlName]. + Future delete({@required String module, String sqlName, @required int id}); + + /// Inserts a record of the [module] with the SQL statement [sqlName] + /// and returns the primary key. + Future insert( + {@required String module, + String sqlName, + @required Map data}); + + /// Returns all records of the [module] specified with [params] using + /// a SQL statement named [sqlName]. + Future>> list( + {@required String module, String sqlName, Map params}); + + /// Returns a record with primary key [id] of the [module] with the + /// SQL statement [sqlName]. + Future> record( + {@required String module, String sqlName, @required int id}); + + /// Updates a record of [module] with the [data] using the SQL statement [sqlName]. + Future update( + {@required String module, + String sqlName, + @required Map data}); +} diff --git a/lib/src/persistence/rest_persistence.dart b/lib/src/persistence/rest_persistence.dart new file mode 100644 index 0000000..2542e92 --- /dev/null +++ b/lib/src/persistence/rest_persistence.dart @@ -0,0 +1,139 @@ +import 'dart:convert' as convert; + +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter_bones/src/persistence/persistence.dart'; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; + +/// A persistence layer using a REST interface to store/fetch the data. +class RestPersistence extends Persistence { + static RestPersistence _instance; + final String application; + final String version; + final BaseLogger logger; + final String host; + + final String scheme; + int port; + int sessionTimeout; + String uriPrefix; + + Map headers; + + factory RestPersistence( + {String application, + String version, + int port, + String host, + BaseLogger logger}) { + _instance ??= RestPersistence.internal( + application: application, + host: host, + port: port, + version: version, + logger: logger); + return _instance; + } + + factory RestPersistence.fromConfig( + BaseConfiguration configuration, BaseLogger logger) { + final section = 'client'; + _instance = RestPersistence.internal( + application: configuration.asString('application', section: section), + version: configuration.asString('version', section: section), + host: configuration.asString('host', section: section), + port: configuration.asInt('port', section: section), + scheme: configuration.asString('schema', + section: section, defaultValue: 'https'), + logger: logger); + + return _instance; + } + + RestPersistence.internal( + {this.application, + @required this.version, + @required this.host, + @required this.port, + this.scheme = 'http', + @required this.logger}) { + uriPrefix = '$scheme://$host:$port'; + } + + @override + Future delete({String module, String sqlName, int id}) async { + sqlName ??= 'delete'; + await runRequest(module, sqlName, 'delete', body: '{${module}_id:$id}'); + } + + @override + Future insert( + {String module, String sqlName, Map data}) async { + sqlName ??= 'insert'; + var rc = 0; + final data2 = data == null ? '{}' : convert.jsonEncode(data); + final answer = await runRequest(module, sqlName, 'insert', body: data2); + if (answer.isNotEmpty) { + final map = convert.jsonDecode(answer); + rc = map['id']; + } + return rc; + } + + /// Handles a HTTP request with a single HTTP connection. + /// [module]: the module which implies the requested table. + /// [sqlName]: the name of the SQL statement. + /// [sqlType]: 'insert', 'record', 'update', 'list', 'delete' + /// [body]: null or the body of the POST request, e.g. '{ 'role_id' + /// [headers]: the optional HTTP headers. + /// [result]: the body of the response or '' + Future runRequest(String module, String sqlName, String sqlType, + {String body, Map headers}) async { + var rc = ''; + final uri = '$uriPrefix/$application/$module/$sqlType/$sqlName/$version'; + http.Response response; + logger.log('request: POST $module $sqlName', LEVEL_LOOP); + response = await http.post(uri, body: body, headers: headers); + logger.log('status: ${response.statusCode}', LEVEL_LOOP); + if (response.statusCode != 200) { + logger.error('$uri: status: ${response.statusCode}'); + } else { + rc = response.body ?? ''; + } + return rc; + } + + @override + Future>> list( + {String module, String sqlName, Map params}) async { + sqlName ??= 'list'; + List> rc; + final answer = await runRequest(module, sqlName, 'list', + body: params == null ? '{}' : convert.jsonEncode(params)); + if (answer.isNotEmpty) { + rc = convert.jsonDecode(answer); + } + return rc; + } + + @override + Future> record( + {String module, String sqlName, int id}) async { + sqlName ??= 'record'; + Map rc; + final answer = + await runRequest(module, sqlName, 'record', body: '{${module}_id:$id}'); + if (answer.isNotEmpty) { + rc = convert.jsonDecode(answer); + } + return rc; + } + + @override + Future update( + {String module, String sqlName, Map data}) async { + sqlName ??= 'update'; + final data2 = data == null ? '{}' : convert.jsonEncode(data); + await runRequest(module, sqlName, 'record', body: data2); + } +} diff --git a/lib/src/widget/dropdown_button_form_bone.dart b/lib/src/widget/dropdown_button_form_bone.dart new file mode 100644 index 0000000..3bbedf6 --- /dev/null +++ b/lib/src/widget/dropdown_button_form_bone.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +/// Implements a [DropdownButtonFormField] with "outsourced" callbacks: +/// [customString] a string mostly used for a name needed in the [customController] +/// [callbackController] handles the callback methods. +class DropdownButtonFormBone extends DropdownButtonFormField { + final String customString; + final ComboboxCallbackController callbackController; + + DropdownButtonFormBone( + this.customString, + this.callbackController, { + Key key, + @required List> items, + T value, + Widget hint, + Widget disabledHint, + int elevation: 8, + TextStyle style, + Widget icon, + Color iconDisabledColor, + Color iconEnabledColor, + double iconSize: 24.0, + bool isDense: true, + bool isExpanded: false, + double itemHeight, + Color focusColor, + FocusNode focusNode, + bool autofocus: false, + Color dropdownColor, + InputDecoration decoration, + FormFieldSetter onSaved, + FormFieldValidator validator, + AutovalidateMode autovalidateMode, + }) : super( + key: key, + items: items, + selectedItemBuilder: callbackController.getOnSelectedItemBuilder( + customString, callbackController), + value: value, + hint: hint, + disabledHint: disabledHint, + onChanged: + callbackController.getOnChanged(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, + dropdownColor: dropdownColor, + decoration: decoration, + autovalidateMode: autovalidateMode, + ); +} + +/// Interface for a [DropdownButtonFormBone] specific callback controller. +abstract class ComboboxCallbackController { + ValueChanged getOnChanged( + String customString, ComboboxCallbackController controller); + + FormFieldSetter getOnSaved( + String customString, ComboboxCallbackController controller); + + DropdownButtonBuilder getOnSelectedItemBuilder( + String customString, ComboboxCallbackController controller); + + VoidCallback getOnTap( + String customString, ComboboxCallbackController controller); + + FormFieldValidator getOnValidator( + String customString, ComboboxCallbackController controller); + + String getName(); + + List texts(); + + List values(); +} diff --git a/lib/src/widget/filters.dart b/lib/src/widget/filters.dart index 99e290f..dd004e7 100644 --- a/lib/src/widget/filters.dart +++ b/lib/src/widget/filters.dart @@ -61,7 +61,7 @@ class FilterItem { class Filters implements - TextCallbackController, + TextFormCallbackController, ButtonCallbackController, TableCallbackController { var filters = []; @@ -80,12 +80,13 @@ class Filters filters.firstWhere((element) => element.name == name, orElse: () => null); @override - getOnChanged(String customString, TextCallbackController controller) { + getOnChanged(String customString, TextFormCallbackController controller) { return null; } @override - getOnEditingComplete(String customString, TextCallbackController controller) { + getOnEditingComplete( + String customString, TextFormCallbackController controller) { return null; } @@ -96,7 +97,8 @@ class Filters } @override - getOnFieldSubmitted(String customString, TextCallbackController controller) { + getOnFieldSubmitted(String customString, + TextFormCallbackController controller) { return null; } @@ -126,19 +128,19 @@ class Filters } @override - getOnSaved(String customString, TextCallbackController controller) { + getOnSaved(String customString, TextFormCallbackController controller) { return (input) { byName(customString).value = input; }; } @override - getOnTap(String customString, TextCallbackController controller) { + getOnTap(String customString, TextFormCallbackController controller) { return null; } @override - getValidator(String customString, TextCallbackController controller) { + getValidator(String customString, TextFormCallbackController controller) { return null; } diff --git a/lib/src/widget/module_controller.dart b/lib/src/widget/module_controller.dart index 49c10c8..6570b82 100644 --- a/lib/src/widget/module_controller.dart +++ b/lib/src/widget/module_controller.dart @@ -11,7 +11,7 @@ import 'widget_helper.dart'; // This interface allows the generic handling of the edit form by a model driven module. class ModuleController - implements TextCallbackController, ButtonCallbackController { + implements TextFormCallbackController, ButtonCallbackController { final ModuleModel moduleModel; String primaryColumn; WidgetList createWidgets; @@ -33,17 +33,19 @@ class ModuleController ModuleModel getModuleModel() => moduleModel; @override - getOnChanged(String customString, TextCallbackController controller) { + getOnChanged(String customString, TextFormCallbackController controller) { return null; } @override - getOnEditingComplete(String customString, TextCallbackController controller) { + getOnEditingComplete( + String customString, TextFormCallbackController controller) { return null; } @override - getOnFieldSubmitted(String customString, TextCallbackController controller) { + getOnFieldSubmitted(String customString, + TextFormCallbackController controller) { return null; } @@ -73,7 +75,7 @@ class ModuleController } @override - getOnSaved(String customString, TextCallbackController controller) { + getOnSaved(String customString, TextFormCallbackController controller) { final rc = (input) { values[customString] = StringHelper.fromString(input, dataTypes[customString]); @@ -82,12 +84,12 @@ class ModuleController } @override - getOnTap(String customString, TextCallbackController controller) { + getOnTap(String customString, TextFormCallbackController controller) { return null; } @override - getValidator(String customString, TextCallbackController controller) { + getValidator(String customString, TextFormCallbackController controller) { return null; } diff --git a/lib/src/widget/text_form_field_bone.dart b/lib/src/widget/text_form_field_bone.dart index e8c01de..e3a9634 100644 --- a/lib/src/widget/text_form_field_bone.dart +++ b/lib/src/widget/text_form_field_bone.dart @@ -2,24 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// Interface for a [TextFormField] specific callback controller. -abstract class TextCallbackController { +abstract class TextFormCallbackController { ValueChanged getOnChanged( - String customString, TextCallbackController controller); + String customString, TextFormCallbackController controller); VoidCallback getOnEditingComplete( - String customString, TextCallbackController controller); + String customString, TextFormCallbackController controller); ValueChanged getOnFieldSubmitted( - String customString, TextCallbackController controller); + String customString, TextFormCallbackController controller); FormFieldSetter getOnSaved( - String customString, TextCallbackController controller); + String customString, TextFormCallbackController controller); - GestureTapCallback getOnTap( - String customString, TextCallbackController controller); + GestureTapCallback getOnTap(String customString, + TextFormCallbackController controller); - FormFieldValidator getValidator( - String customString, TextCallbackController controller); + FormFieldValidator getValidator(String customString, + TextFormCallbackController controller); } /// Implements a [TextFormField] with "outsourced" callbacks: @@ -27,17 +27,16 @@ abstract class TextCallbackController { /// [callbackController] handles the callback methods. class TextFormFieldBone extends TextFormField { final String customString; - final TextCallbackController callbackController; + final TextFormCallbackController callbackController; - TextFormFieldBone( - this.customString, - this.callbackController, { - Key key, - TextEditingController controller, - String initialValue, - FocusNode focusNode, - InputDecoration decoration = const InputDecoration(), - TextInputType keyboardType, + 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, diff --git a/lib/src/widget/view.dart b/lib/src/widget/view.dart index d820d8f..50b8144 100644 --- a/lib/src/widget/view.dart +++ b/lib/src/widget/view.dart @@ -1,14 +1,16 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bones/src/controller/button_controller.dart'; +import '../controller/combobox_controller.dart'; import '../helper/settings.dart'; -import '../model/button_model.dart'; import '../model/empty_line_model.dart'; import '../model/field_model.dart'; import '../model/section_model.dart'; import '../model/text_field_model.dart'; import '../model/text_model.dart'; import '../model/widget_model.dart'; +import 'dropdown_button_form_bone.dart'; import 'raised_button_bone.dart'; class View { @@ -22,32 +24,46 @@ class View { if (_instance == null) { _instance = View.internal(logger); _instance.settings = Settings(logger: logger); - _instance.widgetConfiguration = _instance.settings.widgetConfiguration; + _instance.widgetConfiguration = _instance.settings.configuration; } return _instance; } View.internal(this.logger); - /// Creates a button from the [model]. - Widget button(ButtonModel model) { + /// Creates a button from the [controller]. + Widget button(ButtonController controller) { final rc = RaisedButtonBone( - model.widgetName(), - model, - child: Text(model.text), + controller.getName(), + controller, + child: Text(controller.model.text), ); return rc; } - /// Creates a list of buttons from a list of [models]. - List buttonList(List models) { + /// Creates a list of buttons from a list of [controllers]. + List buttonList(List controllers) { final rc = []; - for (var item in models) { + for (var item in controllers) { rc.add(button(item)); } return rc; } + /// Creates a combobox via the [controller]. + Widget combobox(ComboboxController controller, onTap) { + final texts = controller.texts(); + final values = controller.values() ?? texts; + final items = >[]; + for (var ix = 0; ix < texts.length; ix++) { + items.add(DropdownMenuItem( + onTap: onTap, value: values[ix], child: Text(texts[ix]))); + } + final rc = + DropdownButtonFormBone(controller.getName(), controller, items: items); + return rc; + } + /// Creates a checkbox from the [model]. Widget checkbox(WidgetModel model) { var rc; @@ -82,9 +98,10 @@ class View { Form simpleForm({SectionModel model, 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 = widgetsOfSection(model.children); - final buttons = buttonList(model.buttons); + final controllers = model.buttons.map((button) => ButtonController(button)); + final buttons = buttonList(controllers); final rc = Form( key: formKey, child: Card( @@ -142,7 +159,7 @@ class View { rc.add(textField(child)); break; case WidgetModelType.button: - rc.add(button(child)); + rc.add(button(ButtonController(child))); break; case WidgetModelType.emptyLine: rc.add(emptyLine(child)); diff --git a/test/helpers/settings_test.dart b/test/helpers/settings_test.dart index 5413fd6..dda85c8 100644 --- a/test/helpers/settings_test.dart +++ b/test/helpers/settings_test.dart @@ -1,37 +1,47 @@ +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'; void main() { + Intl.defaultLocale = "de_DE"; + final logger = MemoryLogger(LEVEL_FINE); final map = { 'help': {'de': 'Hilfe'}, - 'wrong name %{0}': {'de': 'falscher name %{0}'} + 'wrong name %{0}': {'de': 'falscher Name %{0}'} }; - setUpAll(() => Settings.setLocaleByNames(language: 'en')); + setUpAll(() => BaseSettings.setLocaleByNames(language: 'en')); group('Settings', () { test('basic', () { - Settings.setLocale(Locale('de', 'de')); - expect(Settings.translate('abc', map), equals('abc')); - expect(Settings.translate('help', map), equals('help')); + BaseSettings.setLocale(Locale('de', 'DE')); + expect(BaseSettings.language, 'de'); + expect(BaseSettings.translate('abc', map), equals('abc')); + expect(BaseSettings.translate('help', map), equals('Hilfe')); expect( - Settings.translate('wrong name %{0}', map, + BaseSettings.translate('wrong name %{0}', map, placeholders: {'0': 'Joe'}), - equals('wrong name Joe')); + equals('falscher Name Joe')); + expect(Settings(logger: logger), isNotNull); }); test('translate-en', () { - Settings.setLocaleByNames(language: 'en'); - expect(Settings.translate('abc', map), equals('abc')); - expect(Settings.translate('help', map), equals('help')); + BaseSettings.setLocaleByNames(language: 'en'); + expect(BaseSettings.language, 'en'); + expect(BaseSettings.translate('abc', map), equals('abc')); + expect(BaseSettings.translate('help', map), equals('help')); expect( - Settings.translate('wrong name %{0}', map, + BaseSettings.translate('wrong name %{0}', map, placeholders: {'0': 'Joe'}), equals('wrong name Joe')); }); test('translate-de', () { - Settings.setLocaleByNames(language: 'de'); - expect(Settings.translate('abc', map), equals('abc')); - expect(Settings.translate('help', map), equals('Hilfe')); - expect(Settings.translate('wrong name %{0}', map, placeholders: {'0' : 'Joe'}), equals('falscher name Joe')); + BaseSettings.setLocaleByNames(language: 'de'); + expect(BaseSettings.translate('abc', map), equals('abc')); + expect(BaseSettings.translate('help', map), equals('Hilfe')); + expect( + BaseSettings.translate('wrong name %{0}', map, + placeholders: {'0': 'Joe'}), + equals('falscher Name Joe')); }); }); } diff --git a/test/model/model_test.dart b/test/model/model_test.dart index 9e85c68..a9bc067 100644 --- a/test/model/model_test.dart +++ b/test/model/model_test.dart @@ -40,7 +40,7 @@ void main() { }); }); group('ModelBase', () { - test('errors', () { + test('basic', () { logger.clear(); final map = cloneOfMap(userModel); final field = { @@ -94,8 +94,31 @@ void main() { }); }); group('CheckboxModel', () { - logger.clear(); + test('basic', () { + logger.clear(); + final map = cloneOfMap(userModel); + final field = { + 'widgetType': 'checkbox', + 'name': 'hidden', + 'label': 'Hidden', + 'options': 'required', + }; + map['pages'][0]['sections'][0]['children'][0] = field; + var module = Demo1(map, logger); + module.parse(); + var errors = logger.errors; + expect(errors.length, equals(0)); + final checkbox = module.pageByName('create').getField('hidden'); + expect(checkbox.hasOption('required'), isTrue); + checkbox.value = true; + expect(checkbox.value, isTrue); + checkbox.value = false; + expect(checkbox.value, isFalse); + checkbox.value = 'true'; + expect(checkbox.value, isTrue); + }); test('errors', () { + logger.clear(); final map = cloneOfMap(userModel); final field = { 'widgetType': 'checkbox', @@ -107,7 +130,7 @@ void main() { var module = Demo1(map, logger); module.parse(); var errors = logger.errors; - expect(errors.length, equals(2)); + expect(errors.length, equals(0)); final checkbox = module.pageByName('create').getField('hidden'); expect(checkbox, isNotNull); expect(checkbox.dataType, DataType.bool); @@ -138,7 +161,7 @@ void main() { var module = Demo1(map, logger); module.parse(); var errors = logger.errors; - expect(errors.length, equals(2)); + expect(errors.length, equals(0)); final combobox = module.pageByName('create').getField('class'); expect(combobox, isNotNull); expect(combobox.dataType, DataType.int); @@ -153,8 +176,8 @@ void main() { }); }); group('Non field widgets', () { - logger.clear(); test('basic', () { + logger.clear(); final map = cloneOfMap(userModel); final list = [ { @@ -166,15 +189,9 @@ void main() { var module = Demo1(map, logger); module.parse(); var errors = logger.errors; - expect(errors.length, equals(2)); + expect(errors.length, equals(0)); final dump = module.dump(StringBuffer()).toString(); - expect(dump, equals('''= module demo1: options: -== page create: PageModelType.create options: - = section simpleForm1: SectionModelType.simpleForm options: [ - emptyLine: options: - text 24 text: *Hi world*: options: rich - ] # create.simpleForm1 -''')); + expect(dump.contains('text: *Hi world*: options: rich'), isTrue); final page = module.pageByName('create'); final allWidgets = page.getWidgets(null); expect(allWidgets.length, equals(2)); @@ -190,12 +207,6 @@ void main() { expect(nonFieldWidgets.length, equals(2)); }); }); - group('standard-models', () { - test('user', () { - final model = UserModel(logger); - model.parse(); - }); - }); } final userModel = { diff --git a/test/model/standard_test.dart b/test/model/standard_test.dart new file mode 100644 index 0000000..27dd8b1 --- /dev/null +++ b/test/model/standard_test.dart @@ -0,0 +1,114 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:test/test.dart'; + +void main() { + final logger = MemoryLogger(LEVEL_FINE); + group('module', () { + test('role', () { + logger.clear(); + final module = RoleModel(logger); + module.parse(); + expect(module.fullName(), equals('role')); + expect(module.widgetName(), equals('role')); + final errors = logger.errors; + expect(errors.length, equals(0)); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, '''= module role: options: +== table role: options: + column role_id: DataType.int "Id" options: primary notnull unique + column role_name: DataType.string "Rolle" options: unique notnull + column role_priority: DataType.int "Priorität" options: + column role_active: DataType.bool "Aktiv" options: + column role_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore + column role_createdby: DataType.string "Erzeugt von" options: hidden doStore + column role_changedat: DataType.dateTime "Geändert" options: hidden null doStore + column role_changedby: DataType.string "Geändert von" options: hidden doStore +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields 10 text: options: + ] # create.simpleForm1 +== page change: PageModelType.change options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields 12 text: options: + ] # change.simpleForm1 +== page list: PageModelType.list options: + = section filterPanel1: SectionModelType.filterPanel options: [ + textField role_name: options: + ] # list.filterPanel1 +'''); + }); + test('user', () { + logger.clear(); + final module = UserModel(logger); + module.parse(); + expect(module.fullName(), equals('user')); + expect(module.widgetName(), equals('user')); + final errors = logger.errors; + expect(errors.length, equals(0)); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, '''= module user: options: +== table user: options: + column user_id: DataType.int "Id" options: primary notnull unique + column user_name: DataType.string "User" options: unique notnull + column user_displayname: DataType.string "Anzeigename" options: unique + column user_email: DataType.string "EMail" options: unique + column user_password: DataType.string "User" options: password + column user_role: DataType.reference "Role" options: + column user_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore + column user_createdby: DataType.string "Erzeugt von" options: hidden doStore + column user_changedat: DataType.dateTime "Geändert" options: hidden null doStore + column user_changedby: DataType.string "Geändert von" options: hidden doStore +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields 26 text: options: + ] # create.simpleForm1 +== page change: PageModelType.change options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields 28 text: options: + ] # change.simpleForm1 +== page list: PageModelType.list options: + = section filterPanel1: SectionModelType.filterPanel options: [ + textField user_name: options: + combobox user_role: texts: options: + ] # list.filterPanel1 +'''); + }); + test('configuration', () { + logger.clear(); + final module = ConfigurationModel(logger); + module.parse(); + expect(module.fullName(), equals('configuration')); + expect(module.widgetName(), equals('configuration')); + final errors = logger.errors; + expect(errors.length, equals(0)); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, startsWith('''= module configuration: options: +== table configuration: options: + column configuration_id: DataType.int "Id" options: primary notnull unique + column configuration_scope: DataType.string "Bereich" options: unique notnull + column configuration_property: DataType.string "Eigenschaft" options: + column configuration_order: DataType.int "Reihe" options: + column configuration_type: DataType.string "Datentyp" options: + column configuration_value: DataType.string "Wert" options: + column configuration_description: DataType.string "Beschreibung" options: + column configuration_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore + column configuration_createdby: DataType.string "Erzeugt von" options: hidden doStore + column configuration_changedat: DataType.dateTime "Geändert" options: hidden null doStore + column configuration_changedby: DataType.string "Geändert von" options: hidden doStore +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields ''')); + /* + expect(dump.contains('''text: options: + ] # create.simpleForm1 +== page change: PageModelType.change options: + = section simpleForm1: SectionModelType.simpleForm options: [ + allDbFields '''), isTrue); + */ + expect(dump, + contains('combobox configuration_scope: texts: options: undef')); + expect(dump, contains('textField configuration_name: options:')); + }); + }); +} diff --git a/test/rest_persistence_test.dart b/test/rest_persistence_test.dart new file mode 100644 index 0000000..8130fa9 --- /dev/null +++ b/test/rest_persistence_test.dart @@ -0,0 +1,41 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + final logger = MemoryLogger(LEVEL_FINE); + setUpAll(() { + final configuration = BaseConfiguration({ + 'client': { + 'host': 'localhost', + 'port': 58011, + 'schema': 'http', + 'application': 'unittest', + 'version': '1.0.0', + } + }, logger); + RestPersistence.fromConfig(configuration, logger); + }); + group('queries', () { + test('list', () async { + final rest = RestPersistence(); + final list = await rest + .list(module: 'role', sqlName: 'list', params: {'role_name': 'A'}); + expect(list.length, equals(1)); + expect(list[0].containsKey('role_id'), isTrue); + }); + test('record', () async { + final rest = RestPersistence(); + final record = await rest.record(module: 'role', id: 2); + expect(record.length, greaterThan(5)); + expect(record.containsKey('role_id'), isTrue); + }); + }); +} diff --git a/test/tool/tool_test.dart b/test/tool/tool_test.dart index 023c466..746a6cc 100644 --- a/test/tool/tool_test.dart +++ b/test/tool/tool_test.dart @@ -14,13 +14,13 @@ void main() { expect(errors.length, equals(0)); expect(content, equals('''DROP TABLE IF EXISTS role; CREATE TABLE role ( - role_id INT(10) notnull unique, - role_name VARCHAR(32) unique notnull, + role_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT, + role_name VARCHAR(32) UNIQUE NOT NULL, role_priority INT(10), role_active CHAR(1), - role_createdat TIMESTAMP null, + role_createdat TIMESTAMP NULL, role_createdby VARCHAR(16), - role_changedat TIMESTAMP null, + role_changedat TIMESTAMP NULL, role_changedby VARCHAR(16), PRIMARY KEY(role_id) );