+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bones/src/widget/page_controller_bones.dart';
+
+import '../helper/string_helper.dart';
+import 'model_list.dart';
+import 'page_controller_bones.dart';
+import 'view.dart';
+
+typedef Function OnEditTap(Map<String, dynamic> row, int index);
+
+abstract class TableCallbackController {
+ OnEditTap getOnEditTap(String customString,
+ TableCallbackController controller, Map<String, dynamic> row);
+}
+
+class ListForm {
+ /// Converts a string with [titles] into a list of widgets of type Text().
+ /// Format of [titles]: first character is the delimiter, e.g. ";Id;Name;"
+ /// This allows to use different delimiters when the title text
+ /// contains characters normally used as delimiters.
+ static List<Widget> stringsToTitles(List<String> titles) {
+ final rc = titles
+ .map((String title) =>
+ Text(title, style: TextStyle(fontWeight: FontWeight.bold)))
+ .toList();
+ return rc;
+ }
+
+ /// Returns a widget with a data table.
+ /// [titles] is used for the table header.
+ /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
+ /// [rows] is a list of rows normally delivered from a database query:
+ /// each row is a map with (key, value) pairs.
+ /// If [showEditItems] is true the edit icon is shown in the additional first column.
+ /// If [sortIndex] is not null the rows will be sorted by this index.
+ static Widget table({
+ @required List<Widget> titles,
+ @required List<String> columnNames,
+ @required PageControllerBones controller,
+ @required Iterable<dynamic> rows,
+ bool showEditIcon = false,
+ bool showDeleteIcon = false,
+ int sortIndex,
+ TableCallbackController tableCallbackController,
+ String customString,
+ }) {
+ if (titles.length != columnNames.length) {
+ controller.moduleModel.logger.error(
+ 'titles.length != columnNames.length: ${titles.length}/${columnNames.length}');
+ }
+ final titles2 = titles.map((item) => DataColumn(label: item)).toList();
+ if (showEditIcon) {
+ titles2.insert(0, DataColumn(label: SizedBox(width: 1)));
+ sortIndex = sortIndex == null ? null : sortIndex + 1;
+ }
+ if (showDeleteIcon) {
+ titles2.add(DataColumn(label: SizedBox(width: 1)));
+ }
+ final taskRightCallBack = controller.page.taskRightCallback;
+ Widget rc = Container(
+ margin: EdgeInsets.all(2),
+ child: DataTable(
+ showCheckboxColumn: true,
+ showBottomBorder: true,
+ sortColumnIndex: sortIndex,
+ columns: titles2,
+ rows: rows.map((row) {
+ final cells = <DataCell>[];
+ if (showEditIcon) {
+ final canEdit = taskRightCallBack == null
+ ? true
+ : taskRightCallBack(TaskRight.listEdit, row);
+ cells.add(DataCell(SizedBox(width: 1), showEditIcon: canEdit,
+ onTap: () {
+ if (canEdit) {
+ controller.onEditTap(customString, controller, row);
+ }
+ }));
+ }
+ for (var key in columnNames) {
+ cells.add(DataCell(
+ Text(StringHelper.asString(row[key], nullString: '')),
+ ));
+ }
+ if (showDeleteIcon) {
+ final canDelete = taskRightCallBack == null
+ ? true
+ : taskRightCallBack(TaskRight.listDelete, row);
+ cells.add(canDelete
+ ? DataCell(
+ Tooltip(
+ message: 'Endgültiges Löschen des Eintrags',
+ child: Icon(
+ Icons.delete_forever_outlined,
+ semanticLabel: 'Eintrag löschen',
+ )),
+ onTap: () => controller.onDeleteTap(
+ customString, controller, row))
+ : DataCell(Tooltip(
+ message: 'Löschen nicht erlaubt',
+ child: Icon(Icons.delete_rounded))));
+ }
+ return DataRow(cells: cells);
+ }).toList()));
+ return rc;
+ }
+
+ /// Returns a widget with a form containing some [filters] and a data table.
+ /// [titles] is used for the table header.
+ /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length
+ /// [rows] is a list of rows normally delivered from a database query:
+ /// each row is a map with (key, value) pairs.
+ /// If [showEditItems] is true the edit icon is shown in the first column.
+ /// If [errorMessage] is not null this message will be shown.
+ static Form listForm(
+ {@required Key key,
+ @required ModelList filters,
+ @required List<Widget> buttons,
+ @required List<Widget> titles,
+ @required List<String> columnNames,
+ @required Iterable<dynamic> rows,
+ bool showDeleteIcon = false,
+ bool showEditIcon = false,
+ String errorMessage,
+ PageControllerBones pageController,
+ @required BaseConfiguration configuration,
+ String customString}) {
+ final padding =
+ configuration.asFloat('form.card.padding', defaultValue: 16.0);
+
+ final view = View(pageController.moduleModel.logger);
+ final widgets = <Widget>[
+ ...view.modelsToWidgets(filters.models, pageController),
+ SizedBox(
+ height: configuration.asFloat('form.gap.field_button.height',
+ defaultValue: 16.0)),
+ ...buttons
+ ];
+ if (errorMessage != null) {
+ widgets.add(View().errorMessage(errorMessage));
+ }
+ widgets.add(table(
+ titles: titles,
+ columnNames: columnNames,
+ rows: rows,
+ showDeleteIcon: showDeleteIcon,
+ showEditIcon: showEditIcon,
+ controller: pageController,
+ customString: customString,
+ ));
+ return Form(
+ key: key,
+ child: Card(
+ margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
+ child: ListView(children: widgets)),
+ ));
+ }
+}
+
+/// Returns a widget with a form containing at least some input fields
+/// and a save/cancel button.
+/// a change page.
+/// [page] is the name of the page in the model.
+Form formDialog({
+ @required Key key,
+ @required PageControllerBones pageController,
+ @required BaseConfiguration configuration,
+ int primaryId,
+ Map initialRow,
+}) {
+ final padding =
+ configuration.asFloat('form.card.padding', defaultValue: 16.0);
+ pageController.buildModelList(initialRow);
+ final widgets = pageController.getWidgets();
+ final view = View();
+ final buttons = view.modelsToWidgets(
+ pageController.page.sections[0].buttonBar, pageController);
+ return Form(
+ key: key,
+ child: Card(
+ margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
+ child: ListView(
+ children: <Widget>[
+ ...widgets,
+ SizedBox(
+ height: configuration.asFloat(
+ 'form.gap.field_button.height',
+ defaultValue: 16.0)),
+ ButtonBar(
+ children: buttons,
+ ),
+ ],
+ ))),
+ );
+}