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].
--- /dev/null
+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);
@override
Widget build(BuildContext context) {
- const padding = 16.0;
+ final padding = GlobalThemeData.padding;
final listItems = (globalData.logger as MemoryLogger)
.messages
.map((line) => Text(line,
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,
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),
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),
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),
@override
Widget build(BuildContext context) {
- final padding = 16.0;
+ final padding = GlobalThemeData.padding;
final listItems = <Widget>[
Text(
'Herzlich Willkommen',
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),
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),
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),
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),
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;
}
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;
+ }
}
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),
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;
}
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;
+ }
}
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',
'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>[
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: () {
return rc;
}
+ void search() {
+ //@ToDo
+ }
@override
void initState() {
super.initState();
}
}
-class FieldData {
+class _FieldData {
String text = '';
int role = 0;
}
--- /dev/null
+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());
+ }));
+ }
+}
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)),
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';
}
}
+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';
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()
/// 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);
}
}
+import 'package:exhibition/base/defines.dart';
import 'package:flutter/material.dart';
import 'package:synchronized/synchronized.dart';
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].
///
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);
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.
/// 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.
--- /dev/null
+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)));
+ }
+ }
+}
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.
///
/// 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(
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
/// 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;
}
}
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),
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';
;"
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
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']!;
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']!;
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
''');
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].
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 '
}
}
}
- 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));
+ }
+ }
+ }
}
--- /dev/null
+---
+# 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;"
--- /dev/null
+---
+# 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.
;"
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());"
;"
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());"
;"
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());"
if (!map.containsKey(parameter)) {
throw SqlException('${toString()}: missing parameter "$parameter"');
} else {
- parameters.add(map[parameter]);
+ parameters.add(map[parameter].toString());
}
}
return sqlPrepared;