Line data Source code
1 : part of apptive_grid_form_widgets;
2 :
3 : /// FormComponent Widget to display a [CrossReferenceFormComponent]
4 : class CrossReferenceFormWidget extends StatefulWidget {
5 : /// Creates a [Checkbox] to display a boolean value contained in [component]
6 2 : const CrossReferenceFormWidget({
7 : Key? key,
8 : required this.component,
9 2 : }) : super(key: key);
10 :
11 : /// Component this Widget should reflect
12 : final CrossReferenceFormComponent component;
13 :
14 1 : @override
15 : _CrossReferenceFormWidgetState createState() =>
16 1 : _CrossReferenceFormWidgetState();
17 : }
18 :
19 : class _CrossReferenceFormWidgetState extends State<CrossReferenceFormWidget> {
20 : Grid? _grid;
21 : dynamic _error;
22 :
23 : final LinkedScrollControllerGroup _scrollControllerGroup =
24 : LinkedScrollControllerGroup();
25 : final _controllers = <String, ScrollController>{};
26 : ScrollController? _headerController;
27 : final _keys = <String, GlobalKey<_RowMenuItemState>>{};
28 :
29 1 : @override
30 : void didChangeDependencies() {
31 1 : super.didChangeDependencies();
32 2 : if (_grid == null && _error == null) {
33 1 : _loadGrid();
34 : }
35 : }
36 :
37 1 : @override
38 : void dispose() {
39 3 : for (final controller in _controllers.values) {
40 1 : controller.dispose();
41 : }
42 2 : _headerController?.dispose();
43 1 : _headerController = null;
44 1 : super.dispose();
45 : }
46 :
47 1 : void _loadGrid() {
48 2 : setState(() {
49 1 : _error = null;
50 1 : _grid = null;
51 : });
52 2 : ApptiveGrid.getClient(context, listen: false)
53 5 : .loadGrid(gridUri: widget.component.data.gridUri)
54 2 : .then((value) {
55 2 : for (final row in value.rows) {
56 3 : _controllers[row.id]?.dispose();
57 5 : _controllers[row.id] = _scrollControllerGroup.addAndGet();
58 :
59 4 : _keys[row.id] ??= GlobalKey();
60 : }
61 1 : _headerController?.dispose();
62 3 : _headerController = _scrollControllerGroup.addAndGet();
63 2 : setState(() {
64 1 : _error = null;
65 1 : _grid = value;
66 : });
67 2 : }).catchError((error) {
68 2 : setState(() {
69 1 : _error = error;
70 1 : _grid = null;
71 : });
72 : });
73 : }
74 :
75 1 : @override
76 : Widget build(BuildContext context) {
77 1 : return GridRowDropdownButtonFormField<GridRowDropdownDataItem?>(
78 : isExpanded: true,
79 1 : items: _items(),
80 1 : onChanged: (newValue) {
81 1 : if (newValue?.entityUri != null) {
82 2 : setState(() {
83 5 : widget.component.data.value = newValue?.displayValue;
84 5 : widget.component.data.entityUri = newValue?.entityUri;
85 : });
86 : }
87 : },
88 1 : validator: (value) {
89 3 : if (widget.component.required &&
90 2 : (value?.entityUri == null || value?.displayValue == null)) {
91 1 : return ApptiveGridLocalization.of(context)!
92 4 : .fieldIsRequired(widget.component.property);
93 : } else {
94 : return null;
95 : }
96 : },
97 1 : selectedItemBuilder: _selectedItems,
98 : autovalidateMode: AutovalidateMode.onUserInteraction,
99 4 : value: widget.component.data.entityUri != null &&
100 4 : widget.component.data.value != null
101 1 : ? GridRowDropdownDataItem(
102 4 : entityUri: widget.component.data.entityUri,
103 4 : displayValue: widget.component.data.value,
104 : )
105 : : null,
106 1 : decoration: InputDecoration(
107 4 : helperText: widget.component.options.description,
108 : helperMaxLines: 100,
109 7 : labelText: widget.component.options.label ?? widget.component.property,
110 2 : errorText: _error?.toString(),
111 : ),
112 : );
113 : }
114 :
115 1 : List<GridRowDropdownMenuItem<GridRowDropdownDataItem?>>? _items() {
116 2 : if (_error != null || _grid == null) {
117 : return null;
118 : } else {
119 2 : final localization = ApptiveGridLocalization.of(context)!;
120 1 : final searchBox = GridRowDropdownMenuItem(
121 : enabled: false,
122 : value: null,
123 1 : child: Padding(
124 : padding: const EdgeInsets.symmetric(horizontal: 16.0),
125 1 : child: TextField(
126 1 : decoration: InputDecoration(
127 : icon: const Icon(Icons.search),
128 1 : hintText: localization.crossRefSearch,
129 : border: InputBorder.none,
130 : ),
131 1 : onChanged: (input) {
132 3 : for (final key in _keys.values) {
133 2 : key.currentState?.updateFilter(input);
134 : }
135 : },
136 : ),
137 : ),
138 : );
139 :
140 1 : final headerRow = GridRowDropdownMenuItem(
141 : enabled: false,
142 : value: null,
143 1 : child: HeaderRowWidget(
144 2 : fields: _grid!.fields,
145 1 : controller: _headerController,
146 : ),
147 : );
148 :
149 4 : final items = _grid!.rows.map((row) {
150 4 : final gridUri = widget.component.data.gridUri;
151 1 : final entityUri = EntityUri(
152 1 : user: gridUri.user,
153 1 : space: gridUri.space,
154 1 : grid: gridUri.grid,
155 1 : entity: row.id,
156 : );
157 1 : return GridRowDropdownMenuItem(
158 1 : value: GridRowDropdownDataItem(
159 : entityUri: entityUri,
160 5 : displayValue: row.entries.first.data.value.toString(),
161 : ),
162 1 : child: _RowMenuItem(
163 3 : key: _keys[row.id],
164 1 : grid: _grid!,
165 : row: row,
166 3 : controller: _controllers[row.id],
167 : ),
168 : );
169 1 : }).toList();
170 2 : return [searchBox, headerRow, ...items];
171 : }
172 : }
173 :
174 1 : List<Widget> _selectedItems(BuildContext context) {
175 1 : final localization = ApptiveGridLocalization.of(context)!;
176 1 : if (_error != null) {
177 1 : return [
178 : const Center(
179 : child: Text('ERROR'),
180 : )
181 : ];
182 1 : } else if (_grid == null) {
183 1 : return [
184 1 : Center(
185 2 : child: Text(localization.loadingGrid),
186 : )
187 : ];
188 : } else {
189 2 : final pleaseSelect = Text(localization.selectEntry);
190 1 : return [
191 1 : ...[pleaseSelect, pleaseSelect],
192 2 : ..._grid!.rows
193 1 : .map(
194 2 : (row) => Text(
195 5 : row.entries.first.data.value?.toString() ?? '',
196 : maxLines: 1,
197 : overflow: TextOverflow.ellipsis,
198 : ),
199 : )
200 1 : .toList()
201 : ];
202 : }
203 : }
204 : }
205 :
206 : class _RowMenuItem extends StatefulWidget {
207 1 : const _RowMenuItem({
208 : Key? key,
209 : required this.grid,
210 : required this.row,
211 : this.controller,
212 1 : }) : super(key: key);
213 :
214 : final Grid grid;
215 : final GridRow row;
216 : final ScrollController? controller;
217 :
218 1 : @override
219 1 : _RowMenuItemState createState() => _RowMenuItemState();
220 : }
221 :
222 : class _RowMenuItemState extends State<_RowMenuItem> {
223 : String? _filter;
224 :
225 1 : void updateFilter(String filter) {
226 2 : setState(() {
227 1 : _filter = filter;
228 : });
229 : }
230 :
231 1 : @override
232 : Widget build(BuildContext context) {
233 4 : if (!widget.row.matchesFilter(_filter)) {
234 : return const SizedBox();
235 : } else {
236 3 : final index = widget.grid.rows
237 4 : .where((row) => row.matchesFilter(_filter))
238 1 : .toList()
239 3 : .indexOf(widget.row);
240 1 : return GridRowWidget(
241 2 : row: widget.row,
242 2 : controller: widget.controller,
243 2 : color: index % 2 != 0
244 3 : ? Theme.of(context).hintColor.withOpacity(0.1)
245 : : null,
246 : );
247 : }
248 : }
249 : }
250 :
251 : extension _GridRowX on GridRow {
252 1 : bool matchesFilter(String? filter) {
253 1 : if (filter == null || filter.isEmpty) return true;
254 :
255 1 : return entries
256 1 : .where(
257 1 : (entry) =>
258 2 : entry.data.schemaValue
259 1 : ?.toString()
260 1 : .toLowerCase()
261 2 : .contains(filter.toLowerCase()) ??
262 : false,
263 : )
264 1 : .isNotEmpty;
265 : }
266 : }
|