]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
ListUserPage, CreateUserPage and EditUserPage work, I18N
authorHamatoma <author.hamatoma.de>
Sat, 9 Oct 2021 06:03:18 +0000 (08:03 +0200)
committerHamatoma <author.hamatoma.de>
Sun, 10 Oct 2021 17:19:24 +0000 (19:19 +0200)
* I18N: new: trDyn()
* new global_widget.dart: fetching data from the backend, building combobox items
* new: base/validators.dart
* create_user_custom: modified to work
* global_data.dart:
** new loginUserName, loginUserId, loginUserRole
** new: GlobalTranslations, GlobalThemeData
* new widget/message_line.dart
* WidgetForm: refactored: switched from widget list to FormItem list.
* sql_generator:
** byId, delete: fix: missing ':' in ':id'
** INSERT: fix: ignoring primary key
** UPDATE: fix: ignoring hidden fields, errors respecting the primary key
* sql_storage: fix: parameters has strings not dynamics.
* AttendePage: new getRecord(), loadRecord(), hasRecordResponse(),
  hasResponse(), listResponse(), recordResponse()
* new: global.sql.yaml, local.sql.yaml

29 files changed:
lib/base/i18n.dart
lib/base/validators.dart [new file with mode: 0644]
lib/page/log_page.dart
lib/page/roles/create_role_custom.dart
lib/page/roles/edit_role_custom.dart
lib/page/roles/list_role_custom.dart
lib/page/start_page.dart
lib/page/structures/create_structure_custom.dart
lib/page/structures/delete_structure_custom.dart
lib/page/structures/edit_structure_custom.dart
lib/page/structures/list_structure_custom.dart
lib/page/users/create_user_custom.dart
lib/page/users/delete_user_custom.dart
lib/page/users/edit_user_custom.dart
lib/page/users/list_user_custom.dart
lib/services/global_widget.dart [new file with mode: 0644]
lib/setting/drawer_exhibition.dart
lib/setting/global_data.dart
lib/widget/attended_page.dart
lib/widget/message_line.dart [new file with mode: 0644]
lib/widget/widget_form.dart
metatool/bin/page_generator.dart
metatool/bin/sql_generator.dart
rest_server/data/sql/precedence/global.sql.yaml [new file with mode: 0644]
rest_server/data/sql/precedence/local.sql.yaml [new file with mode: 0644]
rest_server/data/sql/roles.sql.yaml
rest_server/data/sql/structures.sql.yaml
rest_server/data/sql/users.sql.yaml
rest_server/lib/sql_storage.dart

index ab6c45781d5dff20aa31cc6c096cddb9fde09ac5..e0ccf448944964fa546f1029d55538377fe33400 100644 (file)
@@ -87,6 +87,31 @@ class I18N {
     return rc;
   }
 
+  /// Translates the [key] into the local language using the namespace [module].
+  ///
+  /// Note: Difference to tr(): the parameter [key] is not a constant. Therefore
+  /// that argument cannot be used from the parser.
+  /// A often used example is a database value.
+  ///
+  /// Returns the translation or (if not found) the key.
+  String trDyn(String key, [String module = '!global']) {
+    String? rc;
+    final mapModule = mapModules[module];
+    if (mapModule == null) {
+      rc = removeInvisibleTag(key);
+    } else {
+      rc = mapModule[key];
+      if (rc == null) {
+        if (module == globalModule) {
+          rc = removeInvisibleTag(key);
+        } else {
+          rc = tr(key, globalModule);
+        }
+      }
+    }
+    return rc;
+  }
+
   /// Translates the [key] into the local language using the namespace [module].
   /// [key] contains placeholders "{0}", "{1}"... that is replaced by
   /// the corresponding entry in the array [args].
diff --git a/lib/base/validators.dart b/lib/base/validators.dart
new file mode 100644 (file)
index 0000000..8baf63a
--- /dev/null
@@ -0,0 +1,50 @@
+import 'i18n.dart';
+
+final i18n = I18N();
+
+final _regExprEMailChar = RegExp(r'[\/ "!$%&()?;:,*<>|^°{}\[\]\\=]');
+
+final _regExprEMailFormat = RegExp(r'^[^@]+@[^@]+\.[a-zA-Z]+$');
+
+/// Tests whether [input] is an correct email address.
+///
+/// Returns null on success, the error message otherwise.
+String? isEmail(String? input) {
+  String? rc;
+  if (input != null) {
+    RegExpMatch? match = _regExprEMailChar.firstMatch(input);
+    if (match != null) {
+      rc = i18n.trArgs('Illegal character "{0} in email address', '!global',
+          [match.group(0)!]);
+    } else if (_regExprEMailFormat.firstMatch(input) == null) {
+      rc = i18n.tr('Not an email address: ') + input;
+    }
+  }
+  return rc;
+}
+
+/// Tests whether the input is not empty.
+String? notEmpty(String? input) {
+  final rc = input == null || input.isEmpty ? i18n.tr('Please fill in.') : null;
+  return rc;
+}
+
+/// Executes multiple validators for a given [input].
+///
+/// If any returns an error message this error message is returned.
+///
+/// Returns null: all validators are successful.
+/// Otherwise: the first error message returned by the [validators].
+String? validateMultiple(String? input, List<Validator> validators) {
+  String? rc;
+  for (var validator in validators) {
+    final rc2 = validator(input);
+    if (rc2 != null) {
+      rc = rc2;
+      break;
+    }
+  }
+  return rc;
+}
+
+typedef Validator = String? Function(String? input);
index 522b9c723ee6f6f1174b41544e01f957cb510ff9..81782e3b950b973bffddbfe093291a3064f1c8fd 100644 (file)
@@ -22,7 +22,7 @@ class LogPageState extends State<LogPage> {
 
   @override
   Widget build(BuildContext context) {
-    const padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final listItems = (globalData.logger as MemoryLogger)
         .messages
         .map((line) => Text(line,
@@ -35,10 +35,10 @@ class LogPageState extends State<LogPage> {
         body: Form(
           key: _formKey,
           child: Card(
-              margin: const EdgeInsets.symmetric(
-                  vertical: padding, horizontal: padding),
+              margin:
+                  EdgeInsets.symmetric(vertical: padding, horizontal: padding),
               child: Padding(
-                  padding: const EdgeInsets.symmetric(
+                  padding: EdgeInsets.symmetric(
                       vertical: padding, horizontal: padding),
                   child: ListView(
                     children: listItems,
index 4768a0254e4e4f458499bc86475b012f4e01a559..15a4f82f48302786cba57bc46d67aa8908170628 100644 (file)
@@ -17,7 +17,7 @@ class CreateRoleCustom extends State<CreateRolePage> {
   CreateRoleCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
         drawer: globalData.drawerBuilder(context),
index 5f2006d418e91d6dce51417971b0d42076f2d6b9..e5326de29d56e2e0a27d8b1ce272a0260a779449 100644 (file)
@@ -18,7 +18,7 @@ class EditRoleCustom extends State<EditRolePage> {
   EditRoleCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
         drawer: globalData.drawerBuilder(context),
index d23445c1411236aa26d20b543025e7222f3bda5a..b73090e8598c351f822ff4b6868d6c1f5d9c3e7a 100644 (file)
@@ -17,7 +17,7 @@ class ListRoleCustom extends State<ListRolePage> {
   ListRoleCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
         drawer: globalData.drawerBuilder(context),
index d2876bf8a933fc39433973dd264a6ab846d45f43..8c79d6f59e62de8f702ade7f165593959f854a5d 100644 (file)
@@ -36,7 +36,7 @@ class StartPageState extends State<StartPage> {
 
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final listItems = <Widget>[
       Text(
         'Herzlich Willkommen',
index a0f39d63f1b1283fe44b67988b302e1ab770ea99..113246374523097f2ec692613ed0fdbe0b5690e8 100644 (file)
@@ -17,7 +17,7 @@ class CreateStructureCustom extends State<CreateStructurePage> {
   CreateStructureCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')),
         drawer: globalData.drawerBuilder(context),
index c6b9ba0de1b6b040d66cd942f68556bdab89e7cb..d1dcfb223cb694bea1e76388745609a3b9969e41 100644 (file)
@@ -18,7 +18,7 @@ class DeleteStructureCustom extends State<DeleteStructurePage> {
   DeleteStructureCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')),
         drawer: globalData.drawerBuilder(context),
index 3f2aa2a9af3dc17a3fe6b03f6e70474ecd5f6215..b3310837b3221695873b5dbbffb3b96a30e21018 100644 (file)
@@ -18,7 +18,7 @@ class EditStructureCustom extends State<EditStructurePage> {
   EditStructureCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')),
         drawer: globalData.drawerBuilder(context),
index 3bf1b62b5d53910ab152a06702f9722b393717f0..eb54b7d0b0e6ff1bcee6add70108e0a3d0ff2cb5 100644 (file)
@@ -17,7 +17,7 @@ class ListStructureCustom extends State<ListStructurePage> {
   ListStructureCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')),
         drawer: globalData.drawerBuilder(context),
index 48a581e5b02307a8f0907c2efc46b873d5e03698..99296b19f01c62ecfeede137d1304b74444796ed 100644 (file)
 import 'package:flutter/material.dart';
 
 import '../../base/i18n.dart';
+import '../../base/validators.dart';
+import '../../services/global_widget.dart';
 import '../../setting/global_data.dart';
 import '../../widget/attended_page.dart';
+import '../../widget/message_line.dart';
 import '../../widget/widget_form.dart';
 import 'create_user_page.dart';
 
 final i18n = I18N();
 
-class CreateUserCustom extends State<CreateUserPage> {
+class CreateUserCustom extends State<CreateUserPage> with MessageLine {
   final globalData = GlobalData();
   AttendedPage? attendedPage;
-
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'CreateUser');
   CreateUserCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
+    comboRolesFromBackend(
+        attendedPage: attendedPage!, onDone: () => setState(() => 1));
+    final itemsRole = comboRoles(
+        i18n.trDyn(GlobalTranslations.comboboxSelect), attendedPage!);
+    final formItems = <FormItem>[
+      FormItem(
+          TextFormField(
+            initialValue: _fieldData.name,
+            style: TextStyle(
+                backgroundColor: GlobalThemeData.formTextBackgroundColor,
+                decorationStyle: TextDecorationStyle.double),
+            decoration: InputDecoration(
+              labelText: i18n.tr('Name'),
+            ),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.name = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            initialValue: _fieldData.displayName,
+            decoration: InputDecoration(labelText: i18n.tr('Display name')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.displayName = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            initialValue: _fieldData.email,
+            decoration: InputDecoration(labelText: i18n.tr('Email')),
+            validator: (input) => validateMultiple(
+                input, [(input) => notEmpty(input), (input) => isEmail(input)]),
+            onSaved: (value) => _fieldData.email = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          DropdownButtonFormField<int>(
+            value: _fieldData.role,
+            items: itemsRole,
+            isExpanded: true,
+            decoration: InputDecoration(labelText: i18n.tr('Role')),
+            onChanged: (value) => _fieldData.role = value ?? 0,
+          ),
+          weight: 6),
+      FormItem(
+          ElevatedButton(
+              onPressed: () => verifyAndStore(), child: Text(i18n.tr('Save'))),
+          weight: 8,
+          gapAbove: padding),
+      FormItem(
+          ElevatedButton(
+            onPressed: () {
+              globalData.navigate(context, '/Users/list');
+            },
+            child: Text(i18n.tr('Cancel')),
+          ),
+          weight: 4)
+    ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+        appBar: globalData.appBarBuilder(i18n.tr('Create an User')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
-            child: WidgetForm.flexibleGridAttended(
-                attendedPage!.attendedWidgets(),
-                screenWidth: attendedPage!.pageStates.screenWidth,
-                padding: padding)));
+            child: Card(
+                color: GlobalThemeData.formBackgroundColor,
+                elevation: GlobalThemeData.formElevation,
+                margin: EdgeInsets.symmetric(
+                    vertical: padding, horizontal: padding),
+                child: Padding(
+                    padding: EdgeInsets.symmetric(
+                        vertical: padding, horizontal: padding),
+                    child: WidgetForm.flexibleGrid(
+                      formItems,
+                      screenWidth: attendedPage!.pageStates.screenWidth,
+                      padding: padding,
+                    )))));
     return rc;
   }
 
@@ -33,4 +105,46 @@ class CreateUserCustom extends State<CreateUserPage> {
   void initState() {
     super.initState();
   }
+
+  void store() {
+    final parameters = <String, dynamic>{
+      'module': 'Users',
+      'sql': 'insert',
+    };
+    _fieldData.toMap(parameters);
+    globalData.restPersistence!
+        .store(what: 'store', map: parameters)
+        .then((answer) {
+      if (answer.startsWith('id:')) {
+        final id = int.tryParse(answer.substring(3));
+        if (id == null || id == 0) {
+          setError(i18n.tr('Saving data failed: $answer'));
+          setState(() => 1);
+        } else {
+          globalData.navigate(context, '/Users/edit;$id');
+        }
+      }
+    });
+  }
+
+  void verifyAndStore() {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+      store();
+    }
+  }
+}
+
+class _FieldData {
+  String name = '';
+  String displayName = '';
+  String email = '';
+  int role = 0;
+  void toMap(Map<String, dynamic> map) {
+    map[':name'] = name;
+    map[':displayName'] = displayName;
+    map[':email'] = email;
+    map[':role'] = role;
+    map[':createdBy'] = GlobalData.loginUserName;
+  }
 }
index 7fa7866b7a06d688c764106605661ae275fda60b..7ab1bce031b92f046c26f214edb5f753dbfc4478 100644 (file)
@@ -18,7 +18,7 @@ class DeleteUserCustom extends State<DeleteUserPage> {
   DeleteUserCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
         drawer: globalData.drawerBuilder(context),
index d4ab150414d5ffdde9d7462df75b9671e6b45053..bf6fc649558ce1a2494cc37af2eade86ce217919 100644 (file)
 import 'package:flutter/material.dart';
 
 import '../../base/i18n.dart';
+import '../../base/validators.dart';
+import '../../services/global_widget.dart';
 import '../../setting/global_data.dart';
 import '../../widget/attended_page.dart';
+import '../../widget/message_line.dart';
 import '../../widget/widget_form.dart';
 import 'edit_user_page.dart';
 
 final i18n = I18N();
 
-class EditUserCustom extends State<EditUserPage> {
+class EditUserCustom extends State<EditUserPage> with MessageLine {
   final int primaryKey;
   final globalData = GlobalData();
   AttendedPage? attendedPage;
-
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'EditUser');
+  final nameController = TextEditingController();
+  final displayNameController = TextEditingController();
+  final emailController = TextEditingController();
   EditUserCustom(this.primaryKey);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
+    comboRolesFromBackend(
+        attendedPage: attendedPage!, onDone: () => setState(() => 1));
+    final itemsRole = comboRoles(
+        i18n.trDyn(GlobalTranslations.comboboxSelect), attendedPage!);
+    attendedPage?.loadRecord(
+        name: 'record',
+        reload: () => setState(() => 1),
+        onDone: (record) => _fieldData.fromMap(record),
+        parameters: {'module': 'Users', 'sql': 'byId', ':id': primaryKey});
+    nameController.text = _fieldData.name;
+    displayNameController.text = _fieldData.displayName;
+    emailController.text = _fieldData.email;
+    final formItems = <FormItem>[
+      FormItem(
+          TextFormField(
+            controller: nameController,
+            decoration: InputDecoration(labelText: i18n.tr('Name')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.name = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            controller: displayNameController,
+            decoration: InputDecoration(labelText: i18n.tr('Display name')),
+            validator: (input) => notEmpty(input),
+            onSaved: (value) => _fieldData.displayName = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          TextFormField(
+            controller: emailController,
+            decoration: InputDecoration(labelText: i18n.tr('Email')),
+            validator: (input) => validateMultiple(
+                input, [(input) => notEmpty(input), (input) => isEmail(input)]),
+            onSaved: (value) => _fieldData.email = value ?? '',
+          ),
+          weight: 6),
+      FormItem(
+          DropdownButtonFormField<int>(
+            value: _fieldData.role,
+            items: itemsRole,
+            isExpanded: true,
+            decoration: InputDecoration(labelText: i18n.tr('Role')),
+            onChanged: (value) => _fieldData.role = value ?? 0,
+          ),
+          weight: 6),
+      FormItem(
+          ElevatedButton(
+              onPressed: () => verifyAndStore(), child: Text(i18n.tr('Save'))),
+          weight: 8,
+          gapAbove: 2 * padding),
+      FormItem(
+          ElevatedButton(
+            onPressed: () {
+              attendedPage!.pageStates.dbDataState.clear();
+              globalData.navigate(context, '/Users/list');
+            },
+            child: Text(i18n.tr('Cancel')),
+          ),
+          weight: 4)
+    ];
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+        appBar: globalData.appBarBuilder(i18n.tr('Change an User')),
         drawer: globalData.drawerBuilder(context),
         body: SafeArea(
-            child: WidgetForm.flexibleGridAttended(
-                attendedPage!.attendedWidgets(),
-                screenWidth: attendedPage!.pageStates.screenWidth,
-                padding: padding)));
+            child: Form(
+                key: _formKey,
+                child: Card(
+                    margin: EdgeInsets.symmetric(
+                        vertical: padding, horizontal: padding),
+                    child: WidgetForm.flexibleGrid(
+                      formItems,
+                      screenWidth: attendedPage!.pageStates.screenWidth,
+                      padding: padding,
+                    )))));
     return rc;
   }
 
@@ -34,4 +110,60 @@ class EditUserCustom extends State<EditUserPage> {
   void initState() {
     super.initState();
   }
+
+  void store() {
+    final parameters = <String, dynamic>{
+      'module': 'Users',
+      'sql': 'update',
+      ':id': primaryKey,
+      ':name': _fieldData.name,
+      ':displayName': _fieldData.displayName,
+      ':email': _fieldData.email,
+      ':role': _fieldData.role,
+      ':createdBy': GlobalData.loginUserName,
+    };
+    _fieldData.toMap(parameters);
+    globalData.restPersistence!
+        .store(what: 'store', map: parameters)
+        .then((answer) {
+      if (answer.startsWith('id:')) {
+        final id = int.tryParse(answer.substring(3));
+        if (id == null || id == 0) {
+          setError(i18n.tr('Saving data failed: $answer'));
+          setState(() => 1);
+        } else {
+          attendedPage!.pageStates.dbDataState.clear();
+          globalData.navigate(context, '/Users/edit;$id');
+        }
+      }
+    });
+  }
+
+  void verifyAndStore() {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+      store();
+    }
+  }
+}
+
+class _FieldData {
+  String name = '';
+  String displayName = '';
+  String email = '';
+  int role = 0;
+  void fromMap(Map<String, dynamic> map) {
+    name = map['user_name'];
+    displayName = map['user_displayname'];
+    email = map['user_email'];
+    role = map['user_role'];
+  }
+
+  void toMap(Map<String, dynamic> map) {
+    map[':name'] = name;
+    map[':displayName'] = displayName;
+    map[':email'] = email;
+    map[':role'] = role;
+    map[':createdBy'] = GlobalData.loginUserName;
+  }
 }
index c90b9ec5c2d0484b8617e2a79255ebe10135ad92..3cf2c19698756b449bcd7ee29010b8040a40cc2e 100644 (file)
@@ -6,39 +6,63 @@ import '../../base/i18n.dart';
 import '../../setting/global_data.dart';
 import '../../widget/attended_page.dart';
 import '../../widget/widget_form.dart';
+import '../../services/global_widget.dart';
 import 'list_user_page.dart';
 
 final i18n = I18N();
 
 class ListUserCustom extends State<ListUserPage> {
   final globalData = GlobalData();
-  final fieldData = FieldData();
   AttendedPage? attendedPage;
+  final _fieldData = _FieldData();
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'CreateUser');
   final textController = TextEditingController();
   ListUserCustom();
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
-    final itemsRole = <DropdownMenuItem<int>>[];
-    final fieldWidgets = <Widget>[
-      TextFormField(
-        controller: textController,
-        decoration: InputDecoration(labelText: i18n.tr('Text')),
-        onChanged: (value) => fieldData.text = value,
-      ),
-      DropdownButtonFormField<int>(
-        value: fieldData.role,
-        items: itemsRole,
-        isExpanded: true,
-        decoration: InputDecoration(labelText: i18n.tr('Role')),
-        onChanged: (value) => fieldData.role = value ?? 0,
-      ),
+    final padding = GlobalThemeData.padding;
+    comboRolesFromBackend(
+        attendedPage: attendedPage!, onDone: () => setState(() => 1));
+    final itemsRole = comboRoles(
+        i18n.trDyn(GlobalTranslations.comboboxItemAll), attendedPage!);
+    final formItems = <FormItem>[
+      FormItem(
+          TextFormField(
+            controller: textController,
+            decoration: InputDecoration(labelText: i18n.tr('Text')),
+            onChanged: (value) => _fieldData.text = value,
+          ),
+          weight: 6),
+      FormItem(
+          DropdownButtonFormField<int>(
+            value: _fieldData.role,
+            items: itemsRole,
+            isExpanded: true,
+            decoration: InputDecoration(labelText: i18n.tr('Role')),
+            onChanged: (value) => _fieldData.role = value ?? 0,
+          ),
+          weight: 6),
+      FormItem(
+          ElevatedButton(
+              onPressed: () => search(), child: Text(i18n.tr('Search'))),
+          weight: 12,
+          gapAbove: padding)
     ];
-    final weights = [6, 6];
-    final form = WidgetForm.flexibleGrid(fieldWidgets,
-        weights: weights,
-        screenWidth: attendedPage!.pageStates.screenWidth,
-        padding: padding);
+    final form = Form(
+        key: _formKey,
+        child: Card(
+            color: GlobalThemeData.formBackgroundColor,
+            elevation: GlobalThemeData.formElevation,
+            margin:
+                EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+            child: Padding(
+                padding: EdgeInsets.symmetric(
+                    vertical: padding, horizontal: padding),
+                child: WidgetForm.flexibleGrid(formItems,
+                    screenWidth: attendedPage!.pageStates.screenWidth,
+                    padding: padding))));
+
     final rows = attendedPage!.getRows(
         columnList: 'user_id;user_displayname;user_email;role',
         what: 'query',
@@ -46,10 +70,12 @@ class ListUserCustom extends State<ListUserPage> {
           'module': 'Users',
           'sql': 'list',
           'offset': '0',
-          'size': '10'
+          'size': '10',
+          ':text': _fieldData.text,
+          ':role': _fieldData.role.toString(),
         },
         onDone: () => setState(() => 1),
-        routeEdit: '/users/edit',
+        routeEdit: '/Users/edit',
         context: context);
     final table = DataTable(
       columns: <DataColumn>[
@@ -74,7 +100,7 @@ class ListUserCustom extends State<ListUserPage> {
       SizedBox(width: double.infinity, child: table),
     ]);
     final rc = Scaffold(
-        appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+        appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
         drawer: globalData.drawerBuilder(context),
         floatingActionButton: FloatingActionButton(
           onPressed: () {
@@ -87,13 +113,16 @@ class ListUserCustom extends State<ListUserPage> {
     return rc;
   }
 
+  void search() {
+    //@ToDo
+  }
   @override
   void initState() {
     super.initState();
   }
 }
 
-class FieldData {
+class _FieldData {
   String text = '';
   int role = 0;
 }
diff --git a/lib/services/global_widget.dart b/lib/services/global_widget.dart
new file mode 100644 (file)
index 0000000..c826b27
--- /dev/null
@@ -0,0 +1,65 @@
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../setting/global_data.dart';
+import '../widget/attended_page.dart';
+
+final i18n = I18N();
+
+/// Returns the combobox items filled with data of the table roles.
+///
+/// [textUndefined]: null: no additional entry. Otherwise: a additional entry is
+/// inserted at position 0 with this text and the value 0.
+///
+/// [attendedPage]: the context of the calling page.
+///
+/// Returns a list of combobox items selecting a role.
+List<DropdownMenuItem<int>>? comboRoles(
+    String textUndefined, AttendedPage attendedPage) {
+  final rc = _combo(
+      'comboRoles.global', 'role_id', 'role_name', textUndefined, attendedPage);
+  return rc;
+}
+
+/// Requests the database data for combo boxes from the backend.
+///
+/// [dbDataState]: Stores the request and the answer of the request.
+///
+/// [onDone]: A method executed after arriving the response.
+void comboRolesFromBackend(
+    {required AttendedPage attendedPage, required Function() onDone}) {
+  _requestData('comboRoles.global', attendedPage,
+      {'module': 'global', 'sql': 'comboRoles'}, onDone);
+}
+
+List<DropdownMenuItem<int>>? _combo(String name, String columnValue,
+    String columnText, String? textUndefined, AttendedPage attendedPage) {
+  final records = attendedPage.pageStates.dbDataState.listResponse(name);
+  final rc = records
+      .map((element) => DropdownMenuItem<int>(
+          value: element[columnValue],
+          child: Text(i18n.trDyn(element[columnText]))))
+      .toList(growable: textUndefined != null);
+  if (textUndefined != null) {
+    rc.insert(0, DropdownMenuItem<int>(value: 0, child: Text(textUndefined)));
+  }
+  return rc;
+}
+
+/// Requests the data from the backend.
+///
+/// The request is inserted into the
+void _requestData(String name, AttendedPage attendedPage,
+    Map<String, dynamic> parameters, Function() onDone) {
+  if (!attendedPage.pageStates.dbDataState.hasEntry(name)) {
+    attendedPage.pageStates.dbDataState.addRequest(name).then((_) =>
+        GlobalData()
+            .restPersistence!
+            .query(what: 'query', data: parameters)
+            .then((value) {
+          attendedPage.pageStates.dbDataState
+              .storeDbResult(name, value)
+              .then((_) => onDone());
+        }));
+  }
+}
index d8a156d6d2ff51eafb5af324f04238670af6ca0a..1b46ee344c242d3b51fb652e1d6396d75b682d61 100644 (file)
@@ -114,7 +114,7 @@ class MenuItem {
     final logger = globalData.logger;
     final collection = PageManager();
     return <MenuItem>[
-      MenuItem('Users', () => collection.newPageByRoute('/users/list'),
+      MenuItem('Users', () => collection.newPageByRoute('/Users/list'),
           converter.iconByName('people_outlined', logger)),
       MenuItem('Protokoll', () => collection.newPageByRoute('/log'),
           converter.iconByName('line_weight_outlined', logger)),
index e470cb0a4d33e7c0f67ea8c9462a3ed33b3b2935..bab2129249dfdfd200fd1b4d7ce3adc7b0172cf7 100644 (file)
@@ -1,12 +1,11 @@
 import 'dart:io';
 import 'package:dart_bones/dart_bones.dart';
-import 'package:exhibition/page/page_manager.dart';
-import 'package:exhibition/persistence/rest_persistence.dart';
-import 'package:exhibition/widget/attended_page.dart';
 import 'package:flutter/material.dart';
 import 'package:path/path.dart' as path;
 import 'package:path_provider/path_provider.dart';
 
+import '../persistence/rest_persistence.dart';
+import '../widget/attended_page.dart';
 import '../base/application_name.dart';
 import '../base/defines.dart';
 import '../page/page_controller_exhibition.dart';
@@ -48,6 +47,18 @@ class HomeDirectories {
   }
 }
 
+class GlobalTranslations {
+  static final comboboxItemAll = i18n.tr('<All>');
+  static final comboboxSelect = i18n.tr('<Please select>');
+}
+
+class GlobalThemeData {
+  static final formBackgroundColor = Color.fromARGB(0xee, 0xee, 0xee, 0xee);
+  static final formElevation = 10.0;
+  static final formTextBackgroundColor = Colors.white;
+  static final padding = 16.0;
+}
+
 /// Storage for  global resources. This is a singleton.
 class GlobalData {
   static const version = '2021.08.24.00';
@@ -61,8 +72,10 @@ class GlobalData {
   final FooterBuilder footerBuilder;
   final BaseConfiguration configuration;
   final RestPersistence? restPersistence;
+  static String loginUserName = 'guest';
+  static int loginUserId = 0;
+  static int loginUserRole = 90;
   HomeDirectories homeDirectories = HomeDirectories.dummy();
-  AttendedPage? currentPage;
 
   factory GlobalData() => _instance ?? GlobalData();
   GlobalData.dummy()
@@ -89,10 +102,6 @@ class GlobalData {
 
   /// Switches the page given by a [route].
   void navigate(BuildContext context, String route) {
-    final page = PageManager().existingPageByRoute(route);
-    if (page != null) {
-      currentPage = page as AttendedPage;
-    }
     Navigator.pushNamed(context, route);
   }
 }
index fa19c319b7694704d07eafe8a9858de62b618428..936318b268f3059da879b4097f77458e2bec8ff7 100644 (file)
@@ -1,3 +1,4 @@
+import 'package:exhibition/base/defines.dart';
 import 'package:flutter/material.dart';
 import 'package:synchronized/synchronized.dart';
 
@@ -48,6 +49,32 @@ class AttendedPage {
     return rc;
   }
 
+  /// Returns a database record with a given [primaryKey].
+  JsonMap? getRecord(
+      {required int primaryKey,
+      required String what,
+      required Map<String, dynamic> parameters,
+      required Function() onDone}) {
+    JsonMap? rc;
+    String name = 'record';
+    if (!pageStates.dbDataState.hasEntry(name)) {
+      pageStates.dbDataState.addRequest(name).then((_) => globalData
+              .restPersistence!
+              .query(what: what, data: parameters)
+              .then((value) {
+            pageStates.dbDataState
+                .storeDbResult(name, value)
+                .then((_) => onDone());
+          }));
+    } else {
+      final dbData = pageStates.dbDataState.dataOf(name);
+      if (dbData != null && dbData.singleRecord != null) {
+        rc = dbData.singleRecord!;
+      }
+    }
+    return rc;
+  }
+
   /// Returns a list of DataRow instances to show the content of some
   /// database records in a [DataTable].
   ///
@@ -78,9 +105,10 @@ class AttendedPage {
           final cells = <DataCell>[];
           for (var column in columnList.split(';')) {
             final primaryKey = moduleMetaData.primaryOf()?.columnName;
-            cells.add(DataCell(Text(record[column].toString()),
-                onTap: () => globalData.navigate(
-                    context, routeEdit + ';${record[primaryKey]}')));
+            cells.add(DataCell(Text(record[column].toString()), onTap: () {
+              globalData.navigate(
+                  context, routeEdit + ';${record[primaryKey]}');
+            }));
           }
           final rc3 = DataRow(cells: cells);
           rc!.add(rc3);
@@ -90,6 +118,38 @@ class AttendedPage {
     return rc ?? <DataRow>[];
   }
 
+  /// Loads a record from the backend.
+  ///
+  /// [name]: a unique name over all backend data requests.
+  ///
+  /// [reload]: a function calling setState().
+  ///
+  /// [onDone]: a function storing the record fetched from the backend.
+  ///
+  /// [parameters]: a map specifying the request.
+  /// Example: { 'module': 'Roles', 'sql': 'byId', ':id': primaryKey}.///
+  void loadRecord(
+      {required String name,
+      required void Function() reload,
+      required void Function(JsonMap) onDone,
+      required Map<String, dynamic> parameters}) {
+    if (!pageStates.dbDataState.hasEntry(name)) {
+      pageStates.dbDataState.addRequest(name).then((_) => globalData
+              .restPersistence!
+              .query(what: 'query', data: parameters)
+              .then((value) {
+            pageStates.dbDataState
+                .storeDbResult(name, value)
+                .then((_) => reload());
+          }));
+    } else if (pageStates.dbDataState.dataOf('record') != null) {
+      final dbData = pageStates.dbDataState.dataOf('record');
+      if (dbData != null && dbData.singleRecord != null) {
+        onDone(dbData.singleRecord!);
+      }
+    }
+  }
+
   /// Validates the text [value] of the [textAttended].
   ///
   /// Returns null on success or the error message on error.
@@ -138,12 +198,37 @@ class DbDataState {
   /// Tests whether the map contains an entry with [name].
   bool hasEntry(String name) => dbDataMap.containsKey(name);
 
+  /// Tests whether the map with [name] contains a response from the backend.
+  bool hasRecordResponse(String name) =>
+      dbDataMap.containsKey(name) && dbDataMap[name]!.singleRecord != null;
+
+  /// Tests whether the map with [name] contains a response from the backend.
+  bool hasResponse(String name) {
+    final info = dbDataMap[name];
+    final rc = info == null
+        ? false
+        : (info.recordList != null ||
+            info.singleRecord != null ||
+            info.message != null);
+    return rc;
+  }
+
   /// Tests whether all requests are answered.
   ///
   /// Return true, if all answers of the requested DB data are stored in
   /// [dbDataMap].
   bool isWaiting() => currentCount < expectedCount;
 
+  /// Returns the response of a given [name] or [] otherwise.
+  ///
+  /// Returns a list of records or [].
+  JsonList listResponse(String name) => dbDataMap[name]?.recordList ?? [];
+
+  /// Returns the response of a given [name] or {} otherwise.
+  ///
+  /// Returns a record or {}.
+  JsonMap recordResponse(String name) => dbDataMap[name]?.singleRecord ?? {};
+
   /// Stores the DB data fetched from the backend.
   ///
   /// [name] is a (over all requests) unique name to identify the request.
diff --git a/lib/widget/message_line.dart b/lib/widget/message_line.dart
new file mode 100644 (file)
index 0000000..ed2f691
--- /dev/null
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+
+/// A mixin to handle a message line.
+/// This message line is only visible if the message is not empty.
+abstract class MessageLine {
+  String message = '';
+  void setMessage(String message, {bool isError = true}) {
+    this.message = message;
+  }
+
+  void setError(String message, {bool isError = true}) {
+    this.message = '+++ $message';
+  }
+
+  void addToList(List<Widget> widgetList, double padding) {
+    if (message.isNotEmpty) {
+      widgetList.add(SizedBox(height: padding));
+      widgetList.add(Text(message, style: TextStyle(color: Colors.red)));
+    }
+  }
+
+  void addToFormList(List<Widget> widgetList, double padding) {
+    if (message.isNotEmpty) {
+      widgetList.add(Text(message, style: TextStyle(color: Colors.red)));
+    }
+  }
+}
index b820fae4b3c9035f4dc88e6c58301cc5f8976cbc..9a86b35dc910c168fb94baf5946488e005d44bd5 100644 (file)
@@ -1,6 +1,21 @@
 import 'package:exhibition/widget/attended_widget.dart';
 import 'package:flutter/material.dart';
 
+class FormItem {
+  final Widget widget;
+  final int weight;
+  final double gapAbove;
+
+  /// Constructor.
+  ///
+  /// [weight]: defines the widget width in a 12 column model: 6 means standard
+  /// width in a 2 column form.
+  ///
+  /// [gapAbove]: ignored if 0.0. Otherwise the widget always starts a row
+  /// and the row has that distance to the prior row or top of the form.
+  FormItem(this.widget, {required this.weight, this.gapAbove = 0.0});
+}
+
 class WidgetForm {
   /// Places the widgets in a flexible grid.
   ///
@@ -9,30 +24,36 @@ class WidgetForm {
   /// The place of the row is divided in 12 segments with the same width.
   /// Each widget has a "weight" which means the count of segments to use.
   ///
-  /// [widgets] is the list of widgets to position in the grid.
+  /// [formItems] is the list of widgets to position in the grid.
   ///
   /// [screenWidth] is the width of the output device in pixel.
   ///
   /// [minWidth] is the minimum width of the above defined segment.
   /// If the [screenwidth] has not place for 6 segments all widgets are placed
   /// in a single row.
-  static Widget flexibleGrid(List<Widget> widgets,
-      {required List<int> weights,
-      required double screenWidth,
+  static Widget flexibleGrid(List<FormItem> formItems,
+      {required double screenWidth,
       double minWidth = 800,
       double padding = 16.0}) {
     Widget rc;
-    assert(widgets.length == weights.length);
     if (minWidth > screenWidth) {
+      final widgets = <Widget>[];
+      formItems.forEach((element) {
+        if (element.gapAbove > 0) {
+          widgets.add(SizedBox(height: element.gapAbove));
+        }
+        widgets.add(element.widget);
+      });
       rc = Column(children: widgets);
     } else {
       int position = 0;
       final List<Widget> childrenColumn = [];
       List<Widget> childrenRow = [];
-      for (var ix = 0; ix < widgets.length; ix++) {
-        final widget = widgets[ix];
-        final flex = weights[ix];
-        if (position + flex <= 12) {
+      for (var ix = 0; ix < formItems.length; ix++) {
+        final widget = formItems[ix].widget;
+        final flex = formItems[ix].weight;
+        final gap = formItems[ix].gapAbove;
+        if (position + flex <= 12 && gap <= 0.0) {
           var child2 = position == 0
               ? widget
               : Row(
@@ -42,11 +63,14 @@ class WidgetForm {
           childrenRow.add(Expanded(flex: flex, child: child2));
           position += flex;
         } else {
-          if (position < 12) {
+          if (position < 12 || gap > 0.0) {
             childrenRow
                 .add(Expanded(flex: 12 - position, child: SizedBox(width: 1)));
           }
           position = 0;
+          if (childrenColumn.isNotEmpty || gap > 0.0) {
+            childrenColumn.add(SizedBox(height: gap == 0.0 ? padding : gap));
+          }
           childrenColumn.add(Row(children: childrenRow));
           childrenRow = [];
           final child2 = position == 0
@@ -84,13 +108,17 @@ class WidgetForm {
   /// in a single row.
   static Widget flexibleGridAttended(List<AttendedWidget> widgets,
       {required double screenWidth,
+      List<FormItem>? buttons,
       double minWidth = 800,
       double padding = 16.0}) {
-    final weights =
-        widgets.map((element) => element.propertyMetaData.weight).toList();
-    final widgets2 = widgets.map((element) => element.widgetOf()).toList();
-    final rc =
-        flexibleGrid(widgets2, weights: weights, screenWidth: screenWidth);
+    final widgets2 = widgets
+        .map((element) => FormItem(element.widgetOf(),
+            weight: element.propertyMetaData.weight))
+        .toList();
+    if (buttons != null) {
+      widgets2.addAll(buttons);
+    }
+    final rc = flexibleGrid(widgets2, screenWidth: screenWidth);
     return rc;
   }
 }
index 369b373d82c48137d136f1946dbc8de8bc8c9f8a..c241876ad521dba7fdd68c6b9f977d7f3df6af2d 100644 (file)
@@ -62,7 +62,7 @@ DEF_PRIMARY  final globalData = GlobalData();
   EditUserCustom(THIS_PRIMARY);
   @override
   Widget build(BuildContext context) {
-    final padding = 16.0;
+    final padding = GlobalThemeData.padding;
     final rc = Scaffold(
         appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
         drawer: globalData.drawerBuilder(context),
index bce05472746743875afb880aac49f3d934fd48a1..ecb791f118ff11d3656f78791ae08559e0aa658a 100644 (file)
@@ -1,9 +1,9 @@
 import 'dart:io';
 
 import 'package:dart_bones/dart_bones.dart';
+
 import '../lib/meta/module_meta_data.dart';
 import '../lib/meta/modules.dart';
-
 import 'generator.dart';
 import 'generator_base.dart';
 
@@ -54,11 +54,11 @@ JOINS
     ;"
 byId:
   type: record
-  parameters: [ "${list[0].name}" ]
+  parameters: [ ":${list[0].name}" ]
   sql: "SELECT * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};"
 delete:
   type: delete
-  parameters: [ "${list[0].name}" ]
+  parameters: [ ":${list[0].name}" ]
   sql: "DELETE * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};"
 update:
   type: update
@@ -68,19 +68,25 @@ update:
     var items = module.standardColumns('changedBy');
     var parameters = addToBuffer('  parameters: [', maxLength: 80);
     var assignments = addToBuffer('    ', maxLength: 80);
-    var first = true;
+    var firstParameter = true;
+    var firstAssignment = true;
     for (var item in items) {
-      addToBuffer('":${item.name}"',
-          maxLength: 80,
-          separator: first ? null : ',',
-          indent: 4,
-          buffer: parameters);
-      addToBuffer('${item.columnName}=:${item.name}',
-          maxLength: 80,
-          separator: first ? null : ',',
-          indent: 4,
-          buffer: assignments);
-      first = false;
+      if (!item.hasOption('hidden')) {
+        addToBuffer('":${item.name}"',
+            maxLength: 80,
+            separator: firstParameter ? null : ',',
+            indent: 4,
+            buffer: parameters);
+        firstParameter = false;
+        if (!item.hasOption('primary')) {
+          addToBuffer('${item.columnName}=:${item.name}',
+              maxLength: 80,
+              separator: firstAssignment ? null : ',',
+              indent: 4,
+              buffer: assignments);
+          firstAssignment = false;
+        }
+      }
     }
     parameters.write(']');
     var item = module.properties['changed']!;
@@ -97,24 +103,26 @@ update:
     parameters = addToBuffer('  parameters: [', maxLength: 80);
     final sql1 = addToBuffer('  sql: "INSERT INTO $tableName(', maxLength: 80);
     final sql2 = addToBuffer('    VALUES(', maxLength: 80);
-    first = true;
+    var first = true;
     for (var item in items) {
-      addToBuffer('":${item.name}"',
-          maxLength: 80,
-          separator: first ? null : ',',
-          indent: 4,
-          buffer: parameters);
-      addToBuffer('${item.columnName}',
-          maxLength: 80,
-          separator: first ? null : ',',
-          indent: 6,
-          buffer: sql1);
-      addToBuffer(':${item.name}',
-          maxLength: 80,
-          separator: first ? null : ',',
-          indent: 6,
-          buffer: sql2);
-      first = false;
+      if (!item.hasOption('primary')) {
+        addToBuffer('":${item.name}"',
+            maxLength: 80,
+            separator: first ? null : ',',
+            indent: 4,
+            buffer: parameters);
+        addToBuffer('${item.columnName}',
+            maxLength: 80,
+            separator: first ? null : ',',
+            indent: 6,
+            buffer: sql1);
+        addToBuffer(':${item.name}',
+            maxLength: 80,
+            separator: first ? null : ',',
+            indent: 6,
+            buffer: sql2);
+        first = false;
+      }
     }
     parameters.write(']');
     item = module.properties['created']!;
@@ -122,7 +130,7 @@ update:
         maxLength: 80, separator: ',', indent: 4, buffer: sql1);
     addToBuffer('NOW());"',
         maxLength: 80, separator: ',', indent: 4, buffer: sql2);
-    // parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
+    // parameters: [":name", ":displayname", ":email", ":changedby"]
     buffer.write('''insert:
   type: insert
 ''');
@@ -132,20 +140,6 @@ update:
     return buffer.toString();
   }
 
-  /// Generates the Sql statement file for each module defined in the meta data.
-  void updateSql(Generator generator) {
-    final modules = moduleNames();
-    for (var name in modules) {
-      ModuleMetaData? module = moduleByName(name);
-      if (module == null) {
-        logger.error('+++ unknown module: $name');
-      } else {
-        logger.log('current directory: ${Directory.current.path}');
-        String filename = '../rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
-        writeFile(filename, generator.createSqlStatements(module));
-      }
-    }
-  }
   /// Handles the foreign keys in the [sqlText] of the given [module].
   ///
   /// Replaces the placeholders SELECTS and JOINS in [sqlText].
@@ -155,16 +149,18 @@ update:
     var joins = '';
     var selects = '';
     var referenceNo = 0;
-    for (var property in module.propertyList){
-      if (property.foreignKey != null){
+    for (var property in module.propertyList) {
+      if (property.foreignKey != null) {
         // foreignKey: 'roles.role_id;role_name;role'
         final parts = property.foreignKey!.split(';');
-        if (parts.length != 3){
-          logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
+        if (parts.length != 3) {
+          logger.error(
+              'wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
         } else {
           final keyParts = parts[0].split('.');
-          if (keyParts.length != 2){
-            logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
+          if (keyParts.length != 2) {
+            logger.error(
+                'wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}');
           } else {
             ++referenceNo;
             joins += '    JOIN ${keyParts[0]} t$referenceNo ON '
@@ -174,7 +170,24 @@ update:
         }
       }
     }
-    final rc = sqlText.replaceFirst('JOINS', joins).replaceFirst('SELECTS', selects);
+    final rc =
+        sqlText.replaceFirst('JOINS', joins).replaceFirst('SELECTS', selects);
     return rc;
   }
+
+  /// Generates the Sql statement file for each module defined in the meta data.
+  void updateSql(Generator generator) {
+    final modules = moduleNames();
+    for (var name in modules) {
+      ModuleMetaData? module = moduleByName(name);
+      if (module == null) {
+        logger.error('+++ unknown module: $name');
+      } else {
+        logger.log('current directory: ${Directory.current.path}');
+        String filename =
+            '../rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
+        writeFile(filename, generator.createSqlStatements(module));
+      }
+    }
+  }
 }
diff --git a/rest_server/data/sql/precedence/global.sql.yaml b/rest_server/data/sql/precedence/global.sql.yaml
new file mode 100644 (file)
index 0000000..3dde806
--- /dev/null
@@ -0,0 +1,18 @@
+---
+# This file contains contains SQL queries/requests
+# that can be used by all projects derived from exhibition.
+#
+# Note: There is a file local.sql.yaml for usage only in one project.
+#
+# This file should be maintained by the programmers.
+
+module: global
+comboRoles:
+  type: list
+  parameters: []
+  sql: "SELECT role_id, role_name from roles order by role_name;"
+comboActiveUsers:
+  type: list
+  parameters: []
+  sql: "SELECT user_id, user_displayname from users
+    WHERE user_status = 1 ORDER BY by user_displayname;"
diff --git a/rest_server/data/sql/precedence/local.sql.yaml b/rest_server/data/sql/precedence/local.sql.yaml
new file mode 100644 (file)
index 0000000..ecf0e2d
--- /dev/null
@@ -0,0 +1,7 @@
+---
+# This file contains contains SQL queries/requests
+# that should be used only by the current project (derived from exhibition).
+#
+# Note: There is a file global.sql.yaml for usage in all projects (derived from exhibition).
+#
+# This file should be maintained by the programmers.
index 8e5f31c225cabb60b5b41a896b279ea3d28d654d..616389e5b65a0f23ecf7f7978ceca205d0f435b6 100644 (file)
@@ -13,20 +13,20 @@ list:
     ;"
 byId:
   type: record
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "SELECT * FROM roles WHERE role_id=:id;"
 delete:
   type: delete
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "DELETE * FROM roles WHERE role_id=:id;"
 update:
   type: update
-  parameters: [":id",":name",":changedBy"]
+  parameters: [":id",":name"]
   sql: "UPDATE roles SET
-    role_id=:id,role_name=:name,role_changedby=:changedBy,role_changed=NOW()
+    role_name=:name,role_changed=NOW()
     WHERE role_id=:id;"
 insert:
   type: insert
-  parameters: [":id",":name",":createdBy"]
-  sql: "INSERT INTO roles(role_id,role_name,role_createdby,role_created)
-    VALUES(:id,:name,:createdBy,NOW());"
+  parameters: [":name",":createdBy"]
+  sql: "INSERT INTO roles(role_name,role_createdby,role_created)
+    VALUES(:name,:createdBy,NOW());"
index f09192a7b7d7c6e5a7101a8d11ae8593f40a5e80..111f673cfe846cef288bb2e0314c25c97592c6c1 100644 (file)
@@ -13,23 +13,22 @@ list:
     ;"
 byId:
   type: record
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "SELECT * FROM structures WHERE structure_id=:id;"
 delete:
   type: delete
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "DELETE * FROM structures WHERE structure_id=:id;"
 update:
   type: update
-  parameters: [":id",":scope",":name",":value",":position",":changedBy"]
+  parameters: [":id",":scope",":name",":value",":position"]
   sql: "UPDATE structures SET
-    structure_id=:id,structure_scope=:scope,structure_name=:name,
-    structure_value=:value,structure_position=:position,
-    structure_changedby=:changedBy,structure_changed=NOW()
+    structure_scope=:scope,structure_name=:name,structure_value=:value,
+    structure_position=:position,structure_changed=NOW()
     WHERE structure_id=:id;"
 insert:
   type: insert
-  parameters: [":id",":scope",":name",":value",":position",":createdBy"]
-  sql: "INSERT INTO structures(structure_id,structure_scope,structure_name,
-      structure_value,structure_position,structure_createdby,structure_created)
-    VALUES(:id,:scope,:name,:value,:position,:createdBy,NOW());"
+  parameters: [":scope",":name",":value",":position",":createdBy"]
+  sql: "INSERT INTO structures(structure_scope,structure_name,structure_value,
+      structure_position,structure_createdby,structure_created)
+    VALUES(:scope,:name,:value,:position,:createdBy,NOW());"
index 67cddbacd2e0c0c5d2a5a98f33834882eb5bcf2c..7050e27da20cc96ece64dee5b340f22caf7f5d4e 100644 (file)
@@ -13,22 +13,22 @@ list:
     ;"
 byId:
   type: record
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "SELECT * FROM users WHERE user_id=:id;"
 delete:
   type: delete
-  parameters: [ "id" ]
+  parameters: [ ":id" ]
   sql: "DELETE * FROM users WHERE user_id=:id;"
 update:
   type: update
-  parameters: [":id",":name",":displayName",":email",":role",":changedBy"]
+  parameters: [":id",":name",":displayName",":email",":role"]
   sql: "UPDATE users SET
-    user_id=:id,user_name=:name,user_displayname=:displayName,user_email=:email,
-    user_role=:role,user_changedby=:changedBy,user_changed=NOW()
+    user_name=:name,user_displayname=:displayName,user_email=:email,
+    user_role=:role,user_changed=NOW()
     WHERE user_id=:id;"
 insert:
   type: insert
-  parameters: [":id",":name",":displayName",":email",":role",":createdBy"]
-  sql: "INSERT INTO users(user_id,user_name,user_displayname,user_email,
-      user_role,user_createdby,user_created)
-    VALUES(:id,:name,:displayName,:email,:role,:createdBy,NOW());"
+  parameters: [":name",":displayName",":email",":role",":createdBy"]
+  sql: "INSERT INTO users(user_name,user_displayname,user_email,user_role,
+      user_createdby,user_created)
+    VALUES(:name,:displayName,:email,:role,:createdBy,NOW());"
index 8a18a642ca788fb3782b39a33b1588be7b00e53e..9f179209f9762299478df511127f32bfbf1fcef9 100644 (file)
@@ -86,7 +86,7 @@ class SqlStatement {
       if (!map.containsKey(parameter)) {
         throw SqlException('${toString()}: missing parameter "$parameter"');
       } else {
-        parameters.add(map[parameter]);
+        parameters.add(map[parameter].toString());
       }
     }
     return sqlPrepared;