data/i18n/lokalize-scripts/
rest_server/tools/rest_server
/UpdRestSv
+/lib/page/benchmarks/list_benchmark_custom.01.dart
+/lib/page/users/list_user_custom.01.dart
return rc;
}
+String dbValueToString(dynamic value, DataType dataType) {
+ String rc;
+ if (!(value is String)) {
+ rc = asString(value);
+ } else {
+ switch (dataType) {
+ case DataType.bool:
+ rc = value == 'T' ? i18n.tr('Yes') : i18n.tr('No');
+ break;
+ case DataType.date:
+ rc = sortableDateToStandard(value, dateOnly: true);
+ break;
+ case DataType.datetime:
+ rc = sortableDateToStandard(value);
+ break;
+ default:
+ rc = value;
+ break;
+ }
+ }
+ return rc;
+}
+
/// Converts a string [value] into a [dataType] specific object.
///
/// If conversion is not possible (wrong input), the [defaultValue] is returned
/// This function is only used to avoid compiler warning "unused import"
/// for generated code.
-void helperDummyUsage() {
+void helperDummyUsage([dynamic dummy]) {
// nothing to do
}
+/// Converts a [value] coming from a json decode operation into the "native" value
+/// given by the [dataType].
+///
+/// If the [value] is null the return value is null if [nullReturnsNull] is true
+/// or [defaultValue] otherwise.
+///
+/// In the case of an error the [defaultValue] is returned.
+dynamic jsonToObject(dynamic value,
+ {required DataType dataType,
+ dynamic defaultValue,
+ bool nullReturnsNull = false}) {
+ var rc;
+ if (value == null) {
+ rc = nullReturnsNull ? null : defaultValue;
+ } else if (value is String) {
+ rc = dataType == DataType.string
+ ? value
+ : fromString(value, dataType: dataType, defaultValue: defaultValue);
+ } else if (value is int) {
+ rc = dataType == DataType.int ||
+ dataType == DataType.nat ||
+ dataType == DataType.reference
+ ? value
+ : (dataType == DataType.float || dataType == DataType.currency
+ ? rc = value as double
+ : defaultValue);
+ } else if (value is double) {
+ rc = dataType == DataType.float || dataType == DataType.currency
+ ? value
+ : defaultValue;
+ } else if (value is DateTime) {
+ rc = dataType == DataType.date || dataType == DataType.datetime
+ ? value
+ : defaultValue;
+ } else if (value is bool) {
+ rc = dataType == DataType.bool ? value : defaultValue;
+ } else {
+ rc = defaultValue;
+ }
+ return rc;
+}
+
+/// Converts a number of seconds or milliseconds into a human readable format.
+///
+/// [value]: the value to convert. See [isMilliSeconds].
+///
+/// [milliSeconds]: true: [value] is a number of milliseconds. false: [value]
+/// is a number of seconds.
+///
+/// Returns a days:hours:minutes:seconds.milliseconds format, e.g. '10:59:59.123'.
+/// Leading zero values are ignored: the result never starts with '0:'.
+String secondsAsTime(int value, {bool isMilliSeconds = false}) {
+ final seconds = isMilliSeconds ? value ~/ 1000 : value;
+ var rc = '';
+ if (seconds > 24 * 3600) {
+ rc = sprintf('%d:%02d:%02d:%02d', [
+ seconds ~/ (24 * 3600),
+ seconds % (24 * 3600) ~/ 3600,
+ seconds % 3600 ~/ 60,
+ seconds % 60
+ ]);
+ } else if (seconds > 3600) {
+ rc = sprintf('%02d:%02d:%02d',
+ [seconds ~/ 3600, seconds % 3600 ~/ 60, seconds % 60]);
+ } else if (seconds > 60) {
+ rc = sprintf('%02d:%02d', [seconds ~/ 60, seconds % 60]);
+ } else {
+ rc = seconds.toString();
+ }
+ if (isMilliSeconds) {
+ rc += sprintf('.%03d', [value % 1000]);
+ }
+ return rc;
+}
+
+/// Converts a sortable [date] or date and time ("2021-12-03") into a standard
+/// format ("03.12.2021").
+///
+/// [dateOnly]: if true only the date part (without time) is in the result.
+///
+/// [withSeconds]: if false the seconds are not part of the result.
+///
+/// [separator]: the separator between the date parts: '.' or '-'.
+String sortableDateToStandard(String date,
+ {bool dateOnly = false, bool withSeconds = false, String separator = '.'}) {
+ // 2021-12-22 10:12
+ // 0123456789 12345
+ var rc = date.substring(8, 10) +
+ separator +
+ date.substring(5, 7) +
+ separator +
+ date.substring(0, 4);
+ if (!dateOnly && date.length > 10) {
+ rc += date.substring(10);
+ }
+ if (!withSeconds && rc.length > 16) {
+ rc = rc.substring(0, 16);
+ }
+ return rc;
+}
+
/// Converts a [name] to a camel case string.
///
/// A camelCase string starts with a uppercase character.
if (input != null) {
RegExpMatch? match = _regExprEMailChar.firstMatch(input);
if (match != null) {
- rc = i18n.trArgs('Illegal character "{0} in email address', '!global',
+ 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 a not negative integer.
+String? isInt(String? input) {
+ String? rc;
+ if (input != null) {
+ final value = int.tryParse(input);
+ if (value == null) {
+ rc = i18n.trArgs('Not an integer: {0}', '!global', [input]);
+ }
+ }
+ return rc;
+}
+
+/// Tests whether the [input] is a not negative integer.
+String? isNat(String? input) {
+ String? rc;
+ if (input != null) {
+ final value = int.tryParse(input);
+ if (value == null) {
+ rc = i18n.trArgs('Not an integer: {0}', '!global', [input]);
+ } else if (value < 0) {
+ rc = i18n.trArgs(
+ 'Not negative integer expected, not: {0}', '!global', [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;
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import '../base/helper.dart';
+import '../common/random_data.dart';
+import '../setting/global_data.dart';
+
+class BenchmarkActions {
+ final GlobalData globalData;
+ var random = KissRandom(MemoryLogger());
+ BenchmarkActions(this.globalData) {
+ random = KissRandom(globalData.logger);
+ random.setSeed(DateTime.now().microsecondsSinceEpoch.toString());
+ }
+ String anyName() {
+ String? rc;
+ switch (random.nextInt(max: 4)) {
+ case 0:
+ rc = RandomData.constructWord([
+ WordPart(WordType.adjectiveOrColor, random.nextInt()),
+ WordPart(WordType.animalOrThing, random.nextInt()),
+ ]);
+ break;
+ case 1:
+ rc = RandomData.constructWord([
+ WordPart(WordType.verb, random.nextInt()),
+ WordPart(WordType.thing, random.nextInt()),
+ ]);
+ break;
+ case 2:
+ rc = RandomData.constructWord([
+ WordPart(WordType.color, random.nextInt()),
+ WordPart(WordType.adjective, random.nextInt()),
+ WordPart(WordType.animalOrThing, random.nextInt()),
+ ]);
+ break;
+ default:
+ rc = RandomData.constructWord([
+ WordPart(WordType.verb, random.nextInt()),
+ WordPart(WordType.color, random.nextInt()),
+ WordPart(WordType.adjective, random.nextInt()),
+ WordPart(WordType.animalOrThing, random.nextInt()),
+ ]);
+ break;
+ }
+ return rc;
+ }
+
+ /// Creates a [count] of entries in the table benchmarks.
+ ///
+ /// Returns an error message (if it starts with '+++') or a notice about
+ /// the duration.
+ Future<String> createMultiple(int count) async {
+ final start = DateTime.now();
+ String? rc;
+ for (var ix = 0; ix < count; ix++) {
+ final lastname = anyName();
+ final threeYearSinceEpoche = random.nextInt(max: 24 * 3600 * 30);
+ final birthday =
+ DateTime.fromMillisecondsSinceEpoch(threeYearSinceEpoche * 1000);
+ final parameters = <String, dynamic>{
+ 'module': 'Benchmarks',
+ 'sql': 'insert',
+ ':lastName': lastname,
+ ':firstName': RandomData.firstname(random.nextInt()),
+ ':email': '$lastname$ix@hamatoma.de',
+ ':birthday': asString(birthday, dbFormat: true),
+ ':income': random.nextDouble() * 10000.0,
+ ':weight': 50 + random.nextDouble() * 40,
+ ':active': 'T',
+ ':createdBy': 'benchmark'
+ };
+ final answer = await globalData.restPersistence!
+ .store(what: 'store', map: parameters);
+ if (!answer.startsWith('id:')) {
+ rc = '+++ storage failed: $answer';
+ break;
+ }
+ }
+ rc ??= 'duration: ' +
+ secondsAsTime(DateTime.now().difference(start).inMilliseconds,
+ isMilliSeconds: true);
+ return rc;
+ }
+}
--- /dev/null
+class RandomData {
+ static final verbs = const [
+ 'go', 'read', 'eat', 'jump', 'write', 'say', 'talk', 'pray', 'love',
+ 'hate', 'join', 'select', 'walk', 'hear', 'type', // 15
+ 'cry', 'cook', 'fill', 'bide', 'paint'
+ ];
+ static final adjectives = const [
+ 'high', 'low', 'hot', 'cold', 'wide', 'small', 'pretty', 'dirty',
+ 'simple', 'rich', 'poor', 'full', 'empty', 'happy', 'angry', // 15
+ 'strong', 'wise', 'dirty', 'silly', ''
+ ];
+ static final animals = const [
+ 'dog', 'cat', 'mouse', 'cow', 'unicorn', 'lion', 'tiger', 'sheep', 'bull',
+ 'bison', 'bug', 'turtle', 'camel', 'spider', 'swan', // 15
+ 'bunny', 'deer', 'hare', 'fox', 'pig'
+ ];
+ static final things = const [
+ 'door', 'chair', 'wood', 'ink', 'floor', 'bed', 'bag', 'coat',
+ 'house', 'palace', 'street', 'way', 'city', 'table', 'wall', // 15
+ 'tree', 'gras', 'sky', 'hell', 'garden'
+ ];
+ static final colors = const [
+ 'red',
+ 'blue',
+ 'white',
+ 'green',
+ 'yellow',
+ 'brown',
+ 'lila',
+ 'purpur',
+ 'orange',
+ 'pink'
+ ];
+ static final firstnames = const [
+ 'Adam',
+ 'Alice',
+ 'Bill',
+ 'Berta',
+ 'Charly',
+ 'Celine',
+ 'Dagobert',
+ 'Dora',
+ 'Emil',
+ 'Eve',
+ 'Fred',
+ 'Frida',
+ 'Guido',
+ 'Gerda',
+ 'Henry',
+ 'Helga',
+ 'Isak',
+ 'Isabelle',
+ 'Jeronimo',
+ 'Jutta',
+ 'Karl',
+ 'Karla',
+ 'Luis',
+ 'Lea',
+ 'Marc',
+ 'Mia',
+ 'Nick',
+ 'Natalie',
+ 'Olaf',
+ 'Oda',
+ 'Peter',
+ 'Pia',
+ 'Rolf',
+ 'Renate',
+ 'Siegfried',
+ 'Susi',
+ 'Tom',
+ 'Tara',
+ 'Uwe',
+ 'Udine',
+ 'Wolf',
+ 'Walpurga',
+ 'Xaver',
+ 'Xanthippe',
+ 'Yolando',
+ 'Yvonne',
+ 'Zephyr',
+ 'Zara'
+ ];
+ static final adjectivesOrColors = [...adjectives, ...colors];
+ static final animalsOrThings = [...animals, ...things];
+
+ /// Returns a word constructed with different word types.
+ ///
+ /// [wordParts]: defines which word types should be used.
+ ///
+ /// [normalName]: true: the result starts with an uppercase char, all other
+ /// chars are lowercase.
+ ///
+ /// [gerund]: true: a ver is used in the gerund form: this is better to
+ /// to read and remember.
+ static String constructWord(List<WordPart> wordParts,
+ {bool normalName = true, gerund: true}) {
+ String rc = '';
+ for (var item in wordParts) {
+ switch (item.wordType) {
+ case WordType.adjective:
+ rc += adjectives[item.index % adjectives.length];
+ break;
+ case WordType.verb:
+ var next = verbs[item.index % verbs.length];
+ if (gerund) {
+ next = (next.endsWith('e')
+ ? next.substring(0, next.length - 1)
+ : next) +
+ 'ing';
+ }
+ rc += next;
+ break;
+ case WordType.animal:
+ rc += animals[item.index % animals.length];
+ break;
+ case WordType.thing:
+ rc += things[item.index % things.length];
+ break;
+ case WordType.firstname:
+ rc += firstnames[item.index % firstnames.length];
+ break;
+ case WordType.color:
+ rc += colors[item.index % colors.length];
+ break;
+ case WordType.adjectiveOrColor:
+ rc += adjectivesOrColors[item.index % adjectivesOrColors.length];
+ break;
+ case WordType.animalOrThing:
+ rc += animalsOrThings[item.index % animalsOrThings.length];
+ break;
+ }
+ }
+ if (normalName) {
+ rc = rc[0].toUpperCase() + rc.substring(1).toLowerCase();
+ }
+ return rc;
+ }
+
+ /// Returns a random firstname.
+ ///
+ /// [random]: a random integer number
+ static firstname(int random) {
+ final rc = firstnames[random % firstnames.length];
+ return rc;
+ }
+}
+
+class WordPart {
+ final WordType wordType;
+ final int index;
+ WordPart(this.wordType, this.index);
+}
+
+enum WordType {
+ verb,
+ adjective,
+ animal,
+ thing,
+ color,
+ firstname,
+ adjectiveOrColor,
+ animalOrThing
+}
'lastName', i18n.tr('Last Name'), DataType.string, ':notnull:',
size: 64),
PropertyMetaData('firstName', i18n.tr('First Name', M),
- DataType.string, ':unique:notnull:',
+ DataType.string, ':notnull:',
size: 32),
PropertyMetaData(
- 'email', i18n.tr('EMail', M), DataType.string, ':unique:notnull:',
+ 'email', i18n.tr('EMail', M), DataType.string, ':notnull:',
size: 255, validators: ['isEmail(input)']),
PropertyMetaData(
'birthday', i18n.tr('Birthday'), DataType.date, ':notnull:'),
PropertyMetaData(
- 'active', i18n.tr('Active'), DataType.bool, ':notnull:'),
- PropertyMetaData(
- 'weight', i18n.tr('Weight'), DataType.float, ''),
- PropertyMetaData(
- 'income', i18n.tr('Income'), DataType.currency, ''),
+ 'active', i18n.tr('Active'), DataType.bool, ':notnull:',
+ displayType: DisplayType.switchWidget),
+ PropertyMetaData('weight', i18n.tr('Weight'), DataType.float, ''),
+ PropertyMetaData('income', i18n.tr('Income'), DataType.currency, ''),
PropertyMetaData(
'created', i18n.tr('Created'), DataType.datetime, ':hidden:'),
PropertyMetaData(
'text', i18n.tr('Text'), DataType.string, ':pattern:',
size: 64),
],
- tableColumns: 'benchmark_id;benchmark_lastname;benchmark_firstname;benchmark_birthday',
- tableHeaders: i18n.tr(';Id;Last Name;First Name;Birthday'),
+ tableColumns:
+ 'benchmark_id;benchmark_lastname;benchmark_firstname;benchmark_birthday;benchmark_active;benchmark_income',
+ tableHeaders:
+ i18n.tr(';Id;Last Name;First Name;Birthday;Active;Income'),
whereCondition: '''(:text='' OR benchmark_lastname like :text
OR benchmark_firstname like :text)''',
orderBy: 'benchmark_id',
checkbox,
combobox,
custom,
+ switchWidget,
text,
}
final String orderBy;
final String selectItems;
final String joinItems;
+ final String widgetsBelowFilter;
/// Constructor.
///
/// the created joins (t1, t2, ...), use j1, j2 ...
/// Example: '''JOIN sessions j1 ON j1.user_id=t0.user_id
/// JOIN logins j2 ON j2.user_id=t0.user_id'''
+ ///
+ /// [widgetsBelowFilter]: additional widget
ListPageMetaData(String label,
{String name = '',
required List<WidgetMetaData> fields,
this.whereCondition = '',
this.orderBy = '',
this.selectItems = '',
- this.joinItems = ''})
+ this.joinItems = '',
+ this.widgetsBelowFilter = ''})
: super(label, PageType.list,
name: name, fields: fields, globalComboBoxes: globalComboBoxes);
}
return rc;
}
+ /// Returns a property given by the [columnName].
+ PropertyMetaData? propertyByColumnName(String columnName) {
+ PropertyMetaData? rc;
+ for (var field in propertyList) {
+ if (field.columnName == columnName) {
+ rc = field;
+ break;
+ }
+ }
+ return rc;
+ }
+
/// Returns the properties that are not in [metaColumns].
/// : if the name of the property is [included] the property is always part of
/// the result.
}
this.name = name;
}
+
+ /// Does things when the instance is inititialized.
+ ///
+ /// Must be called after the constructor.
void onInitialized() {
var newFields = <WidgetMetaData>[];
var toDelete = <WidgetMetaData>[];
final firstNameController = TextEditingController();
final emailController = TextEditingController();
final birthdayController = TextEditingController();
- final activeController = TextEditingController();
final weightController = TextEditingController();
final incomeController = TextEditingController();
CreateBenchmarkCustom();
lastNameController.text = _fieldData.lastName;
firstNameController.text = _fieldData.firstName;
emailController.text = _fieldData.email;
- birthdayController.text = asString(_fieldData.birthday);
- activeController.text = asString(_fieldData.active);
+ birthdayController.text = asString(_fieldData.birthday, dateOnly: true);
weightController.text = asString(_fieldData.weight);
incomeController.text = asString(_fieldData.income);
final formItems = <FormItem>[
controller: birthdayController,
decoration: InputDecoration(labelText: i18n.tr('Birthday')),
validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.birthday = fromString(value ?? '', dataType: DataType.date)
+ onSaved: (value) => _fieldData.birthday = jsonToObject(value ?? '', dataType: DataType.date)
),
weight: 6),
FormItem(
- TextFormField(
- controller: activeController,
- decoration: InputDecoration(labelText: i18n.tr('Active')),
- validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.active = fromString(value ?? '', dataType: DataType.bool)
- ),
+ Row(children: [
+ Switch(
+ value: _fieldData.active,
+ onChanged: (bool value) {
+ //_fieldData.active = value;
+ setState(() => _fieldData.active = value);
+ },
+ ),
+ Expanded(
+ child: Text(i18n.tr('Active')),
+ ),
+ ]),
weight: 6),
FormItem(
TextFormField(
controller: weightController,
decoration: InputDecoration(labelText: i18n.tr('Weight')),
- onSaved: (value) => _fieldData.weight = fromString(value ?? '', dataType: DataType.float)
+ onSaved: (value) => _fieldData.weight = jsonToObject(value ?? '', dataType: DataType.float)
),
weight: 6),
FormItem(
TextFormField(
controller: incomeController,
decoration: InputDecoration(labelText: i18n.tr('Income')),
- onSaved: (value) => _fieldData.income = fromString(value ?? '', dataType: DataType.currency)
+ onSaved: (value) => _fieldData.income = jsonToObject(value ?? '', dataType: DataType.currency)
),
weight: 6),
FormItem(
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
lastNameController.dispose();
firstNameController.dispose();
emailController.dispose();
birthdayController.dispose();
- activeController.dispose();
weightController.dispose();
incomeController.dispose();
super.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String lastName = '';
String firstName = '';
String email = '';
map[':lastName'] = lastName;
map[':firstName'] = firstName;
map[':email'] = email;
- map[':birthday'] = asString(birthday);
- map[':active'] = asString(active);
- map[':weight'] = asString(weight);
- map[':income'] = asString(income);
+ map[':birthday'] = asString(birthday, dbFormat: true, dateOnly: true);
+ map[':active'] = asString(active, dbFormat: true);
+ map[':weight'] = asString(weight, dbFormat: true);
+ map[':income'] = asString(income, dbFormat: true);
map[':createdBy'] = GlobalData.loginUserName;
}
}
@override
_CreateBenchmarkPageState createState() {
final rc = _CreateBenchmarkPageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), BenchmarkMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ BenchmarkMeta.instance.pageByName('create')!,
+ BenchmarkMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
final firstNameController = TextEditingController();
final emailController = TextEditingController();
final birthdayController = TextEditingController();
- final activeController = TextEditingController();
final weightController = TextEditingController();
final incomeController = TextEditingController();
DeleteBenchmarkCustom(this.primaryKey);
@override
Widget build(BuildContext context) {
final padding = GlobalThemeData.padding;
- attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': 'Benchmarks', 'sql': 'byId', ':id': primaryKey});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Benchmarks',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
lastNameController.text = _fieldData.lastName;
firstNameController.text = _fieldData.firstName;
emailController.text = _fieldData.email;
- birthdayController.text = asString(_fieldData.birthday);
- activeController.text = asString(_fieldData.active);
+ birthdayController.text = asString(_fieldData.birthday, dateOnly: true);
weightController.text = asString(_fieldData.weight);
incomeController.text = asString(_fieldData.income);
final formItems = <FormItem>[
controller: birthdayController,
decoration: InputDecoration(labelText: i18n.tr('Birthday')),
validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.birthday = fromString(value ?? '', dataType: DataType.date)
+ onSaved: (value) => _fieldData.birthday = jsonToObject(value ?? '', dataType: DataType.date)
),
weight: 6),
FormItem(
- TextFormField(
- controller: activeController,
- decoration: InputDecoration(labelText: i18n.tr('Active')),
- validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.active = fromString(value ?? '', dataType: DataType.bool)
- ),
+ Row(children: [
+ Switch(
+ value: _fieldData.active,
+ onChanged: (bool value) {
+ //_fieldData.active = value;
+ setState(() => _fieldData.active = value);
+ },
+ ),
+ Expanded(
+ child: Text(i18n.tr('Active')),
+ ),
+ ]),
weight: 6),
FormItem(
TextFormField(
controller: weightController,
decoration: InputDecoration(labelText: i18n.tr('Weight')),
- onSaved: (value) => _fieldData.weight = fromString(value ?? '', dataType: DataType.float)
+ onSaved: (value) => _fieldData.weight = jsonToObject(value ?? '', dataType: DataType.float)
),
weight: 6),
FormItem(
TextFormField(
controller: incomeController,
decoration: InputDecoration(labelText: i18n.tr('Income')),
- onSaved: (value) => _fieldData.income = fromString(value ?? '', dataType: DataType.currency)
+ onSaved: (value) => _fieldData.income = jsonToObject(value ?? '', dataType: DataType.currency)
),
weight: 6),
FormItem(
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
lastNameController.dispose();
firstNameController.dispose();
emailController.dispose();
birthdayController.dispose();
- activeController.dispose();
weightController.dispose();
incomeController.dispose();
super.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String lastName = '';
String firstName = '';
String email = '';
lastName = map['benchmark_lastname'];
firstName = map['benchmark_firstname'];
email = map['benchmark_email'];
- birthday = map['benchmark_birthday'];
- active = map['benchmark_active'];
- weight = map['benchmark_weight'];
- income = map['benchmark_income'];
+ birthday = jsonToObject(map['benchmark_birthday'], dataType: DataType.date);
+ active = jsonToObject(map['benchmark_active'], dataType: DataType.bool);
+ weight = jsonToObject(map['benchmark_weight'], dataType: DataType.float);
+ income = jsonToObject(map['benchmark_income'], dataType: DataType.currency);
}
}
@override
_DeleteBenchmarkPageState createState() {
final rc = _DeleteBenchmarkPageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), BenchmarkMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ BenchmarkMeta.instance.pageByName('delete')!,
+ BenchmarkMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
final firstNameController = TextEditingController();
final emailController = TextEditingController();
final birthdayController = TextEditingController();
- final activeController = TextEditingController();
final weightController = TextEditingController();
final incomeController = TextEditingController();
EditBenchmarkCustom(this.primaryKey);
@override
Widget build(BuildContext context) {
final padding = GlobalThemeData.padding;
- attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': 'Benchmarks', 'sql': 'byId', ':id': primaryKey});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Benchmarks',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
lastNameController.text = _fieldData.lastName;
firstNameController.text = _fieldData.firstName;
emailController.text = _fieldData.email;
- birthdayController.text = asString(_fieldData.birthday);
- activeController.text = asString(_fieldData.active);
+ birthdayController.text = asString(_fieldData.birthday, dateOnly: true);
weightController.text = asString(_fieldData.weight);
incomeController.text = asString(_fieldData.income);
final formItems = <FormItem>[
controller: birthdayController,
decoration: InputDecoration(labelText: i18n.tr('Birthday')),
validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.birthday = fromString(value ?? '', dataType: DataType.date)
+ onSaved: (value) => _fieldData.birthday = jsonToObject(value ?? '', dataType: DataType.date)
),
weight: 6),
FormItem(
- TextFormField(
- controller: activeController,
- decoration: InputDecoration(labelText: i18n.tr('Active')),
- validator: (input) => notEmpty(input),
- onSaved: (value) => _fieldData.active = fromString(value ?? '', dataType: DataType.bool)
- ),
+ Row(children: [
+ Switch(
+ value: _fieldData.active,
+ onChanged: (bool value) {
+ //_fieldData.active = value;
+ setState(() => _fieldData.active = value);
+ },
+ ),
+ Expanded(
+ child: Text(i18n.tr('Active')),
+ ),
+ ]),
weight: 6),
FormItem(
TextFormField(
controller: weightController,
decoration: InputDecoration(labelText: i18n.tr('Weight')),
- onSaved: (value) => _fieldData.weight = fromString(value ?? '', dataType: DataType.float)
+ onSaved: (value) => _fieldData.weight = jsonToObject(value ?? '', dataType: DataType.float)
),
weight: 6),
FormItem(
TextFormField(
controller: incomeController,
decoration: InputDecoration(labelText: i18n.tr('Income')),
- onSaved: (value) => _fieldData.income = fromString(value ?? '', dataType: DataType.currency)
+ onSaved: (value) => _fieldData.income = jsonToObject(value ?? '', dataType: DataType.currency)
),
weight: 6),
FormItem(
_fieldData.toMap(parameters);
globalData.restPersistence!
.store(what: 'store', map: parameters)
- .then((answer) {});
+ .then((answer) {
+ _fieldData.isFromBackend = false;
+ attendedPage!.pageStates.dbDataState.clear();
+ setState(() => 1);
+ });
}
void verifyAndStore() {
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
lastNameController.dispose();
firstNameController.dispose();
emailController.dispose();
birthdayController.dispose();
- activeController.dispose();
weightController.dispose();
incomeController.dispose();
super.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String lastName = '';
String firstName = '';
String email = '';
lastName = map['benchmark_lastname'];
firstName = map['benchmark_firstname'];
email = map['benchmark_email'];
- birthday = map['benchmark_birthday'];
- active = map['benchmark_active'];
- weight = map['benchmark_weight'];
- income = map['benchmark_income'];
+ birthday = jsonToObject(map['benchmark_birthday'], dataType: DataType.date);
+ active = jsonToObject(map['benchmark_active'], dataType: DataType.bool);
+ weight = jsonToObject(map['benchmark_weight'], dataType: DataType.float);
+ income = jsonToObject(map['benchmark_income'], dataType: DataType.currency);
}
void toMap(Map<String, dynamic> map) {
map[':lastName'] = lastName;
map[':firstName'] = firstName;
map[':email'] = email;
- map[':birthday'] = asString(birthday);
- map[':active'] = asString(active);
- map[':weight'] = asString(weight);
- map[':income'] = asString(income);
+ map[':birthday'] = asString(birthday, dbFormat: true, dateOnly: true);
+ map[':active'] = asString(active, dbFormat: true);
+ map[':weight'] = asString(weight, dbFormat: true);
+ map[':income'] = asString(income, dbFormat: true);
map[':changedBy'] = GlobalData.loginUserName;
}
}
@override
_EditBenchmarkPageState createState() {
final rc = _EditBenchmarkPageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), BenchmarkMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ BenchmarkMeta.instance.pageByName('edit')!,
+ BenchmarkMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
// It will never overridden by the meta_tool.
import 'package:flutter/material.dart';
+import '../../base/defines.dart';
import '../../base/helper.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/widget_form.dart';
+import '../../persistence/persistence.dart';
+import '../../common/benchmark_actions.dart';
import 'list_benchmark_page.dart';
final i18n = I18N();
class ListBenchmarkCustom extends State<ListBenchmarkPage> {
final globalData = GlobalData();
+ late Future<DbData> _futureDbData;
AttendedPage? attendedPage;
final _fieldData = _FieldData();
final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: 'CreateBenchmark');
final textController = TextEditingController();
+ final countController = TextEditingController();
+ final resultController = TextEditingController();
ListBenchmarkCustom();
@override
Widget build(BuildContext context) {
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Overview benchmarks')),
+ drawer: globalData.drawerBuilder(context),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ globalData.navigate(context, '/Benchmarks/create');
+ },
+ child: const Icon(Icons.add),
+ ),
+ body: SafeArea(
+ child: FutureBuilder<DbData>(
+ future: _futureDbData,
+ builder: (context, snapshot) {
+ Widget rc;
+ if (snapshot.connectionState != ConnectionState.done) {
+ rc = const CircularProgressIndicator();
+ } else {
+ if (snapshot.hasData) {
+ final rows = attendedPage!.getRows(dbData: snapshot.data!,
+ columnList: 'benchmark_id;benchmark_lastname;benchmark_firstname;benchmark_birthday;benchmark_active;benchmark_income',
+ onDone: () => setState(() => 1),
+ routeEdit: '/Benchmarks/edit',
+ context: context);
+ rc = buildFrame(rows: rows);
+ } else if (snapshot.hasError) {
+ rc = Text('Backend problem: ${snapshot.error}');
+ } else {
+ rc = const CircularProgressIndicator();
+ }
+ }
+ return rc;
+ },
+ )),
+ );
+ return rc;
+ }
+
+ Widget buildFrame({required JsonList rows}){
final padding = GlobalThemeData.padding;
final formItems = <FormItem>[
FormItem(
weight: 12,
gapAbove: padding),
];
+ final belowForm = Padding(
+ padding: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ child: Column(children: [
+ SizedBox(
+ height: padding,
+ ),
+ Row(children: [
+ Expanded(
+ child: TextFormField(
+ controller: countController,
+ decoration: InputDecoration(labelText: i18n.tr('Count')),
+ onSaved: (value) => _fieldData.count = value ?? '',
+ validator: (input) => isNat(input),
+ )),
+ SizedBox(width: padding),
+ Expanded(
+ child: ElevatedButton(
+ onPressed: () => createNew(),
+ child: Text(i18n.tr('Create new elements')))),
+ SizedBox(width: padding),
+ Expanded(
+ child: ElevatedButton(
+ onPressed: () => readItems(),
+ child: Text(i18n.tr('Read existing elements')))),
+ ]),
+ SizedBox(height: padding),
+ Row(children: [
+ Expanded(
+ child: TextFormField(
+ controller: resultController,
+ decoration: InputDecoration(labelText: i18n.tr('Message')),
+ readOnly: true,
+ ))
+ ])
+ ]));
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: 'benchmark_id;benchmark_lastname;benchmark_firstname;benchmark_birthday',
- what: 'query',
- parameters: {
- 'module': 'Benchmarks',
- 'sql': 'list',
- 'offset': '0',
- 'size': '10',
- ':text': asPattern(_fieldData.text),
- },
- onDone: () => setState(() => 1),
- routeEdit: '/Benchmarks/edit',
- context: context);
+ child: Column(children: [
+ 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))),
+ belowForm,
+ ]));
final table = DataTable(
columns: <DataColumn>[
DataColumn(
DataColumn(
label: Text(i18n.tr('Birthday')),
),
+ DataColumn(
+ label: Text(i18n.tr('Active')),
+ ),
+ DataColumn(
+ label: Text(i18n.tr('Income')),
+ ),
],
- rows: rows,
+ rows: rows as List<DataRow>,
);
- final frameWidget = Column(children: [
+ Widget? tabBar =
+ attendedPage!.buildChipBar(onTap: (offset) => setState(() => offset));
+ final frameWidget = ListView(children: [
form,
+ if (tabBar != null) tabBar,
SizedBox(height: padding),
SizedBox(width: double.infinity, child: table),
]);
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Overview benchmarks')),
- drawer: globalData.drawerBuilder(context),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- globalData.navigate(context, '/Benchmarks/create');
- },
- child: const Icon(Icons.add),
- //backgroundColor: Colors.green,
- ),
- body: SafeArea(child: frameWidget));
- return rc;
+ return frameWidget;
}
@override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _futureDbData = globalData.restPersistence!.query(what: 'query', data: {
+ 'module': 'Benchmarks',
+ 'sql': 'list',
+ 'offset': _fieldData.theOffset,
+ 'size': _fieldData.thePageSize,
+ ':text': asPattern(_fieldData.text),
+ });
+ }
+
+ void createNew() {
+ final benchMark = BenchmarkActions(globalData);
+ benchMark
+ .createMultiple(int.tryParse(countController.text) ?? 1)
+ .then((message) => setState(() => resultController.text = message));
+ }
+
+@override
void dispose() {
- helperDummyUsage();
+ helperDummyUsage(DataType.string);
globalWidgetDummyUsage();
textController.dispose();
+ countController.dispose();
+ resultController.dispose();
super.dispose();
}
super.initState();
}
+ void readItems() {
+ final benchMark = BenchmarkActions(globalData);
+ // resultController.text = benchMark.createMultiple(
+ // int.tryParse(_fieldData.count) ?? 1);
+ setState(() => 1);
+ }
+
void search() {
attendedPage!.pageStates.dbDataState.clear();
if (_formKey.currentState!.validate()) {
}
class _FieldData {
+ int thePageSize = 10;
+ int theOffset = 0;
String text = '';
+ String count = '';
}
@override
_ListBenchmarkPageState createState() {
final rc = _ListBenchmarkPageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), BenchmarkMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ BenchmarkMeta.instance.pageByName('list')!,
+ BenchmarkMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
nameController.dispose();
super.dispose();
}
}
class _FieldData {
+ bool isFromBackend = false;
String name = '';
@override
_CreateRolePageState createState() {
final rc = _CreateRolePageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ RoleMeta.instance.pageByName('create')!,
+ RoleMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
@override
Widget build(BuildContext context) {
final padding = GlobalThemeData.padding;
- attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': 'Roles', 'sql': 'byId', ':id': primaryKey});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Roles',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
nameController.text = _fieldData.name;
final formItems = <FormItem>[
FormItem(
_fieldData.toMap(parameters);
globalData.restPersistence!
.store(what: 'store', map: parameters)
- .then((answer) {});
+ .then((answer) {
+ _fieldData.isFromBackend = false;
+ attendedPage!.pageStates.dbDataState.clear();
+ setState(() => 1);
+ });
}
void verifyAndStore() {
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
nameController.dispose();
super.dispose();
}
}
class _FieldData {
+ bool isFromBackend = false;
String name = '';
void fromMap(Map<String, dynamic> map) {
@override
_EditRolePageState createState() {
final rc = _EditRolePageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ RoleMeta.instance.pageByName('edit')!,
+ RoleMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
// It will never overridden by the meta_tool.
import 'package:flutter/material.dart';
+import '../../base/defines.dart';
import '../../base/helper.dart';
import '../../base/i18n.dart';
import '../../services/global_widget.dart';
import '../../setting/global_data.dart';
import '../../widget/attended_page.dart';
import '../../widget/widget_form.dart';
+import '../../persistence/persistence.dart';
import 'list_role_page.dart';
final i18n = I18N();
class ListRoleCustom extends State<ListRolePage> {
final globalData = GlobalData();
+ late Future<DbData> _futureDbData;
AttendedPage? attendedPage;
final _fieldData = _FieldData();
final GlobalKey<FormState> _formKey =
ListRoleCustom();
@override
Widget build(BuildContext context) {
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Overview roles')),
+ drawer: globalData.drawerBuilder(context),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ globalData.navigate(context, '/Roles/create');
+ },
+ child: const Icon(Icons.add),
+ ),
+ body: SafeArea(
+ child: FutureBuilder<DbData>(
+ future: _futureDbData,
+ builder: (context, snapshot) {
+ Widget rc;
+ if (snapshot.connectionState != ConnectionState.done) {
+ rc = const CircularProgressIndicator();
+ } else {
+ if (snapshot.hasData) {
+ final rows = attendedPage!.getRows(dbData: snapshot.data!,
+ columnList: 'role_id;role_name',
+ onDone: () => setState(() => 1),
+ routeEdit: '/Roles/edit',
+ context: context);
+ rc = buildFrame(rows: rows);
+ } else if (snapshot.hasError) {
+ rc = Text('Backend problem: ${snapshot.error}');
+ } else {
+ rc = const CircularProgressIndicator();
+ }
+ }
+ return rc;
+ },
+ )),
+ );
+ return rc;
+ }
+
+ Widget buildFrame({required JsonList rows}){
final padding = GlobalThemeData.padding;
final formItems = <FormItem>[
FormItem(
color: GlobalThemeData.formBackgroundColor,
elevation: GlobalThemeData.formElevation,
margin:
- EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ 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: 'role_id;role_name',
- what: 'query',
- parameters: {
- 'module': 'Roles',
- 'sql': 'list',
- 'offset': '0',
- 'size': '10',
- ':text': _fieldData.text,
- },
- onDone: () => setState(() => 1),
- routeEdit: '/Roles/edit',
- context: context);
final table = DataTable(
columns: <DataColumn>[
DataColumn(
label: Text(i18n.tr('d;Name')),
),
],
- rows: rows,
+ rows: rows as List<DataRow>,
);
- final frameWidget = Column(children: [
+ Widget? tabBar =
+ attendedPage!.buildChipBar(onTap: (offset) => setState(() => offset));
+ final frameWidget = ListView(children: [
form,
+ if (tabBar != null) tabBar,
SizedBox(height: padding),
SizedBox(width: double.infinity, child: table),
]);
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Overview roles')),
- drawer: globalData.drawerBuilder(context),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- globalData.navigate(context, '/Roles/create');
- },
- child: const Icon(Icons.add),
- //backgroundColor: Colors.green,
- ),
- body: SafeArea(child: frameWidget));
- return rc;
+ return frameWidget;
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _futureDbData = globalData.restPersistence!.query(what: 'query', data: {
+ 'module': 'Roles',
+ 'sql': 'list',
+ 'offset': _fieldData.theOffset,
+ 'size': _fieldData.thePageSize,
+ ':text': _fieldData.text,
+ });
}
@override
void dispose() {
- helperDummyUsage();
+ helperDummyUsage(DataType.string);
globalWidgetDummyUsage();
textController.dispose();
super.dispose();
}
class _FieldData {
+ int thePageSize = 10;
+ int theOffset = 0;
String text = '';
}
@override
_ListRolePageState createState() {
final rc = _ListRolePageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ RoleMeta.instance.pageByName('list')!,
+ RoleMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
TextFormField(
controller: positionController,
decoration: InputDecoration(labelText: i18n.tr('Position')),
- onSaved: (value) => _fieldData.position = fromString(value ?? '', dataType: DataType.int)
+ onSaved: (value) => _fieldData.position = jsonToObject(value ?? '', dataType: DataType.int)
),
weight: 6),
FormItem(
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
scopeController.dispose();
nameController.dispose();
valueController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String scope = '';
String name = '';
String value = '';
map[':scope'] = scope;
map[':name'] = name;
map[':value'] = value;
- map[':position'] = asString(position);
+ map[':position'] = asString(position, dbFormat: true);
map[':createdBy'] = GlobalData.loginUserName;
}
}
@override
_CreateStructurePageState createState() {
final rc = _CreateStructurePageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ StructureMeta.instance.pageByName('create')!,
+ StructureMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
@override
Widget build(BuildContext context) {
final padding = GlobalThemeData.padding;
- attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': 'Structures', 'sql': 'byId', ':id': primaryKey});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Structures',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
scopeController.text = _fieldData.scope;
nameController.text = _fieldData.name;
valueController.text = _fieldData.value;
TextFormField(
controller: positionController,
decoration: InputDecoration(labelText: i18n.tr('Position')),
- onSaved: (value) => _fieldData.position = fromString(value ?? '', dataType: DataType.int)
+ onSaved: (value) => _fieldData.position = jsonToObject(value ?? '', dataType: DataType.int)
),
weight: 6),
FormItem(
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
scopeController.dispose();
nameController.dispose();
valueController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String scope = '';
String name = '';
String value = '';
scope = map['structure_scope'];
name = map['structure_name'];
value = map['structure_value'];
- position = map['structure_position'];
+ position = jsonToObject(map['structure_position'], dataType: DataType.int);
}
}
@override
_DeleteStructurePageState createState() {
final rc = _DeleteStructurePageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ StructureMeta.instance.pageByName('delete')!,
+ StructureMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
@override
Widget build(BuildContext context) {
final padding = GlobalThemeData.padding;
- attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': 'Structures', 'sql': 'byId', ':id': primaryKey});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Structures',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
scopeController.text = _fieldData.scope;
nameController.text = _fieldData.name;
valueController.text = _fieldData.value;
TextFormField(
controller: positionController,
decoration: InputDecoration(labelText: i18n.tr('Position')),
- onSaved: (value) => _fieldData.position = fromString(value ?? '', dataType: DataType.int)
+ onSaved: (value) => _fieldData.position = jsonToObject(value ?? '', dataType: DataType.int)
),
weight: 6),
FormItem(
_fieldData.toMap(parameters);
globalData.restPersistence!
.store(what: 'store', map: parameters)
- .then((answer) {});
+ .then((answer) {
+ _fieldData.isFromBackend = false;
+ attendedPage!.pageStates.dbDataState.clear();
+ setState(() => 1);
+ });
}
void verifyAndStore() {
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
scopeController.dispose();
nameController.dispose();
valueController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String scope = '';
String name = '';
String value = '';
scope = map['structure_scope'];
name = map['structure_name'];
value = map['structure_value'];
- position = map['structure_position'];
+ position = jsonToObject(map['structure_position'], dataType: DataType.int);
}
void toMap(Map<String, dynamic> map) {
map[':scope'] = scope;
map[':name'] = name;
map[':value'] = value;
- map[':position'] = asString(position);
+ map[':position'] = asString(position, dbFormat: true);
map[':changedBy'] = GlobalData.loginUserName;
}
}
@override
_EditStructurePageState createState() {
final rc = _EditStructurePageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ StructureMeta.instance.pageByName('edit')!,
+ StructureMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
// It will never overridden by the meta_tool.
import 'package:flutter/material.dart';
+import '../../base/defines.dart';
import '../../base/helper.dart';
import '../../base/i18n.dart';
import '../../services/global_widget.dart';
import '../../setting/global_data.dart';
import '../../widget/attended_page.dart';
import '../../widget/widget_form.dart';
+import '../../persistence/persistence.dart';
import 'list_structure_page.dart';
final i18n = I18N();
class ListStructureCustom extends State<ListStructurePage> {
final globalData = GlobalData();
+ late Future<DbData> _futureDbData;
AttendedPage? attendedPage;
final _fieldData = _FieldData();
final GlobalKey<FormState> _formKey =
ListStructureCustom();
@override
Widget build(BuildContext context) {
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Overview structures')),
+ drawer: globalData.drawerBuilder(context),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ globalData.navigate(context, '/Structures/create');
+ },
+ child: const Icon(Icons.add),
+ ),
+ body: SafeArea(
+ child: FutureBuilder<DbData>(
+ future: _futureDbData,
+ builder: (context, snapshot) {
+ Widget rc;
+ if (snapshot.connectionState != ConnectionState.done) {
+ rc = const CircularProgressIndicator();
+ } else {
+ if (snapshot.hasData) {
+ final rows = attendedPage!.getRows(dbData: snapshot.data!,
+ columnList: 'structure_id;structure_scope;structure_name;structure_value;structure_position',
+ onDone: () => setState(() => 1),
+ routeEdit: '/Structures/edit',
+ context: context);
+ rc = buildFrame(rows: rows);
+ } else if (snapshot.hasError) {
+ rc = Text('Backend problem: ${snapshot.error}');
+ } else {
+ rc = const CircularProgressIndicator();
+ }
+ }
+ return rc;
+ },
+ )),
+ );
+ return rc;
+ }
+
+ Widget buildFrame({required JsonList rows}){
final padding = GlobalThemeData.padding;
final formItems = <FormItem>[
FormItem(
color: GlobalThemeData.formBackgroundColor,
elevation: GlobalThemeData.formElevation,
margin:
- EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ 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: 'structure_id;structure_scope;structure_name;structure_value;structure_position',
- what: 'query',
- parameters: {
- 'module': 'Structures',
- 'sql': 'list',
- 'offset': '0',
- 'size': '10',
- ':text': _fieldData.text,
- },
- onDone: () => setState(() => 1),
- routeEdit: '/Structures/edit',
- context: context);
final table = DataTable(
columns: <DataColumn>[
DataColumn(
label: Text(i18n.tr('d;Scope;Name;Value;Position')),
),
],
- rows: rows,
+ rows: rows as List<DataRow>,
);
- final frameWidget = Column(children: [
+ Widget? tabBar =
+ attendedPage!.buildChipBar(onTap: (offset) => setState(() => offset));
+ final frameWidget = ListView(children: [
form,
+ if (tabBar != null) tabBar,
SizedBox(height: padding),
SizedBox(width: double.infinity, child: table),
]);
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Overview structures')),
- drawer: globalData.drawerBuilder(context),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- globalData.navigate(context, '/Structures/create');
- },
- child: const Icon(Icons.add),
- //backgroundColor: Colors.green,
- ),
- body: SafeArea(child: frameWidget));
- return rc;
+ return frameWidget;
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _futureDbData = globalData.restPersistence!.query(what: 'query', data: {
+ 'module': 'Structures',
+ 'sql': 'list',
+ 'offset': _fieldData.theOffset,
+ 'size': _fieldData.thePageSize,
+ ':text': _fieldData.text,
+ });
}
@override
void dispose() {
- helperDummyUsage();
+ helperDummyUsage(DataType.string);
globalWidgetDummyUsage();
textController.dispose();
super.dispose();
}
class _FieldData {
+ int thePageSize = 10;
+ int theOffset = 0;
String text = '';
}
@override
_ListStructurePageState createState() {
final rc = _ListStructurePageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), StructureMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ StructureMeta.instance.pageByName('list')!,
+ StructureMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
nameController.dispose();
displayNameController.dispose();
emailController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String name = '';
String displayName = '';
String email = '';
map[':name'] = name;
map[':displayName'] = displayName;
map[':email'] = email;
- map[':role'] = asString(role);
+ map[':role'] = asString(role, dbFormat: true);
map[':createdBy'] = GlobalData.loginUserName;
}
}
@override
_CreateUserPageState createState() {
final rc = _CreateUserPageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ UserMeta.instance.pageByName('create')!,
+ UserMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
attendedPage: attendedPage!, onDone: () => setState(() => 1));
final itemsRoles = 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});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Users',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
nameController.text = _fieldData.name;
displayNameController.text = _fieldData.displayName;
emailController.text = _fieldData.email;
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
nameController.dispose();
displayNameController.dispose();
emailController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String name = '';
String displayName = '';
String email = '';
name = map['user_name'];
displayName = map['user_displayname'];
email = map['user_email'];
- role = map['user_role'];
+ role = jsonToObject(map['user_role'], dataType: DataType.reference);
}
}
@override
_DeleteUserPageState createState() {
final rc = _DeleteUserPageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ UserMeta.instance.pageByName('delete')!,
+ UserMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
attendedPage: attendedPage!, onDone: () => setState(() => 1));
final itemsRoles = 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});
+ if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': 'Users',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
nameController.text = _fieldData.name;
displayNameController.text = _fieldData.displayName;
emailController.text = _fieldData.email;
_fieldData.toMap(parameters);
globalData.restPersistence!
.store(what: 'store', map: parameters)
- .then((answer) {});
+ .then((answer) {
+ _fieldData.isFromBackend = false;
+ attendedPage!.pageStates.dbDataState.clear();
+ setState(() => 1);
+ });
}
void verifyAndStore() {
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
nameController.dispose();
displayNameController.dispose();
emailController.dispose();
}
class _FieldData {
+ bool isFromBackend = false;
String name = '';
String displayName = '';
String email = '';
name = map['user_name'];
displayName = map['user_displayname'];
email = map['user_email'];
- role = map['user_role'];
+ role = jsonToObject(map['user_role'], dataType: DataType.reference);
}
void toMap(Map<String, dynamic> map) {
map[':name'] = name;
map[':displayName'] = displayName;
map[':email'] = email;
- map[':role'] = asString(role);
+ map[':role'] = asString(role, dbFormat: true);
map[':changedBy'] = GlobalData.loginUserName;
}
}
@override
_EditUserPageState createState() {
final rc = _EditUserPageState(this.primaryKey);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ UserMeta.instance.pageByName('edit')!,
+ UserMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
// It will never overridden by the meta_tool.
import 'package:flutter/material.dart';
+import '../../base/defines.dart';
import '../../base/helper.dart';
import '../../base/i18n.dart';
import '../../services/global_widget.dart';
import '../../setting/global_data.dart';
import '../../widget/attended_page.dart';
import '../../widget/widget_form.dart';
+import '../../persistence/persistence.dart';
import 'list_user_page.dart';
final i18n = I18N();
class ListUserCustom extends State<ListUserPage> {
final globalData = GlobalData();
+ late Future<DbData> _futureDbData;
AttendedPage? attendedPage;
final _fieldData = _FieldData();
final GlobalKey<FormState> _formKey =
ListUserCustom();
@override
Widget build(BuildContext context) {
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
+ drawer: globalData.drawerBuilder(context),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ globalData.navigate(context, '/Users/create');
+ },
+ child: const Icon(Icons.add),
+ ),
+ body: SafeArea(
+ child: FutureBuilder<DbData>(
+ future: _futureDbData,
+ builder: (context, snapshot) {
+ Widget rc;
+ if (snapshot.connectionState != ConnectionState.done) {
+ rc = const CircularProgressIndicator();
+ } else {
+ if (snapshot.hasData) {
+ final rows = attendedPage!.getRows(dbData: snapshot.data!,
+ columnList: 'user_id;user_name;user_displayname;user_email;role',
+ onDone: () => setState(() => 1),
+ routeEdit: '/Users/edit',
+ context: context);
+ rc = buildFrame(rows: rows);
+ } else if (snapshot.hasError) {
+ rc = Text('Backend problem: ${snapshot.error}');
+ } else {
+ rc = const CircularProgressIndicator();
+ }
+ }
+ return rc;
+ },
+ )),
+ );
+ return rc;
+ }
+
+ Widget buildFrame({required JsonList rows}){
final padding = GlobalThemeData.padding;
comboRolesFromBackend(
attendedPage: attendedPage!, onDone: () => setState(() => 1));
color: GlobalThemeData.formBackgroundColor,
elevation: GlobalThemeData.formElevation,
margin:
- EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ 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_name;user_displayname;user_email;role',
- what: 'query',
- parameters: {
- 'module': 'Users',
- 'sql': 'list',
- 'offset': '0',
- 'size': '10',
- ':text': asPattern(_fieldData.text),
- ':role': _fieldData.role.toString(),
- },
- onDone: () => setState(() => 1),
- routeEdit: '/Users/edit',
- context: context);
final table = DataTable(
columns: <DataColumn>[
DataColumn(
label: Text(i18n.tr('Role')),
),
],
- rows: rows,
+ rows: rows as List<DataRow>,
);
- final frameWidget = Column(children: [
+ Widget? tabBar =
+ attendedPage!.buildChipBar(onTap: (offset) => setState(() => offset));
+ final frameWidget = ListView(children: [
form,
+ if (tabBar != null) tabBar,
SizedBox(height: padding),
SizedBox(width: double.infinity, child: table),
]);
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
- drawer: globalData.drawerBuilder(context),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- globalData.navigate(context, '/Users/create');
- },
- child: const Icon(Icons.add),
- //backgroundColor: Colors.green,
- ),
- body: SafeArea(child: frameWidget));
- return rc;
+ return frameWidget;
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _futureDbData = globalData.restPersistence!.query(what: 'query', data: {
+ 'module': 'Users',
+ 'sql': 'list',
+ 'offset': _fieldData.theOffset,
+ 'size': _fieldData.thePageSize,
+ ':text': asPattern(_fieldData.text),
+ ':role': _fieldData.role.toString(),
+ });
}
@override
void dispose() {
- helperDummyUsage();
+ helperDummyUsage(DataType.string);
globalWidgetDummyUsage();
textController.dispose();
super.dispose();
}
class _FieldData {
+ int thePageSize = 10;
+ int theOffset = 0;
String text = '';
int role = 0;
}
@override
_ListUserPageState createState() {
final rc = _ListUserPageState();
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ UserMeta.instance.pageByName('list')!,
+ UserMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
} else {
final content = await file.readAsString();
final data = convert.jsonDecode(content);
+ final pageSize = data['pageSize'] ?? 10;
+ final offset = data['offset'] ?? 0;
if (data is Map) {
rc = DbData.single(data as JsonMap);
} else if (data is List) {
- rc = DbData.list(data, -1);
+ rc = DbData.list(data, -1, offset, pageSize);
} else if (data is String) {
rc = DbData.message(data);
} else {
final JsonList? recordList;
final int? count;
final String? message;
- DbData(this.singleRecord, this.recordList, this.count, this.message);
- DbData.single(JsonMap record) : this(record, null, null, null);
- DbData.list(JsonList records, int count) : this(null, records, count, null);
- DbData.message(String message) : this(null, null, null, message);
+ final int pageSize;
+ final int offset;
+ DbData(this.singleRecord, this.recordList, this.count, this.message,
+ this.offset, this.pageSize);
+ DbData.single(JsonMap record) : this(record, null, null, null, 0, 0);
+ DbData.list(JsonList records, int count, int offset, int pageSize)
+ : this(null, records, count, null, offset, pageSize);
+ DbData.message(String message) : this(null, null, null, message, 0, 0);
}
abstract class Persistence {
} else if (answer.startsWith('{')) {
rc = DbData.single(convert.jsonDecode(answer));
} else if (answer.startsWith('[')) {
- rc = DbData.list(convert.jsonDecode(answer), -1);
+ rc = DbData.list(convert.jsonDecode(answer), -1, data?['offset'] ?? 0,
+ data?['pageSize'] ?? 0);
} else if (answer.startsWith('#')) {
int ix = answer.indexOf('[');
if (ix >= 2 && ix < 8) {
final count = int.tryParse(answer.substring(1, ix));
- final data = convert.jsonDecode(answer.substring(ix));
- rc = DbData.list(data as JsonList, count ?? -1);
+ final data2 = convert.jsonDecode(answer.substring(ix));
+ rc = DbData.list(data2 as JsonList, count ?? -1, data?['offset'] ?? 0,
+ data?['pageSize'] ?? 0);
} else {
rc = DbData.message('query(): unexpected answer prefix: '
'${answer.substring(0, min<int>(answer.length, 8))}');
case 'settings_applications_outlined':
rc = Icons.settings_applications_outlined;
break;
+ case 'analytics_outlined':
+ rc = Icons.analytics_outlined;
+ break;
default:
logger.error('MenuConverter.iconByName(): unknown icon $name');
break;
final logger = globalData.logger;
final collection = PageManager();
return <MenuItem>[
+ MenuItem(
+ 'Benchmarks',
+ () => collection.newPageByRoute('/Benchmarks/list'),
+ converter.iconByName('analytics_outlined', logger)),
MenuItem('Users', () => collection.newPageByRoute('/Users/list'),
converter.iconByName('people_outlined', logger)),
MenuItem('Protokoll', () => collection.newPageByRoute('/log'),
import 'package:flutter/material.dart';
import 'package:synchronized/synchronized.dart';
+import '../../base/helper.dart';
import '../../base/i18n.dart';
import '../../persistence/persistence.dart';
import '../../setting/global_data.dart';
/// states directly in this class. Store it in [PageStates].
class AttendedPage {
final ModuleMetaData moduleMetaData;
+ final PageMetaData pageMetaData;
final GlobalData globalData;
final StatefulWidget statefulWidget;
final State<StatefulWidget> state;
final PageStates pageStates;
AttendedPage(this.statefulWidget, this.state, this.globalData,
- this.moduleMetaData, this.pageStates);
+ this.pageMetaData, this.moduleMetaData, this.pageStates);
/// Returns the list of attended widgets of this page.
List<AttendedWidget> attendedWidgets() {
case DisplayType.text:
rc.add(TextAttended(item, this));
break;
+ case DisplayType.switchWidget:
+ rc.add(SwitchAttended(item, this));
+ break;
case DisplayType.combobox:
rc.add(ComboboxAttended(item, this));
break;
return rc;
}
+ /// Creates a chip bar: a row of ChoiceChip instances (special buttons).
+ ///
+ /// That chips allow to change the page of the db result.
+ ///
+ /// [onTap]: a function which is called if another chip is selected.
+ Widget? buildChipBar({required Function(int offset) onTap}) {
+ final dbData = pageStates.dbDataState.dataOf('rows');
+ final pageSize = dbData?.pageSize ?? 10;
+ //pageSize = 10;
+ final totalCount = dbData?.count ?? pageSize;
+ //totalCount = 1;
+ final offset = dbData?.offset ?? 0;
+ // offset = 30;
+ final pageCount = (totalCount + pageSize - 1) ~/ pageSize;
+ final currentPage = 1 + offset ~/ pageSize;
+ int currentIndex;
+ var chipCount = pageCount >= 7 ? 7 : pageCount;
+ final inTop = currentPage < 7 - 1;
+ int pageNoOfIndex1;
+ bool inTail = pageCount > 7 && currentPage >= pageCount - 3;
+ if (pageCount <= 7 || inTop) {
+ pageNoOfIndex1 = 2;
+ currentIndex = currentPage - 1;
+ } else if (inTail) {
+ pageNoOfIndex1 = pageCount - 6 + 1;
+ currentIndex = 1 + currentPage - pageNoOfIndex1;
+ } else {
+ currentIndex = 3;
+ pageNoOfIndex1 = currentPage - 2;
+ }
+ final widgets = <Widget>[];
+ for (var ix = 0; ix < chipCount; ix++) {
+ int label;
+ IconData? icon;
+ if (ix == 0) {
+ icon = currentPage == 1
+ ? Icons.beenhere_outlined
+ : Icons.first_page_outlined;
+ } else if (ix == chipCount - 1) {
+ icon = currentPage == 1
+ ? Icons.beenhere_outlined
+ : Icons.last_page_outlined;
+ } else if (ix == currentIndex) {
+ icon = Icons.beenhere_outlined;
+ } else if (ix < currentIndex) {
+ icon = Icons.chevron_left_outlined;
+ } else {
+ icon = Icons.chevron_right_outlined;
+ }
+ if (ix == 0) {
+ label = 1;
+ } else if (ix == chipCount - 1) {
+ label = pageCount;
+ } else {
+ label = pageNoOfIndex1 + ix - 1;
+ }
+ final currentChip = ChoiceChip(
+ avatar: CircleAvatar(child: Icon(icon)),
+ label: Text(label.toString()),
+ selected: currentIndex == ix,
+ onSelected: (selected) {
+ if (selected) {
+ onTap((label - 1) * pageSize);
+ }
+ },
+ );
+ widgets.add(currentChip);
+ if (pageCount > 7 && (ix == 0 && !inTop || ix == 7 - 2 && !inTail)) {
+ widgets.add(Text(' ... '));
+ }
+ }
+ Widget? rc = Wrap(children: widgets);
+ return rc;
+ }
+
/// Returns a database record with a given [primaryKey].
+ ///
+ /// [what] specifies the backend service, normally 'query'.
+ ///
+ /// [parameters] is a map specifying the data request.
+ /// Example: {'module': 'Users', 'sql': 'record', ':id': 223 }
+ ///
+ /// [onDone]: a function called when the answer is arrived.
JsonMap? getRecord(
{required int primaryKey,
required String what,
///
/// [columnList]: a ';' delimited list of column names, e.g. 'user_id;user_name'
List<DataRow> getRows(
- {required String columnList,
- required String what,
- required Map<String, dynamic> parameters,
+ {required DbData dbData,
+ required String columnList,
required Function() onDone,
required String routeEdit,
required BuildContext context}) {
List<DataRow>? rc;
- String name = 'rows';
- 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.recordList != null) {
- rc = <DataRow>[];
- dbData.recordList!.forEach((record) {
- 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]}');
- }));
+ if (dbData != null && dbData.recordList != null) {
+ rc = <DataRow>[];
+ dbData.recordList!.forEach((record) {
+ final cells = <DataCell>[];
+ for (var column in columnList.split(';')) {
+ final primaryKey = moduleMetaData.primaryOf()?.columnName;
+ String value;
+ final property = moduleMetaData.propertyByColumnName(column);
+ if (property == null) {
+ value = record[column].toString();
+ } else {
+ value = dbValueToString(record[column], property.dataType);
}
- final rc3 = DataRow(cells: cells);
- rc!.add(rc3);
- });
- }
+ cells.add(DataCell(Text(value), onTap: () {
+ globalData.navigate(context, routeEdit + ';${record[primaryKey]}');
+ }));
+ }
+ final rc3 = DataRow(cells: cells);
+ rc!.add(rc3);
+ });
}
return rc ?? <DataRow>[];
}
import 'package:flutter/material.dart';
+import '../base/helper.dart';
import '../meta/module_meta_data.dart';
import '../widget/attended_page.dart';
PropertyMetaData propertyMetaData);
/// Manages a widget controlled by the meta data.
-abstract class AttendedWidget {
+abstract class AttendedWidget<T> {
+ T? value;
final AttendedPage attendedPage;
final PropertyMetaData propertyMetaData;
Widget widgetOf();
}
-class ComboboxAttended<T> extends FieldAttended {
- T? value;
+class ComboboxAttended<T> extends FieldAttended<T> {
MenuItemBuilder menuItemBuilder =
<T>(PropertyMetaData _) => <DropdownMenuItem<T>>[];
// MenuItemBuilder menuItemBuilder =
// ((PropertyMetaData _) => <DropdownMenuItem<T>>[]) as MenuItemBuilder;
ComboboxAttended(PropertyMetaData propertyMetaData, AttendedPage attendedPage)
: super(propertyMetaData, attendedPage);
+ @override
Widget widgetOf() {
final rc = DropdownButtonFormField<T>(
value: value,
}
}
-abstract class FieldAttended extends AttendedWidget {
+abstract class FieldAttended<T> extends AttendedWidget<T> {
var readonly = false;
var enabled = true;
FieldAttended(PropertyMetaData propertyMetaData, AttendedPage attendedPage)
/// Implements a single or multiline line text field controlled by the
/// meta data of a module.
-class TextAttended extends FieldAttended {
+class SwitchAttended extends FieldAttended<bool> {
+ SwitchAttended(PropertyMetaData propertyMetaData, AttendedPage attendedPage)
+ : super(propertyMetaData, attendedPage);
+
+ /// Returns a concrete widget representing the instance.
+ @override
+ Widget widgetOf() {
+ final rc = SwitchListTile(
+ title: Text(propertyMetaData.label),
+ value: value ?? false,
+ onChanged: (bool value) {
+ this.value = jsonToObject(value, dataType: propertyMetaData.dataType);
+ },
+ );
+ return rc;
+ }
+}
+
+/// Implements a single or multiline line text field controlled by the
+/// meta data of a module.
+class TextAttended<T> extends FieldAttended<T> {
final controller = TextEditingController();
int minLines = 1;
int maxLines = 1;
- String value = '';
TextAttended(PropertyMetaData propertyMetaData, AttendedPage attendedPage)
: super(propertyMetaData, attendedPage);
/// Returns a concrete widget representing the instance.
+ @override
Widget widgetOf() {
final rc = TextFormField(
controller: controller,
minLines: minLines,
maxLines: maxLines,
- onSaved: (value) => this.value = value ?? '',
+ onSaved: (value) => this.value =
+ fromString(value ?? '', dataType: propertyMetaData.dataType),
validator: (value) => attendedPage.validateText(value, this),
decoration: InputDecoration(labelText: propertyMetaData.label),
);
}
widgets.add(element.widget);
});
- rc = Column(children: widgets);
+ rc = ListView(children: widgets);
} else {
int position = 0;
final List<Widget> childrenColumn = [];
@override
_EditUserPageState createState() {
final rc = _EditUserPageState(THIS_PRIMARY);
- rc.attendedPage =
- AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates);
+ rc.attendedPage = AttendedPage(
+ this,
+ rc,
+ GlobalData(),
+ UserMeta.instance.pageByName('edit')!,
+ UserMeta.instance,
+ pageStates);
pageStates.attendedPage = rc.attendedPage;
return rc;
}
// It will never overridden by the meta_tool.
import 'package:flutter/material.dart';
+import '../../base/defines.dart';
import '../../base/helper.dart';
import '../../base/i18n.dart';
import '../../services/global_widget.dart';
import '../../setting/global_data.dart';
import '../../widget/attended_page.dart';
import '../../widget/widget_form.dart';
+import '../../persistence/persistence.dart';
import 'list_user_page.dart';
final i18n = I18N();
class ListUserCustom extends State<ListUserPage> {
final globalData = GlobalData();
+ late Future<DbData> _futureDbData;
AttendedPage? attendedPage;
final _fieldData = _FieldData();
final GlobalKey<FormState> _formKey =
#DEF_CONTROLLERS ListUserCustom();
@override
Widget build(BuildContext context) {
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
+ drawer: globalData.drawerBuilder(context),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ globalData.navigate(context, '/Users/create');
+ },
+ child: const Icon(Icons.add),
+ ),
+ body: SafeArea(
+ child: FutureBuilder<DbData>(
+ future: _futureDbData,
+ builder: (context, snapshot) {
+ Widget rc;
+ if (snapshot.connectionState != ConnectionState.done) {
+ rc = const CircularProgressIndicator();
+ } else {
+ if (snapshot.hasData) {
+ final rows = attendedPage!.getRows(dbData: snapshot.data!,
+ columnList: '#ROW_COLUMNS',
+ onDone: () => setState(() => 1),
+ route#EDIT1: '/Users/#EDIT2',
+ context: context);
+ rc = buildFrame(rows: rows);
+ } else if (snapshot.hasError) {
+ rc = Text('Backend problem: \${snapshot.error}');
+ } else {
+ rc = const CircularProgressIndicator();
+ }
+ }
+ return rc;
+ },
+ )),
+ );
+ return rc;
+ }
+
+ Widget buildFrame({required JsonList rows}){
final padding = GlobalThemeData.padding;
#INIT_COMBOS final formItems = <FormItem>[
#FORM_ITEMS FormItem(
color: GlobalThemeData.formBackgroundColor,
elevation: GlobalThemeData.formElevation,
margin:
- EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ 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: '#ROW_COLUMNS',
- what: 'query',
- parameters: {
- 'module': 'Users',
- 'sql': 'list',
- 'offset': '0',
- 'size': '10',
-#PARAM_DEF },
- onDone: () => setState(() => 1),
- route#EDIT1: '/Users/#EDIT2',
- context: context);
final table = DataTable(
columns: <DataColumn>[
#TABLE_HEADER ],
- rows: rows,
+ rows: rows as List<DataRow>,
);
- final frameWidget = Column(children: [
+ Widget? tabBar =
+ attendedPage!.buildChipBar(onTap: (offset) => setState(() => offset));
+ final frameWidget = ListView(children: [
form,
+ if (tabBar != null) tabBar,
SizedBox(height: padding),
SizedBox(width: double.infinity, child: table),
]);
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Overview users')),
- drawer: globalData.drawerBuilder(context),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- globalData.navigate(context, '/Users/create');
- },
- child: const Icon(Icons.add),
- //backgroundColor: Colors.green,
- ),
- body: SafeArea(child: frameWidget));
- return rc;
+ return frameWidget;
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _futureDbData = globalData.restPersistence!.query(what: 'query', data: {
+ 'module': 'Users',
+ 'sql': 'list',
+ 'offset': _fieldData.theOffset,
+ 'size': _fieldData.thePageSize,
+#PARAM_DEF });
}
@override
void dispose() {
- helperDummyUsage();
+ helperDummyUsage(DataType.string);
globalWidgetDummyUsage();
#DISPOSE_CONTROLLER super.dispose();
}
}
class _FieldData {
+ int thePageSize = 10;
+ int theOffset = 0;
#DEF_FIELDS}
''';
@override
void dispose() {
+ helperDummyUsage(DataType.int);
+ globalWidgetDummyUsage();
#DISP_CONTROLLER super.dispose();
}
}
class _FieldData {
+ bool isFromBackend = false;
#DEF_FIELD
#FROM_MAP#TO_MAP}
''';
}
}
''';
- static final templateStorageDoneEdit = ' setState(() => 1);';
+ static final templateStorageDoneEdit = '''
+
+ _fieldData.isFromBackend = false;
+ attendedPage!.pageStates.dbDataState.clear();
+ setState(() => 1);
+ ''';
static final templateStorageDoneDelete =
"\n globalData.navigate(context, '/#MODULE/list');\n ";
static final templateFromMap = ''' void fromMap(Map<String, dynamic> map) {
(field as PropertyMetaData).displayType == DisplayType.text) {
var value = '_fieldData.${field.name}';
if (field.dataType != DataType.string) {
- value = 'asString($value)';
+ value = field.dataType == DataType.date ? 'asString($value, dateOnly: true)'
+ : 'asString($value)';
}
buffer.writeln(' ${field.name}Controller.text = $value;');
}
final name = field.name;
String rc = '';
switch (field.displayType) {
+ case DisplayType.switchWidget:
+ rc = '''^Row(children: [
+^ Switch(
+^ value: _fieldData.#NAME,
+^ onChanged: (bool value) {
+^ //_fieldData.#NAME = value;
+^ setState(() => _fieldData.#NAME = value);
+^ },
+^ ),
+^ Expanded(
+^ child: Text(i18n.tr('#LABEL')),
+^ ),
+^])'''
+ .replaceAll('#NAME', field.name)
+ .replaceFirst('#LABEL', field.label)
+ .replaceAll('^', indent);
+ break;
case DisplayType.text:
final validators = <String>[];
if (field.hasOption(':notnull:')) {
final value = field.dataType == DataType.string
? "value ?? ''"
: (field.dataType == DataType.date
- ? "fromString(value ?? '', dataType: DataType.date)"
- : "fromString(value ?? '', dataType: ${field.dataType})");
+ ? "jsonToObject(value ?? '', dataType: DataType.date)"
+ : "jsonToObject(value ?? '', dataType: ${field.dataType})");
rc = '''^TextFormField(
#CONTR^ decoration: InputDecoration(labelText: i18n.tr('${field.label}')),
#VALIDATOR^ onSaved: (value) => _fieldData.$name = $value
StringBuffer(' void fromMap(Map<String, dynamic> map) {\n');
for (var field in page.fields) {
if (field is PropertyMetaData && !field.hasOption(':primary:')) {
- buffer.writeln(" ${field.name} = map['${field.columnName}'];");
+ var value = "map['${field.columnName}']";
+ if (field.dataType != DataType.string) {
+ value = 'jsonToObject($value, dataType: ${field.dataType})';
+ }
+ buffer.writeln(" ${field.name} = $value;");
}
}
buffer.writeln(' }');
String buildLoadRecord(PageMetaData page) {
String? rc;
if (page.pageType == PageType.edit || page.pageType == PageType.delete) {
- rc = ''' attendedPage?.loadRecord(
- name: 'record',
- reload: () => setState(() => 1),
- onDone: (record) => _fieldData.fromMap(record),
- parameters: {'module': '${page.module.moduleName}', 'sql': 'byId', ':id': primaryKey});
+ rc = ''' if (!_fieldData.isFromBackend) {
+ attendedPage?.loadRecord(
+ name: 'record',
+ reload: () => setState(() => 1),
+ onDone: (record) {
+ _fieldData.fromMap(record);
+ _fieldData.isFromBackend = true;
+ },
+ parameters: {
+ 'module': '${page.module.moduleName}',
+ 'sql': 'byId',
+ ':id': primaryKey
+ });
+ }
''';
}
return rc ?? '';
if (field is PropertyMetaData && field.hasOption(':pattern')) {
value = 'asPattern($value)';
}
- buffer.writeln(" ':$name': $value,");
+ buffer.writeln(" ':$name': $value,");
}
return buffer.toString();
}
continue;
}
final name = field.name;
- final value =
- field.dataType == DataType.string ? name : 'asString($name)';
+ final value = field.dataType == DataType.string
+ ? name
+ : (field.dataType == DataType.date
+ ? 'asString($name, dbFormat: true, dateOnly: true)'
+ : 'asString($name, dbFormat: true)');
buffer.writeln(" map[':$name'] = $value;");
}
buffer.writeln(" map[':#'] = GlobalData.loginUserName;".replaceFirst(
sqlType = 'update';
storageDone = templateStorageDoneEdit;
hasPrimary = true;
- storageDone = '';
break;
case PageType.delete:
setPrimary = " parameters[':id'] = primaryKey;\n";
//final baseDir = init(logger);
//final targetDir = join(baseDir, nodeTarget);
//fileSync.ensureDirectory(targetDir);
+ group('secondsAsTime', () {
+ test('days', () {
+ expect(secondsAsTime(3 * 86400 + 5 * 3600 + 14 * 60 + 32), '3:05:14:32');
+ });
+ test('hours', () {
+ expect(secondsAsTime(23 * 3600 + 9 * 60 + 4), '23:09:04');
+ });
+ test('minutes', () {
+ expect(secondsAsTime(3 * 60 + 19), '03:19');
+ });
+ test('seconds', () {
+ expect(secondsAsTime(59), '59');
+ });
+ test('days-millisecnds', () {
+ expect(
+ secondsAsTime((3 * 86400 + 5 * 3600 + 14 * 60 + 32) * 1000 + 49,
+ isMilliSeconds: true),
+ '3:05:14:32.049');
+ });
+ });
+ group('jsonToObject', () {
+ test('bool', () {
+ expect(jsonToObject('T', dataType: DataType.bool, defaultValue: false),
+ isTrue);
+ expect(jsonToObject('F', dataType: DataType.bool, defaultValue: true),
+ isFalse);
+ expect(jsonToObject('x', dataType: DataType.bool, defaultValue: true),
+ isTrue);
+ expect(jsonToObject('y', dataType: DataType.bool, defaultValue: false),
+ isFalse);
+ });
+ test('datetime', () {
+ expect(
+ jsonToObject('2021-8-3 2:4',
+ dataType: DataType.datetime, defaultValue: false),
+ DateTime(2021, 8, 3, 2, 4));
+ });
+ test('date', () {
+ expect(
+ jsonToObject('2021-10-3',
+ dataType: DataType.date, defaultValue: false),
+ DateTime(2021, 10, 3));
+ });
+ test('int-nat-reference', () {
+ expect(jsonToObject(332211, dataType: DataType.nat), 332211);
+ expect(jsonToObject('332209', dataType: DataType.int), 332209);
+ expect(jsonToObject('220944', dataType: DataType.reference), 220944);
+ expect(jsonToObject(2209443, dataType: DataType.reference), 2209443);
+ expect(jsonToObject('xy', dataType: DataType.reference), isNull);
+ });
+ });
group('fromString', () {
test('bool', () {
- expect(fromString('T', dataType: DataType.bool, defaultValue: false), isTrue);
- expect(fromString('F', dataType: DataType.bool, defaultValue: true), isFalse);
- expect(fromString(i18n.tr('Yes'), dataType: DataType.bool, defaultValue: false), isTrue);
- expect(fromString(i18n.tr('No'), dataType: DataType.bool, defaultValue: true), isFalse);
- expect(fromString('', dataType: DataType.bool, defaultValue: false), isFalse);
- expect(fromString('', dataType: DataType.bool, defaultValue: true), isTrue);
- });
- test('int', ()
- {
- expect(fromString('123', dataType: DataType.int, defaultValue: -1),
- 123);
- expect(fromString('-1234', dataType: DataType.int, defaultValue: -1),
- -1234);
- expect(fromString('', dataType: DataType.int, defaultValue: -1),
- -1);
- expect(fromString('a15', dataType: DataType.int, defaultValue: -1),
- -1);
+ expect(fromString('T', dataType: DataType.bool, defaultValue: false),
+ isTrue);
+ expect(fromString('F', dataType: DataType.bool, defaultValue: true),
+ isFalse);
+ expect(
+ fromString(i18n.tr('Yes'),
+ dataType: DataType.bool, defaultValue: false),
+ isTrue);
+ expect(
+ fromString(i18n.tr('No'),
+ dataType: DataType.bool, defaultValue: true),
+ isFalse);
+ expect(fromString('', dataType: DataType.bool, defaultValue: false),
+ isFalse);
+ expect(
+ fromString('', dataType: DataType.bool, defaultValue: true), isTrue);
});
- test('nat', ()
- {
- expect(fromString('123', dataType: DataType.nat, defaultValue: -1),
- 123);
- expect(fromString('-1234', dataType: DataType.nat, defaultValue: -1),
- -1);
- expect(fromString('', dataType: DataType.nat, defaultValue: -1),
- -1);
- expect(fromString('a15', dataType: DataType.nat, defaultValue: -1),
- -1);
+ test('int', () {
+ expect(fromString('123', dataType: DataType.int, defaultValue: -1), 123);
+ expect(
+ fromString('-1234', dataType: DataType.int, defaultValue: -1), -1234);
+ expect(fromString('', dataType: DataType.int, defaultValue: -1), -1);
+ expect(fromString('a15', dataType: DataType.int, defaultValue: -1), -1);
});
- test('reference', ()
- {
+ test('nat', () {
+ expect(fromString('123', dataType: DataType.nat, defaultValue: -1), 123);
+ expect(fromString('-1234', dataType: DataType.nat, defaultValue: -1), -1);
+ expect(fromString('', dataType: DataType.nat, defaultValue: -1), -1);
+ expect(fromString('a15', dataType: DataType.nat, defaultValue: -1), -1);
+ });
+ test('reference', () {
expect(fromString('123', dataType: DataType.reference, defaultValue: -1),
123);
- expect(fromString('-1234', dataType: DataType.reference, defaultValue: -1),
+ expect(
+ fromString('-1234', dataType: DataType.reference, defaultValue: -1),
-1234);
- expect(fromString('', dataType: DataType.reference, defaultValue: -1),
- -1);
+ expect(
+ fromString('', dataType: DataType.reference, defaultValue: -1), -1);
expect(fromString('a15', dataType: DataType.reference, defaultValue: -1),
-1);
});
- test('float', ()
- {
+ test('float', () {
expect(fromString('123.76', dataType: DataType.float, defaultValue: -1),
123.76);
expect(fromString('-4123.76', dataType: DataType.float, defaultValue: -1),
-4123.76);
expect(fromString('', dataType: DataType.float, defaultValue: -99999999),
-99999999);
- expect(fromString('Nothing', dataType: DataType.float, defaultValue: -99999999),
+ expect(
+ fromString('Nothing',
+ dataType: DataType.float, defaultValue: -99999999),
-99999999);
});
- test('currency', ()
- {
- expect(fromString('123.76', dataType: DataType.currency, defaultValue: -1),
+ test('currency', () {
+ expect(
+ fromString('123.76', dataType: DataType.currency, defaultValue: -1),
123.76);
- expect(fromString('-4123.76', dataType: DataType.currency, defaultValue: -1),
+ expect(
+ fromString('-4123.76', dataType: DataType.currency, defaultValue: -1),
-4123.76);
- expect(fromString('', dataType: DataType.currency, defaultValue: -99999999),
+ expect(
+ fromString('', dataType: DataType.currency, defaultValue: -99999999),
-99999999);
- expect(fromString('Nothing', dataType: DataType.currency, defaultValue: -99999999),
+ expect(
+ fromString('Nothing',
+ dataType: DataType.currency, defaultValue: -99999999),
-99999999);
});
- test('date', ()
- {
+ test('date', () {
expect(fromString('7.3.1987', dataType: DataType.date),
DateTime(1987, 3, 7));
expect(fromString('17.12.2035', dataType: DataType.date),
DateTime(2012, 6, 2));
expect(fromString('1923.10.31', dataType: DataType.date),
DateTime(1923, 10, 31));
- expect(fromString('17.12.21', dataType: DataType.date),
- isNull);
+ expect(fromString('17.12.21', dataType: DataType.date), isNull);
});
- test('datetime', ()
- {
+ test('datetime', () {
expect(fromString('17.12.2035', dataType: DataType.datetime),
DateTime(2035, 12, 17));
expect(fromString('7.3.1987', dataType: DataType.datetime),
DateTime(2012, 6, 2));
expect(fromString('1923.10.31', dataType: DataType.datetime),
DateTime(1923, 10, 31));
- expect(fromString('17.12.21', dataType: DataType.datetime),
- isNull);
+ expect(fromString('17.12.21', dataType: DataType.datetime), isNull);
expect(fromString('7.3.1987T2:44', dataType: DataType.datetime),
DateTime(1987, 3, 7, 2, 44));
expect(fromString('17.12.2035 13:59:22', dataType: DataType.datetime),
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:test/test.dart';
+
+import '../lib/base/validators.dart' as validators;
+import '../lib/base/i18n.dart';
+
+final i18n = I18N();
+
+void main() {
+ final logger = MemoryLogger(LEVEL_FINE);
+ I18N.internal(logger);
+ FileSync.initialize(logger);
+ group('isEmail', () {
+ test('standard', () {
+ expect(validators.isEmail('abc@example.com'), isNull);
+ expect(validators.isEmail(null), isNull);
+ });
+ test('wrong char', () {
+ expect(validators.isEmail('/abc@example.com'), 'Illegal character "/" in email address');
+ });
+ test('wrong syntax', () {
+ expect(validators.isEmail('abc@example'), 'Not an email address: abc@example');
+ });
+ });
+ group('isInt', () {
+ test('OK', () {
+ expect(validators.isInt('123456'), isNull);
+ expect(validators.isInt('-123456'), isNull);
+ expect(validators.isInt(null), isNull);
+ });
+ test('errors', () {
+ expect(validators.isInt('x'), 'Not an integer: x');
+ expect(validators.isInt('123.77'), 'Not an integer: 123.77');
+ });
+ });
+ group('isNat', () {
+ test('OK', () {
+ expect(validators.isNat('123456'), isNull);
+ expect(validators.isNat('0'), isNull);
+ expect(validators.isNat(null), isNull);
+ });
+ test('errors', () {
+ expect(validators.isNat('-123456'), 'Not negative integer expected, not: -123456');
+ expect(validators.isNat('x'), 'Not an integer: x');
+ expect(validators.isNat('123.77'), 'Not an integer: 123.77');
+ });
+ });
+ group('notEmpty', () {
+ test('OK', () {
+ expect(validators.notEmpty('Go'), isNull);
+ });
+ test('error', () {
+ expect(validators.notEmpty(''), 'Please fill in.');
+ expect(validators.notEmpty(null), 'Please fill in.');
+ });
+ });
+ group('validateMultiple', () {
+ test('OK', () {
+ expect(validators.validateMultiple('abc@example.com', [(x) => validators.notEmpty(x),
+ (x) => validators.isEmail(x)]), isNull);
+ });
+ test('Error', () {
+ expect(validators.validateMultiple('0x72z', [(x) => validators.notEmpty(x),
+ (x) => validators.isInt(x)]), 'Not an integer: 0x72z');
+ });
+ });
+}
sql2 = sql;
} else {
final ix = sql.lastIndexOf(';');
- sql2 = sql.substring(0, ix) +
- ' limit ${parameters["offset"]},${parameters["size"]};';
- final ix2 = sql.indexOf(' FROM ${module.toLowerCase()} t0 ');
- if (ix2 > 0)
- {
- final sqlCount =
- 'SELECT count(*) ' + sql.substring(ix2);
- final countParams = '?'
- .allMatches(sqlCount)
- .length;
- final positionalParameters2 = countParams ==
- positionalParameters.length
- ? positionalParameters
- : positionalParameters
- .skip(positionalParameters.length - countParams)
- .toList();
+ if (ix > 0) {
+ sql2 = sql.substring(0, ix) +
+ ' limit ${parameters["offset"]},${parameters["size"]};';
+ } else {
+ sql2 = sql + ' limit ${parameters["offset"]},${parameters["size"]};';
+ }
+ final fromPart = 'FROM ${module.toLowerCase()} t0';
+ final ix2 = sql.indexOf(fromPart);
+ if (ix2 > 0) {
+ final sqlCount = 'SELECT count(*) ' + sql.substring(ix2);
+ final countParams = '?'.allMatches(sqlCount).length;
+ final positionalParameters2 =
+ countParams == positionalParameters.length
+ ? positionalParameters
+ : positionalParameters
+ .skip(positionalParameters.length - countParams)
+ .toList();
prefix =
- '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}';
+ '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}';
}
}
final records = await db!.readAll(sql2, params: positionalParameters);