--- /dev/null
+DROP TABLE IF EXISTS configuration;
+CREATE TABLE configuration (
+ configuration_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+ configuration_scope VARCHAR(64) UNIQUE NOT NULL,
+ configuration_property VARCHAR(64),
+ configuration_order INT(10),
+ configuration_type VARCHAR(16),
+ configuration_value VARCHAR(255),
+ configuration_description VARCHAR(255),
+ configuration_createdat TIMESTAMP NULL,
+ configuration_createdby VARCHAR(16),
+ configuration_changedat TIMESTAMP NULL,
+ configuration_changedby VARCHAR(16),
+ PRIMARY KEY(configuration_id)
+);
--- /dev/null
+DROP TABLE IF EXISTS role;
+CREATE TABLE role (
+ role_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+ role_name VARCHAR(32) UNIQUE NOT NULL,
+ role_priority INT(10),
+ role_active CHAR(1),
+ role_createdat TIMESTAMP NULL,
+ role_createdby VARCHAR(16),
+ role_changedat TIMESTAMP NULL,
+ role_changedby VARCHAR(16),
+ PRIMARY KEY(role_id)
+);
--- /dev/null
+DROP TABLE IF EXISTS user;
+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_password VARCHAR(128),
+ user_role INT(10) UNSIGNED,
+ user_createdat TIMESTAMP NULL,
+ user_createdby VARCHAR(16),
+ user_changedat TIMESTAMP NULL,
+ user_changedby VARCHAR(16),
+ PRIMARY KEY(user_id)
+);
--- /dev/null
+---
+# configuration of the bones backend for role:
+created: 2020.10.20 15:25:01
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+ - module: role
+ list:
+ - name: insert
+ type: insert
+ sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby)
+ VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby);"
+ - name: update
+ type: update
+ sql: "UPDATE role SET
+ role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_changedat=NOW(),role_changedby=:role_changedby
+ WHERE role_id=:role_id;"
+ - name: delete
+ type: delete
+ sql: "DELETE from role WHERE role_id=:role_id;"
+ - name: record
+ type: record
+ sql: "SELECT * from role WHERE role_id=:role_id;"
+ - name: by_role_name
+ type: record
+ sql: "SELECT * from role WHERE role_name=:role_name&&role_id!=:excluded;"
+ - name: list
+ type: list
+ sql: "SELECT * from role
+ WHERE role_name like :role_name;"
--- /dev/null
+---
+# configuration of the bones backend for user:
+created: 2020.10.20 15:25:01
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+ - module: user
+ list:
+ - name: insert
+ type: insert
+ sql: "INSERT INTO user(user_name,user_displayname,user_email,user_password,user_role,user_createdat,user_createdby)
+ VALUES(:user_name,:user_displayname,:user_email,:user_password,: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
+ WHERE user_id=:user_id;"
+ - name: delete
+ type: delete
+ sql: "DELETE from user WHERE user_id=:user_id;"
+ - name: record
+ type: record
+ sql: "SELECT * from user WHERE user_id=:user_id;"
+ - name: by_user_name
+ type: record
+ sql: "SELECT * from user WHERE user_name=:user_name&&user_id!=:excluded;"
+ - name: by_user_displayname
+ type: record
+ sql: "SELECT * from user WHERE user_displayname=:user_displayname&&user_id!=:excluded;"
+ - name: by_user_email
+ type: record
+ sql: "SELECT * from user WHERE user_email=:user_email&&user_id!=:excluded;"
+ - name: list
+ type: list
+ sql: "SELECT * from user
+ WHERE user_name like :user_name AND user_role like :user_role AND user_role like :user_role;"
FileSync.ensureDirectory(dirDDL);
FileSync.ensureDirectory(dirREST);
if (args.isEmpty) {
- args = ['user', 'role'];
+ args = ['user', 'role', 'configuration'];
}
while (args.isNotEmpty) {
final name = args[0];
case 'role':
module = RoleModel(logger);
break;
+ case 'configuration':
+ module = ConfigurationModel(logger);
+ break;
default:
logger.error('unknown table');
break;
export 'src/model/module_model.dart';
export 'src/model/page_model.dart';
export 'src/model/section_model.dart';
+export 'src/model/standard/configuration_model.dart';
export 'src/model/standard/role_model.dart';
+export 'src/model/standard/standard_modules.dart';
export 'src/model/standard/user_model.dart';
export 'src/model/text_field_model.dart';
export 'src/model/text_model.dart';
export 'src/page/page_data.dart';
export 'src/page/role/role_create_page.dart';
export 'src/page/user_page.dart';
+export 'src/persistence/persistence.dart';
+export 'src/persistence/rest_persistence.dart';
export 'src/widget/raised_button_bone.dart';
export 'src/widget/simple_form.dart';
export 'src/widget/text_form_field_bone.dart';
--- /dev/null
+import 'package:flutter_bones/flutter_bones.dart';
+
+/// Base class of all controller classes in this package.
+abstract class BaseController {
+ /// Returns the assigned model.
+ WidgetModel getModel();
+
+ /// Returns a unique name of the controller inside the module, mostly the
+ /// widget name of the assigned model.
+ String getName();
+}
--- /dev/null
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:flutter_bones/src/controller/base_controller.dart';
+
+import '../widget/raised_button_bone.dart';
+
+class ButtonController extends BaseController
+ implements ButtonCallbackController {
+ ButtonModel model;
+
+ ButtonController(this.model);
+
+ @override
+ WidgetModel getModel() {
+ return model;
+ }
+
+ @override
+ String getName() {
+ return model.widgetName();
+ }
+
+ @override
+ getOnHighlightChanged(
+ String customString, ButtonCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnLongPressed(String customString, ButtonCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnPressed(String customString, ButtonCallbackController controller) {
+ var rc;
+ switch (model.buttonModelType) {
+ case ButtonModelType.cancel:
+ break;
+ case ButtonModelType.custom:
+ break;
+ case ButtonModelType.search:
+ break;
+ case ButtonModelType.store:
+ break;
+ }
+ return rc;
+ }
+}
--- /dev/null
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../widget/dropdown_button_form_bone.dart';
+import 'base_controller.dart';
+
+class ComboboxController<T> extends BaseController
+ implements ComboboxCallbackController {
+ ComboboxModel model;
+
+ ComboboxController(this.model);
+
+ @override
+ WidgetModel getModel() {
+ return model;
+ }
+
+ @override
+ String getName() {
+ return model.widgetName();
+ }
+
+ @override
+ getOnChanged(String customString, ComboboxCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnSaved(String customString, ComboboxCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnSelectedItemBuilder(
+ String customString, ComboboxCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnTap(String customString, ComboboxCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnValidator(String customString, ComboboxCallbackController controller) {
+ return null;
+ }
+
+ @override
+ List<String> texts() {
+ return model.texts;
+ }
+
+ @override
+ List<dynamic> values() {
+ return model.values;
+ }
+}
--- /dev/null
+import 'package:flutter_bones/flutter_bones.dart';
+
+import '../widget/text_form_field_bone.dart';
+import 'base_controller.dart';
+
+class TextFormController extends BaseController
+ implements TextFormCallbackController {
+ ComboboxModel model;
+
+ TextFormController(this.model);
+
+ @override
+ WidgetModel getModel() {
+ return model;
+ }
+
+ @override
+ String getName() {
+ return model.widgetName();
+ }
+
+ @override
+ getOnChanged(String customString, TextFormCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnEditingComplete(
+ String customString, TextFormCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnFieldSubmitted(
+ String customString, TextFormCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnSaved(String customString, TextFormCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getOnTap(String customString, TextFormCallbackController controller) {
+ return null;
+ }
+
+ @override
+ getValidator(String customString, TextFormCallbackController controller) {
+ return null;
+ }
+}
import 'package:dart_bones/dart_bones.dart';
import 'package:flutter/material.dart';
+import 'package:intl/date_symbol_data_local.dart';
+import 'package:intl/intl.dart';
-class Settings {
+class BaseSettings {
static Locale locale = Locale('US', 'en');
- static final mapWidgetData = <String, dynamic>{
- 'form.card.padding': '16.0',
- 'form.gap.field_button.height': '16.0',
- };
- static Settings _instance;
+ static get language => locale.languageCode;
- final BaseConfiguration widgetConfiguration;
+ final BaseConfiguration configuration;
final BaseLogger logger;
- factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) {
- if (_instance == null) {
- _instance = Settings.internal(
- widgetConfiguration == null
- ? BaseConfiguration(mapWidgetData, logger)
- : widgetConfiguration,
- logger);
- }
- return _instance;
- }
-
- Settings.internal(this.widgetConfiguration, this.logger);
+ BaseSettings(this.configuration, this.logger);
/// Sets the locale code.
/// [locale] the info about localisation
/// Finding [locale] of an app: @see https://github.com/flutter/website/blob/master/examples/internationalization/minimal/lib/main.dart
- static setLocale(locale) => Settings.locale = locale;
+ static setLocale(locale) {
+ BaseSettings.locale = locale;
+ Intl.defaultLocale = "${locale.languageCode}_$locale.countryCode";
+ initializeDateFormatting();
+ }
- static setLocaleByNames({String country = 'US', String language = 'en'}) =>
- Settings.locale = Locale(country, language);
+ /// Sets the locales with [language] and [country].
+ static setLocaleByNames({String country, @required String language}) {
+ country ??= language;
+ country = country.toUpperCase();
+ country = country == 'EN' ? 'US' : country;
+ language = language.toLowerCase();
+ setLocale(Locale(language, country));
+ }
/// Translates a [text] with a given translation [map].
/// Structure of the [map]: { <English text> : { <language code> : translation } }
return rc;
}
}
+
+class Settings extends BaseSettings {
+ static final mapWidgetData = <String, dynamic>{
+ 'form.card.padding': '16.0',
+ 'form.gap.field_button.height': '16.0',
+ };
+ static Settings _instance;
+
+ factory Settings({BaseLogger logger, BaseConfiguration widgetConfiguration}) {
+ if (_instance == null) {
+ _instance = Settings.internal(
+ widgetConfiguration == null
+ ? BaseConfiguration(mapWidgetData, logger)
+ : widgetConfiguration,
+ logger);
+ }
+ return _instance;
+ }
+
+ Settings.internal(BaseConfiguration configuration, BaseLogger logger)
+ : super(configuration, logger);
+}
{'0': input});
String _vt(String key, [Map<String, String> placeholders]) =>
- Settings.translate(key, ValidatorTranslations.translations,
+ BaseSettings.translate(key, ValidatorTranslations.translations,
placeholders: placeholders);
@protected
},
'Not a phone number: %{0} Examples: "089-123452 "+49-89-12345"': {
'de':
- 'Keine gültige Telefonnummer: %{0} Beispiele: "089-123452 "+49-89-12345"',
+ 'Keine gültige Telefonnummer: %{0} Beispiele: "089-123452 "+49-89-12345"',
},
};
}
typedef ButtonOnPressed = void Function(String name);
/// Describes a button widget.
-class ButtonModel extends WidgetModel implements ButtonCallbackController {
+class ButtonModel extends WidgetModel {
static final regExprOptions = RegExp(r'^(undef)$');
String text;
String name;
@override
String widgetName() => name;
-
- @override
- getOnHighlightChanged(
- String customString, ButtonCallbackController controller) {
- return null;
- }
-
- @override
- getOnLongPressed(String customString, ButtonCallbackController controller) {
- return null;
- }
-
- @override
- getOnPressed(String customString, ButtonCallbackController controller) {
- return onPressed;
- }
}
enum ButtonModelType {
/// Dumps the instance into a [StringBuffer]
StringBuffer dump(StringBuffer stringBuffer) {
- stringBuffer.write(
- ' checkbox $name: text: options: ${listToString(options)}\n');
+ stringBuffer
+ .write(' checkbox $name: text: options: ${listToString(options)}\n');
return stringBuffer;
}
}
super.parse();
checkSuperfluousAttributes(
map,
- 'name label dataType options texts toolTip widgetType values'
+ 'name label dataType filterType options texts toolTip widgetType values'
.split(' '));
texts = parseStringList('texts', map);
values = parseValueList('values', map, dataType);
/// Base class of widgets with user interaction like text field, combobox, checkbox.
abstract class FieldModel extends WidgetModel {
- static final regExprOptions =
- RegExp(r'^(undef|readonly|disabled|password|required)$');
String name;
String label;
String toolTip;
buffer.write(''' sql: "INSERT INTO ${table.name}(''');
final columns = table.columns
.where((col) =>
- col.hasOption('doStore') ||
- (!col.hasOption('primary') && (!col.hasOption('hidden'))))
+ col.hasOption('doStore') ||
+ (!col.hasOption('primary') && (!col.hasOption('hidden'))))
.toList();
addColumnIfMissing(columns, table.columns, '${table.name}_createdat');
addColumnIfMissing(columns, table.columns, '${table.name}_createdby');
/// Writes the list SQL part into the [buffer].
void exportList(StringBuffer buffer, TableModel table) {
- final page = pages
- .firstWhere((element) => element.pageModelType == PageModelType.list,
+ final page = pages.firstWhere(
+ (element) => element.pageModelType == PageModelType.list,
orElse: () => null);
if (page != null) {
buffer.write(''' - name: list
case FilterType.dateTimeTil:
buffer.write('${field.name}<=:${field.name}');
break;
+ case FilterType.equals:
+ buffer.write('${field.name} like :${field.name}');
+ break;
case FilterType.pattern:
buffer.write('${field.name} like :${field.name}');
break;
}
}
+ /// Writes the record SQL part into the [buffer].
+ void exportRecord(StringBuffer buffer, TableModel table) {
+ exportRecordOne(
+ 'record', '${table.name}_id=:${table.name}_id', buffer, table);
+ table.columns
+ .where((col) => col.hasOption('unique') && !col.hasOption('primary'))
+ .forEach((col) {
+ exportRecordOne(
+ 'by_${col.name}',
+ '${col.name}=:${col.name}&&${table.name}_id!=:excluded', buffer,
+ table);
+ });
+ }
+
+ /// Writes the record SQL part into the [buffer].
+ void exportRecordOne(String sqlName, String condition, StringBuffer buffer,
+ TableModel table) {
+ buffer.write(' - name: $sqlName\n');
+ buffer.write(' type: record\n');
+ buffer.write(
+ ' sql: "SELECT * from ${table.name} WHERE $condition;"\n');
+ }
+
/// Exports a YAML file (as String) describing the SQL statements for insert, update ...
/// testDate: the fix date 2020.01.01 will be used (for unit tests)
String exportSqlBackend({bool testDate: false}) {
buffer.write(''' - name: delete
type: delete
sql: "DELETE from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
- - name: record
- type: record
- sql: "SELECT * from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
''');
+ exportRecord(buffer, table);
exportList(buffer, table);
}
return buffer.toString();
type = 'DOUBLE';
break;
case DataType.int:
- type = 'INT(10)';
+ type = column.hasOption('primary') ? 'INT(10) UNSIGNED' : 'INT(10)';
break;
case DataType.reference:
type = 'INT(10) UNSIGNED';
}
String options = '';
for (var option in column.options) {
- if ('notnull null unique'.contains(option)) {
- options += ' ' + option;
+ if (option == 'notnull') {
+ options += ' NOT NULL';
+ } else if ('null unique'.contains(option)) {
+ options += ' ' + option.toUpperCase();
}
}
+ if (column.hasOption('primary')) {
+ options += ' AUTO_INCREMENT';
+ }
buffer.write(' ${column.name} $type$options,\n');
}
buffer.write(' PRIMARY KEY(${table.name}_id)\n');
addColumnIfMissing(columns, table.columns, '${table.name}_changedby');
buffer.write(columns.fold(
'',
- (prev, col) =>
- (prev as String) +
+ (prev, col) =>
+ (prev as String) +
((prev as String).isEmpty ? '' : ',') +
col.name +
'=' +
((col.name.endsWith('changedat') ? 'NOW()' : ':' + col.name))));
- buffer.write('\n WHERE ${table.name}_id=:${table.name}_id";\n');
+ buffer.write('\n WHERE ${table.name}_id=:${table.name}_id;"\n');
}
/// Returns the name including the names of the parent
/// Returns a child page given by [name], null otherwise.
PageModel pageByName(String name) {
- final found = pages.firstWhere((element) => element.name == name,
- orElse: () => null);
+ final found =
+ pages.firstWhere((element) => element.name == name, orElse: () => null);
return found;
}
import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/foundation.dart';
import 'button_model.dart';
import 'field_model.dart';
final List<SectionModel> sections = [];
PageModelType pageModelType;
List<String> options;
- @protected
final fields = <String, FieldModel>{};
- @protected
final buttons = <String, ButtonModel>{};
final widgets = <WidgetModel>[];
/// Dumps the internal structure into a [stringBuffer]
StringBuffer dump(StringBuffer stringBuffer) {
stringBuffer.write(
- ' = section $name: $sectionModelType options: ${options.join(
- ' ')} [\n');
+ ' = section $name: $sectionModelType options: ${options.join(' ')} [\n');
for (var child in children) {
child.dump(stringBuffer);
}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+///
+class ConfigurationModel extends ModuleModel {
+ static final yamlMap = <String, dynamic>{
+ //
+ "module": "configuration",
+ "tables": [
+ {
+ // configuration_id | configuration_scope | configuration_property | configuration_order | configuration_type
+ // | configuration_value | configuration_description
+ 'table': 'configuration',
+ 'columns': [
+ {
+ 'column': 'configuration_id',
+ 'dataType': 'int',
+ 'label': 'Id',
+ 'options': 'primary',
+ },
+ {
+ 'column': 'configuration_scope',
+ 'dataType': 'string',
+ 'label': 'Bereich',
+ 'size': 64,
+ 'options': 'unique;notnull',
+ },
+ {
+ 'column': 'configuration_property',
+ 'dataType': 'string',
+ 'size': 64,
+ 'label': 'Eigenschaft',
+ },
+ {
+ 'column': 'configuration_order',
+ 'dataType': 'int',
+ 'label': 'Reihe',
+ },
+ {
+ 'column': 'configuration_type',
+ 'dataType': 'string',
+ 'size': 16,
+ 'label': 'Datentyp',
+ },
+ {
+ 'column': 'configuration_value',
+ 'dataType': 'string',
+ 'size': 255,
+ 'label': 'Wert',
+ },
+ {
+ 'column': 'configuration_description',
+ 'dataType': 'string',
+ 'size': 255,
+ 'label': 'Beschreibung',
+ },
+ ]
+ },
+ ],
+ 'pages': [
+ {
+ "page": "create",
+ "pageType": "create",
+ "sections": [
+ {
+ "sectionType": "simpleForm",
+ "children": [
+ {
+ "widgetType": "allDbFields",
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "page": "change",
+ "pageType": "change",
+ "sections": [
+ {
+ "sectionType": "simpleForm",
+ "children": [
+ {
+ "widgetType": "allDbFields",
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "page": "list",
+ "pageType": "list",
+ "sections": [
+ {
+ "sectionType": "filterPanel",
+ "children": [
+ {
+ "name": "configuration_name",
+ "widgetType": "textField",
+ "filterType": "pattern",
+ },
+ {
+ "name": "configuration_scope",
+ "widgetType": "combobox",
+ "filterType": "equals",
+ "options": 'undef',
+ },
+ ]
+ }
+ ]
+ },
+ ],
+ };
+
+ ConfigurationModel(BaseLogger logger) : super(yamlMap, logger);
+
+ /// Returns the name including the names of the parent
+ @override
+ String fullName() => name;
+
+ @override
+ String widgetName() => name;
+}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+/// Returns an instance of a standard module given by [name].
+ModuleModel standardModule(String name, BaseLogger logger) {
+ var rc;
+ switch (name) {
+ case 'role':
+ rc = RoleModel(logger);
+ break;
+ case 'user':
+ rc = UserModel(logger);
+ break;
+ case 'configuration':
+ rc = ConfigurationModel(logger);
+ break;
+ default:
+ logger.error('unknown standard module: $name');
+ break;
+ }
+ return rc;
+}
+
+/// Returns the names of the standard modules.
+List<String> standardModules() => ['configuration', 'role', 'user'];
}
}
checkOptionsByRegExpr(options, regExprOptions);
- _addIfMissing('${name}_createdat', 'Erzeugt', DataType.dateTime,
- ['hidden', 'null', 'doStore']);
- _addIfMissing('${name}_createdby', 'Erzeugt von', DataType.string,
- ['hidden', 'doStore'], 16);
- _addIfMissing('${name}_changedat', 'Geändert', DataType.dateTime,
- ['hidden', 'null', 'doStore']);
- _addIfMissing('${name}_changedby', 'Geändert von', DataType.string,
- ['hidden', 'doStore'], 16);
+ _addIfMissing(
+ '${name}_createdat', 'Erzeugt', DataType.dateTime, ['hidden', 'null']);
+ _addIfMissing(
+ '${name}_createdby', 'Erzeugt von', DataType.string, ['hidden'], 16);
+ _addIfMissing(
+ '${name}_changedat', 'Geändert', DataType.dateTime, ['hidden', 'null']);
+ _addIfMissing(
+ '${name}_changedby', 'Geändert von', DataType.string, ['hidden'], 16);
}
@override
: super(logger) {
this.id = ++lastId;
}
+
/// Dumps the internal structure into a [stringBuffer]
StringBuffer dump(StringBuffer stringBuffer);
class LoginPage extends StatefulWidget {
final PageData pageData;
- LoginPage(this.pageData, { Key key }) : super(key: key);
+ LoginPage(this.pageData, {Key key}) : super(key: key);
+
@override
LoginPageState createState() {
// LoginPageState.setPageData(pageData);
}
}
-class LoginPageState extends State<LoginPage>{
+class LoginPageState extends State<LoginPage> {
LoginPageState(this.pageData);
+
final PageData pageData;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
final user = LoginUser();
return Scaffold(
- appBar: pageData.appBarBuilder('login'),
+ appBar: pageData.appBarBuilder('login'),
drawer: pageData.drawerBuilder(context),
body: SimpleForm.simpleForm(
key: _formKey,
onSaved: (input) => user.name = input,
),
TextFormField(
- validator: (input) => Validation.isEmail(input) || Validation.isPhoneNumber(input) ? null : 'keine Emailadresse und keine Telefonnummer: $input',
+ 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,
class LoginUser {
String name;
String password;
-}
\ No newline at end of file
+}
/// Data class for storing parameter to build a page.
class PageData {
BaseConfiguration configuration;
+
/// Signature: AppBar func(String title)
AppBar Function(String title) appBarBuilder;
Drawer Function(dynamic context) drawerBuilder;
+
/// Constructor.
/// [configuration] is a map with the widget data (e.g. padding)
/// [appBarBuilder] is a factory of a function returning a outside designed AppBar
/// [drawerBuilder] is a factory of a function returning a Drawer which handles the "Hamburger menu"
PageData(this.configuration, this.appBarBuilder, this.drawerBuilder);
-}
\ No newline at end of file
+}
showEditIcon: true,
buttons: <Widget>[
RaisedButtonBone(
- 'search', filters,
+ 'search',
+ filters,
child: Text('Suchen'),
),
],
class UserPage extends StatefulWidget {
final PageData pageData;
- UserPage(this.pageData, { Key key }) : super(key: key);
+ UserPage(this.pageData, {Key key}) : super(key: key);
+
@override
UserPageState createState() {
// UserPageState.setPageData(pageData);
}
}
-class UserPageState extends State<UserPage>{
+class UserPageState extends State<UserPage> {
UserPageState(this.pageData);
+
final PageData pageData;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
final user = User();
return Scaffold(
- appBar: pageData.appBarBuilder('Benutzer'),
+ appBar: pageData.appBarBuilder('Benutzer'),
drawer: pageData.drawerBuilder(context),
body: SimpleForm.simpleForm(
key: _formKey,
onSaved: (input) => user.name = input,
),
TextFormField(
- validator: (input) => Validation.isEmail(input) || Validation.isPhoneNumber(input) ? null : 'keine Emailadresse und keine Telefonnummer: $input',
+ 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,
String displayName;
String password;
int role;
-}
\ No newline at end of file
+}
--- /dev/null
+import 'package:meta/meta.dart';
+
+abstract class Persistence {
+ /// Makes a customized query for [module] with a SQL statement named [sqlName]
+ /// and the [sqlType] using some [params].
+ /// Returns null or a record (as Map) (if sqlType == 'record')
+ /// or a list of records (List<Map>).
+ Future<dynamic> customQuery(
+ {@required String module,
+ @required String sqlName,
+ @required String sqlType,
+ Map<String, dynamic> params});
+
+ /// Deletes a record with primary key [id] of the [module] with the
+ /// SQL statement [sqlName].
+ Future delete({@required String module, String sqlName, @required int id});
+
+ /// Inserts a record of the [module] with the SQL statement [sqlName]
+ /// and returns the primary key.
+ Future<int> insert(
+ {@required String module,
+ String sqlName,
+ @required Map<String, dynamic> data});
+
+ /// Returns all records of the [module] specified with [params] using
+ /// a SQL statement named [sqlName].
+ Future<dynamic> list(
+ {@required String module, String sqlName, Map<String, dynamic> params});
+
+ /// Returns a record with primary key [id] of the [module] with the
+ /// SQL statement [sqlName].
+ Future<Map<String, dynamic>> record({@required String module, String sqlName, @required int id});
+
+ /// Updates a record of [module] with the [data] using the SQL statement [sqlName].
+ Future update({@required String module,
+ String sqlName,
+ @required Map<String, dynamic> data});
+}
--- /dev/null
+import 'dart:convert' as convert;
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/src/persistence/persistence.dart';
+import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
+
+/// A persistence layer using a REST interface to store/fetch the data.
+class RestPersistence extends Persistence {
+ static RestPersistence _instance;
+ final String application;
+ final String version;
+ final BaseLogger logger;
+ final String host;
+ int sqlTraceLimit = 80;
+
+ final String scheme;
+ int port;
+ int sessionTimeout;
+ String uriPrefix;
+
+ Map<String, String> headers;
+
+ factory RestPersistence(
+ {String application,
+ String version,
+ int port,
+ String host,
+ BaseLogger logger}) {
+ _instance ??= RestPersistence.internal(
+ application: application,
+ host: host,
+ port: port,
+ version: version,
+ logger: logger);
+ return _instance;
+ }
+
+ factory RestPersistence.fromConfig(
+ BaseConfiguration configuration, BaseLogger logger) {
+ final section = 'client';
+ _instance = RestPersistence.internal(
+ application: configuration.asString('application', section: section),
+ version: configuration.asString('version', section: section),
+ host: configuration.asString('host', section: section),
+ port: configuration.asInt('port', section: section),
+ scheme: configuration.asString('schema',
+ section: section, defaultValue: 'https'),
+ logger: logger);
+
+ return _instance;
+ }
+
+ RestPersistence.internal({this.application,
+ @required this.version,
+ @required this.host,
+ @required this.port,
+ this.scheme = 'http',
+ @required this.logger}) {
+ uriPrefix = '$scheme://$host:$port';
+ }
+
+ @override
+ Future customQuery({String module,
+ String sqlName,
+ String sqlType,
+ Map<String, dynamic> params}) async {
+ var rc;
+ assert(['list', 'record', 'update'].contains(sqlType));
+ final params2 = params == null ? '{}' : convert.jsonEncode(params);
+ final answer = await runRequest(module, sqlName, sqlType, body: params2);
+ if (answer.isNotEmpty) {
+ rc = convert.jsonDecode(answer);
+ }
+ if (logger.logLevel >= LEVEL_FINE) {
+ logger.log('answer: ${StringUtils.limitString(answer, sqlTraceLimit)}');
+ }
+ return rc;
+ }
+
+ @override
+ Future delete({String module, String sqlName, int id}) async {
+ sqlName ??= 'delete';
+ await runRequest(module, sqlName, 'delete', body: '{":${module}_id":$id}');
+ }
+
+ @override
+ Future<int> insert(
+ {String module, String sqlName, Map<String, dynamic> data}) async {
+ sqlName ??= 'insert';
+ var rc = 0;
+ final data2 = data == null ? '{}' : convert.jsonEncode(data);
+ final answer = await runRequest(module, sqlName, 'insert', body: data2);
+ if (answer.isNotEmpty) {
+ final map = convert.jsonDecode(answer);
+ rc = map['id'];
+ }
+ return rc;
+ }
+
+ @override
+ Future<dynamic> list(
+ {String module, String sqlName, Map<String, dynamic> params}) async {
+ sqlName ??= 'list';
+ List<dynamic> rc;
+ final answer = await runRequest(module, sqlName, 'list',
+ body: params == null ? '{}' : convert.jsonEncode(params));
+ if (answer.isNotEmpty) {
+ rc = convert.jsonDecode(answer);
+ }
+ if (logger.logLevel >= LEVEL_FINE) {
+ logger.log('answer: ${StringUtils.limitString(answer, sqlTraceLimit)}');
+ }
+ return rc;
+ }
+
+ @override
+ Future<Map<String, dynamic>> record(
+ {String module, String sqlName, int id}) async {
+ sqlName ??= 'record';
+ Map rc;
+ final answer = await runRequest(module, sqlName, 'record',
+ body: '{":${module}_id":$id}');
+ if (answer.isNotEmpty) {
+ rc = convert.jsonDecode(answer);
+ }
+ return rc;
+ }
+
+ /// Handles a HTTP request with a single HTTP connection.
+ /// [module]: the module which implies the requested table.
+ /// [sqlName]: the name of the SQL statement.
+ /// [sqlType]: 'insert', 'record', 'update', 'list', 'delete'
+ /// [body]: null or the body of the POST request, e.g. '{ 'role_id'
+ /// [headers]: the optional HTTP headers.
+ /// [result]: the body of the response or ''
+ Future<String> runRequest(String module, String sqlName, String sqlType,
+ {String body, Map<String, String> headers}) async {
+ var rc = '';
+ final uri = '$uriPrefix/$application/$module/$sqlType/$sqlName/$version';
+ http.Response response;
+ logger.log('request: POST $module $sqlName', LEVEL_LOOP);
+ response = await http.post(uri, body: body, headers: headers);
+ logger.log('status: ${response.statusCode}', LEVEL_LOOP);
+ if (response.statusCode != 200) {
+ logger.error('$uri: status: ${response.statusCode}');
+ } else {
+ rc = response.body ?? '';
+ }
+ return rc;
+ }
+
+ @override
+ Future update(
+ {String module, String sqlName, Map<String, dynamic> data}) async {
+ sqlName ??= 'update';
+ final data2 = data == null ? '{}' : convert.jsonEncode(data);
+ await runRequest(module, sqlName, 'update', body: data2);
+ }
+}
));
return rc;
}
+
static Widget buildListView(context) {
final list = MenuItem.menuItems();
final rc = Card(
--- /dev/null
+import 'package:flutter/material.dart';
+
+/// Implements a [DropdownButtonFormField] with "outsourced" callbacks:
+/// [customString] a string mostly used for a name needed in the [customController]
+/// [callbackController] handles the callback methods.
+class DropdownButtonFormBone<T> extends DropdownButtonFormField<T> {
+ final String customString;
+ final ComboboxCallbackController callbackController;
+
+ DropdownButtonFormBone(
+ this.customString,
+ this.callbackController, {
+ Key key,
+ @required List<DropdownMenuItem<T>> items,
+ T value,
+ Widget hint,
+ Widget disabledHint,
+ int elevation: 8,
+ TextStyle style,
+ Widget icon,
+ Color iconDisabledColor,
+ Color iconEnabledColor,
+ double iconSize: 24.0,
+ bool isDense: true,
+ bool isExpanded: false,
+ double itemHeight,
+ Color focusColor,
+ FocusNode focusNode,
+ bool autofocus: false,
+ Color dropdownColor,
+ InputDecoration decoration,
+ FormFieldSetter<T> onSaved,
+ FormFieldValidator<T> validator,
+ AutovalidateMode autovalidateMode,
+ }) : super(
+ key: key,
+ items: items,
+ selectedItemBuilder: callbackController.getOnSelectedItemBuilder(
+ customString, callbackController),
+ value: value,
+ hint: hint,
+ disabledHint: disabledHint,
+ onChanged:
+ callbackController.getOnChanged(customString, callbackController),
+ onTap: callbackController.getOnTap(customString, callbackController),
+ elevation: elevation,
+ style: style,
+ icon: icon,
+ iconDisabledColor: iconDisabledColor,
+ iconEnabledColor: iconEnabledColor,
+ iconSize: iconSize,
+ isDense: isDense,
+ isExpanded: isExpanded,
+ itemHeight: itemHeight,
+ focusColor: focusColor,
+ focusNode: focusNode,
+ autofocus: autofocus,
+ dropdownColor: dropdownColor,
+ decoration: decoration,
+ autovalidateMode: autovalidateMode,
+ );
+}
+
+/// Interface for a [DropdownButtonFormBone] specific callback controller.
+abstract class ComboboxCallbackController<T> {
+ ValueChanged<T> getOnChanged(
+ String customString, ComboboxCallbackController controller);
+
+ FormFieldSetter<T> getOnSaved(
+ String customString, ComboboxCallbackController controller);
+
+ DropdownButtonBuilder getOnSelectedItemBuilder(
+ String customString, ComboboxCallbackController controller);
+
+ VoidCallback getOnTap(
+ String customString, ComboboxCallbackController controller);
+
+ FormFieldValidator<T> getOnValidator(
+ String customString, ComboboxCallbackController controller);
+
+ String getName();
+
+ List<String> texts();
+
+ List<dynamic> values();
+}
class Filters
implements
- TextCallbackController,
+ TextFormCallbackController,
ButtonCallbackController,
TableCallbackController {
var filters = <FilterItem>[];
filters.firstWhere((element) => element.name == name, orElse: () => null);
@override
- getOnChanged(String customString, TextCallbackController controller) {
+ getOnChanged(String customString, TextFormCallbackController controller) {
return null;
}
@override
- getOnEditingComplete(String customString, TextCallbackController controller) {
+ getOnEditingComplete(
+ String customString, TextFormCallbackController controller) {
return null;
}
}
@override
- getOnFieldSubmitted(String customString, TextCallbackController controller) {
+ getOnFieldSubmitted(
+ String customString, TextFormCallbackController controller) {
return null;
}
}
@override
- getOnSaved(String customString, TextCallbackController controller) {
+ getOnSaved(String customString, TextFormCallbackController controller) {
return (input) {
byName(customString).value = input;
};
}
@override
- getOnTap(String customString, TextCallbackController controller) {
+ getOnTap(String customString, TextFormCallbackController controller) {
return null;
}
@override
- getValidator(String customString, TextCallbackController controller) {
+ getValidator(String customString, TextFormCallbackController controller) {
return null;
}
for (var key in columnNames) {
cells.add(DataCell(
Text(row[key]),
- onTap: () =>
- tableCallbackController.getOnEditTap(
- customString, tableCallbackController, row),
+ onTap: () => tableCallbackController.getOnEditTap(
+ customString, tableCallbackController, row),
));
}
return DataRow(cells: cells);
// This interface allows the generic handling of the edit form by a model driven module.
class ModuleController
- implements TextCallbackController, ButtonCallbackController {
+ implements TextFormCallbackController, ButtonCallbackController {
final ModuleModel moduleModel;
String primaryColumn;
WidgetList createWidgets;
ModuleModel getModuleModel() => moduleModel;
@override
- getOnChanged(String customString, TextCallbackController controller) {
+ getOnChanged(String customString, TextFormCallbackController controller) {
return null;
}
@override
- getOnEditingComplete(String customString, TextCallbackController controller) {
+ getOnEditingComplete(
+ String customString, TextFormCallbackController controller) {
return null;
}
@override
- getOnFieldSubmitted(String customString, TextCallbackController controller) {
+ getOnFieldSubmitted(
+ String customString, TextFormCallbackController controller) {
return null;
}
}
@override
- getOnSaved(String customString, TextCallbackController controller) {
+ getOnSaved(String customString, TextFormCallbackController controller) {
final rc = (input) {
values[customString] =
StringHelper.fromString(input, dataTypes[customString]);
}
@override
- getOnTap(String customString, TextCallbackController controller) {
+ getOnTap(String customString, TextFormCallbackController controller) {
return null;
}
@override
- getValidator(String customString, TextCallbackController controller) {
+ getValidator(String customString, TextFormCallbackController controller) {
return null;
}
Duration animationDuration,
Widget child})
: super(
- onPressed: callbackController.getOnPressed(
- customString, callbackController),
- onLongPress: callbackController.getOnLongPressed(
- customString, callbackController),
- onHighlightChanged: callbackController.getOnHighlightChanged(
- customString, callbackController),
- textTheme: textTheme,
- textColor: textColor,
- disabledTextColor: disabledTextColor,
- color: color,
- disabledColor: disabledColor,
- focusColor: focusColor,
- hoverColor: hoverColor,
- highlightColor: highlightColor,
- splashColor: splashColor,
- colorBrightness: colorBrightness,
+ onPressed: callbackController.getOnPressed(
+ customString, callbackController),
+ onLongPress: callbackController.getOnLongPressed(
+ customString, callbackController),
+ onHighlightChanged: callbackController.getOnHighlightChanged(
+ customString, callbackController),
+ textTheme: textTheme,
+ textColor: textColor,
+ disabledTextColor: disabledTextColor,
+ color: color,
+ disabledColor: disabledColor,
+ focusColor: focusColor,
+ hoverColor: hoverColor,
+ highlightColor: highlightColor,
+ splashColor: splashColor,
+ colorBrightness: colorBrightness,
elevation: elevation,
focusElevation: focusElevation,
hoverElevation: hoverElevation,
import 'package:flutter/services.dart';
/// Interface for a [TextFormField] specific callback controller.
-abstract class TextCallbackController {
+abstract class TextFormCallbackController {
ValueChanged<String> getOnChanged(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
VoidCallback getOnEditingComplete(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
ValueChanged<String> getOnFieldSubmitted(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
FormFieldSetter<String> getOnSaved(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
GestureTapCallback getOnTap(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
FormFieldValidator<String> getValidator(
- String customString, TextCallbackController controller);
+ String customString, TextFormCallbackController controller);
}
/// Implements a [TextFormField] with "outsourced" callbacks:
/// [callbackController] handles the callback methods.
class TextFormFieldBone extends TextFormField {
final String customString;
- final TextCallbackController callbackController;
+ final TextFormCallbackController callbackController;
- TextFormFieldBone(
- this.customString,
- this.callbackController, {
- Key key,
- TextEditingController controller,
- String initialValue,
- FocusNode focusNode,
- InputDecoration decoration = const InputDecoration(),
- TextInputType keyboardType,
- TextCapitalization textCapitalization = TextCapitalization.none,
- TextInputAction textInputAction,
- TextStyle style,
- StrutStyle strutStyle,
- TextDirection textDirection,
- TextAlign textAlign = TextAlign.start,
- TextAlignVertical textAlignVertical,
- bool autofocus = false,
- bool readOnly = false,
- ToolbarOptions toolbarOptions,
+ TextFormFieldBone(this.customString,
+ this.callbackController, {
+ Key key,
+ TextEditingController controller,
+ String initialValue,
+ FocusNode focusNode,
+ InputDecoration decoration = const InputDecoration(),
+ TextInputType keyboardType,
+ TextCapitalization textCapitalization = TextCapitalization.none,
+ TextInputAction textInputAction,
+ TextStyle style,
+ StrutStyle strutStyle,
+ TextDirection textDirection,
+ TextAlign textAlign = TextAlign.start,
+ TextAlignVertical textAlignVertical,
+ bool autofocus = false,
+ bool readOnly = false,
+ ToolbarOptions toolbarOptions,
bool showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
import 'package:dart_bones/dart_bones.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/controller/button_controller.dart';
+import '../controller/combobox_controller.dart';
import '../helper/settings.dart';
-import '../model/button_model.dart';
import '../model/empty_line_model.dart';
import '../model/field_model.dart';
import '../model/section_model.dart';
import '../model/text_field_model.dart';
import '../model/text_model.dart';
import '../model/widget_model.dart';
+import 'dropdown_button_form_bone.dart';
import 'raised_button_bone.dart';
class View {
if (_instance == null) {
_instance = View.internal(logger);
_instance.settings = Settings(logger: logger);
- _instance.widgetConfiguration = _instance.settings.widgetConfiguration;
+ _instance.widgetConfiguration = _instance.settings.configuration;
}
return _instance;
}
View.internal(this.logger);
- /// Creates a button from the [model].
- Widget button(ButtonModel model) {
+ /// Creates a button from the [controller].
+ Widget button(ButtonController controller) {
final rc = RaisedButtonBone(
- model.widgetName(),
- model,
- child: Text(model.text),
+ controller.getName(),
+ controller,
+ child: Text(controller.model.text),
);
return rc;
}
- /// Creates a list of buttons from a list of [models].
- List<Widget> buttonList(List<WidgetModel> models) {
+ /// Creates a list of buttons from a list of [controllers].
+ List<Widget> buttonList(List<ButtonController> controllers) {
final rc = <Widget>[];
- for (var item in models) {
+ for (var item in controllers) {
rc.add(button(item));
}
return rc;
}
+ /// Creates a combobox via the [controller].
+ Widget combobox<T>(ComboboxController controller, onTap) {
+ final texts = controller.texts();
+ final values = controller.values() ?? texts;
+ final items = <DropdownMenuItem<T>>[];
+ for (var ix = 0; ix < texts.length; ix++) {
+ items.add(DropdownMenuItem(
+ onTap: onTap, value: values[ix], child: Text(texts[ix])));
+ }
+ final rc =
+ DropdownButtonFormBone(controller.getName(), controller, items: items);
+ return rc;
+ }
+
/// Creates a checkbox from the [model].
Widget checkbox(WidgetModel model) {
var rc;
final padding =
widgetConfiguration.asFloat('form.card.padding', defaultValue: 16.0);
final children = widgetsOfSection(model.children);
- final buttons = buttonList(model.buttons);
+ final controllers = model.buttons.map((button) => ButtonController(button));
+ final buttons = buttonList(controllers);
final rc = Form(
key: formKey,
child: Card(
rc.add(textField(child));
break;
case WidgetModelType.button:
- rc.add(button(child));
+ rc.add(button(ButtonController(child)));
break;
case WidgetModelType.emptyLine:
rc.add(emptyLine(child));
sdk: flutter
flutter_localizations:
sdk: flutter
- dart_bones: "^0.4.3"
+ dart_bones: "^0.4.5"
# The following adds the Cupertino Icons font to your application.
+import 'package:dart_bones/dart_bones.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bones/flutter_bones.dart';
+import 'package:intl/intl.dart';
import 'package:test/test.dart';
void main() {
+ Intl.defaultLocale = "de_DE";
+ final logger = MemoryLogger(LEVEL_FINE);
final map = {
'help': {'de': 'Hilfe'},
- 'wrong name %{0}': {'de': 'falscher name %{0}'}
+ 'wrong name %{0}': {'de': 'falscher Name %{0}'}
};
- setUpAll(() => Settings.setLocaleByNames(language: 'en'));
+ setUpAll(() => BaseSettings.setLocaleByNames(language: 'en'));
group('Settings', () {
test('basic', () {
- Settings.setLocale(Locale('de', 'de'));
- expect(Settings.translate('abc', map), equals('abc'));
- expect(Settings.translate('help', map), equals('help'));
+ BaseSettings.setLocale(Locale('de', 'DE'));
+ expect(BaseSettings.language, 'de');
+ expect(BaseSettings.translate('abc', map), equals('abc'));
+ expect(BaseSettings.translate('help', map), equals('Hilfe'));
expect(
- Settings.translate('wrong name %{0}', map,
+ BaseSettings.translate('wrong name %{0}', map,
placeholders: {'0': 'Joe'}),
- equals('wrong name Joe'));
+ equals('falscher Name Joe'));
+ expect(Settings(logger: logger), isNotNull);
});
test('translate-en', () {
- Settings.setLocaleByNames(language: 'en');
- expect(Settings.translate('abc', map), equals('abc'));
- expect(Settings.translate('help', map), equals('help'));
+ BaseSettings.setLocaleByNames(language: 'en');
+ expect(BaseSettings.language, 'en');
+ expect(BaseSettings.translate('abc', map), equals('abc'));
+ expect(BaseSettings.translate('help', map), equals('help'));
expect(
- Settings.translate('wrong name %{0}', map,
+ BaseSettings.translate('wrong name %{0}', map,
placeholders: {'0': 'Joe'}),
equals('wrong name Joe'));
});
test('translate-de', () {
- Settings.setLocaleByNames(language: 'de');
- expect(Settings.translate('abc', map), equals('abc'));
- expect(Settings.translate('help', map), equals('Hilfe'));
- expect(Settings.translate('wrong name %{0}', map, placeholders: {'0' : 'Joe'}), equals('falscher name Joe'));
+ BaseSettings.setLocaleByNames(language: 'de');
+ expect(BaseSettings.translate('abc', map), equals('abc'));
+ expect(BaseSettings.translate('help', map), equals('Hilfe'));
+ expect(
+ BaseSettings.translate('wrong name %{0}', map,
+ placeholders: {'0': 'Joe'}),
+ equals('falscher Name Joe'));
});
});
}
});
});
group('ModelBase', () {
- test('errors', () {
+ test('basic', () {
logger.clear();
final map = cloneOfMap(userModel);
final field = <String, dynamic>{
});
});
group('CheckboxModel', () {
- logger.clear();
+ test('basic', () {
+ logger.clear();
+ final map = cloneOfMap(userModel);
+ final field = <String, dynamic>{
+ 'widgetType': 'checkbox',
+ 'name': 'hidden',
+ 'label': 'Hidden',
+ 'options': 'required',
+ };
+ map['pages'][0]['sections'][0]['children'][0] = field;
+ var module = Demo1(map, logger);
+ module.parse();
+ var errors = logger.errors;
+ expect(errors.length, equals(0));
+ final checkbox = module.pageByName('create').getField('hidden');
+ expect(checkbox.hasOption('required'), isTrue);
+ checkbox.value = true;
+ expect(checkbox.value, isTrue);
+ checkbox.value = false;
+ expect(checkbox.value, isFalse);
+ checkbox.value = 'true';
+ expect(checkbox.value, isTrue);
+ });
test('errors', () {
+ logger.clear();
final map = cloneOfMap(userModel);
final field = <String, dynamic>{
'widgetType': 'checkbox',
var module = Demo1(map, logger);
module.parse();
var errors = logger.errors;
- expect(errors.length, equals(2));
+ expect(errors.length, equals(0));
final checkbox = module.pageByName('create').getField('hidden');
expect(checkbox, isNotNull);
expect(checkbox.dataType, DataType.bool);
var module = Demo1(map, logger);
module.parse();
var errors = logger.errors;
- expect(errors.length, equals(2));
+ expect(errors.length, equals(0));
final combobox = module.pageByName('create').getField('class');
expect(combobox, isNotNull);
expect(combobox.dataType, DataType.int);
});
});
group('Non field widgets', () {
- logger.clear();
test('basic', () {
+ logger.clear();
final map = cloneOfMap(userModel);
final list = [
{
var module = Demo1(map, logger);
module.parse();
var errors = logger.errors;
- expect(errors.length, equals(2));
+ expect(errors.length, equals(0));
final dump = module.dump(StringBuffer()).toString();
- expect(dump, equals('''= module demo1: options:
-== page create: PageModelType.create options:
- = section simpleForm1: SectionModelType.simpleForm options: [
- emptyLine: options:
- text 24 text: *Hi world*: options: rich
- ] # create.simpleForm1
-'''));
+ expect(dump.contains('text: *Hi world*: options: rich'), isTrue);
final page = module.pageByName('create');
final allWidgets = page.getWidgets(null);
expect(allWidgets.length, equals(2));
expect(nonFieldWidgets.length, equals(2));
});
});
- group('standard-models', () {
- test('user', () {
- final model = UserModel(logger);
- model.parse();
- });
- });
}
final userModel = <String, dynamic>{
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+import 'package:test/test.dart';
+
+void main() {
+ final logger = MemoryLogger(LEVEL_FINE);
+ group('module', () {
+ test('role', () {
+ logger.clear();
+ final module = RoleModel(logger);
+ module.parse();
+ expect(module.fullName(), equals('role'));
+ expect(module.widgetName(), equals('role'));
+ final errors = logger.errors;
+ expect(errors.length, equals(0));
+ final dump = module.dump(StringBuffer()).toString();
+ expect(dump, '''= module role: options:
+== table role: options:
+ column role_id: DataType.int "Id" options: primary notnull unique
+ column role_name: DataType.string "Rolle" options: unique notnull
+ column role_priority: DataType.int "Priorität" options:
+ column role_active: DataType.bool "Aktiv" options:
+ column role_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+ column role_createdby: DataType.string "Erzeugt von" options: hidden doStore
+ column role_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+ column role_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 10 text: options:
+ ] # create.simpleForm1
+== page change: PageModelType.change options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 12 text: options:
+ ] # change.simpleForm1
+== page list: PageModelType.list options:
+ = section filterPanel1: SectionModelType.filterPanel options: [
+ textField role_name: options:
+ ] # list.filterPanel1
+''');
+ });
+ test('user', () {
+ logger.clear();
+ final module = UserModel(logger);
+ module.parse();
+ expect(module.fullName(), equals('user'));
+ expect(module.widgetName(), equals('user'));
+ final errors = logger.errors;
+ expect(errors.length, equals(0));
+ final dump = module.dump(StringBuffer()).toString();
+ expect(dump, '''= module user: options:
+== table user: options:
+ column user_id: DataType.int "Id" options: primary notnull unique
+ column user_name: DataType.string "User" options: unique notnull
+ column user_displayname: DataType.string "Anzeigename" options: unique
+ column user_email: DataType.string "EMail" options: unique
+ column user_password: DataType.string "User" options: password
+ column user_role: DataType.reference "Role" options:
+ column user_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+ column user_createdby: DataType.string "Erzeugt von" options: hidden doStore
+ column user_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+ column user_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 26 text: options:
+ ] # create.simpleForm1
+== page change: PageModelType.change options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields 28 text: options:
+ ] # change.simpleForm1
+== page list: PageModelType.list options:
+ = section filterPanel1: SectionModelType.filterPanel options: [
+ textField user_name: options:
+ combobox user_role: texts: options:
+ ] # list.filterPanel1
+''');
+ });
+ test('configuration', () {
+ logger.clear();
+ final module = ConfigurationModel(logger);
+ module.parse();
+ expect(module.fullName(), equals('configuration'));
+ expect(module.widgetName(), equals('configuration'));
+ final errors = logger.errors;
+ expect(errors.length, equals(0));
+ final dump = module.dump(StringBuffer()).toString();
+ expect(dump, startsWith('''= module configuration: options:
+== table configuration: options:
+ column configuration_id: DataType.int "Id" options: primary notnull unique
+ column configuration_scope: DataType.string "Bereich" options: unique notnull
+ column configuration_property: DataType.string "Eigenschaft" options:
+ column configuration_order: DataType.int "Reihe" options:
+ column configuration_type: DataType.string "Datentyp" options:
+ column configuration_value: DataType.string "Wert" options:
+ column configuration_description: DataType.string "Beschreibung" options:
+ column configuration_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+ column configuration_createdby: DataType.string "Erzeugt von" options: hidden doStore
+ column configuration_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+ column configuration_changedby: DataType.string "Geändert von" options: hidden doStore
+== page create: PageModelType.create options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields '''));
+ /*
+ expect(dump.contains('''text: options:
+ ] # create.simpleForm1
+== page change: PageModelType.change options:
+ = section simpleForm1: SectionModelType.simpleForm options: [
+ allDbFields '''), isTrue);
+ */
+ expect(dump,
+ contains('combobox configuration_scope: texts: options: undef'));
+ expect(dump, contains('textField configuration_name: options:'));
+ });
+ });
+}
--- /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_test/flutter_test.dart';
+
+void main() async {
+ final logger = MemoryLogger(LEVEL_FINE);
+ setUpAll(() {
+ final configuration = BaseConfiguration({
+ 'client': {
+ 'host': 'localhost',
+ 'port': 58011,
+ 'schema': 'http',
+ 'application': 'unittest',
+ 'version': '1.0.0',
+ }
+ }, logger);
+ RestPersistence.fromConfig(configuration, logger);
+ });
+ group('queries', () {
+ test('list', () async {
+ final rest = RestPersistence();
+ final list = await rest.list(
+ module: 'role', sqlName: 'list', params: {'":role_name"': '"A%"'});
+ expect(list, isNotNull);
+ expect(list.length, equals(1));
+ expect(list[0].containsKey('role_id'), isTrue);
+ });
+ test('record', () async {
+ final rest = RestPersistence();
+ final record = await rest.record(module: 'role', id: 2);
+ expect(record, isNotNull);
+ expect(record.length, greaterThan(4));
+ expect(record.containsKey('role_id'), isTrue);
+ });
+ });
+ group('modify', () {
+ test('insert+update+delete', () async {
+ final rest = RestPersistence();
+ final rec = await rest.customQuery(
+ module: 'role',
+ sqlName: 'by_role_name',
+ sqlType: 'record',
+ params: {':excluded': 0, ':role_name': 'dummy'});
+ if (rec != 0 && rec.containsKey('role_id')) {
+ await rest.delete(module: 'role', id: rec['role_id']);
+ }
+ final id = await rest.insert(module: 'role', sqlName: 'insert', data: {
+ ':role_name': 'dummy',
+ ':role_priority': 111,
+ ':role_active': 'F',
+ ':role_createdby': 'joe'
+ });
+ expect(id is int, isTrue);
+ final answer2 =
+ await rest.update(module: 'role', sqlName: 'update', data: {
+ ':role_id': id,
+ ':role_name': 'dummy2',
+ ':role_priority': 112,
+ ':role_active': 'T',
+ ':role_changedby': 'eve'
+ });
+ final answer = rest.record(module: 'role', id: id);
+ logger.log('log: $answer');
+ await rest.delete(module: 'role', id: id);
+ });
+ });
+}
expect(errors.length, equals(0));
expect(content, equals('''DROP TABLE IF EXISTS role;
CREATE TABLE role (
- role_id INT(10) notnull unique,
- role_name VARCHAR(32) unique notnull,
+ role_id INT(10) UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT,
+ role_name VARCHAR(32) UNIQUE NOT NULL,
role_priority INT(10),
role_active CHAR(1),
- role_createdat TIMESTAMP null,
+ role_createdat TIMESTAMP NULL,
role_createdby VARCHAR(16),
- role_changedat TIMESTAMP null,
+ role_changedat TIMESTAMP NULL,
role_changedby VARCHAR(16),
PRIMARY KEY(role_id)
);
list:
- name: insert
type: insert
- sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby,role_changedat,role_changedby)
- VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby,:role_changedat,:role_changedby);"
+ sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby)
+ VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby);"
- name: update
type: update
sql: "UPDATE role SET
- role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_createdat=:role_createdat,role_createdby=:role_createdby,role_changedat=NOW(),role_changedby=:role_changedby
- WHERE role_id=:role_id";
+ role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_changedat=NOW(),role_changedby=:role_changedby
+ WHERE role_id=:role_id;"
- name: delete
type: delete
sql: "DELETE from role WHERE role_id=:role_id;"
- name: record
type: record
sql: "SELECT * from role WHERE role_id=:role_id;"
+ - name: by_role_name
+ type: record
+ sql: "SELECT * from role WHERE role_name=:role_name&&role_id!=:excluded;"
- name: list
type: list
sql: "SELECT * from role
WHERE role_name like :role_name;"
'''));
});
+ test('standard_modules', () {
+ logger.clear();
+ final dir = FileSync.tempDirectory('data', subDirs: 'unittest');
+ for (var name in standardModules()) {
+ final module = standardModule(name, logger);
+ module.parse();
+ expect(logger.errors.isEmpty, isTrue);
+ final content = module.exportSqlBackend();
+ FileSync.toFile(FileSync.joinPaths(dir, '$name.yaml'), content);
+ }
+ });
+ test('standard_modules-errors', () {
+ logger.clear();
+ final module = standardModule('not-exists', logger);
+ expect(module, isNull);
+ expect(logger.contains('unknown standard module: not-exists'), isTrue);
+ });
});
}
});
group('ModuleController', () {
test('basic', () {
- PageData pageData =
- PageData(BaseConfiguration({}, logger), (title) {}, (context) {});
+ PageData pageData = PageData(
+ BaseConfiguration({}, logger), (title) => null, (context) => null);
final role = RoleCreatePage(pageData);
if (role.lastState == null) {
role.createState();