typedef ButtonOnPressed = void Function(String name);
+/// Describes a button widget.
class ButtonModel extends WidgetModel {
static final regExprOptions = RegExp(r'^(undef)$');
String text;
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);
}
--- /dev/null
+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);
+ }
+ }
+ }
+}
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;
/// 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);
--- /dev/null
+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);
+ }
+}
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;
/// 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);
}
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)$');
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;
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) {
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;
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;
/// 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;
return rc;
}
+ String widgetName();
+
/// Tests whether an [object] is a list type.
/// Works for JSArray<dynamic>, List
static bool isList(Object object) {
/// Returns the name including the names of the parent
@override
String fullName() => name;
+
+ @override
+ String widgetName() => name;
}
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;
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;
}
@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 {
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;
}
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>{};
/// 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)) {
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();
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;
/// 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')) {
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(
--- /dev/null
+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);
+ }
+ }
+ }
+}
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)$');
/// 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) {
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;
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;
enum WidgetModelType {
button,
checkbox,
+ column,
combobox,
+ dbReference,
emptyLine,
image,
section,
+ table,
text,
textField,
}
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;
return rc;
}
+ Widget dbReference(WidgetModel child) {
+ var rc;
+ return rc;
+ }
+
/// Creates an empty line from the [model].
Widget emptyLine(EmptyLineModel model) {
var rc;
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;
--- /dev/null
+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;
+}