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

juancastillo0 / json_form / 10986779271

23 Sep 2024 02:41AM UTC coverage: 78.638% (+4.3%) from 74.365%
10986779271

Pull #6

github

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

479 of 585 new or added lines in 27 files covered. (81.88%)

26 existing lines in 6 files now uncovered.

1594 of 2027 relevant lines covered (78.64%)

1.27 hits per line

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

89.66
/lib/src/builder/widget_builder.dart
1
import 'dart:convert';
2
import 'dart:developer';
3

4
import 'package:cross_file/cross_file.dart';
5
import 'package:flutter/material.dart';
6
import 'package:json_form/src/builder/array_schema_builder.dart';
7
import 'package:json_form/src/builder/logic/widget_builder_logic.dart';
8
import 'package:json_form/src/builder/object_schema_builder.dart';
9
import 'package:json_form/src/builder/property_schema_builder.dart';
10
import 'package:json_form/src/models/json_form_schema_style.dart';
11
import 'package:json_form/src/models/models.dart';
12

13
/// Returns a map of paths keys to functions that return the files selected by the user.
14
typedef FileHandler = Future<List<XFile>?> Function() Function(
15
  JsonFormField<Object?> field,
16
);
17

18
/// Returns a map of paths keys to functions that receives a map values to strings
19
/// and returns the selected value, or null if none was selected.
20
typedef CustomPickerHandler
21
    = Future<Object?> Function(Map<Object?, String> options)? Function(
22
  JsonFormField<Object?> field,
23
);
24

25
typedef CustomValidatorHandler = String? Function(dynamic newValue)? Function(
26
  JsonFormField<Object?> field,
27
);
28

29
/// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
30
/// You may use [controller] for added functionalities.
31
class JsonForm extends StatefulWidget {
32
  /// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
33
  /// You may use [controller] for added functionalities.
34
  const JsonForm({
1✔
35
    super.key,
36
    required this.jsonSchema,
37
    required this.onFormDataSaved,
38
    this.controller,
39
    this.uiSchema,
40
    this.uiConfig,
41
    this.fieldValidator,
42
    this.fieldDropdownPicker,
43
    this.fieldFilePicker,
44
  });
45

46
  /// The JSON schema to build the form from
47
  final String jsonSchema;
48

49
  /// Callback function to be called when the form is submitted
50
  final void Function(Object) onFormDataSaved;
51

52
  /// The controller to be used for the form.
53
  /// It can be used to set the initial data, get/set the form data,
54
  /// subscribe to changes, etc.
55
  final JsonFormController? controller;
56

57
  /// The UI schema with input configurations for each field
58
  final String? uiSchema;
59

60
  /// The UI configuration with global styles, texts, builders and other Flutter configurations
61
  final JsonFormUiConfig? uiConfig;
62

63
  /// A custom validator that receives the field and returns an error message
64
  /// or null if the field is valid. If the error is an empty String, it will
65
  /// be considered an error in validation, but no error message will be shown.
66
  final CustomValidatorHandler? fieldValidator;
67

68
  /// A custom picker that receives the field and returns a Future with the selected value.
69
  /// If no Future is returned, the default selector will be shown.
70
  /// If the Future completes with a null value, the default field will be considered as not selected.
71
  final CustomPickerHandler? fieldDropdownPicker;
72

73
  /// A file picker that receives the field and returns a Function that selects the files.
74
  final FileHandler? fieldFilePicker;
75

76
  @override
1✔
77
  State<JsonForm> createState() => _JsonFormState();
1✔
78
}
79

80
class _JsonFormState extends State<JsonForm> {
81
  late JsonFormController controller;
82
  late Schema mainSchema;
83
  GlobalKey<FormState> get _formKey => controller.formKey!;
3✔
84

85
  @override
1✔
86
  void initState() {
87
    super.initState();
1✔
88
    initMainSchema(controllerChanged: true, schemaChanged: true);
1✔
89
  }
90

91
  void initMainSchema({
1✔
92
    required bool controllerChanged,
93
    required bool schemaChanged,
94
  }) {
95
    if (controllerChanged) {
96
      controller = widget.controller ?? JsonFormController(initialData: {});
5✔
97
      controller.formKey ??= GlobalKey<FormState>();
3✔
98
      if (controller.mainSchema != null &&
2✔
99
          (!schemaChanged || widget.jsonSchema.isEmpty)) {
3✔
100
        return;
101
      }
102
    }
103
    final mainSchema = Schema.fromJson(
1✔
104
      json.decode(widget.jsonSchema) as Map<String, Object?>,
3✔
105
      id: kGenesisIdKey,
106
    );
107
    final map = widget.uiSchema != null
2✔
108
        ? json.decode(widget.uiSchema!) as Map<String, Object?>
3✔
109
        : null;
110
    if (map != null) {
111
      mainSchema.setUiSchema(map, fromOptions: false);
1✔
112
    }
113
    this.mainSchema = mainSchema;
1✔
114
  }
115

116
  @override
1✔
117
  void didUpdateWidget(covariant JsonForm oldWidget) {
118
    super.didUpdateWidget(oldWidget);
1✔
119
    final controllerChanged = oldWidget.controller != widget.controller;
4✔
120
    final schemaChanged = oldWidget.jsonSchema != widget.jsonSchema ||
4✔
121
        oldWidget.uiSchema != widget.uiSchema;
4✔
122
    if (schemaChanged || controllerChanged) {
123
      initMainSchema(
1✔
124
        controllerChanged: controllerChanged,
125
        schemaChanged: schemaChanged,
126
      );
127
    }
128
  }
129

130
  @override
1✔
131
  Widget build(BuildContext context) {
132
    return WidgetBuilderInherited(
1✔
133
      controller: controller,
1✔
134
      jsonForm: widget,
1✔
135
      context: context,
136
      baseConfig: widget.uiConfig,
2✔
137
      child: Builder(
1✔
138
        builder: (context) {
1✔
139
          final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
140
          final uiConfig = widgetBuilderInherited.uiConfig;
1✔
141

142
          final formChild = Column(
1✔
143
            children: <Widget>[
1✔
144
              if (uiConfig.debugMode)
1✔
145
                TextButton(
×
146
                  onPressed: () {
×
147
                    inspect(mainSchema);
×
148
                  },
149
                  child: const Text('INSPECT'),
150
                ),
151
              _buildHeaderTitle(context),
1✔
152
              FormFromSchemaBuilder(
1✔
153
                mainSchema: mainSchema,
1✔
154
                formValue: null,
155
              ),
156
              uiConfig.submitButtonBuilder?.call(onSubmit) ??
2✔
157
                  Padding(
1✔
158
                    padding: const EdgeInsets.symmetric(vertical: 10),
159
                    child: ElevatedButton(
1✔
160
                      key: const Key('JsonForm_submitButton'),
161
                      onPressed: onSubmit,
1✔
162
                      child: Text(
1✔
163
                        uiConfig.localizedTexts.submit(),
2✔
164
                      ),
165
                    ),
166
                  ),
167
            ],
168
          );
169

170
          return SingleChildScrollView(
1✔
171
            key: const Key('JsonForm_scrollView'),
172
            child: uiConfig.formBuilder?.call(_formKey, formChild) ??
1✔
173
                Form(
1✔
174
                  key: _formKey,
1✔
175
                  autovalidateMode: uiConfig.autovalidateMode,
1✔
176
                  child: Padding(
1✔
177
                    padding: const EdgeInsets.all(12.0),
178
                    child: formChild,
179
                  ),
180
                ),
181
          );
182
        },
183
      ),
184
    );
185
  }
186

187
  Widget _buildHeaderTitle(BuildContext context) {
1✔
188
    final uiConfig = WidgetBuilderInherited.of(context).uiConfig;
2✔
189
    final custom = uiConfig.titleAndDescriptionBuilder?.call(mainSchema);
1✔
190
    if (custom != null) return custom;
191

192
    return Column(
1✔
193
      crossAxisAlignment: CrossAxisAlignment.start,
194
      children: <Widget>[
1✔
195
        if (mainSchema.title != null)
2✔
196
          SizedBox(
1✔
197
            width: double.infinity,
198
            child: Text(
1✔
199
              mainSchema.title!,
2✔
200
              style: uiConfig.title,
1✔
201
              textAlign: uiConfig.titleAlign,
1✔
202
            ),
203
          ),
204
        const Divider(),
1✔
205
        if (mainSchema.description != null)
2✔
206
          SizedBox(
1✔
207
            width: double.infinity,
208
            child: Text(
1✔
209
              mainSchema.description!,
2✔
210
              style: uiConfig.description,
1✔
211
              textAlign: uiConfig.titleAlign,
1✔
212
            ),
213
          ),
214
      ],
215
    );
216
  }
217

218
  //  Form methods
219
  void onSubmit() {
1✔
220
    final data = controller.submit();
2✔
221
    if (data != null) {
222
      widget.onFormDataSaved(data);
3✔
223
    }
224
  }
225
}
226

227
class FormFromSchemaBuilder extends StatelessWidget {
228
  const FormFromSchemaBuilder({
1✔
229
    super.key,
230
    required this.mainSchema,
231
    required this.formValue,
232
    this.schemaObject,
233
  });
234
  final Schema mainSchema;
235
  final JsonFormValue? formValue;
236
  final SchemaObject? schemaObject;
237

238
  @override
1✔
239
  Widget build(BuildContext context) {
240
    final schema = formValue?.schema ?? mainSchema;
3✔
241
    return JsonFormKeyPath(
1✔
242
      context: context,
243
      id: formValue?.id ?? schema.id,
3✔
244
      child: Builder(
1✔
245
        builder: (context) {
1✔
246
          if (schema.uiSchema.hidden) {
2✔
247
            return const SizedBox.shrink();
248
          }
249
          if (schema is SchemaProperty) {
1✔
250
            return PropertySchemaBuilder(
1✔
251
              mainSchema: mainSchema,
1✔
252
              formValue: formValue!,
1✔
253
            );
254
          }
255
          if (schema is SchemaArray) {
1✔
256
            return ArraySchemaBuilder(
1✔
257
              mainSchema: mainSchema,
1✔
258
              schemaArray: schema,
259
            );
260
          }
261

262
          if (schema is SchemaObject) {
1✔
263
            return ObjectSchemaBuilder(
1✔
264
              mainSchema: mainSchema,
1✔
265
              schemaObject: schema,
266
            );
267
          }
268

269
          return const SizedBox.shrink();
270
        },
271
      ),
272
    );
273
  }
274
}
275

276
class JsonFormKeyPath extends InheritedWidget {
277
  JsonFormKeyPath({
1✔
278
    super.key,
279
    required BuildContext context,
280
    required this.id,
281
    required super.child,
282
  }) : parent = maybeGet(context);
1✔
283

284
  final String id;
285
  final JsonFormKeyPath? parent;
286

287
  String get path => appendId(parent?.path, id);
5✔
288

289
  static String getPath(BuildContext context, {String id = ''}) {
1✔
290
    return JsonFormKeyPath(
1✔
291
      id: id,
292
      context: context,
293
      child: const SizedBox(),
294
    ).path;
1✔
295
  }
296

297
  static String appendId(String? path, String id) {
1✔
298
    return path == null || path.isEmpty || path == kGenesisIdKey
2✔
299
        ? id
300
        : id.isEmpty
1✔
301
            ? path
302
            : '$path.$id';
1✔
303
  }
304

305
  @override
1✔
306
  bool updateShouldNotify(JsonFormKeyPath oldWidget) {
307
    return id != oldWidget.id;
3✔
308
  }
309

NEW
310
  static JsonFormKeyPath? maybeOf(BuildContext context) =>
×
NEW
311
      context.dependOnInheritedWidgetOfExactType<JsonFormKeyPath>();
×
312

NEW
313
  static JsonFormKeyPath of(BuildContext context) {
×
NEW
314
    final result = maybeOf(context);
×
NEW
315
    assert(result != null, 'No JsonFormKeyPath found in context');
×
316
    return result!;
317
  }
318

319
  static JsonFormKeyPath? maybeGet(BuildContext context) =>
1✔
320
      context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>()?.widget
2✔
321
          as JsonFormKeyPath?;
322

NEW
323
  static JsonFormKeyPath get(BuildContext context) {
×
324
    final result =
NEW
325
        context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>();
×
NEW
326
    assert(result != null, 'No JsonFormKeyPath found in context');
×
NEW
327
    return result!.widget as JsonFormKeyPath;
×
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