• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

juancastillo0 / json_form / 10909757877

17 Sep 2024 07:29PM UTC coverage: 78.271% (+3.9%) from 74.365%
10909757877

Pull #6

github

web-flow
Merge 549f44dbe into 419da37b9
Pull Request #6: Form value in controller recursive

380 of 453 new or added lines in 27 files covered. (83.89%)

22 existing lines in 5 files now uncovered.

1639 of 2094 relevant lines covered (78.27%)

1.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.19
/lib/src/builder/array_schema_builder.dart
1
import 'package:flutter/material.dart';
2
import 'package:json_form/json_form.dart';
3
import 'package:json_form/src/builder/general_subtitle_widget.dart';
4
import 'package:json_form/src/builder/logic/widget_builder_logic.dart';
5
import 'package:json_form/src/builder/widget_builder.dart';
6
import 'package:json_form/src/fields/shared.dart';
7
import 'package:json_form/src/models/json_form_schema_style.dart';
8
import 'package:json_form/src/models/models.dart';
9

10
class ArraySchemaBuilder extends StatefulWidget {
11
  const ArraySchemaBuilder({
1✔
12
    super.key,
13
    required this.mainSchema,
14
    required this.schemaArray,
15
  });
16
  final Schema mainSchema;
17
  final SchemaArray schemaArray;
18

19
  @override
1✔
20
  State<ArraySchemaBuilder> createState() => _ArraySchemaBuilderState();
1✔
21
}
22

23
class _ArraySchemaBuilderState extends State<ArraySchemaBuilder>
24
    implements JsonFormField<List<Object?>> {
25
  late FormFieldState<List<Object?>> field;
26
  late final JsonFormValue formValue;
27
  SchemaArray get schemaArray => widget.schemaArray;
3✔
28
  int lastItemId = 1;
29
  bool showItems = true;
30

31
  @override
1✔
32
  String get idKey => formValue.idKey;
2✔
33

34
  String generateItemId() => (lastItemId++).toString();
4✔
35

36
  bool get isCheckboxes => schemaArray.uiSchema.widget == 'checkboxes';
5✔
37
  List<Object?>? _initialValue;
38

39
  @override
1✔
40
  void initState() {
41
    super.initState();
1✔
42
    formValue = JsonFormController.setField(context, schemaArray, this);
4✔
43
    formValue.value ??= [];
3✔
44
    _initialValue = formValue.value! as List;
3✔
45
    if (_initialValue!.isNotEmpty) {
2✔
46
      // update children
47
      value = _initialValue!;
2✔
48
    }
49
  }
50

51
  @override
1✔
52
  void dispose() {
53
    // TODO: clean up
54
    // if (schemaArray.formField == this) {
55
    //   schemaArray.formField = null;
56
    // }
57
    super.dispose();
1✔
58
  }
59

60
  @override
1✔
61
  Widget build(BuildContext context) {
62
    final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
63
    final uiConfig = widgetBuilderInherited.uiConfig;
1✔
64

65
    final widgetBuilder = FormField<List<Object?>>(
1✔
66
      validator: (_) {
1✔
67
        return uiConfig.localizedTexts
1✔
68
            .arrayPropertiesError(schemaArray.arrayProperties, value);
4✔
69
      },
70
      initialValue: _initialValue,
1✔
71
      builder: (field) {
1✔
72
        this.field = field;
1✔
73
        return Focus(
1✔
74
          focusNode: focusNode,
1✔
75
          autofocus: schemaArray.uiSchema.autofocus,
3✔
76
          child: Builder(
1✔
77
            builder: (context) {
1✔
78
              if (isCheckboxes) {
1✔
79
                final schema = schemaArray.itemsBaseSchema as SchemaProperty;
2✔
80
                final options =
81
                    schema.enumm ?? schema.numberProperties.options();
1✔
82
                int _index = 0;
83
                return Column(
1✔
84
                  crossAxisAlignment: CrossAxisAlignment.start,
85
                  children: [
1✔
86
                    GeneralSubtitle(
1✔
87
                      field: schemaArray,
1✔
88
                      mainSchema: widget.mainSchema,
2✔
89
                    ),
90
                    Wrap(
1✔
91
                      children: options.map((option) {
2✔
92
                        final index = _index++;
1✔
93
                        final title = schema.uiSchema.enumNames != null
2✔
94
                            ? schema.uiSchema.enumNames![index]
×
95
                            : option.toString();
1✔
96
                        return CheckboxListTile(
1✔
97
                          key: Key('JsonForm_item_${idKey}_$index'),
3✔
98
                          title: Text(
1✔
99
                            title,
100
                            style: uiConfig.fieldInput,
1✔
101
                          ),
102
                          value: field.value != null &&
1✔
103
                              field.value!.contains(option),
2✔
104
                          onChanged: (_) {
1✔
105
                            selectCheckbox(option);
1✔
106
                          },
107
                        );
108
                      }).toList(growable: false),
1✔
109
                    ),
110
                    if (field.hasError) CustomErrorText(text: field.errorText!),
1✔
111
                  ],
112
                );
113
              }
114

115
              int _index = 0;
116
              final items = formValue.children.map((item) {
4✔
117
                final index = _index++;
1✔
118
                final idKey = JsonFormKeyPath.appendId(this.idKey, item.id);
3✔
119

120
                return Column(
1✔
121
                  key: Key('JsonForm_item_$idKey'),
2✔
122
                  mainAxisSize: MainAxisSize.min,
123
                  crossAxisAlignment: CrossAxisAlignment.start,
124
                  children: [
1✔
125
                    Row(
1✔
126
                      mainAxisAlignment: MainAxisAlignment.end,
127
                      children: [
1✔
128
                        if (uiConfig.labelPosition == LabelPosition.table)
2✔
129
                          Text(
1✔
130
                            '${index + 1}.',
2✔
131
                            style: uiConfig.fieldLabel,
1✔
132
                          ),
133
                        const Spacer(),
1✔
134
                        const SizedBox(height: 5),
1✔
135
                        if (schemaArray.uiSchema.copyable)
3✔
136
                          uiConfig.copyItemWidget(
1✔
137
                            idKey,
138
                            () => _copyItem(index),
2✔
139
                          ),
140
                        if (schemaArray.uiSchema.removable)
3✔
141
                          uiConfig.removeItemWidget(
1✔
142
                            idKey,
143
                            () => _removeItem(index),
2✔
144
                          ),
145
                        if (schemaArray.uiSchema.orderable)
3✔
146
                          ReorderableDragStartListener(
1✔
147
                            index: index,
148
                            child: const Icon(Icons.drag_handle),
149
                          ),
150
                      ],
151
                    ),
152
                    Padding(
1✔
153
                      padding: const EdgeInsets.only(bottom: 5.0, left: 5.0),
154
                      child: WidgetBuilderInherited(
1✔
155
                        controller: widgetBuilderInherited.controller,
1✔
156
                        customPickerHandler:
157
                            widgetBuilderInherited.customPickerHandler,
1✔
158
                        customValidatorHandler:
159
                            widgetBuilderInherited.customValidatorHandler,
1✔
160
                        fileHandler: widgetBuilderInherited.fileHandler,
1✔
161
                        uiConfig: widgetBuilderInherited.uiConfig,
1✔
162
                        context: context,
163
                        child: FormFromSchemaBuilder(
1✔
164
                          mainSchema: widget.mainSchema,
2✔
165
                          formValue: item,
166
                        ),
167
                      ),
168
                    ),
169
                  ],
170
                );
171
              });
172

173
              return Column(
1✔
174
                crossAxisAlignment: CrossAxisAlignment.start,
175
                children: [
1✔
176
                  const SizedBox(width: double.infinity),
177
                  GeneralSubtitle(
1✔
178
                    field: schemaArray,
1✔
179
                    mainSchema: widget.mainSchema,
2✔
180
                    trailing: IconButton(
1✔
181
                      key: Key('JsonForm_showOrHideItems_$idKey'),
3✔
182
                      tooltip: showItems
1✔
183
                          ? uiConfig.localizedTexts.hideItems()
2✔
184
                          : uiConfig.localizedTexts.showItems(),
2✔
185
                      visualDensity: VisualDensity.compact,
186
                      onPressed: () {
1✔
187
                        setState(() {
2✔
188
                          showItems = !showItems;
2✔
189
                        });
190
                      },
191
                      icon: Row(
1✔
192
                        children: [
1✔
193
                          Text(
1✔
194
                            formValue.children.length.toString(),
4✔
195
                            style: uiConfig.subtitle,
1✔
196
                          ),
197
                          if (showItems)
1✔
198
                            const Icon(Icons.arrow_drop_up_outlined)
1✔
199
                          else
200
                            const Icon(Icons.arrow_drop_down_outlined),
1✔
201
                        ],
202
                      ),
203
                    ),
204
                  ),
205
                  if (!showItems)
1✔
206
                    const SizedBox()
1✔
207
                  else if (schemaArray.uiSchema.orderable)
3✔
208
                    ReorderableListView(
1✔
209
                      shrinkWrap: true,
210
                      buildDefaultDragHandles: false,
211
                      physics: const NeverScrollableScrollPhysics(),
212
                      onReorder: (oldIndex, newIndex) {
×
213
                        setState(() {
×
214
                          final toRemove =
215
                              newIndex > oldIndex ? oldIndex : oldIndex + 1;
×
NEW
216
                          final array = formValue.children;
×
NEW
217
                          formValue.children.insert(newIndex, array[oldIndex]);
×
NEW
218
                          array.removeAt(toRemove);
×
219
                        });
220
                      },
221
                      children: items.toList(growable: false),
1✔
222
                    )
223
                  else
224
                    ...items,
×
225
                  if (field.hasError) CustomErrorText(text: field.errorText!),
3✔
226
                ],
227
              );
228
            },
229
          ),
230
        );
231
      },
232
    );
233

234
    return FormSection(
1✔
235
      child: Column(
1✔
236
        children: [
1✔
237
          widgetBuilder,
238
          if (!schemaArray.isArrayMultipleFile() &&
2✔
239
              schemaArray.uiSchema.addable &&
3✔
240
              !isCheckboxes)
1✔
241
            Align(
1✔
242
              alignment: Alignment.centerRight,
243
              child: uiConfig.addItemWidget(formValue, _addItem),
3✔
244
            ),
245
        ],
246
      ),
247
    );
248
  }
249

250
  void _addItem() {
1✔
251
    setState(() {
2✔
252
      formValue.addArrayChildren(null, generateItemId());
3✔
253
    });
254
  }
255

256
  void _removeItem(int index) {
1✔
257
    setState(() {
2✔
258
      formValue.children.removeAt(index);
3✔
259

260
      /// cleans up the output data in the controller
261
      WidgetBuilderInherited.of(context)
2✔
262
          .controller
1✔
263
          .updateDataInPlace(idKey, (a) => a);
3✔
264
    });
265
  }
266

267
  void _copyItem(int index) {
1✔
268
    setState(() {
2✔
269
      final newValue = formValue.children[index].copyWith(
4✔
270
        id: generateItemId(),
1✔
271
        parent: formValue,
1✔
272
      );
273
      formValue.children.add(newValue);
3✔
274
    });
275
  }
276

277
  void selectCheckbox(Object? option) {
1✔
278
    setState(() {
2✔
279
      WidgetBuilderInherited.of(context).controller.updateDataInPlace(
4✔
280
        idKey,
1✔
281
        (a) {
1✔
282
          final valueList = (a as List?)?.toList() ?? [];
1✔
283
          final i = valueList.indexOf(option);
1✔
284
          if (i != -1) {
2✔
285
            valueList.removeAt(i);
1✔
286
          } else {
287
            valueList.add(option);
1✔
288
          }
289
          field.didChange(valueList);
2✔
290
          return valueList;
291
        },
292
      );
293
    });
294
  }
295

296
  @override
1✔
297
  List<Object?> get value =>
298
      isCheckboxes ? field.value! : formValue.toJson()! as List<Object?>;
5✔
299

300
  @override
301
  final focusNode = FocusNode();
302

303
  @override
1✔
304
  SchemaUiInfo get property => schemaArray;
1✔
305

306
  @override
1✔
307
  set value(List<Object?> newValue) {
308
    if (isCheckboxes) {
1✔
309
      field.didChange(newValue);
2✔
310
      formValue.value = newValue;
2✔
311
    } else {
312
      while (formValue.children.length != newValue.length) {
5✔
313
        if (formValue.children.length < newValue.length) {
5✔
314
          formValue.addArrayChildren(null, generateItemId());
3✔
315
        } else {
NEW
316
          formValue.children.removeLast();
×
317
        }
318
      }
319
      for (var i = 0; i < newValue.length; i++) {
2✔
320
        final item = formValue.children[i];
3✔
321
        if (item.field == null) {
1✔
322
          if (mounted) setState(() {});
3✔
323
          break;
324
        }
NEW
325
        item.field!.value = newValue[i];
×
326
      }
327
    }
328
  }
329
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc