]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
+TableModel +ColumnModel +DbReferenceModel
authorHamatoma <author@hamatoma.de>
Sat, 3 Oct 2020 22:56:06 +0000 (00:56 +0200)
committerHamatoma <author@hamatoma.de>
Sat, 3 Oct 2020 22:56:06 +0000 (00:56 +0200)
18 files changed:
lib/src/model/button_model.dart
lib/src/model/checkbox_model.dart
lib/src/model/column_model.dart [new file with mode: 0644]
lib/src/model/combobox_model.dart
lib/src/model/db_reference_model.dart [new file with mode: 0644]
lib/src/model/empty_line_model.dart
lib/src/model/field_model.dart
lib/src/model/model_base.dart
lib/src/model/module/user_model.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/table_model.dart [new file with mode: 0644]
lib/src/model/text_field_model.dart
lib/src/model/text_model.dart
lib/src/model/widget_model.dart
lib/src/widget/view.dart
test/model/db_model_test.dart [new file with mode: 0644]

index 2b0d8f5dc1616db99364d825eda383f6f32a0cf2..3fba73b8e2e486211eae53cc21e4a2a09228aa9e 100644 (file)
@@ -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;
index 40f17218a05e04a09161f506674c5619c760751d..78990d15f942a7a02ec82d9bddbdc579cd771dee 100644 (file)
@@ -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<String, dynamic> 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 (file)
index 0000000..f3763a4
--- /dev/null
@@ -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<String> options;
+  final TableModel table;
+  final Map<String, dynamic> 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>('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<dynamic> 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);
+      }
+    }
+  }
+}
index 5f6e98d476b702963e79a31ad7ac0450c19fcec2..395c660974b528d7ba17840b0d130bb0d552622e 100644 (file)
@@ -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<String> 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 (file)
index 0000000..5e9d9d1
--- /dev/null
@@ -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<String> validator;
+
+  FormFieldSetter onSaved;
+
+  final Map<String, dynamic> 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);
+  }
+}
index 92236be1e53ca4cf911ad72fc770e7c64a672dab..f233c54b22c5424e77187de3b467d830ce92208e 100644 (file)
@@ -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<String> 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);
   }
 
index 1d81dd3fcbe64a16abe2ed72571478e0724af043..1d205f5ceef297d458f1d4d83af4858143bf3bb9 100644 (file)
@@ -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)$');
index ce8007ef82c382df4fde58be76f9dd0df5d0be81..abccce1ab5ca119a733d59cae73e78c9e22f0cb4 100644 (file)
@@ -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 = <String>[];
     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<dynamic> parseValueList(String key, Map<String, dynamic> map,
-      DataType dataType,
+  List<dynamic> parseValueList(
+      String key, Map<String, dynamic> 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<dynamic>, List
   static bool isList(Object object) {
index 942bb3a953452c43a5dd50049145f981fe601324..898592ca180eee2404a420a12474da8bf8763ae6 100644 (file)
@@ -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;
 }
index d53761c1c46e67d9af522f5caec9a71312402922..85a40ac76a01a79a1a31d422e8f7e507d604dc74 100644 (file)
@@ -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<String, dynamic> map;
@@ -10,23 +19,32 @@ class ModuleModel extends ModelBase {
   List<String> options;
   @protected
   final pages = <PageModel>[];
-  @protected
-  final pageMap = <String, PageModel>{};
+  final tables = <TableModel>[];
 
   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;
 }
index c49ab8d5afed4a67e57009a7eb5e16173be8f414..3a8d65898cb44a707747e4e88af59b72ff3d444e 100644 (file)
@@ -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<String, dynamic> map;
   String name;
-  PageModelType pageModelType;
   final List<SectionModel> sections = [];
+  PageModelType pageModelType;
   List<String> options;
   @protected
   final fields = <String, FieldModel>{};
@@ -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<PageModelType>(
         '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<dynamic> list,
-      BaseLogger logger) {
+  static void parseList(
+      ModuleModel module, List<dynamic> list, BaseLogger logger) {
     // Note: "List<Map<String, dynamic> 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();
index 63546eb757870e7d7f452c90de343814c8d8215f..a771ba4b1ae959bdc6fb226bc89b48e8133ef73d 100644 (file)
@@ -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<SectionModelType>(
         '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 (file)
index 0000000..f1d56f8
--- /dev/null
@@ -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<String, dynamic> map;
+  String name;
+  List<String> options;
+  @protected
+  final columnMap = <String, ColumnModel>{};
+  final columns = <ColumnModel>[];
+
+  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<dynamic> list, BaseLogger logger) {
+    // Note: "List<Map<String, dynamic> 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);
+      }
+    }
+  }
+}
index 2fc2691c8b0242c770e8e995929694b3e789b91b..0cae1dde31ed03a4a0936aa24942ce83e3a975d2 100644 (file)
@@ -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) {
index d4a668d05ae7e5c3d02dc29912927b516db69089..454b1f9ff771a5661b07669eb87b9b0ad5c4461d 100644 (file)
@@ -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<String> options;
index bf77cb5e3f692d84e8d8d6f6536da8db8d85ff2b..f04d3888ee6fecb1e243de83d2891d01d39239ba 100644 (file)
@@ -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,
 }
index b93990ae1f4b26a2c652dfd34d8d74d4de07f7c1..ec23fb6ffd95032207177b741abc62914145a9bd 100644 (file)
@@ -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 (file)
index 0000000..8001a31
--- /dev/null
@@ -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 = <String, dynamic>{
+  '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<String, dynamic> cloneOfMap(Map<String, dynamic> map) {
+  final rc = json.decode(json.encode(map));
+  return rc;
+}
+
+class Demo1 extends ModuleModel {
+  Demo1(Map<String, dynamic> map, BaseLogger logger) : super(map, logger);
+
+  /// Returns the name including the names of the parent
+  @override
+  String fullName() => name;
+}