MODULE=$1
if [ -z "$MODULE" ]; then
echo "Missing module name"
+ echo "example: $0 menu"
else
cd lib/src/page
if [ -d $MODULE ]; then
cd $MODULE
perl -pi -e"s/role/$MODULE/g;s/Role/$MODULE2/g;" *.dart
rename -v "s/role(.*)/${MODULE}\$1/;" *.dart
-fi
\ No newline at end of file
+fi
--- /dev/null
+DROP TABLE IF EXISTS menu;
+CREATE TABLE menu (
+ menu_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+ menu_name VARCHAR(64) UNIQUE NOT NULL,
+ menu_icon INT(10) UNSIGNED,
+ menu_createdat TIMESTAMP NULL,
+ menu_createdby VARCHAR(16),
+ menu_changedat TIMESTAMP NULL,
+ menu_changedby VARCHAR(16),
+ PRIMARY KEY(menu_id)
+);
CREATE TABLE user (
user_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
user_name VARCHAR(64) UNIQUE NOT NULL,
- user_displayname VARCHAR(32) UNIQUE,
- user_email VARCHAR(128) UNIQUE,
+ user_displayname VARCHAR(32) UNIQUE NOT NULL,
+ user_email VARCHAR(128) UNIQUE NOT NULL,
user_password VARCHAR(128),
user_role INT(10) UNSIGNED,
user_createdat TIMESTAMP NULL,
---
# configuration of the bones backend for configuration:
-created: 2020.10.29 22:35:03
+created: 2020.11.10 23:47:43
author: flutter_bones.module_model.exportSqlBackend()
version: 1.0.0
modules:
- module: configuration
- list:
+ sqlInfos:
- name: insert
type: insert
sql: "INSERT INTO configuration(configuration_scope,configuration_property,configuration_order,configuration_type,configuration_value,configuration_description,configuration_createdat,configuration_createdby)
--- /dev/null
+---
+# configuration of the bones backend for user:
+created: 2020.11.09 07:25
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+ - module: user
+ sqlInfos:
+ - name: update_pw
+ type: update
+ sql: "UPDATE user SET
+ user_password=:user_password,user_changedat=NOW(),user_changedby=:user_changedby
+ WHERE user_id=:user_id;"
+ - name: login
+ type: record
+ sql: "SELECT u.*, r.* FROM user u
+ LEFT JOIN role r ON u.user_role=r.role_id
+ WHERE (user_name=:user or user_email=:user);"
--- /dev/null
+---
+# configuration of the bones backend for menu:
+created: 2020.11.10 23:47:43
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+ - module: menu
+ sqlInfos:
+ - name: insert
+ type: insert
+ sql: "INSERT INTO menu(menu_name,menu_icon,menu_createdat,menu_createdby)
+ VALUES(:menu_name,:menu_icon,NOW(),:menu_createdby);"
+ - name: update
+ type: update
+ sql: "UPDATE menu SET
+ menu_name=:menu_name,menu_icon=:menu_icon,menu_changedat=NOW(),menu_changedby=:menu_changedby
+ WHERE menu_id=:menu_id;"
+ - name: delete
+ type: delete
+ sql: "DELETE from menu WHERE menu_id=:menu_id;"
+ - name: record
+ type: record
+ sql: "SELECT * from menu WHERE menu_id=:menu_id;"
+ - name: by_menu_name
+ type: record
+ sql: "SELECT * from menu WHERE menu_name=:menu_name&&menu_id!=:excluded;"
+ - name: list
+ type: list
+ sql: "SELECT t0.*,t1.configuration_property as menu_icon from menu t0
+ left join configuration t1 on t1.configuration_id=t0.menu_icon
+ WHERE menu_name like :menu_name;"
---
# configuration of the bones backend for role:
-created: 2020.10.29 22:35:03
+created: 2020.11.10 23:47:43
author: flutter_bones.module_model.exportSqlBackend()
version: 1.0.0
modules:
- module: role
- list:
+ sqlInfos:
- name: insert
type: insert
sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby)
---
# configuration of the bones backend for user:
-created: 2020.10.29 22:35:03
+created: 2020.11.10 23:47:43
author: flutter_bones.module_model.exportSqlBackend()
version: 1.0.0
modules:
- module: user
- list:
+ sqlInfos:
- name: insert
type: insert
- sql: "INSERT INTO user(user_name,user_displayname,user_email,user_password,user_role,user_createdat,user_createdby)
- VALUES(:user_name,:user_displayname,:user_email,:user_password,:user_role,NOW(),:user_createdby);"
+ sql: "INSERT INTO user(user_name,user_displayname,user_email,user_role,user_createdat,user_createdby)
+ VALUES(:user_name,:user_displayname,:user_email,:user_role,NOW(),:user_createdby);"
- 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_changedat=NOW(),user_changedby=:user_changedby
+ user_name=:user_name,user_displayname=:user_displayname,user_email=:user_email,user_role=:user_role,user_changedat=NOW(),user_changedby=:user_changedby
WHERE user_id=:user_id;"
- name: delete
type: delete
type: list
sql: "SELECT t0.*,t1.role_name as user_role from user t0
left join role t1 on t1.role_id=t0.user_role
- WHERE user_name like :user_name AND (:user_role IS NULL OR t0.user_role=:user_role);"
+ WHERE user_name like :user_name AND (:user_role IS NULL OR user_role=:user_role);"
import 'src/helper/settings.dart';
import 'src/page/demo_page.dart';
import 'src/page/async_example_page.dart';
-import 'src/page/login_page.dart';
import 'src/page/role/role_create_page.dart';
import 'src/page/role/role_list_page.dart';
import 'src/page/user/user_create_page.dart';
import 'src/page/user/user_list_page.dart';
+import 'src/page/user/user_login_page.dart';
+import 'src/page/menu/menu_list_page.dart';
+import 'src/page/menu/menu_create_page.dart';
import 'src/page/configuration/configuration_create_page.dart';
import 'src/page/configuration/configuration_list_page.dart';
import 'src/private/bsettings.dart';
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
- initialRoute: '/user/list',
+ initialRoute: '/menu/list',
//initialRoute: '/async',
onGenerateRoute: _getRoute,
);
case '/configuration/create':
page = ConfigurationCreatePage(BSettings.lastInstance.pageData);
break;
+ case '/menu/list':
+ page = MenuCreatePage(BSettings.lastInstance.pageData);
+ break;
+ case '/menu/create':
+ page = MenuListPage(BSettings.lastInstance.pageData);
+ break;
+ case '/user/login':
default:
- page = LoginPage(BSettings.lastInstance.pageData);
+ page = UserLoginPage(BSettings.lastInstance.pageData);
break;
}
if (page != null) {
export 'src/model/standard/role_model.dart';
export 'src/model/standard/standard_modules.dart';
export 'src/model/standard/user_model.dart';
+export 'src/model/standard/menu_model.dart';
export 'src/model/text_field_model.dart';
export 'src/model/text_model.dart';
export 'src/model/widget_model.dart';
-export 'src/page/login_page.dart';
+export 'src/page/login_page.dart.01';
export 'src/page/application_data.dart';
export 'src/page/role/role_create_page.dart';
export 'src/page/user/user_create_page.dart';
}
/// Converts a string to a given [dataType].
+ /// Returns null on error.
static dynamic fromString(String value, DataType dataType) {
dynamic rc;
if (dataType == DataType.string) {
break;
case DataType.date:
case DataType.dateTime:
- rc = StringUtils.stringToDateTime(value);
+ try {
+ rc = StringUtils.stringToDateTime(value);
+ } on FormatException {
+ rc = null;
+ }
break;
case DataType.int:
case DataType.reference:
import 'package:dart_bones/dart_bones.dart';
+import 'model_types.dart';
import 'widget_model.dart';
import 'section_model.dart';
import 'page_model.dart';
/// Describes a button widget.
class ButtonModel extends WidgetModel {
static final regExprOptions = RegExp(r'^(undef)$');
- String text;
+ String label;
String name;
String toolTip;
final Map<String, dynamic> map;
List<String> options;
ButtonModelType buttonModelType;
+ VoidCallbackBones onPressed;
/// Constructs an instance by parsing the map for some properties.
ButtonModel(SectionModel section, PageModel page, this.map, BaseLogger logger)
BaseLogger logger)
: super(section, page, WidgetModelType.button, logger) {
this.name = name;
- this.text = text;
+ this.label = text;
this.toolTip = toolTip;
this.options = options;
this.buttonModelType = buttonModelType;
/// Dumps a the instance into a [stringBuffer]
StringBuffer dump(StringBuffer stringBuffer) {
stringBuffer.write(
- ' button $name: text: options: $text ${listToString(options)}\n');
+ ' button $name: text: options: $label ${listToString(options)}\n');
return stringBuffer;
}
name = parseString('name', map, required: true);
checkSuperfluousAttributes(map,
'buttonType label name options text toolTip widgetType'.split(' '));
- text = parseString('text', map);
+ label = parseString('label', map, required: true);
toolTip = parseString('toolTip', map);
buttonModelType =
parseEnum<ButtonModelType>('buttonType', map, ButtonModelType.values);
map, 'name label options toolTip widgetType'.split(' '));
options = parseOptions('options', map);
checkOptionsByRegExpr(options, regExprOptions);
+ parseFinish();
}
/// Dumps the instance into a [StringBuffer]
import 'package:dart_bones/dart_bones.dart';
import 'package:meta/meta.dart';
+import 'combo_base_model.dart';
import 'model_base.dart';
import 'model_types.dart';
import 'table_model.dart';
import 'widget_model.dart';
-import 'combo_base_model.dart';
/// Describes a column of a database table.
class ColumnModel extends ComboBaseModel {
static final regExprOptions = RegExp(
- r'^(undef|readonly|disabled|doStore|hidden|null|notnull|password|primary|required|unique)$');
+ r'^(disabled|doStore|hidden|null|notnull|password|primary|readonly|required|undef|unique)$');
static final regExprListDbOption =
RegExp(r'^\w+\.\w+;\w+ \w+(?:;(:? ?:\w+=[^ ]*?)+)?$');
static final regExprForeignKey = RegExp(r'^\w+\.\w+ \w+$');
name = parseString('column', map, required: true);
checkSuperfluousAttributes(
map,
- 'column dataType defaultValue foreignKey label listOption listType options rows size texts tooTip values widgetType'
+ 'column dataType defaultValue foreignKey label listOption listType options rows size texts tooTip validators validatorsText values widgetType'
.split(' '));
super.parse();
dataType =
/// Describes a combobox widget.
class ComboboxModel extends ComboBaseModel {
- static final regExprOptions = RegExp(r'^(readonly|disabled|required|undef)$');
+ static final regExprOptions = RegExp(r'^(disabled|readonly|required|undef)$');
ComboboxModel(
SectionModel section, PageModel page, Map map, BaseLogger logger)
void parse() {
checkSuperfluousAttributes(
map,
- 'dataType defaultValue filterType label listOption listType name options texts toolTip values widgetType'
+ 'dataType defaultValue filterType label listOption listType name options texts toolTip values validators validatorsText widgetType'
.split(' '));
super.parse();
checkOptionsByRegExpr(options, regExprOptions);
+ parseFinish();
}
}
this.column = column;
maxSize = column.size;
rows = column.rows;
+ messageOfValidator.addAll(column.messageOfValidator);
+ validatorsString = column.validatorsString;
+ parameterOfValidator.addAll(column.parameterOfValidator);
if (column.hasOption('primary') && !column.hasOption('readonly')) {
options.add('readonly');
}
+ parseFinish();
}
/// Dumps the internal structure into a [stringBuffer]
listType ??= column.listType;
checkOptionsByRegExpr(options, regExprOptions);
options += column.options;
+ parseFinish();
}
}
import 'section_model.dart';
import 'widget_model.dart';
+typedef FieldValidator = String Function(
+ String input, FieldModel model, ValidatorController controller);
+
/// Base class of widgets with user interaction like text field, combobox, checkbox.
abstract class FieldModel extends WidgetModel {
+ static const reValidatorString = r'[a-z][a-zA-Z]+(=\S+)?';
+ static const reValidatorsString =
+ '^($reValidatorString)( ($reValidatorString))*\$';
+ static final regExpValidators = RegExp(reValidatorsString);
+
+ /// Note: changes of reValidatorNames needs changes in checkValidator()
+ static const reValidatorNames =
+ 'dateTime|email|int|maxDate|maxDateTime|maxInt|minDate|minDateTime|minInt|regExpr|required|unique';
+ static const reValidatorTextItem = '^($reValidatorNames)\$';
+ static final regExprValidatorText = RegExp(reValidatorTextItem);
String name;
String label;
String toolTip;
dynamic _value;
dynamic defaultValue;
+ /// list of validator functions:
+ final validators = <FieldValidator>[];
+
+ /// definitions of the validators, e.g. "minInt=1 maxInt=99"
+ String validatorsString;
+
+ /// options of the validator functions:
+ /// key: validator name, e.g. "minInt"
+ /// value: option, e.g. 1
+ final parameterOfValidator = <String, dynamic>{};
+
+ /// a model specific error message:
+ /// key: validator name, e.g. 'regExpr'
+ /// value: the error message
+ final messageOfValidator = <String, String>{};
final Map<String, dynamic> map;
FieldModel(SectionModel section, PageModel page, this.map,
}
}
+ /// Tests the validity of a validator term.
+ /// Note: a new name in checkValidator() needs a change of reValidatorNames
+ void checkValidator(String element) {
+ final ix = element.indexOf('=');
+ String name, argument;
+ if (ix < 0) {
+ name = element;
+ } else {
+ name = element.substring(0, ix);
+ argument = element.substring(ix + 1);
+ }
+ switch (name) {
+ case 'date':
+ case 'dateTime':
+ case 'email':
+ case 'int':
+ case 'required':
+ case 'unique':
+ if (argument != null) {
+ logger.error(
+ '${fullName()}: argument not allowed for $name: $argument');
+ }
+ break;
+ case 'equals':
+ FieldModel otherField;
+ if (argument == null) {
+ logger.error('missing reference field in ${fullName()}.equals');
+ } else if ((otherField = page.fieldByName(argument, required: false)) ==
+ null) {
+ logger.error('unknown field $argument in ${fullName()}.equals');
+ } else {
+ parameterOfValidator[name] =
+ otherField.widgetName() + ':' + otherField.label;
+ }
+ break;
+ case 'minInt':
+ case 'maxInt':
+ int intValue;
+ if (argument == null ||
+ (intValue = StringUtils.asInt(argument)) == null) {
+ logger.error('${fullName()}: not an integer in $name=$argument');
+ } else {
+ parameterOfValidator[name] = intValue;
+ }
+ break;
+ case 'minDate':
+ case 'maxDate':
+ try {
+ DateTime dateValue;
+ if (argument == null ||
+ argument.contains(':') ||
+ (dateValue = StringUtils.stringToDateTime(argument)) == null) {
+ throw ArgumentError('');
+ }
+ parameterOfValidator[name] = dateValue;
+ } on ArgumentError {
+ logger.error('${fullName()}: invalid date for $name');
+ }
+ break;
+ case 'minDateTime':
+ case 'maxDateTime':
+ try {
+ DateTime dateValue;
+ if (argument == null ||
+ (dateValue = StringUtils.stringToDateTime(argument)) == null) {
+ throw ArgumentError('');
+ }
+ parameterOfValidator[name] = dateValue;
+ } on ArgumentError {
+ logger.error('${fullName()}: invalid timestamp for $name');
+ }
+ break;
+ case 'regExpr':
+ if (argument == null) {
+ logger.error('${fullName()}: missing regular expression in $name');
+ } else {
+ if (RegExp('^i?/').firstMatch(argument) == null) {
+ logger.error('${fullName()}: missing "i/" or "/" in $name');
+ } else {
+ try {
+ final caseSensitive = !argument.startsWith('i');
+ final regExp = RegExp(argument.substring(caseSensitive ? 1 : 2),
+ caseSensitive: caseSensitive);
+ parameterOfValidator[name] = regExp;
+ } on FormatException catch (exc) {
+ logger.error('${fullName()}: error in $name: $exc');
+ }
+ }
+ }
+ break;
+ default:
+ logger.error('unknown validator name $name in ${fullName()}');
+ break;
+ }
+ }
+
/// Returns the name including the names of the parent
@override
String fullName() => '${page.name}.$name';
toolTip = parseString('toolTip', map, required: false);
filterType = parseEnum<FilterType>('filterType', map, FilterType.values);
options = parseOptions('options', map);
+ validatorsString = parseString('validators', map);
+ parseValidatorsText(parseString('validatorsText', map));
dataType = parseEnum<DataType>('dataType', map, DataType.values);
if (dataType == null) {
switch (widgetModelType) {
value is String ? StringHelper.fromString(value, dataType) : value;
}
+ /// Finishes the parse process for [FieldModel]s.
+ /// Must be called at the end of parse() of the parent, e.g. [TextFieldModel].
+ void parseFinish() {
+ if (validatorsString != null &&
+ !validatorsString.startsWith('required') &&
+ validatorsString.contains('required')) {
+ validatorsString = validatorsString
+ .replaceAll('required ', '')
+ .replaceAll('required', '');
+ if (validatorsString.isEmpty) {
+ validatorsString = 'required';
+ } else {
+ validatorsString = 'required ' + validatorsString;
+ }
+ }
+ if (hasOption('required') || hasOption('notnull')) {
+ if (validatorsString == null) {
+ validatorsString = 'required';
+ } else if (!validatorsString.contains('required')) {
+ validatorsString = 'required ' + validatorsString;
+ }
+ }
+ if (validatorsString != null) {
+ validatorsString.split(' ').forEach((element) => checkValidator(element));
+ }
+ }
+
+ /// Splits [text] into [messageOfValidator].
+ void parseValidatorsText(String text) {
+ if (text != null) {
+ text.split('|').forEach((element) {
+ final ix = element.indexOf('=');
+ if (ix < 0) {
+ logger.error('missing "=": $element in ${fullName()}.validatorsText');
+ } else if (ix == 0) {
+ logger.error(
+ '"=" at first position: $element in ${fullName()}.validatorsText');
+ } else {
+ final name = element.substring(0, ix);
+ if (regExprValidatorText.firstMatch(name) == null) {
+ logger.error('unknown "$name" in ${fullName()}.validatorsText');
+ } else {
+ messageOfValidator[name] = element.substring(ix + 1);
+ }
+ }
+ });
+ }
+ }
+
+ /// Returns null or the option of the validator named [name], e.g. "minInt"
+ dynamic validatorParameter(String name) =>
+ parameterOfValidator.containsKey(name)
+ ? parameterOfValidator[name]
+ : null;
+
/// Gets the [value] from a [row] like a db record:
/// Key of the entry in [row] is the [name].
void valueFromRow(Map row) {
@override
String widgetName() => name;
}
+
+abstract class ValidatorController {}
pattern,
}
enum WaitState { undef, initial, waiting, ready }
+typedef VoidCallbackBones = void Function();
import 'package:dart_bones/dart_bones.dart';
-import 'column_model.dart';
-import 'table_model.dart';
import 'package:meta/meta.dart';
-import 'model_base.dart';
-import 'page_model.dart';
+
+import '../helper/string_helper.dart';
+import 'column_model.dart';
import 'field_model.dart';
+import 'model_base.dart';
import 'model_types.dart';
+import 'page_model.dart';
+import 'table_model.dart';
import 'widget_model.dart';
-import '../helper/string_helper.dart';
/// Describes a module.
/// A module realizes a model of an entitiy which is normally related to one
/// database table.
/// The module administrate some pages to display that entity.
class ModuleModel extends ModelBase {
- static final regExprOptions = RegExp(r'^(unknown)$');
+ static final regExprOptions = RegExp(r'^(noPages)$');
final Map<String, dynamic> map;
String name;
List<String> options;
version: 1.0.0
modules:
- module: ${table.name}
- list:
+ sqlInfos:
- name: insert
type: insert
''');
return rc;
}
+ /// Tests whether an options named 'name' is set.
+ bool hasOption(String name) => options != null && options.contains(name);
+
/// Returns the main table of the module.
/// This is the first defined table.
TableModel mainTable() {
if (map.containsKey('tables')) {
TableModel.parseList(this, map['tables'], logger);
}
- if (!map.containsKey('pages')) {
- logger.error('Module $name: missing pages');
- } else {
- final item = map['pages'];
- if (!ModelBase.isList(item)) {
- logger.error(
- 'curious item in page list of ${fullName()}: ${StringUtils.limitString("$item", 80)}');
+ options = parseOptions('options', map);
+ if (!hasOption('noPages')) {
+ if (!map.containsKey('pages')) {
+ logger.error('Module $name: missing pages');
} else {
- PageModel.parseList(this, item, logger);
+ final item = map['pages'];
+ if (!ModelBase.isList(item)) {
+ logger.error(
+ 'curious item in page list of ${fullName()}: ${StringUtils.limitString("$item", 80)}');
+ } else {
+ PageModel.parseList(this, item, logger);
+ }
}
}
- options = parseOptions('options', map);
checkOptionsByRegExpr(options, regExprOptions);
}
final fields = <FieldModel>[];
final buttons = <ButtonModel>[];
final widgets = <WidgetModel>[];
+ String sql;
List<String> tableTitles;
List<String> tableColumns;
/// Parses the map and stores the data in the instance.
void parse() {
name = parseString('page', map, required: true);
- checkSuperfluousAttributes(map,
- 'options page pageType sections tableColumns tableTitles'.split(' '));
+ checkSuperfluousAttributes(
+ map,
+ 'options page pageType sections sql tableColumns tableTitles'
+ .split(' '));
pageModelType = parseEnum<PageModelType>(
'pageType', map, PageModelType.values,
required: true);
SectionModel.parseSections(this, null, item, logger);
}
}
+ sql = parseString('sql', map);
tableTitles = parseStringList('tableTitles', map);
tableColumns = parseString('tableColumns', map)?.split(' ');
if (pageModelType != PageModelType.list &&
'different sizes of tableTitles/tableColumns: ${tableTitles.length}/${tableColumns.length}');
}
- if (!options.contains('noAutoButton') &&
- buttonByName('search', required: false) == null) {
+ if (!options.contains('noAutoButton')) {
final section = null;
- final button = ButtonModel.direct(section, this, 'search', 'Suchen',
- ButtonModelType.search, [], null, logger);
- addButton(button);
+ switch (pageModelType) {
+ case PageModelType.list:
+ if (buttonByName('search', required: false) == null) {
+ addButton(ButtonModel.direct(section, this, 'search', 'Suchen',
+ ButtonModelType.search, [], null, logger));
+ }
+ break;
+ case PageModelType.create:
+ case PageModelType.change:
+ if (buttonByName('cancel', required: false) == null) {
+ addButton(ButtonModel.direct(section, this, 'cancel', 'Abbruch',
+ ButtonModelType.cancel, [], null, logger));
+ }
+ if (buttonByName('store', required: false) == null) {
+ addButton(ButtonModel.direct(section, this, 'store', 'Speichern',
+ ButtonModelType.store, [], null, logger));
+ }
+ break;
+ default:
+ break;
+ }
}
checkOptionsByRegExpr(options, regExprOptions);
}
'curious item in section list of ${module.fullName()}: ${StringUtils.limitString("$item", 80)}');
} else {
final page = PageModel(module, item, logger);
+ // we need a name before adding to module
page.parse();
module.addPage(page);
}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+
+import '../module_model.dart';
+
+class MenuModel extends ModuleModel {
+ static final mapMenu = <String, dynamic>{
+ 'module': 'menu',
+ 'tables': [
+ {
+ 'table': 'menu',
+ 'columns': [
+ {
+ 'column': 'menu_id',
+ 'dataType': 'int',
+ 'label': 'Id',
+ 'options': 'primary',
+ },
+ {
+ 'column': 'menu_name',
+ 'dataType': 'string',
+ 'label': 'Name',
+ 'size': 64,
+ 'options': 'unique notnull',
+ },
+ {
+ 'column': 'menu_icon',
+ 'dataType': 'reference',
+ 'label': 'Bild',
+ 'foreignKey':
+ 'configuration.configuration_id configuration_property',
+ 'listType': 'configuration',
+ 'listOption': 'scope:icons',
+ },
+ ]
+ },
+ ],
+ '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',
+ 'tableColumns': 'menu_id menu_name menu_icon',
+ 'tableTitles': ';Id;Name;Bild',
+ 'sections': [
+ {
+ 'sectionType': 'filterPanel',
+ 'children': [
+ {
+ 'widgetType': 'dbReference',
+ 'filterType': 'pattern',
+ 'name': 'menu_name',
+ 'column': 'menu_name',
+ 'toolTip':
+ "Filter bezüglich des Namens der anzuzeigenden Einträge: Joker '*' (beliebiger String) ist erlaubt."
+ },
+ ]
+ }
+ ]
+ },
+ ]
+ };
+
+ MenuModel(BaseLogger logger) : super(mapMenu, logger);
+
+ /// Returns the name including the names of the parent
+ @override
+ String fullName() => name;
+
+ @override
+ String widgetName() => name;
+}
import '../module_model.dart';
import 'role_model.dart';
import 'user_model.dart';
+import 'menu_model.dart';
import 'configuration_model.dart';
/// Returns an instance of a standard module given by [name].
case 'configuration':
rc = ConfigurationModel(logger);
break;
+ case 'menu':
+ rc = MenuModel(logger);
+ break;
default:
logger.error('unknown standard module: $name');
break;
}
/// Returns the names of the standard modules.
-List<String> standardModules() => ['configuration', 'role', 'user'];
+List<String> standardModules() => ['configuration', 'menu', 'role', 'user'];
'dataType': 'string',
'label': 'Anzeigename',
'size': 32,
- 'options': 'unique',
+ 'options': 'unique notnull',
},
{
'column': 'user_email',
'dataType': 'string',
'label': 'EMail',
'size': 128,
- 'options': 'unique',
+ 'options': 'unique notnull',
+ 'validators': 'email',
},
{
'column': 'user_password',
'dataType': 'string',
'label': 'Passwort',
'size': 128,
- 'options': 'password',
+ 'options': 'password hidden',
},
{
'column': 'user_role',
"children": [
{
"widgetType": "allDbFields",
- }
+ },
+ {
+ "widgetType": "button",
+ "name": "set_password",
+ "label": "Passwort ändern",
+ "buttonType": "custom",
+ },
+ ]
+ }
+ ]
+ },
+ {
+ "page": "password",
+ "pageType": "change",
+ "sql": "update_pw",
+ "sections": [
+ {
+ "sectionType": "simpleForm",
+ "children": [
+ {
+ "widgetType": "textField",
+ "name": "user_password",
+ "label": "Passwort",
+ "options": "password",
+ "validators": "required",
+ },
+ {
+ "widgetType": "textField",
+ "name": "repetition",
+ "label": "Wiederholung",
+ "options": "password",
+ "validators": "required equals=user_password"
+ },
]
}
]
}
]
},
+ {
+ "page": "login",
+ "pageType": "change",
+ "options": "noAutoButton",
+ "sections": [
+ {
+ "sectionType": "simpleForm",
+ "children": [
+ {
+ "widgetType": "textField",
+ "name": "user",
+ "label": "Benutzer oder EMail",
+ "validators": "required",
+ },
+ {
+ "widgetType": "textField",
+ "name": "password",
+ "label": "Password",
+ "options": "password",
+ "validators": "required",
+ },
+ {
+ "widgetType": "button",
+ "buttonType": "custom",
+ "name": "login",
+ "label": "Anmelden",
+ },
+ ]
+ }
+ ]
+ },
]
};
/// Describes a form text field widget.
class TextFieldModel extends FieldModel {
static final regExprOptions =
- RegExp(r'^(readonly|disabled|password|required|unique)$');
+ RegExp(r'^(disabled|password|readonly|required|unique)$');
int maxSize;
int rows;
var value;
super.parse();
checkSuperfluousAttributes(
map,
- 'dataType filterType label maxSize name options rows toolTip value widgetType'
+ 'dataType filterType label maxSize name options rows toolTip validators validatorsText value widgetType'
.split(' '));
maxSize = parseInt('maxSize', map, required: false);
rows = parseInt('rows', map, required: false);
}
options = parseOptions('options', map);
checkOptionsByRegExpr(options, regExprOptions);
+ parseFinish();
}
}
abstract class WidgetModel extends ModelBase {
static int lastId = 0;
final SectionModel section;
- final PageModel page;
+ PageModel page;
final WidgetModelType widgetModelType;
int id;
import 'package:dart_bones/dart_bones.dart';
import 'package:flutter/material.dart';
-import '../widget/callback_controller_bones.dart';
-import '../widget/page_controller_bones.dart';
-import '../persistence/persistence_cache.dart';
import '../persistence/persistence.dart';
+import '../persistence/persistence_cache.dart';
+import '../widget/page_controller_bones.dart';
/// Data class for storing parameter to build a page.
class ApplicationData {
final Drawer Function(dynamic context) drawerBuilder;
final Persistence persistence;
PersistenceCache persistenceCache;
- String currentUser;
+ int currentUserId;
+ String currentUserName;
int currentRoleId;
+ String currentRoleName;
+ int currentRolePriority;
/// for unittests:
dynamic lastModuleState;
/// [drawerBuilder] is a factory of a function returning a Drawer which handles the "Hamburger menu"
ApplicationData(this.configuration, this.appBarBuilder, this.drawerBuilder,
this.persistence, this.logger) {
- currentUser = 'Gast';
- currentRoleId = 100;
+ currentUserId = 0;
+ currentUserName = 'Gast';
+ currentRoleId = 0;
+ currentRolePriority = 100;
+ currentRoleName = 'Gast';
persistenceCache = PersistenceCache(persistence, logger);
}
}
}
+ /// Stacks the caller of the current page.
void pushCaller(PageControllerBones controller) {
callerStack.add(controller);
}
+ /// Removes the caller of the current page from the stack's top.
void popCaller() {
if (callerStack.isNotEmpty) {
callerStack.removeLast();
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
+import '../../helper/settings.dart';
import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'configuration_controller.dart';
class ConfigurationChangePage extends StatefulWidget {
final Map initialRow;
final logger = Settings().logger;
final int primaryId;
+
//ConfigurationChangePageState lastState;
ConfigurationChangePage(this.primaryId, this.applicationData, this.initialRow,
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../model/standard/configuration_model.dart';
import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'configuration_change_page.dart';
class ConfigurationController extends PageControllerBones {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../widget/edit_form.dart';
+import '../application_data.dart';
import 'configuration_controller.dart';
class ConfigurationCreatePage extends StatefulWidget {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
-import '../../widget/widget_list.dart';
import '../../widget/list_form.dart';
+import '../../widget/page_controller_bones.dart';
import '../application_data.dart';
import 'configuration_controller.dart';
GlobalKey<FormState>(debugLabel: 'configuration_list');
Iterable<dynamic> rowsDeprecated;
ConfigurationController controller;
- WidgetList filters;
ConfigurationListPageState(this.applicationData);
+ @override
+ void initState() {
+ super.initState();
+ controller = ConfigurationController(
+ _formKey, this, 'list', context, applicationData, redrawCallback:
+ (RedrawReason reason,
+ {String customString, RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.fetchList:
+ controller.buildRows();
+ break;
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ controller.buildWidgetList();
+ controller.buildRows();
+ controller.completeAsync();
+ }
+
@override
Widget build(BuildContext context) {
- if (controller == null) {
- controller = ConfigurationController(
- _formKey, this, 'list', context, applicationData, redrawCallback:
- (RedrawReason reason,
- {String customString,
- RedrawCallbackFunctionSimple callback}) {
- switch (reason) {
- case RedrawReason.fetchList:
- controller.buildRows();
- break;
- case RedrawReason.callback:
- callback(RedrawReason.custom, customString);
- setState(() {});
- break;
- default:
- setState(() {});
- break;
- }
- });
- controller.initialize();
- controller.buildWidgetList();
- controller.buildRows();
- } else {
- controller = controller;
- }
- filters ??= controller.filterSet(pageName: 'list');
+ controller.buildHandler(context);
return Scaffold(
appBar: applicationData.appBarBuilder('Konfigurationen'),
drawer: applicationData.drawerBuilder(context),
),
]),
],
- filters: filters,
+ filters: controller.widgetList,
errorMessage:
applicationData.lastErrorMessage(controller.page.fullName()),
),
+++ /dev/null
-import 'package:flutter/material.dart';
-import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-
-class LoginPage extends StatefulWidget {
- final ApplicationData pageData;
- LoginPage(this.pageData, {Key key}) : super(key: key);
-
- @override
- LoginPageState createState() {
- // LoginPageState.setPageData(pageData);
- final rc = LoginPageState(pageData);
-
- return rc;
- }
-}
-
-class LoginPageState extends State<LoginPage> {
- LoginPageState(this.pageData);
-
- final ApplicationData pageData;
-
- final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
- static User currentUser = User();
-
- @override
- Widget build(BuildContext context) {
- final user = LoginUser();
- return Scaffold(
- appBar: pageData.appBarBuilder('login'),
- drawer: pageData.drawerBuilder(context),
- body: SimpleForm.simpleForm(
- key: _formKey,
- configuration: pageData.configuration,
- fields: <Widget>[
- Text('Bitte loggen Sie sich ein.'),
- TextFormField(
- validator: checkNotEmpty,
- decoration: InputDecoration(labelText: 'Name'),
- onSaved: (input) => user.name = input,
- ),
- TextFormField(
- validator: (input) =>
- Validation.isEmail(input) || Validation.isPhoneNumber(input)
- ? null
- : 'keine Emailadresse und keine Telefonnummer: $input',
- decoration: InputDecoration(labelText: 'Passwort'),
- onSaved: (input) => user.password = input,
- obscureText: true,
- ),
- ],
- buttons: <Widget>[
- RaisedButton(
- onPressed: () => login(context),
- child: Text('Anmelden'),
- ),
- ],
- ));
- }
-
- void login(context) async {
- if (_formKey.currentState.validate()) {
- _formKey.currentState.save();
- //@ToDo: store in database
- }
- }
-}
-
-class LoginUser {
- String name;
- String password;
-}
--- /dev/null
+import 'package:flutter/material.dart';
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+class LoginPage extends StatefulWidget {
+ final ApplicationData pageData;
+ LoginPage(this.pageData, {Key key}) : super(key: key);
+
+ @override
+ LoginPageState createState() {
+ // LoginPageState.setPageData(pageData);
+ final rc = LoginPageState(pageData);
+
+ return rc;
+ }
+}
+
+class LoginPageState extends State<LoginPage> {
+ LoginPageState(this.pageData);
+
+ final ApplicationData pageData;
+
+ final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+ static User currentUser = User();
+
+ @override
+ Widget build(BuildContext context) {
+ final user = LoginUser();
+ return Scaffold(
+ appBar: pageData.appBarBuilder('login'),
+ drawer: pageData.drawerBuilder(context),
+ body: SimpleForm.simpleForm(
+ key: _formKey,
+ configuration: pageData.configuration,
+ fields: <Widget>[
+ Text('Bitte loggen Sie sich ein.'),
+ TextFormField(
+ validator: checkNotEmpty,
+ decoration: InputDecoration(labelText: 'Name'),
+ onSaved: (input) => user.name = input,
+ ),
+ TextFormField(
+ validator: (input) =>
+ Validation.isEmail(input) || Validation.isPhoneNumber(input)
+ ? null
+ : 'keine Emailadresse und keine Telefonnummer: $input',
+ decoration: InputDecoration(labelText: 'Passwort'),
+ onSaved: (input) => user.password = input,
+ obscureText: true,
+ ),
+ ],
+ buttons: <Widget>[
+ RaisedButton(
+ onPressed: () => login(context),
+ child: Text('Anmelden'),
+ ),
+ ],
+ ));
+ }
+
+ void login(context) async {
+ if (_formKey.currentState.validate()) {
+ _formKey.currentState.save();
+ //@ToDo: store in database
+ }
+ }
+}
+
+class LoginUser {
+ String name;
+ String password;
+}
--- /dev/null
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'menu_controller.dart';
+
+class MenuChangePage extends StatefulWidget {
+ final ApplicationData applicationData;
+ final Map initialRow;
+ final logger = Settings().logger;
+ final int primaryId;
+
+ //MenuChangePageState lastState;
+
+ MenuChangePage(this.primaryId, this.applicationData, this.initialRow,
+ {Key key})
+ : super(key: key);
+
+ @override
+ MenuChangePageState createState() {
+ final rc = MenuChangePageState(primaryId, applicationData, initialRow);
+
+ /// for unittests:
+ applicationData.lastModuleState = rc;
+ return rc;
+ }
+}
+
+class MenuChangePageState extends State<MenuChangePage> {
+ final ApplicationData applicationData;
+ final int primaryId;
+ final Map initialRow;
+ final GlobalKey<FormState> _formKey =
+ GlobalKey<FormState>(debugLabel: 'menu_change');
+
+ MenuController controller;
+
+ MenuChangePageState(this.primaryId, this.applicationData, this.initialRow);
+
+ @override
+ Widget build(BuildContext context) {
+ if (controller == null) {
+ controller =
+ MenuController(_formKey, this, 'change', context, applicationData,
+ redrawCallback: (RedrawReason reason,
+ {String customString,
+ RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ }
+ // controller.buildWidgetList() is called in editForm
+ return Scaffold(
+ appBar: applicationData.appBarBuilder('Startmenü ändern'),
+ drawer: applicationData.drawerBuilder(context),
+ body: EditForm.editForm(
+ key: _formKey,
+ pageController: controller,
+ configuration: applicationData.configuration,
+ primaryId: primaryId,
+ initialRow: initialRow,
+ ));
+ }
+
+ void dispose() {
+ controller.dispose();
+ super.dispose();
+ }
+}
--- /dev/null
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/standard/menu_model.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'menu_change_page.dart';
+
+class MenuController extends PageControllerBones {
+ /// Controller for a page named [pageName].
+ MenuController(GlobalKey<FormState> formKey, State<StatefulWidget> parent,
+ String pageName, BuildContext context, ApplicationData applicationData,
+ {Function redrawCallback})
+ : super(formKey, parent, MenuModel(Settings().logger), pageName, context,
+ applicationData, redrawCallback) {
+ moduleModel.parse();
+ }
+ @override
+ void startChange(int id, Map row) {
+ applicationData.pushCaller(this);
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => MenuChangePage(id, applicationData, row)));
+ }
+}
--- /dev/null
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../widget/edit_form.dart';
+import '../application_data.dart';
+import 'menu_controller.dart';
+
+class MenuCreatePage extends StatefulWidget {
+ final ApplicationData applicationData;
+ final logger = Settings().logger;
+
+ MenuCreatePage(this.applicationData, {Key key}) : super(key: key);
+
+ @override
+ MenuCreatePageState createState() {
+ final rc = MenuCreatePageState(applicationData);
+
+ /// for unittests:
+ applicationData.lastModuleState = rc;
+ return rc;
+ }
+}
+
+class MenuCreatePageState extends State<MenuCreatePage> {
+ final ApplicationData applicationData;
+
+ final GlobalKey<FormState> _formKey =
+ GlobalKey<FormState>(debugLabel: 'menu_create');
+
+ MenuController controller;
+
+ MenuCreatePageState(this.applicationData);
+
+ @override
+ Widget build(BuildContext context) {
+ if (controller == null) {
+ controller =
+ MenuController(_formKey, this, 'create', context, applicationData);
+ controller.initialize();
+ }
+ controller.buildWidgetList();
+ return Scaffold(
+ appBar: applicationData.appBarBuilder('Neues Startmenü'),
+ drawer: applicationData.drawerBuilder(context),
+ body: EditForm.editForm(
+ key: _formKey,
+ pageController: controller,
+ configuration: applicationData.configuration,
+ ));
+ }
+}
--- /dev/null
+import 'package:flutter/material.dart';
+
+import '../../widget/list_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'menu_controller.dart';
+
+class MenuListPage extends StatefulWidget {
+ final ApplicationData applicationData;
+
+ MenuListPage(this.applicationData, {Key key}) : super(key: key);
+
+ @override
+ MenuListPageState createState() {
+ // MenuListPageState.setPageData(pageData);
+ final rc = MenuListPageState(applicationData);
+
+ /// for unittests:
+ applicationData.lastModuleState = rc;
+ return rc;
+ }
+}
+
+class MenuListPageState extends State<MenuListPage> {
+ final ApplicationData applicationData;
+
+ final GlobalKey<FormState> _formKey =
+ GlobalKey<FormState>(debugLabel: 'menu_list');
+ Iterable<dynamic> rowsDeprecated;
+ MenuController controller;
+
+ MenuListPageState(this.applicationData);
+
+ @override
+ void initState() {
+ super.initState();
+ controller =
+ MenuController(_formKey, this, 'list', context, applicationData,
+ redrawCallback: (RedrawReason reason,
+ {String customString, RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.fetchList:
+ controller.buildRows();
+ break;
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ controller.buildWidgetList();
+ controller.buildRows();
+ controller.completeAsync();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ controller.buildHandler(context);
+ return Scaffold(
+ appBar: applicationData.appBarBuilder('Startmenüs'),
+ drawer: applicationData.drawerBuilder(context),
+ body: ListForm.listForm(
+ key: _formKey,
+ configuration: applicationData.configuration,
+ titles: ListForm.stringsToTitles(controller.page.tableTitles),
+ columnNames: controller.page.tableColumns ?? [],
+ rows: controller.listRows ?? [],
+ showEditIcon: true,
+ pageController: controller,
+ buttons: <Widget>[
+ ButtonBar(alignment: MainAxisAlignment.center, children: [
+ controller.searchButton(),
+ RaisedButton(
+ child: Text('Neues Startmenü'),
+ onPressed: () {
+ Navigator.pushNamed(context, '/menu/create');
+
+ /// Force the redraw on return:
+ controller.listRows = null;
+ },
+ ),
+ ]),
+ ],
+ filters: controller.widgetList,
+ errorMessage:
+ applicationData.lastErrorMessage(controller.page.fullName()),
+ ),
+ );
+ }
+}
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
+import '../../helper/settings.dart';
import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'role_controller.dart';
class RoleChangePage extends StatefulWidget {
final Map initialRow;
final logger = Settings().logger;
final int primaryId;
+
//RoleChangePageState lastState;
RoleChangePage(this.primaryId, this.applicationData, this.initialRow,
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../model/standard/role_model.dart';
import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'role_change_page.dart';
class RoleController extends PageControllerBones {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../widget/edit_form.dart';
+import '../application_data.dart';
import 'role_controller.dart';
class RoleCreatePage extends StatefulWidget {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
-import '../../widget/widget_list.dart';
import '../../widget/list_form.dart';
+import '../../widget/page_controller_bones.dart';
import '../application_data.dart';
import 'role_controller.dart';
GlobalKey<FormState>(debugLabel: 'role_list');
Iterable<dynamic> rowsDeprecated;
RoleController controller;
- WidgetList filters;
RoleListPageState(this.applicationData);
+ @override
+ void initState() {
+ super.initState();
+ controller =
+ RoleController(_formKey, this, 'list', context, applicationData,
+ redrawCallback: (RedrawReason reason,
+ {String customString, RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.fetchList:
+ controller.buildRows();
+ break;
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ controller.buildWidgetList();
+ controller.buildRows();
+ controller.completeAsync();
+ }
+
@override
Widget build(BuildContext context) {
- if (controller == null) {
- controller =
- RoleController(_formKey, this, 'list', context, applicationData,
- redrawCallback: (RedrawReason reason,
- {String customString,
- RedrawCallbackFunctionSimple callback}) {
- switch (reason) {
- case RedrawReason.fetchList:
- controller.buildRows();
- break;
- case RedrawReason.callback:
- callback(RedrawReason.custom, customString);
- setState(() {});
- break;
- default:
- setState(() {});
- break;
- }
- });
- controller.initialize();
- controller.buildWidgetList();
- controller.buildRows();
- } else {
- controller = controller;
- }
- filters ??= controller.filterSet(pageName: 'list');
+ controller.buildHandler(context);
return Scaffold(
appBar: applicationData.appBarBuilder('Rollen'),
drawer: applicationData.drawerBuilder(context),
),
]),
],
- filters: filters,
+ filters: controller.widgetList,
errorMessage:
applicationData.lastErrorMessage(controller.page.fullName()),
),
--- /dev/null
+import 'package:crypto/crypto.dart';
+import 'dart:convert';
+
+String hashPassword(String user, String password) {
+ var bytes = utf8.encode(user + " + " + password); // data being hashed
+ var digest = sha1.convert(bytes);
+ return digest.toString();
+}
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
-
+import '../../helper/settings.dart';
+import '../../model/button_model.dart';
import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'user_controller.dart';
+import 'user_password_page.dart';
class UserChangePage extends StatefulWidget {
final ApplicationData applicationData;
final Map initialRow;
final logger = Settings().logger;
final int primaryId;
+
//UserChangePageState lastState;
UserChangePage(this.primaryId, this.applicationData, this.initialRow,
}
});
controller.initialize();
+ customize();
}
// controller.buildWidgetList() is called in editForm
return Scaffold(
));
}
+ void customize() {
+ ButtonModel button = controller.page.buttonByName('set_password');
+ button?.onPressed = () {
+ String userName = controller.page.fieldByName('user_name').value;
+ applicationData.pushCaller(controller);
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => UserPasswordPage(
+ primaryId, userName, applicationData, null)));
+ };
+ }
+
void dispose() {
controller.dispose();
super.dispose();
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../model/standard/user_model.dart';
import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
import 'user_change_page.dart';
class UserController extends PageControllerBones {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/flutter_bones.dart';
+import '../../helper/settings.dart';
import '../../widget/edit_form.dart';
+import '../application_data.dart';
import 'user_controller.dart';
class UserCreatePage extends StatefulWidget {
import 'package:flutter/material.dart';
-import 'package:flutter_bones/src/widget/callback_controller_bones.dart';
import '../../widget/list_form.dart';
+import '../../widget/page_controller_bones.dart';
import '../application_data.dart';
import 'user_controller.dart';
--- /dev/null
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/button_model.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'user_controller.dart';
+import 'hash.dart';
+
+class UserLoginPage extends StatefulWidget {
+ final ApplicationData applicationData;
+ final logger = Settings().logger;
+
+ //UserLoginPageState lastState;
+
+ UserLoginPage(this.applicationData, {Key key}) : super(key: key);
+
+ @override
+ UserLoginPageState createState() {
+ final rc = UserLoginPageState(applicationData);
+
+ /// for unittests:
+ applicationData.lastModuleState = rc;
+ return rc;
+ }
+}
+
+class UserLoginPageState extends State<UserLoginPage> {
+ final ApplicationData applicationData;
+ final GlobalKey<FormState> _formKey =
+ GlobalKey<FormState>(debugLabel: 'user.login');
+
+ UserController controller;
+
+ UserLoginPageState(this.applicationData);
+
+ @override
+ Widget build(BuildContext context) {
+ if (controller == null) {
+ controller =
+ UserController(_formKey, this, 'login', context, applicationData,
+ redrawCallback: (RedrawReason reason,
+ {String customString,
+ RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ customize();
+ }
+ // controller.buildWidgetList() is called in editForm
+ return Scaffold(
+ appBar: applicationData.appBarBuilder('Passwort ändern'),
+ drawer: applicationData.drawerBuilder(context),
+ body: EditForm.editForm(
+ key: _formKey,
+ pageController: controller,
+ configuration: applicationData.configuration,
+ primaryId: 0,
+ ));
+ }
+
+ void customize() {
+ ButtonModel button = controller.page.buttonByName('login');
+ button?.onPressed = () {
+ if (_formKey.currentState.validate()) {
+ _formKey.currentState.save();
+ final user = controller.page.fieldByName('user').value;
+
+ final params = {
+ ':user': user,
+ };
+ applicationData.persistence
+ .recordByParameter(
+ module: controller.moduleModel.name,
+ sqlName: 'login',
+ parameters: params)
+ .then((row) {
+ if (row != null && row.containsKey('user_name')) {
+ final user = row['user_name'];
+ final code = row['user_password'];
+ final code2 = hashPassword(
+ user, controller.page.fieldByName('password').value);
+ if (code == code2) {
+ applicationData.currentUserId = row['user_id'];
+ applicationData.currentUserName = row['user_displayname'];
+ applicationData.currentRoleId = row['user_role'];
+ applicationData.currentRoleName = row['role_name'];
+ applicationData.currentRolePriority = row['role_priority'];
+ applicationData.pushCaller(controller);
+ Navigator.pushNamed(context, '/user/list');
+ }
+ }
+ });
+ }
+ };
+ }
+
+ void dispose() {
+ controller.dispose();
+ super.dispose();
+ }
+}
--- /dev/null
+import 'package:flutter/material.dart';
+
+import '../../helper/settings.dart';
+import '../../model/button_model.dart';
+import '../../widget/edit_form.dart';
+import '../../widget/page_controller_bones.dart';
+import '../application_data.dart';
+import 'user_controller.dart';
+import 'hash.dart';
+
+class UserPasswordPage extends StatefulWidget {
+ final ApplicationData applicationData;
+ final Map initialRow;
+ final logger = Settings().logger;
+ final int primaryId;
+ final String userName;
+
+ //UserPasswordPageState lastState;
+
+ UserPasswordPage(
+ this.primaryId, this.userName, this.applicationData, this.initialRow,
+ {Key key})
+ : super(key: key);
+
+ @override
+ UserPasswordPageState createState() {
+ final rc = UserPasswordPageState(primaryId, this.userName, applicationData);
+
+ /// for unittests:
+ applicationData.lastModuleState = rc;
+ return rc;
+ }
+}
+
+class UserPasswordPageState extends State<UserPasswordPage> {
+ final ApplicationData applicationData;
+ final int primaryId;
+ final String userName;
+ final GlobalKey<FormState> _formKey =
+ GlobalKey<FormState>(debugLabel: 'user.password');
+
+ UserController controller;
+
+ UserPasswordPageState(this.primaryId, this.userName, this.applicationData);
+
+ @override
+ Widget build(BuildContext context) {
+ if (controller == null) {
+ controller =
+ UserController(_formKey, this, 'password', context, applicationData,
+ redrawCallback: (RedrawReason reason,
+ {String customString,
+ RedrawCallbackFunctionSimple callback}) {
+ switch (reason) {
+ case RedrawReason.callback:
+ callback(RedrawReason.custom, customString);
+ setState(() {});
+ break;
+ default:
+ setState(() {});
+ break;
+ }
+ });
+ controller.initialize();
+ customize();
+ }
+ // controller.buildWidgetList() is called in editForm
+ return Scaffold(
+ appBar: applicationData.appBarBuilder('Passwort ändern'),
+ drawer: applicationData.drawerBuilder(context),
+ body: EditForm.editForm(
+ key: _formKey,
+ pageController: controller,
+ configuration: applicationData.configuration,
+ primaryId: primaryId,
+ ));
+ }
+
+ void customize() {
+ ButtonModel button = controller.page.buttonByName('store');
+ button?.onPressed = () {
+ if (_formKey.currentState.validate()) {
+ _formKey.currentState.save();
+ final application = controller.applicationData;
+ final code = hashPassword(
+ userName, controller.page.fieldByName('user_password').value);
+ final params = {
+ ':user_id': primaryId,
+ ':user_password': code,
+ ':user_changedby': application.currentUserName
+ };
+ applicationData.persistence
+ .update(
+ module: controller.moduleModel.name,
+ sqlName: 'update_pw',
+ data: params)
+ .then((value) {
+ applicationData.callerRedraw(RedrawReason.fetchList);
+ applicationData.popCaller();
+ Navigator.pop(controller.getContext());
+ });
+ }
+ };
+ }
+
+ void dispose() {
+ controller.dispose();
+ super.dispose();
+ }
+}
import '../page/configuration/configuration_list_page.dart';
import '../page/demo_page.dart';
-import '../page/login_page.dart';
import '../page/role/role_list_page.dart';
import '../page/user/user_list_page.dart';
+import '../page/user/user_login_page.dart';
+import '../page/application_data.dart';
import 'bsettings.dart';
class MenuItem {
MenuItem(this.title, this.page, this.icon);
+ static StatefulWidget pageByName(
+ String name, ApplicationData applicationData) {
+ var rc;
+ switch (name) {
+ case 'user.login':
+ rc = UserLoginPage(applicationData);
+ break;
+ case 'user.list':
+ rc = UserListPage(applicationData);
+ break;
+ case 'role.list':
+ rc = RoleListPage(applicationData);
+ break;
+ case 'configuration.list':
+ rc = RoleListPage(applicationData);
+ break;
+ case 'menu.list':
+ rc = RoleListPage(applicationData);
+ break;
+ case 'demo':
+ rc = DemoPage(applicationData);
+ break;
+ }
+ return rc;
+ }
+
static List<MenuItem> menuItems() {
final settings = BSettings.lastInstance;
return <MenuItem>[
- MenuItem('Login', () => LoginPage(settings.pageData),
+ MenuItem('Login', () => pageByName('user.login', settings.pageData),
+ Icons.account_box_outlined),
+ MenuItem('Rollen', () => pageByName('role.list', settings.pageData),
Icons.account_box_outlined),
- MenuItem('Rollen', () => RoleListPage(settings.pageData),
+ MenuItem('Benutzer', () => pageByName('user.list', settings.pageData),
Icons.account_box_outlined),
- MenuItem('Benutzer', () => UserListPage(settings.pageData),
+ MenuItem(
+ 'Konfiguration',
+ () => pageByName('configuration.list', settings.pageData),
Icons.account_box_outlined),
- MenuItem('Konfiguration', () => ConfigurationListPage(settings.pageData),
+ MenuItem('Startmenü', () => pageByName('menu.list', settings.pageData),
Icons.account_box_outlined),
- MenuItem('Demo', () => DemoPage(settings.pageData),
+ MenuItem('Demo', () => pageByName('demo', settings.pageData),
Icons.account_box_outlined),
];
}
+++ /dev/null
-import 'package:flutter/material.dart';
-
-import '../model/combobox_model.dart';
-import '../model/combo_base_model.dart';
-import '../model/field_model.dart';
-import '../page/application_data.dart';
-
-typedef RedrawCallbackFunctionSimple = Function(
- RedrawReason reason, String customString);
-typedef RedrawCallbackFunction = Function(RedrawReason reason,
- {String customString, RedrawCallbackFunctionSimple callback});
-
-/// Interface for a callback controller for flutter_bones specific widgets.
-/// flutter_bones specific widgets: [CheckboxListTileBone],
-/// [DropDownButtonFormBone], [RaisedButtonBone], [TextFormFieldBone]
-abstract class CallbackControllerBones {
- /// Retrieves the rows shown in the list page.
- void buildRows();
-
- /// Returns the [ComboboxData] (texts and values) of the [ComboboxModel] named [name].
- ComboboxData comboboxData(String name);
-
- /// Frees all resources.
- void dispose();
-
- /// Returns the container for application specific information.
- ApplicationData getApplicationData();
-
- /// Returns the [BuildContext] instance of the page.
- BuildContext getContext();
-
- /// Returns the [model] named [name].
- FieldModel getModel(String name);
-
- ValueChanged<String> getOnChanged(
- String customString, CallbackControllerBones controller);
- ValueChanged<bool> getOnChangedCheckbox(
- String customString, CallbackControllerBones controller);
-
- ValueChanged<T> getOnChangedCombobox<T>(
- String customString, CallbackControllerBones controller);
-
- VoidCallback getOnEditingComplete(
- String customString, CallbackControllerBones controller);
-
- ValueChanged<String> getOnFieldSubmitted(
- String customString, CallbackControllerBones controller);
-
- ValueChanged<bool> getOnHighlightChanged(
- String customString, CallbackControllerBones controller);
-
- VoidCallback getOnLongPressed(
- String customString, CallbackControllerBones controller);
-
- VoidCallback getOnPressed(
- String customString, CallbackControllerBones controller);
-
- FormFieldSetter<String> getOnSaved(
- String customString, CallbackControllerBones controller);
-
- DropdownButtonBuilder getOnSelectedItemBuilder(
- String customString, CallbackControllerBones controller);
-
- GestureTapCallback getOnTap(
- String customString, CallbackControllerBones controller);
-
- FormFieldValidator getOnValidator(
- String customString, CallbackControllerBones controller);
-
- FormFieldValidator<String> getValidator(
- String customString, CallbackControllerBones controller);
-
- /// Handles the tap event in the table of the list page.
- void onEditTap(
- String customString, CallbackControllerBones controller, Map row);
-
- /// Rebuilds the view of the page.
- /// [reason] and [customString] will be forwarded to the callback function.
- void redraw(RedrawReason reason,
- {String customString, RedrawCallbackFunctionSimple callback});
-
- /// Returns a standard search button for the list page.
- Widget searchButton();
-
- /// Starts the change page to edit the record with primary key [id].
- /// [row] contains the db record of [id].
- void startChange(int id, Map row);
-
- /// Returns the [TextEditingController] instance of a [TextFormField] assigned
- /// to a model named [name].
- TextEditingController textController(String name);
-
- /// Returns the field value of [FieldModel] named [fieldName] or null if not found.
- dynamic valueOf(String fieldName);
-}
-
-enum RedrawReason {
- custom,
- callback,
- fetchList,
- fetchRecord,
- redraw,
- setError,
-}
import 'package:flutter/material.dart';
-import 'callback_controller_bones.dart';
+import 'page_controller_bones.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<T> extends DropdownButtonFormField<T> {
final String customString;
- final CallbackControllerBones callbackController;
+ final PageControllerBones callbackController;
DropdownButtonFormBone(
this.customString,
import 'package:flutter/material.dart';
import 'page_controller_bones.dart';
-import 'raised_button_bone.dart';
+import 'view.dart';
typedef Function OnEditTap(Map<String, dynamic> row, int index);
configuration.asFloat('form.card.padding', defaultValue: 16.0);
pageController.buildWidgetList(initialRow);
final widgets = pageController.getWidgets();
+ final view = View();
+ final buttons =
+ view.modelsToWidgets(pageController.page.buttons, pageController);
return Form(
key: key,
child: Card(
'form.gap.field_button.height',
defaultValue: 16.0)),
ButtonBar(
- children: <Widget>[
- FlatButton(
- child: Text('Abbruch'),
- onPressed: pageController.getOnPressed(
- 'cancel', pageController),
- ),
- RaisedButtonBone(
- 'store',
- pageController,
- child: Text('Speichern'),
- ),
- ],
+ children: buttons,
),
],
))),
import 'package:dart_bones/dart_bones.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bones/flutter_bones.dart';
-import 'package:flutter_bones/src/model/combo_base_model.dart';
import 'package:flutter_bones/src/widget/page_controller_bones.dart';
-import 'text_form_field_bone.dart';
-
typedef FilterPredicate = bool Function(Map<String, dynamic> row);
@deprecated
List<Widget> getWidgets() {
final rc = filters.map((filter) {
final model = pageController.page.fieldByName(filter.name);
- if (model is ComboBaseModel) {
- Widget rc3 = View().combobox(model, pageController, null);
- return rc3;
- } else {
- Widget rc2 = TextFormFieldBone(
- filter.name,
- pageController,
- decoration: InputDecoration(labelText: filter.label),
- );
- if (filter.toolTip != null) {
- rc2 = Tooltip(message: filter.toolTip, child: rc2);
- }
- return rc2;
- }
+ final widget = View().modelToWidget(model, pageController);
+ return widget;
}).toList();
return rc;
}
import '../model/page_model.dart';
import '../model/widget_model.dart';
import '../page/application_data.dart';
-import 'callback_controller_bones.dart';
import 'view.dart';
import 'widget_list.dart';
+import 'widget_validators.dart';
-// This interface allows the generic handling of the edit form by a model driven module.
-class PageControllerBones implements CallbackControllerBones {
+typedef RedrawCallbackFunction = Function(RedrawReason reason,
+ {String customString, RedrawCallbackFunctionSimple callback});
+typedef RedrawCallbackFunctionSimple = Function(
+ RedrawReason reason, String customString);
+
+class PageControllerBones implements ValidatorController {
final ModuleModel moduleModel;
String primaryColumn;
WidgetList widgetList;
}
}
- @override
- buildRows() {
+ /// Retrieves the rows shown in the list page.
+ void buildRows() {
final persistence = applicationData.persistence;
final params = buildSqlParams() ?? {};
persistence.list(module: moduleModel.name, params: params).then((list) {
completeModels(model);
widgetList.addWidget(model.name, view.modelToWidget(model, this, value));
});
+ page.fields.forEach((element) => prepareModel(element));
}
@deprecated
return completer.future;
}
- @override
+ /// Frees all resources.
void dispose() {
textControllers.values.forEach((controller) => controller.dispose());
textControllers.clear();
return rc;
}
- @override
+ /// Returns the container for application specific information.
ApplicationData getApplicationData() {
return applicationData;
}
- @override
+ /// Returns the [BuildContext] instance of the page.
BuildContext getContext() {
return context;
}
- @override
+ /// Returns the [model] named [name].
FieldModel getModel(String name) {
final rc = moduleModel.pageByName(pageName)?.fieldByName(name);
return rc;
ModuleModel getModuleModel() => moduleModel;
- @override
- getOnChanged(String customString, CallbackControllerBones controller) {
+ getOnChanged(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnChangedCheckbox(
- String customString, CallbackControllerBones controller) {
+ getOnChangedCheckbox(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnChangedCombobox<T>(
- String customString, CallbackControllerBones controller) {
+ getOnChangedCombobox<T>(String customString, PageControllerBones controller) {
var rc;
rc = (value) {
final model = page.fieldByName(customString);
return rc;
}
- @override
- getOnEditingComplete(
- String customString, CallbackControllerBones controller) {
+ getOnEditingComplete(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnFieldSubmitted(String customString, CallbackControllerBones controller) {
+ getOnFieldSubmitted(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnHighlightChanged(
- String customString, CallbackControllerBones controller) {
+ getOnHighlightChanged(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnLongPressed(String customString, CallbackControllerBones controller) {
+ getOnLongPressed(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnPressed(String customString, CallbackControllerBones controller) {
- var rc;
- 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;
- switch (pageType) {
- case PageModelType.create:
- applicationData.persistence
- .insert(module: moduleModel.name, data: params)
- .then((id) {
- applicationData.callerRedraw(RedrawReason.fetchList);
- applicationData.popCaller();
- Navigator.pop(controller.getContext());
- });
- break;
- case PageModelType.change:
- applicationData.persistence
- .update(module: moduleModel.name, data: params)
- .then((value) {
- applicationData.callerRedraw(RedrawReason.fetchList);
- applicationData.popCaller();
- Navigator.pop(controller.getContext());
- });
- break;
- default:
- moduleModel.logger
- .error('unexpected pageType $pageType for $customString');
- break;
- }
- }
- };
- } else if (customString == 'cancel') {
- rc = () {
- applicationData.popCaller();
- Navigator.pop(controller.getContext());
- };
+ getOnPressed(String customString, PageControllerBones controller) {
+ var rc = page.buttonByName(customString)?.onPressed;
+ if (rc == null) {
+ switch (customString) {
+ case 'search':
+ rc = () {
+ if (globalKey.currentState.validate()) {
+ globalKey.currentState.save();
+ listRows = null;
+ buildRows();
+ // controller.fetchListRows();
+ }
+ };
+ break;
+ case 'store':
+ rc = () {
+ if (globalKey.currentState.validate()) {
+ globalKey.currentState.save();
+ final params = widgetList.buildSqlParams(this);
+ PageModelType pageType =
+ moduleModel.pageByName(pageName).pageModelType;
+ switch (pageType) {
+ case PageModelType.create:
+ applicationData.persistence
+ .insert(module: moduleModel.name, data: params)
+ .then((id) {
+ applicationData.callerRedraw(RedrawReason.fetchList);
+ applicationData.popCaller();
+ Navigator.pop(controller.getContext());
+ });
+ break;
+ case PageModelType.change:
+ applicationData.persistence
+ .update(module: moduleModel.name, data: params)
+ .then((value) {
+ applicationData.callerRedraw(RedrawReason.fetchList);
+ applicationData.popCaller();
+ Navigator.pop(controller.getContext());
+ });
+ break;
+ default:
+ moduleModel.logger
+ .error('unexpected pageType $pageType for $customString');
+ break;
+ }
+ }
+ };
+ break;
+ case 'cancel':
+ rc = () {
+ applicationData.popCaller();
+ Navigator.pop(controller.getContext());
+ };
+ break;
+ case 'custom':
+ default:
+ break;
+ }
}
return rc;
}
- @override
- getOnSaved(String customString, CallbackControllerBones controller) {
+ getOnSaved(String customString, PageControllerBones controller) {
final rc = (input) {
FieldModel model = page.fieldByName(customString);
model.value = StringHelper.fromString(input, model.dataType);
return rc;
}
- @override
getOnSelectedItemBuilder(
- String customString, CallbackControllerBones controller) {
+ String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnTap(String customString, CallbackControllerBones controller) {
+ getOnTap(String customString, PageControllerBones controller) {
return null;
}
- @override
- getOnValidator(String customString, CallbackControllerBones controller) {
- return null;
- }
-
- @override
- getValidator(String customString, CallbackControllerBones controller) {
- return null;
+ /// Returns a callback used as validator.
+ /// [customString]: name of the model (of the current page).
+ getValidator(String customString, PageControllerBones controller) {
+ var rc;
+ final model = page.fieldByName(customString);
+ if (model.validators.isNotEmpty) {
+ rc = (String input) {
+ String rc2;
+ for (var validator in model.validators) {
+ rc2 = validator(input, model, controller);
+ if (rc2 != null) {
+ break;
+ }
+ }
+ return rc2;
+ };
+ }
+ return rc;
}
/// Returns the widgets with at least the input fields of the form defined
});
}
- @override
- onEditTap(String customString, CallbackControllerBones controller, Map row) {
+ onEditTap(String customString, PageControllerBones controller, Map row) {
ColumnModel primary = moduleModel.mainTable().primary;
final id = row[primary.name];
applicationData.persistence
.then((row) => startChange(id, row));
}
- @override
+ /// Rebuilds the view of the page.
+ /// [reason] and [customString] will be forwarded to the callback function.
void redraw(RedrawReason reason,
{String customString, RedrawCallbackFunctionSimple callback}) {
if (redrawCallback == null) {
Navigator.pushNamed(context, route);
}
- @override
+ /// Returns a standard search button for the list page.
Widget searchButton() {
final rc = View(moduleModel.logger)
.modelToWidget(page.buttonByName('search'), this);
return rc;
}
- @override
+ /// Starts the change page to edit the record with primary key [id].
+ /// [row] contains the db record of [id].
void startChange(int id, Map row) {
moduleModel.logger.error(
'missing override of startChange() in ${moduleModel.fullName()}');
}
- /// Returns the [TextEditingController] of a [TextFormField] assigned to
- /// a model named [name].
- @override
+ /// Returns the [TextEditingController] instance of a [TextFormField] assigned
+ /// to a model named [name].
TextEditingController textController(String name) {
if (!textControllers.containsKey(name)) {
textControllers[name] = TextEditingController();
return textControllers[name];
}
- @override
+ /// Returns the field value of [FieldModel] named [fieldName] or null if not found.
dynamic valueOf(String fieldName) {
final rc = page.fieldByName(fieldName)?.value;
return rc;
}
}
+
+// This interface allows the generic handling of the edit form by a model driven module.
+enum RedrawReason {
+ custom,
+ callback,
+ fetchList,
+ fetchRecord,
+ redraw,
+ setError,
+}
import 'package:flutter/material.dart';
-import 'callback_controller_bones.dart';
+import '../widget/page_controller_bones.dart';
/// Implements a raised button with two additional properties:
/// [customString] a string often used for a name needed in a callback method
/// [customObject] an object known by the controller, often used in callback methods like onPressed
class RaisedButtonBone extends RaisedButton {
final String customString;
- final CallbackControllerBones callbackController;
+ final PageControllerBones callbackController;
RaisedButtonBone(this.customString, this.callbackController,
{ButtonTextTheme textTheme,
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'callback_controller_bones.dart';
+import '../widget/page_controller_bones.dart';
/// Interface for a [TextFormField] specific callback controller.
abstract class TextFormCallbackController {
/// [callbackController] handles the callback methods.
class TextFormFieldBone extends TextFormField {
final String customString;
- final CallbackControllerBones callbackController;
+ final PageControllerBones callbackController;
TextFormFieldBone(
this.customString,
bool expands = false,
int maxLength,
List<TextInputFormatter> inputFormatters,
- bool enabled,
+ bool enabled = true,
double cursorWidth = 2.0,
double cursorHeight,
Radius cursorRadius,
AutovalidateMode autovalidateMode,
}) : super(
controller: controller,
- initialValue: initialValue,
+ initialValue: controller != null ? null : initialValue,
focusNode: focusNode,
decoration: decoration,
keyboardType: keyboardType,
minLines: minLines,
expands: expands,
maxLength: maxLength,
- onChanged:
- callbackController.getOnChanged(customString, callbackController),
- onTap: callbackController.getOnTap(customString, callbackController),
+ onChanged: readOnly
+ ? null
+ : callbackController.getOnChanged(
+ customString, callbackController),
+ onTap: readOnly
+ ? null
+ : callbackController.getOnTap(customString, callbackController),
onEditingComplete: callbackController.getOnEditingComplete(
customString, callbackController),
onFieldSubmitted: callbackController.getOnFieldSubmitted(
customString, callbackController),
onSaved:
callbackController.getOnSaved(customString, callbackController),
- validator:
- callbackController.getValidator(customString, callbackController),
+ validator: readOnly
+ ? null
+ : callbackController.getValidator(
+ customString, callbackController),
inputFormatters: inputFormatters,
enabled: enabled,
cursorWidth: cursorWidth,
import '../helper/settings.dart';
import '../helper/string_helper.dart';
+import '../model/button_model.dart';
+import '../model/combo_base_model.dart';
import '../model/db_reference_model.dart';
-import '../model/model_types.dart';
import '../model/empty_line_model.dart';
import '../model/field_model.dart';
+import '../model/model_types.dart';
import '../model/section_model.dart';
import '../model/text_model.dart';
import '../model/widget_model.dart';
-import '../model/button_model.dart';
-import '../model/combo_base_model.dart';
-import 'page_controller_bones.dart';
import 'checkbox_list_tile_bone.dart';
import 'dropdown_button_form_bone.dart';
+import 'page_controller_bones.dart';
import 'raised_button_bone.dart';
import 'text_form_field_bone.dart';
/// Creates a button from the [controller].
Widget button(ButtonModel model, PageControllerBones controller) {
Widget rc;
- rc = RaisedButtonBone(
- model.name,
- controller,
- child: Text(model.text),
- );
+ if (model.buttonModelType == ButtonModelType.cancel) {
+ rc = FlatButton(
+ child: Text(model.label ?? 'Cancel'),
+ onPressed: controller.getOnPressed('cancel', controller),
+ );
+ } else {
+ rc = RaisedButtonBone(
+ model.name,
+ controller,
+ child: Text(model.label ?? 'Save'),
+ );
+ }
if (model.toolTip != null) {
rc = Tooltip(message: model.toolTip, child: rc);
}
/// Creates a form text field from the [model].
Widget textField(
FieldModel model, PageControllerBones controller, initialValue) {
- final value =
- initialValue == null ? null : StringHelper.asString(initialValue);
+ final readOnly = model.hasOption('readonly');
+ final value = StringHelper.asString(
+ initialValue ?? model.value ?? model.defaultValue);
final textController = controller.textController(model.name);
- textController.text = value;
+ if (textController != null) {
+ textController.text = value;
+ initialValue = null;
+ }
var rc = toolTip(
TextFormFieldBone(
model.name, controller,
//validator: model.validator,
+ initialValue: value,
controller: textController,
- readOnly: model.hasOption('readonly'),
+ // ToDo: readonly doesn't work: workaround with "disable"
+ enabled: !model.hasOption('disabled') && !readOnly,
+ readOnly: readOnly,
decoration: InputDecoration(labelText: model.label),
obscureText: model.hasOption('password'),
),
final name = controller.moduleModel.mainTable().name +
(isCreatePage ? '_createdby' : '_changedby');
if (!rc.containsKey(name)) {
- rc[':$name'] = controller.getApplicationData().currentUser;
+ rc[':$name'] = controller.getApplicationData().currentUserName;
}
return rc;
}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
+
+/// Converts the [model.validatorsString] into entries of [model.validators].
+/// Note: This method is not part of [FieldModel] because the model don't know
+/// [PageControllerBones].
+void prepareModel(FieldModel model) {
+ var validator;
+ if (model.validatorsString != null) {
+ model.validatorsString.split(' ').forEach((element) {
+ final nameOption = element.split('=');
+ switch (nameOption[0]) {
+ case 'date':
+ validator = validateDate;
+ break;
+ case 'dateTime':
+ validator = validateDateTime;
+ break;
+ case 'email':
+ validator = validateEmail;
+ break;
+ case 'equals':
+ validator = validateEquals;
+ break;
+ case 'int':
+ validator = validateInt;
+ break;
+ case 'maxDate':
+ validator = validateMaxDate;
+ break;
+ case 'maxDateTime':
+ validator = validateMaxDateTime;
+ break;
+ case 'maxInt':
+ validator = validateMaxInt;
+ break;
+ case 'minDate':
+ validator = validateMinDate;
+ break;
+ case 'minDateTime':
+ validator = validateMinDateTime;
+ break;
+ case 'minInt':
+ validator = validateMinInt;
+ break;
+ case 'regExpr':
+ validator = validateRegExpr;
+ break;
+ case 'required':
+ validator = validateRequired;
+ break;
+ case 'unique':
+ default:
+ model.logger.error('prepareModel(): not implemented: $element');
+ break;
+ }
+ if (validator != null) {
+ model.validators.add(validator);
+ }
+ });
+ }
+}
+
+/// Overwrites a model specific error message.
+/// [defaultMessage]: null or the standard error message.
+/// [name]: the name of the validator, e.g. "regExpr"
+/// [model]: the model containing the model specific message.
+/// Returns null if defaultMessage is null. Otherwise the model specific error
+/// message is returned or [defaultMessage] if there is no model specific error.
+String updateMessage(String defaultMessage, String name, FieldModel model) {
+ String rc = defaultMessage;
+ if (rc != null && model.messageOfValidator.containsKey(name)) {
+ rc = model.messageOfValidator[name];
+ }
+ return rc;
+}
+
+/// Validates whether [input] is a valid date.
+/// Return null on success or error message otherwise.
+String validateDate(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ StringUtils.stringToDateTime(input);
+ if (input.contains(':')) {
+ rc = "Datum mit Uhrzeit ist nicht erlaubt, nur Datum eingeben";
+ }
+ } on ArgumentError {
+ rc = 'kein Datum erkannt: $input';
+ }
+ return updateMessage(rc, 'date', model);
+}
+
+/// Validates whether [input] is a valid date time.
+/// Return null on success or error message otherwise.
+String validateDateTime(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ StringUtils.stringToDateTime(input);
+ } on ArgumentError {
+ rc = 'keinen Zeitpunkt erkannt: $input';
+ }
+ return updateMessage(rc, 'dateTime', model);
+}
+
+/// Validates whether [input] is a valid email address.
+/// Return null on success or error message otherwise.
+String validateEmail(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc = checkEmail(input);
+ return rc;
+}
+
+/// Validates whether [input] is a valid email address.
+/// Return null on success or error message otherwise.
+String validateInt(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc = checkInt(input);
+ return updateMessage(rc, 'int', model);
+}
+
+/// Validates whether [input] is a valid date and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMaxDate(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ final date = StringUtils.stringToDateTime(input);
+ final limit = model.validatorParameter('maxDate');
+ if (date.isAfter(limit)) {
+ rc =
+ 'Datum zu jung: $input > ${StringHelper.asDatabaseString(limit, DataType.date)}';
+ }
+ } on ArgumentError {
+ rc = 'kein Datum erkannt: $input';
+ }
+ return updateMessage(rc, 'maxDate', model);
+}
+
+/// Validates whether [input] is a valid date time and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMaxDateTime(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ final date = StringUtils.stringToDateTime(input);
+ final limit = model.validatorParameter('maxDateTime');
+ if (date.isAfter(limit)) {
+ rc =
+ 'Zeitpunkt zu jung: $input > ${StringHelper.asDatabaseString(limit, DataType.dateTime)}';
+ }
+ } on ArgumentError {
+ rc = 'keinen Zeitpunkt erkannt: $input';
+ }
+ return updateMessage(rc, 'maxDateTime', model);
+}
+
+/// Validates whether [input] is a valid date time and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMaxInt(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ final current = StringUtils.asInt(input);
+ if (current == null) {
+ rc = 'keine Ganzzahl erkannt: $input';
+ } else {
+ final limit = model.validatorParameter('maxInt');
+ if (current > limit) {
+ rc = 'Zahl zu groß: $input > $limit';
+ }
+ }
+ return updateMessage(rc, 'maxInt', model);
+}
+
+/// Validates whether [input] is a valid date and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMinDate(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ final date = StringUtils.stringToDateTime(input);
+ final limit = model.validatorParameter('minDate');
+ if (date.isBefore(limit)) {
+ rc =
+ 'Datum zu alt: $input > ${StringHelper.asDatabaseString(limit, DataType.date)}';
+ }
+ } on ArgumentError {
+ rc = 'kein Datum erkannt: $input';
+ }
+ return updateMessage(rc, 'minDate', model);
+}
+
+/// Validates whether [input] is a valid date time and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMinDateTime(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ try {
+ final date = StringUtils.stringToDateTime(input);
+ final limit = model.validatorParameter('minDateTime');
+ if (date.isBefore(limit)) {
+ rc =
+ 'Zeitpunkt zu alt: $input > ${StringHelper.asDatabaseString(limit, DataType.dateTime)}';
+ }
+ } on ArgumentError {
+ rc = 'keinen Zeitpunkt erkannt: $input';
+ }
+ return updateMessage(rc, 'minDateTime', model);
+}
+
+/// Validates whether [input] is a valid date time and tests whether [input] is
+/// before or equals a given limit.
+/// Return null on success or error message otherwise.
+String validateMinInt(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ final current = StringUtils.asInt(input);
+ if (current == null) {
+ rc = 'keine Ganzzahl erkannt: $input';
+ } else {
+ final limit = model.validatorParameter('minInt');
+ if (current < limit) {
+ rc = 'Ganzzahl zu klein: $input < $limit';
+ }
+ }
+ return updateMessage(rc, 'minInt', model);
+}
+
+/// Validates whether [input] is equals to another field content.
+/// Return null on success or error message otherwise.
+String validateEquals(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ PageControllerBones controller2 = controller;
+ List<String> nameLabel = model.validatorParameter('equals')?.split(':');
+ final name = nameLabel == null ? null : nameLabel[0];
+ final label = nameLabel == null ? 'vorigem Feld' : nameLabel[1];
+ final value = controller2.textControllers.containsKey(name)
+ ? controller2.textControllers[name].value.text
+ : null;
+ if (input != value) {
+ rc = 'Eingaben von ${model?.label} und $label stimmen nicht überein';
+ }
+ return updateMessage(rc, 'equals', model);
+}
+
+/// Validates whether [input] is a valid date.
+/// Return null on success or error message otherwise.
+String validateRegExpr(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ if (input.isNotEmpty) {
+ try {
+ RegExp regEx = model.validatorParameter('regExpr');
+ if (regEx?.firstMatch(input) == null) {
+ rc = 'Unzulässige Eingabe "$input" in ${model.label}';
+ }
+ } on FormatException catch (exc) {
+ rc = '${model.fullName()}: invalid regexpr: $exc';
+ }
+ }
+ return updateMessage(rc, 'regExpr', model);
+}
+
+/// Validates whether [input] is a valid date.
+/// Return null on success or error message otherwise.
+String validateRequired(
+ String input, FieldModel model, ValidatorController controller) {
+ String rc;
+ if (input.isEmpty) {
+ rc = 'Bitte Feld ${model.label} ausfüllen';
+ }
+ return updateMessage(rc, 'required', model);
+}
--- /dev/null
+#! /bin/bash
+dart lib/main.dart
+
flutter_localizations:
sdk: flutter
dart_bones: "^0.4.5"
-
+ crypto: ^2.1.5
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
] # create.simpleForm1
== page change: PageModelType.change options:
= section simpleForm1: SectionModelType.simpleForm options: [
- allDbFields 16 options:
+ allDbFields 17 options:
] # change.simpleForm1
== page list: PageModelType.list options:
= section filterPanel1: SectionModelType.filterPanel options: [
== table user: options:
column user_id: DataType.int "Id" options: primary notnull unique readonly
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 "Passwort" options: password
- column user_role: DataType.reference "Role" options:
+ column user_displayname: DataType.string "Anzeigename" options: unique notnull
+ column user_email: DataType.string "EMail" options: unique notnull
+ column user_password: DataType.string "Passwort" options: password hidden
+ column user_role: DataType.reference "Rolle" options: undef
column user_createdat: DataType.dateTime "Erzeugt" options: hidden null
column user_createdby: DataType.string "Erzeugt von" options: hidden
column user_changedat: DataType.dateTime "Geändert" options: hidden null
== page change: PageModelType.change options:
= section simpleForm1: SectionModelType.simpleForm options: [
allDbFields 20 options:
+ button set_password: text: options: Passwort ändern
] # change.simpleForm1
+== page password: PageModelType.change options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ textField user_password: options: password
+ textField repetition: options: password
+ ] # password.simpleForm1
== page list: PageModelType.list options:
= section filterPanel1: SectionModelType.filterPanel options: [
- textField user_name: options:
- textField user_role: options:
+ textField user_name: options: unique notnull
+ textField user_role: options: undef
] # list.filterPanel1
+== page login: PageModelType.change options: noAutoButton
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ textField user: options:
+ textField password: options: password
+ button login: text: options: Anmelden
+ ] # login.simpleForm1
'''));
});
test('configuration', () {
] # create.simpleForm1
== page change: PageModelType.change options:
= section simpleForm1: SectionModelType.simpleForm options: [
- allDbFields 22 options:
+ allDbFields 23 options:
] # change.simpleForm1
== page list: PageModelType.list options:
= section filterPanel1: SectionModelType.filterPanel options: [
textField configuration_scope: options:
textField configuration_property: options:
] # list.filterPanel1
+'''));
+ });
+ test('menu', () {
+ WidgetModel.lastId = 0;
+ logger.clear();
+ final module = MenuModel(logger);
+ module.parse();
+ expect(module.fullName(), equals('menu'));
+ expect(module.widgetName(), equals('menu'));
+ final errors = logger.errors;
+ expect(errors.length, equals(0));
+ final dump = module.dump(StringBuffer()).toString();
+ expect(dump, equals('''= module menu: options:
+== table menu: options:
+ column menu_id: DataType.int "Id" options: primary notnull unique readonly
+ column menu_name: DataType.string "Name" options: unique notnull
+ column menu_icon: DataType.reference "Bild" options:
+ column menu_createdat: DataType.dateTime "Erzeugt" options: hidden null
+ column menu_createdby: DataType.string "Erzeugt von" options: hidden
+ column menu_changedat: DataType.dateTime "Geändert" options: hidden null
+ column menu_changedby: DataType.string "Geändert von" options: hidden
+== page create: PageModelType.create options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 9 options:
+ ] # create.simpleForm1
+== page change: PageModelType.change options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 15 options:
+ ] # change.simpleForm1
+== page list: PageModelType.list options:
+ = section filterPanel1: SectionModelType.filterPanel options: [
+ textField menu_name: options: unique notnull
+ ] # list.filterPanel1
'''));
});
});
--- /dev/null
+// 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_bones/src/widget/widget_validators.dart';
+import 'package:test/test.dart';
+
+void main() {
+ final logger = MemoryLogger();
+ final widgetConfiguration = BaseConfiguration({}, logger);
+ Settings(logger: logger, widgetConfiguration: widgetConfiguration);
+ //Persistence persistence;
+ setUpAll(() {
+ // final configuration = BaseConfiguration({
+ // 'client': {
+ // 'host': 'localhost',
+ // 'port': 58011,
+ // 'schema': 'http',
+ // 'application': 'unittest',
+ // 'version': '1.0.0',
+ // }
+ // }, logger);
+ //persistence = RestPersistence.fromConfig(configuration, logger);
+ });
+ group('independent', () {
+ test('int', () {
+ WidgetModel.lastId = 0;
+ logger.clear();
+ final map = <String, dynamic>{
+ 'module': 'demo1',
+ 'options': 'noPages',
+ 'tables': [
+ {
+ 'table': 'test',
+ 'columns': [
+ {
+ 'column': 'test_int',
+ 'dataType': 'int',
+ 'validators': 'int minInt=3 maxInt=10',
+ },
+ ]
+ },
+ ],
+ };
+ final module = ModuleModel(map, logger);
+ module.parse();
+ final table = module.tableByName('test');
+ expect(table, isNotNull);
+ final model = table.columnByName('test_int');
+ model.parseFinish();
+ expect(model, isNotNull);
+ expect(logger.errors.length, 0);
+ prepareModel(model);
+ expect(model.validators.length, 3);
+ expect(model.validators[0]('33', model, null), isNull);
+ expect(model.validators[0]('blub', model, null),
+ equals('Not an integer: blub'));
+ expect(model.validators[1]('4', model, null), isNull);
+ expect(model.validators[1]('blub', model, null),
+ equals('keine Ganzzahl erkannt: blub'));
+ expect(model.validators[1]('2', model, null),
+ equals('Ganzzahl zu klein: 2 < 3'));
+ expect(model.validators[2]('4', model, null), isNull);
+ expect(model.validators[2]('blub', model, null),
+ equals('keine Ganzzahl erkannt: blub'));
+ expect(model.validators[2]('11', model, null),
+ equals('Zahl zu groß: 11 > 10'));
+ });
+ test('date', () {
+ WidgetModel.lastId = 0;
+ logger.clear();
+ final map = <String, dynamic>{
+ 'module': 'demo1',
+ 'options': 'noPages',
+ 'tables': [
+ {
+ 'table': 'test',
+ 'columns': [
+ {
+ 'column': 'test_date',
+ 'dataType': 'date',
+ 'validators': 'date minDate=2020.2.1 maxDate=2020.10.3',
+ },
+ ]
+ },
+ ],
+ };
+ final module = ModuleModel(map, logger);
+ module.parse();
+ final table = module.tableByName('test');
+ expect(table, isNotNull);
+ final model = table.columnByName('test_date');
+ expect(model, isNotNull);
+ model.parseFinish();
+ expect(logger.errors.length, 0);
+ prepareModel(model);
+ expect(model.validators.length, 3);
+ expect(model.validators[0]('2020.2.2', model, null), isNull);
+ expect(model.validators[0]('blub', model, null),
+ equals('kein Datum erkannt: blub'));
+ expect(model.validators[1]('2020.2.2', model, null), isNull);
+ expect(model.validators[1]('blub', model, null),
+ equals('kein Datum erkannt: blub'));
+ expect(model.validators[1]('2020.1.1', model, null),
+ equals('Datum zu alt: 2020.1.1 > 2020-02-01'));
+ expect(model.validators[2]('2020.2.2', model, null), isNull);
+ expect(model.validators[2]('blub', model, null),
+ equals('kein Datum erkannt: blub'));
+ expect(model.validators[2]('2020.11.1', model, null),
+ equals('Datum zu jung: 2020.11.1 > 2020-10-03'));
+ });
+ test('dateTime', () {
+ WidgetModel.lastId = 0;
+ logger.clear();
+ final map = <String, dynamic>{
+ 'module': 'demo1',
+ 'options': 'noPages',
+ 'tables': [
+ {
+ 'table': 'test',
+ 'columns': [
+ {
+ 'column': 'test_date',
+ 'dataType': 'dateTime',
+ 'validators':
+ 'dateTime minDateTime=2020.2.1-11:03 maxDateTime=2020.10.3-10:44',
+ },
+ ]
+ },
+ ],
+ };
+ final module = ModuleModel(map, logger);
+ module.parse();
+ final table = module.tableByName('test');
+ expect(table, isNotNull);
+ final model = table.columnByName('test_date');
+ expect(model, isNotNull);
+ expect(logger.errors.length, 0);
+ prepareModel(model);
+ model.parseFinish();
+ expect(model.validators.length, 3);
+ expect(model.validators[0]('2020.2.2-10:33', model, null), isNull);
+ expect(model.validators[0]('blub', model, null),
+ equals('keinen Zeitpunkt erkannt: blub'));
+ expect(model.validators[1]('2020.2.2-02:44', model, null), isNull);
+ expect(model.validators[1]('blub', model, null),
+ equals('keinen Zeitpunkt erkannt: blub'));
+ expect(model.validators[1]('2020.1.1-3:44', model, null),
+ equals('Zeitpunkt zu alt: 2020.1.1-3:44 > 2020-02-01 11:03:00'));
+ expect(model.validators[2]('2020.2.2-23:59', model, null), isNull);
+ expect(model.validators[2]('blub', model, null),
+ equals('keinen Zeitpunkt erkannt: blub'));
+ expect(model.validators[2]('2020.11.1-00:22', model, null),
+ equals('Zeitpunkt zu jung: 2020.11.1-00:22 > 2020-10-03 10:44:00'));
+ });
+ test('string', () {
+ WidgetModel.lastId = 0;
+ logger.clear();
+ final map = <String, dynamic>{
+ 'module': 'demo1',
+ 'options': 'noPages',
+ 'tables': [
+ {
+ 'table': 'test',
+ 'columns': [
+ {
+ 'column': 'test',
+ 'dataType': 'string',
+ 'label': 'str',
+ 'size': '1',
+ 'validators': r'email required regExpr=i/^[a-z]$',
+ },
+ {
+ 'column': 'test2',
+ 'dataType': 'string',
+ 'size': '1',
+ 'validators': r'required equals=test regExpr=/^[a-z]$',
+ 'validatorsText': 'required=missing|regExpr=No Name',
+ },
+ ]
+ },
+ ],
+ };
+ final module = ModuleModel(map, logger);
+ module.parse();
+ final table = module.tableByName('test');
+ expect(table, isNotNull);
+ var model = table.columnByName('test');
+ final page = PageModel(module, null, logger);
+ page.addField(model);
+ model.page = page;
+ expect(model, isNotNull);
+ model.parseFinish();
+ model.value = 'B';
+ expect(logger.errors.length, 0);
+ prepareModel(model);
+ expect(model.validators.length, 3);
+ expect(model.validators[0]('x', model, null), isNull);
+ expect(model.validators[0]('', model, null),
+ equals('Bitte Feld str ausfüllen'));
+ expect(model.validators[1]('a@br.de', model, null), isNull);
+ expect(model.validators[1]('a.br.de', model, null),
+ equals('Not an email address: a.br.de Example: joe@example.com'));
+ expect(model.validators[2]('A', model, null), isNull);
+ expect(model.validators[2]('ab', model, null),
+ 'Unzulässige Eingabe "ab" in str');
+ expect(model.validators[2]('5', model, null),
+ 'Unzulässige Eingabe "5" in str');
+ model = table.columnByName('test2');
+ expect(model, isNotNull);
+ page.addField(model);
+ model.page = page;
+ model.parseFinish();
+ prepareModel(model);
+ expect(model, isNotNull);
+ expect(model.validators.length, 3);
+ expect(model.validators[0]('', model, null), equals('missing'));
+ expect(model.validators[1]('B', model, null), isNull);
+ expect(model.validators[1]('A', model, null), equals('Eingaben von null und str stimmen nicht überein'));
+ expect(model.validators[2]('A', model, null), equals('No Name'));
+ });
+ });
+}