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 1 : final searchBox = GridRowDropdownMenuItem(
120 : enabled: false,
121 : value: null,
122 1 : child: Padding(
123 : padding: const EdgeInsets.symmetric(horizontal: 16.0),
124 1 : child: TextField(
125 : decoration: const InputDecoration(
126 : icon: Icon(Icons.search),
127 : hintText: 'Search',
128 : border: InputBorder.none,
129 : ),
130 1 : onChanged: (input) {
131 3 : for (final key in _keys.values) {
132 2 : key.currentState?.updateFilter(input);
133 : }
134 : },
135 : ),
136 : ),
137 : );
138 :
139 1 : final headerRow = GridRowDropdownMenuItem(
140 : enabled: false,
141 : value: null,
142 1 : child: HeaderRowWidget(
143 2 : fields: _grid!.fields,
144 1 : controller: _headerController,
145 : ),
146 : );
147 :
148 4 : final items = _grid!.rows.map((row) {
149 4 : final gridUri = widget.component.data.gridUri;
150 1 : final entityUri = EntityUri(
151 1 : user: gridUri.user,
152 1 : space: gridUri.space,
153 1 : grid: gridUri.grid,
154 1 : entity: row.id,
155 : );
156 1 : return GridRowDropdownMenuItem(
157 1 : value: GridRowDropdownDataItem(
158 : entityUri: entityUri,
159 5 : displayValue: row.entries.first.data.value.toString(),
160 : ),
161 1 : child: _RowMenuItem(
162 3 : key: _keys[row.id],
163 1 : grid: _grid!,
164 : row: row,
165 3 : controller: _controllers[row.id],
166 : ),
167 : );
168 1 : }).toList();
169 2 : return [searchBox, headerRow, ...items];
170 : }
171 : }
172 :
173 1 : List<Widget> _selectedItems(BuildContext context) {
174 1 : final localization = ApptiveGridLocalization.of(context)!;
175 1 : if (_error != null) {
176 1 : return [
177 : const Center(
178 : child: Text('ERROR'),
179 : )
180 : ];
181 1 : } else if (_grid == null) {
182 1 : return [
183 1 : Center(
184 2 : child: Text(localization.loadingGrid),
185 : )
186 : ];
187 : } else {
188 2 : final pleaseSelect = Text(localization.selectEntry);
189 1 : return [
190 1 : ...[pleaseSelect, pleaseSelect],
191 2 : ..._grid!.rows
192 1 : .map(
193 2 : (row) => Text(
194 5 : row.entries.first.data.value?.toString() ?? '',
195 : maxLines: 1,
196 : overflow: TextOverflow.ellipsis,
197 : ),
198 : )
199 1 : .toList()
200 : ];
201 : }
202 : }
203 : }
204 :
205 : class _RowMenuItem extends StatefulWidget {
206 1 : const _RowMenuItem({
207 : Key? key,
208 : required this.grid,
209 : required this.row,
210 : this.controller,
211 1 : }) : super(key: key);
212 :
213 : final Grid grid;
214 : final GridRow row;
215 : final ScrollController? controller;
216 :
217 1 : @override
218 1 : _RowMenuItemState createState() => _RowMenuItemState();
219 : }
220 :
221 : class _RowMenuItemState extends State<_RowMenuItem> {
222 : String? _filter;
223 :
224 1 : void updateFilter(String filter) {
225 2 : setState(() {
226 1 : _filter = filter;
227 : });
228 : }
229 :
230 1 : @override
231 : Widget build(BuildContext context) {
232 4 : if (!widget.row.matchesFilter(_filter)) {
233 : return const SizedBox();
234 : } else {
235 3 : final index = widget.grid.rows
236 4 : .where((row) => row.matchesFilter(_filter))
237 1 : .toList()
238 3 : .indexOf(widget.row);
239 1 : return GridRowWidget(
240 2 : row: widget.row,
241 2 : controller: widget.controller,
242 2 : color: index % 2 != 0
243 3 : ? Theme.of(context).hintColor.withOpacity(0.1)
244 : : null,
245 : );
246 : }
247 : }
248 : }
249 :
250 : extension _GridRowX on GridRow {
251 1 : bool matchesFilter(String? filter) {
252 1 : if (filter == null || filter.isEmpty) return true;
253 :
254 1 : return entries
255 1 : .where(
256 1 : (entry) =>
257 2 : entry.data.schemaValue
258 1 : ?.toString()
259 1 : .toLowerCase()
260 2 : .contains(filter.toLowerCase()) ??
261 : false,
262 : )
263 1 : .isNotEmpty;
264 : }
265 : }
|