From c74c84cda758472b8c4a2d099f738f2c595ecb7f Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Sun, 4 Oct 2020 00:56:06 +0200 Subject: [PATCH] +TableModel +ColumnModel +DbReferenceModel --- lib/src/model/button_model.dart | 1 + lib/src/model/checkbox_model.dart | 13 ++- lib/src/model/column_model.dart | 74 ++++++++++++++ lib/src/model/combobox_model.dart | 9 +- lib/src/model/db_reference_model.dart | 49 +++++++++ lib/src/model/empty_line_model.dart | 8 +- lib/src/model/field_model.dart | 8 +- lib/src/model/model_base.dart | 15 +-- lib/src/model/module/user_model.dart | 3 + lib/src/model/module_model.dart | 72 +++++++++++-- lib/src/model/page_model.dart | 26 +++-- lib/src/model/section_model.dart | 21 +++- lib/src/model/table_model.dart | 97 ++++++++++++++++++ lib/src/model/text_field_model.dart | 10 +- lib/src/model/text_model.dart | 6 +- lib/src/model/widget_model.dart | 10 +- lib/src/widget/view.dart | 24 ++++- test/model/db_model_test.dart | 140 ++++++++++++++++++++++++++ 18 files changed, 541 insertions(+), 45 deletions(-) create mode 100644 lib/src/model/column_model.dart create mode 100644 lib/src/model/db_reference_model.dart create mode 100644 lib/src/model/table_model.dart create mode 100644 test/model/db_model_test.dart diff --git a/lib/src/model/button_model.dart b/lib/src/model/button_model.dart index 2b0d8f5..3fba73b 100644 --- a/lib/src/model/button_model.dart +++ b/lib/src/model/button_model.dart @@ -4,6 +4,7 @@ import 'package:flutter_bones/flutter_bones.dart'; typedef ButtonOnPressed = void Function(String name); +/// Describes a button widget. class ButtonModel extends WidgetModel { static final regExprOptions = RegExp(r'^(undef)$'); String text; diff --git a/lib/src/model/checkbox_model.dart b/lib/src/model/checkbox_model.dart index 40f1721..78990d1 100644 --- a/lib/src/model/checkbox_model.dart +++ b/lib/src/model/checkbox_model.dart @@ -1,20 +1,25 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'field_model.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes a checkbox widget. class CheckboxModel extends FieldModel { static final regExprOptions = RegExp(r'^(readonly|disabled|required|undef)$'); final Map map; - CheckboxModel(SectionModel section, PageModel page, this.map, - BaseLogger logger) + CheckboxModel( + SectionModel section, PageModel page, this.map, BaseLogger logger) : super(section, page, map, WidgetModelType.checkbox, logger); /// Parses the map and stores the data in the instance. void parse() { + super.parse(); checkSuperfluousAttributes( map, 'name label options toolTip widgetType'.split(' ')); - super.parse(); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } diff --git a/lib/src/model/column_model.dart b/lib/src/model/column_model.dart new file mode 100644 index 0000000..f3763a4 --- /dev/null +++ b/lib/src/model/column_model.dart @@ -0,0 +1,74 @@ +import 'package:dart_bones/dart_bones.dart'; + +import 'model_base.dart'; +import 'model_types.dart'; +import 'table_model.dart'; +import 'widget_model.dart'; + +/// Describes a column of a database table. +class ColumnModel extends WidgetModel { + static final regExprOptions = + RegExp(r'^(undef|readonly|disabled|primary|required|unique)$'); + String name; + String label; + String toolTip; + DataType dataType; + int size; + int rows; + List options; + final TableModel table; + final Map map; + String foreignKey; + + ColumnModel(this.table, this.map, BaseLogger logger) + : super(null, null, WidgetModelType.column, logger); + + /// Dumps the instance into a [StringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write( + ' column $name: $dataType "$label" options: ${options.join(' ')}\n'); + return stringBuffer; + } + + /// Returns the name including the names of the parent + @override + String fullName() => '${table.name}.$name'; + + /// Parses the map and stores the data in the instance. + void parse() { + name = parseString('name', map, required: true); + checkSuperfluousAttributes( + map, + 'dataType foreignKey label name options rows size tooTip widgetType' + .split(' ')); + dataType = + parseEnum('dataType', map, DataType.values, required: true); + label = parseString('label', map, required: false); + toolTip = parseString('toolTip', map, required: false); + size = parseInt('size', map, required: dataType == DataType.string); + rows = parseInt('size', map); + + options = parseOptions('options', map); + checkOptionsByRegExpr(options, regExprOptions); + } + + @override + String widgetName() => name; + + /// Parses a list of columns. + static void parseColumns( + TableModel table, List list, BaseLogger logger) { + int no = 0; + for (var item in list) { + no++; + if (!ModelBase.isMap(item)) { + logger.error( + 'curious item (position $no) in column list of ${table.fullName()}: ${StringUtils.limitString("$item", 80)}'); + } else { + final current = ColumnModel(table, item, logger); + current.parse(); + table.addColumn(current); + } + } + } +} diff --git a/lib/src/model/combobox_model.dart b/lib/src/model/combobox_model.dart index 5f6e98d..395c660 100644 --- a/lib/src/model/combobox_model.dart +++ b/lib/src/model/combobox_model.dart @@ -1,7 +1,12 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'field_model.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes a combobox widget. class ComboboxModel extends FieldModel { static final regExprOptions = RegExp(r'^(readonly|disabled|required|undef)$'); List texts; @@ -24,11 +29,11 @@ class ComboboxModel extends FieldModel { /// Parses the map and stores the data in the instance. void parse() { + super.parse(); checkSuperfluousAttributes( map, 'name label dataType options texts toolTip widgetType values' .split(' ')); - super.parse(); texts = parseStringList('texts', map); values = parseValueList('values', map, dataType); options = parseOptions('options', map); diff --git a/lib/src/model/db_reference_model.dart b/lib/src/model/db_reference_model.dart new file mode 100644 index 0000000..5e9d9d1 --- /dev/null +++ b/lib/src/model/db_reference_model.dart @@ -0,0 +1,49 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/material.dart'; + +import 'column_model.dart'; +import 'field_model.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes a form text field widget. +class DbReferenceModel extends FieldModel { + static final regExprOptions = + RegExp(r'^(readonly|disabled|required|password|unique)$'); + int maxSize; + int rows; + var value; + ColumnModel column; + FormFieldValidator validator; + + FormFieldSetter onSaved; + + final Map map; + + DbReferenceModel( + SectionModel section, PageModel page, this.map, BaseLogger logger) + : super(section, page, map, WidgetModelType.textField, logger); + + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer + .write(' textField $name: options: ${listToString(options)}\n'); + return stringBuffer; + } + + /// Parses the map and stores the data in the instance. + void parse() { + super.parse(); + checkSuperfluousAttributes( + map, + 'column label maxSize name options rows toolTip value widgetType' + .split(' ')); + final columnName = parseString('column', map, required: true); + column = page.module.getColumn(columnName, this); + maxSize = parseInt('maxSize', map, required: false); + rows = parseInt('rows', map, required: false); + options = parseOptions('options', map); + checkOptionsByRegExpr(options, regExprOptions); + } +} diff --git a/lib/src/model/empty_line_model.dart b/lib/src/model/empty_line_model.dart index 92236be..f233c54 100644 --- a/lib/src/model/empty_line_model.dart +++ b/lib/src/model/empty_line_model.dart @@ -1,6 +1,10 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes an empty line used as separator between two other widgets. class EmptyLineModel extends WidgetModel { static final regExprOptions = RegExp(r'^(unknown)$'); List options; @@ -23,8 +27,8 @@ class EmptyLineModel extends WidgetModel { /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluousAttributes(map, 'options widgetType'.split(' ')); options = parseOptions('options', map); + checkSuperfluousAttributes(map, 'options widgetType'.split(' ')); checkOptionsByRegExpr(options, regExprOptions); } diff --git a/lib/src/model/field_model.dart b/lib/src/model/field_model.dart index 1d81dd3..1d205f5 100644 --- a/lib/src/model/field_model.dart +++ b/lib/src/model/field_model.dart @@ -1,7 +1,13 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import '../helper/string_helper.dart'; +import 'model_types.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Base class of widgets with user interaction like text field, combobox, checkbox. abstract class FieldModel extends WidgetModel { static final regExprOptions = RegExp(r'^(undef|readonly|disabled|password|required)$'); diff --git a/lib/src/model/model_base.dart b/lib/src/model/model_base.dart index ce8007e..abccce1 100644 --- a/lib/src/model/model_base.dart +++ b/lib/src/model/model_base.dart @@ -1,8 +1,9 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; import 'package:meta/meta.dart'; +import 'model_types.dart'; + /// Base class of all models. abstract class ModelBase { BaseLogger logger; @@ -72,7 +73,7 @@ abstract class ModelBase { int rc; if (!map.containsKey(key)) { if (required) { - logger.error('missing $key in ${fullName()}'); + logger.error('missing int attribute "$key" in ${fullName()}'); } else { final value = map[key]; if (value != null) { @@ -116,7 +117,7 @@ abstract class ModelBase { String rc; if (!map.containsKey(key)) { if (required) { - logger.error('missing $key in ${fullName()}'); + logger.error('missing string attribute $key in ${fullName()}'); } } else { rc = map[key] as String; @@ -133,7 +134,7 @@ abstract class ModelBase { var rc = []; if (!map.containsKey(key)) { if (required) { - logger.error('missing $key in ${fullName()}'); + logger.error('missing string list attribute $key in ${fullName()}'); } } else { final value = map[key] as String; @@ -151,8 +152,8 @@ abstract class ModelBase { /// This entry is splitted by the delimiter given at index 0. /// Example: ";a;b" returns ['a', 'b']. /// An error is logged if [required] is true and the map does not contain the key. - List parseValueList(String key, Map map, - DataType dataType, + List parseValueList( + String key, Map map, DataType dataType, {bool required = false}) { if (dataType == null) { dataType = DataType.string; @@ -178,6 +179,8 @@ abstract class ModelBase { return rc; } + String widgetName(); + /// Tests whether an [object] is a list type. /// Works for JSArray, List static bool isList(Object object) { diff --git a/lib/src/model/module/user_model.dart b/lib/src/model/module/user_model.dart index 942bb3a..898592c 100644 --- a/lib/src/model/module/user_model.dart +++ b/lib/src/model/module/user_model.dart @@ -52,4 +52,7 @@ class UserModel extends ModuleModel { /// Returns the name including the names of the parent @override String fullName() => name; + + @override + String widgetName() => name; } diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index d53761c..85a40ac 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -1,8 +1,17 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/model/column_model.dart'; +import 'package:flutter_bones/src/model/table_model.dart'; import 'package:meta/meta.dart'; +import 'model_base.dart'; +import 'page_model.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)$'); final Map map; @@ -10,23 +19,32 @@ class ModuleModel extends ModelBase { List options; @protected final pages = []; - @protected - final pageMap = {}; + final tables = []; ModuleModel(this.map, BaseLogger logger) : super(logger); /// Appends a [page] to the instance. void addPage(PageModel page) { - pages.add(page); - pageMap[page.name] = page; + if (pages.where((element) => element.name == page.name).isNotEmpty) { + logger.error('page ${page.name} is already defined in module $name'); + } else { + pages.add(page); + } + } + + void addTable(TableModel table) { + if (pages.where((element) => element.name == table.name).isNotEmpty) { + logger.error('page ${table.name} is already defined in module $name'); + } else { + tables.add(table); + } } /// Dumps the internal structure into a [stringBuffer] StringBuffer dump(StringBuffer stringBuffer) { stringBuffer.write('= module $name: options: ${options.join(' ')}\n'); - for (var page in pages) { - page.dump(stringBuffer); - } + tables.forEach((element) => element.dump(stringBuffer)); + pages.forEach((element) => element.dump(stringBuffer)); return stringBuffer; } @@ -34,14 +52,39 @@ class ModuleModel extends ModelBase { @override String fullName() => name; + /// Returns a column given by [columnName] or null on error. + /// [caller] is used for error logging. + ColumnModel getColumn(String columnName, WidgetModel caller) { + ColumnModel rc; + TableModel table = tables[0]; + if (columnName.contains('.')) { + final parts = columnName.split('.'); + table = tables.firstWhere((element) => element.name == parts[0]); + if (table == null) { + logger.error( + 'unknown reference (table) "$columnName" in ${caller.fullName()}'); + } + columnName = parts[1]; + } + if (table != null) { + rc = table.getColumn(columnName); + } + return rc; + } + /// Returns a child page given by [name], null otherwise. - PageModel pageByName(String name) => - pageMap.containsKey(name) ? pageMap[name] : null; + PageModel pageByName(String name) { + final found = pages.firstWhere((element) => element.name == name); + return found; + } /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluousAttributes(map, 'module options pages'.split(' ')); name = parseString('module', map, required: true); + checkSuperfluousAttributes(map, 'module options pages tables'.split(' ')); + if (map.containsKey('tables')) { + TableModel.parseList(this, map['tables'], logger); + } if (!map.containsKey('pages')) { logger.error('Module $name: missing pages'); } else { @@ -56,4 +99,13 @@ class ModuleModel extends ModelBase { options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } + + /// Returns a child table given by [name], null otherwise. + TableModel tableByName(String name) { + final found = tables.firstWhere((element) => element.name == name); + return found; + } + + @override + String widgetName() => name; } diff --git a/lib/src/model/page_model.dart b/lib/src/model/page_model.dart index c49ab8d..3a8d658 100644 --- a/lib/src/model/page_model.dart +++ b/lib/src/model/page_model.dart @@ -1,17 +1,23 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_bones/flutter_bones.dart'; + +import 'button_model.dart'; +import 'field_model.dart'; +import 'model_base.dart'; +import 'module_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; typedef FilterWidget = bool Function(WidgetModel item); -/// Represents one screen of the application. +/// Represents one screen of the module. class PageModel extends ModelBase { static final regExprOptions = RegExp(r'^(unknown)$'); final ModuleModel module; final Map map; String name; - PageModelType pageModelType; final List sections = []; + PageModelType pageModelType; List options; @protected final fields = {}; @@ -101,15 +107,15 @@ class PageModel extends ModelBase { /// Parses the map and stores the data in the instance. void parse() { + name = parseString('name', map, required: true); checkSuperfluousAttributes( map, 'name options pageType sections'.split(' ')); - name = parseString('name', map, required: true); pageModelType = parseEnum( 'pageType', map, PageModelType.values, required: true); options = parseOptions('options', map); if (!map.containsKey('sections')) { - logger.error('missing sections in ${fullName()}'); + logger.error('missing sections in page ${fullName()}'); } else { final item = map['sections']; if (!ModelBase.isList(item)) { @@ -122,15 +128,17 @@ class PageModel extends ModelBase { checkOptionsByRegExpr(options, regExprOptions); } + @override + String widgetName() => name; + /// Returns a list of Pages constructed by the Json like [list]. - static void parseList(ModuleModel module, List list, - BaseLogger logger) { + static void parseList( + ModuleModel module, List list, BaseLogger logger) { // Note: "List list" does not work with json maps. for (var item in list) { if (!ModelBase.isMap(item)) { module.logger.error( - 'curious item in section list of ${module.fullName()}: ${StringUtils - .limitString("$item", 80)}'); + 'curious item in section list of ${module.fullName()}: ${StringUtils.limitString("$item", 80)}'); } else { final page = PageModel(module, item, logger); page.parse(); diff --git a/lib/src/model/section_model.dart b/lib/src/model/section_model.dart index 63546eb..a771ba4 100644 --- a/lib/src/model/section_model.dart +++ b/lib/src/model/section_model.dart @@ -1,7 +1,17 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/model/db_reference_model.dart'; -/// A part of a page represented by one widget. +import 'button_model.dart'; +import 'checkbox_model.dart'; +import 'combobox_model.dart'; +import 'empty_line_model.dart'; +import 'model_base.dart'; +import 'page_model.dart'; +import 'text_field_model.dart'; +import 'text_model.dart'; +import 'widget_model.dart'; + +/// Represents a container of widgets and sub sections. class SectionModel extends WidgetModel { static final regExprOptions = RegExp(r'^(unknown)$'); SectionModelType sectionModelType; @@ -35,12 +45,12 @@ class SectionModel extends WidgetModel { /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluousAttributes( - map, 'children fields name options sectionType widgetType'.split(' ')); sectionModelType = parseEnum( 'sectionType', map, SectionModelType.values); name = parseString('name', map) ?? '${StringUtils.enumToString(sectionModelType)}$no'; + checkSuperfluousAttributes( + map, 'children fields name options sectionType widgetType'.split(' ')); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); if (!map.containsKey('children')) { @@ -87,6 +97,9 @@ class SectionModel extends WidgetModel { case WidgetModelType.emptyLine: widget = EmptyLineModel(this, page, child, logger); break; + case WidgetModelType.dbReference: + widget = DbReferenceModel(this, page, child, logger); + break; default: //@ToDo: nested section logger.error( diff --git a/lib/src/model/table_model.dart b/lib/src/model/table_model.dart new file mode 100644 index 0000000..f1d56f8 --- /dev/null +++ b/lib/src/model/table_model.dart @@ -0,0 +1,97 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/foundation.dart'; + +import 'column_model.dart'; +import 'model_base.dart'; +import 'module_model.dart'; + +/// Represents a database table describing the data model of the model. +class TableModel extends ModelBase { + static final regExprOptions = RegExp(r'^(unknown)$'); + final ModuleModel module; + final Map map; + String name; + List options; + @protected + final columnMap = {}; + final columns = []; + + TableModel(this.module, this.map, BaseLogger logger) : super(logger); + + /// Adds a [button] to the [this.buttons]. + /// If already defined and error is logged. + void addColumn(ColumnModel column) { + final name = column.name; + if (columnMap.containsKey(column)) { + logger.error('column ${column.fullName()} already defined: ' + + columnMap[name].fullName()); + } else { + columnMap[name] = column; + columns.add(column); + } + } + + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write('== table $name: options: ${options.join(' ')}\n'); + for (var column in columns) { + column.dump(stringBuffer); + } + return stringBuffer; + } + + /// Returns the name including the names of the parent + @override + String fullName() => '${module.name}.$name'; + + /// Returns a column by [name] or null on error. + ColumnModel getColumn(String name, {bool required = true}) { + ColumnModel rc; + if (!columnMap.containsKey(name)) { + if (required) { + logger.error('missing column $name in table ${fullName()}'); + } + } else { + rc = columnMap[name]; + } + return rc; + } + + /// Parses the map and stores the data in the instance. + void parse() { + name = parseString('name', map, required: true); + checkSuperfluousAttributes(map, 'name options columns'.split(' ')); + options = parseOptions('options', map); + if (!map.containsKey('columns')) { + logger.error('missing columns in table ${fullName()}'); + } else { + final item = map['columns']; + if (!ModelBase.isList(item)) { + logger.error('"columns" is not an array in ${fullName()}: ' + '${StringUtils.limitString(item.toString(), 80)}'); + } else { + ColumnModel.parseColumns(this, item, logger); + } + } + checkOptionsByRegExpr(options, regExprOptions); + } + + @override + widgetName() => name; + + /// Returns a list of tables constructed by the Json like [list]. + static void parseList( + ModuleModel module, List list, BaseLogger logger) { + // Note: "List list" does not work with json maps. + for (var item in list) { + if (!ModelBase.isMap(item)) { + module.logger.error( + 'curious item in section list of ${module.fullName()}: ${StringUtils.limitString("$item", 80)}'); + } else { + final table = TableModel(module, item, logger); + table.parse(); + module.addTable(table); + } + } + } +} diff --git a/lib/src/model/text_field_model.dart b/lib/src/model/text_field_model.dart index 2fc2691..0cae1dd 100644 --- a/lib/src/model/text_field_model.dart +++ b/lib/src/model/text_field_model.dart @@ -1,7 +1,13 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'field_model.dart'; +import 'model_types.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes a form text field widget. class TextFieldModel extends FieldModel { static final regExprOptions = RegExp(r'^(readonly|disabled|password|required|unique)$'); @@ -28,11 +34,11 @@ class TextFieldModel extends FieldModel { /// Parses the map and stores the data in the instance. void parse() { + super.parse(); checkSuperfluousAttributes( map, 'dataType label maxSize name options rows toolTip value widgetType' .split(' ')); - super.parse(); maxSize = parseInt('maxSize', map, required: false); rows = parseInt('rows', map, required: false); switch (dataType) { diff --git a/lib/src/model/text_model.dart b/lib/src/model/text_model.dart index d4a668d..454b1f9 100644 --- a/lib/src/model/text_model.dart +++ b/lib/src/model/text_model.dart @@ -1,6 +1,10 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +import 'page_model.dart'; +import 'section_model.dart'; +import 'widget_model.dart'; + +/// Describes a text widget without user interaction. class TextModel extends WidgetModel { static final regExprOptions = RegExp(r'^(richtext)$'); List options; diff --git a/lib/src/model/widget_model.dart b/lib/src/model/widget_model.dart index bf77cb5..f04d388 100644 --- a/lib/src/model/widget_model.dart +++ b/lib/src/model/widget_model.dart @@ -1,7 +1,10 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter_bones/flutter_bones.dart'; -/// A base class for items inside a page: SectionModel FieldModel TextModel... +import 'model_base.dart'; +import 'page_model.dart'; +import 'section_model.dart'; + +/// A base class for items inside a page: SectionModel ButtonModel TextModel... abstract class WidgetModel extends ModelBase { static int lastId = 0; final SectionModel section; @@ -25,10 +28,13 @@ abstract class WidgetModel extends ModelBase { enum WidgetModelType { button, checkbox, + column, combobox, + dbReference, emptyLine, image, section, + table, text, textField, } diff --git a/lib/src/widget/view.dart b/lib/src/widget/view.dart index b93990a..ec23fb6 100644 --- a/lib/src/widget/view.dart +++ b/lib/src/widget/view.dart @@ -1,7 +1,15 @@ 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/widget_model.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 'raised_button_bone.dart'; class View { static View _instance; @@ -45,6 +53,11 @@ class View { return rc; } + Widget dbReference(WidgetModel child) { + var rc; + return rc; + } + /// Creates an empty line from the [model]. Widget emptyLine(EmptyLineModel model) { var rc; @@ -148,6 +161,13 @@ class View { case WidgetModelType.section: rc.add(section(child)); break; + case WidgetModelType.dbReference: + rc.add(dbReference(child)); + break; + case WidgetModelType.table: + case WidgetModelType.column: + logger.error('not allowed in section: ${child.fullName()}'); + break; } } return rc; diff --git a/test/model/db_model_test.dart b/test/model/db_model_test.dart new file mode 100644 index 0000000..8001a31 --- /dev/null +++ b/test/model/db_model_test.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; + +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('module', () { + logger.clear(); + final module = Demo1(cloneOfMap(userModel), logger); + module.parse(); + final errors = logger.errors; + expect(errors.length, equals(0)); + final page = module.pageByName('create'); + expect(page?.fullName(), equals('demo1.create')); + expect(module.fullName(), equals('demo1')); + final table = module.tableByName('users'); + expect(table.fullName(), equals('demo1.users')); + expect(table.widgetName(), equals('users')); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, '''= module demo1: options: +== table users: options: + column user_id: DataType.int "Id" options: primary + column user_name: DataType.string "User" options: unique + column user_role: DataType.reference "Role" options: +== table roles: options: + column role_id: DataType.int "Id" options: primary + column role_name: DataType.string "Role" options: unique +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + textField user: options: required unique + textField role: options: + button buttonStore: text: options: null + ] # create.simpleForm1 +'''); + final userField = table.getColumn('user_id'); + expect(userField, isNotNull); + expect(userField.widgetName(), 'user_id'); + expect(userField.fullName(), equals('users.user_id')); + final userRef = page.getField('user'); + expect(userRef, isNotNull); + expect(userRef.fullName(), equals('create.user')); + expect(userRef.widgetName(), equals('user')); + }); + }); +} + +final userModel = { + 'module': 'demo1', + 'tables': [ + { + 'name': 'users', + 'columns': [ + { + 'name': 'user_id', + 'dataType': 'int', + 'label': 'Id', + 'options': 'primary', + }, + { + 'name': 'user_name', + 'dataType': 'string', + 'label': 'User', + 'size': 32, + 'options': 'unique', + }, + { + 'name': 'user_role', + 'dataType': 'reference', + 'label': 'Role', + 'foreignKey': 'role.role_id', + 'widgetType': 'combobox', + }, + ] + }, + { + 'name': 'roles', + 'columns': [ + { + 'name': 'role_id', + 'dataType': 'int', + 'label': 'Id', + 'options': 'primary', + }, + { + 'name': 'role_name', + 'dataType': 'string', + 'label': 'Role', + 'size': 32, + 'options': 'unique', + }, + ] + }, + ], + 'pages': [ + { + 'name': 'create', + 'pageType': 'create', + 'sections': [ + { + 'sectionType': 'simpleForm', + 'children': [ + { + 'widgetType': 'dbReference', + 'name': 'user', + 'label': 'User', + 'column': 'user_id', + 'options': 'required;unique', + }, + { + 'widgetType': 'dbReference', + 'name': 'role', + 'column': 'roles.role_id', + }, + { + 'widgetType': 'button', + 'name': 'buttonStore', + 'label': 'Save', + }, + ] + } + ] + }, + ], +}; + +Map cloneOfMap(Map map) { + final rc = json.decode(json.encode(map)); + return rc; +} + +class Demo1 extends ModuleModel { + Demo1(Map map, BaseLogger logger) : super(map, logger); + + /// Returns the name including the names of the parent + @override + String fullName() => name; +} -- 2.39.5