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

juancastillo0 / json_form / 11788932365

12 Nov 2024 01:21AM UTC coverage: 80.651% (+6.3%) from 74.365%
11788932365

push

github

juancastillo0
Add more README docs

1659 of 2057 relevant lines covered (80.65%)

1.3 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/fields/shared.dart';
11
import 'package:json_form/src/models/json_form_schema_style.dart';
12
import 'package:json_form/src/models/models.dart';
13

14
/// For each field, gives a function that returns the files selected by the user.
15
/// The inner function is called when adding a file for input fields with format "data-url".
16
typedef JsonFormFilePickerHandler = Future<List<XFile>?> Function() Function(
17
  JsonFormField<Object?> field,
18
);
19

20
/// For each field, gives a function that receives a map of values (items) to strings (labels)
21
/// and returns the selected value, or null if none was selected.
22
typedef JsonFormSelectPickerHandler
23
    = Future<Object?> Function(Map<Object?, String> options)? Function(
24
  JsonFormField<Object?> field,
25
);
26

27
/// For each field, gives a function that receives the new value and returns an error,
28
/// or null if the validation was successful and no error was found.
29
/// If the error is an empty String, it will be considered an error in validation,
30
/// but no error message will be shown.
31
typedef JsonFormValidatorHandler = String? Function(Object? newValue)? Function(
32
  JsonFormField<Object?> field,
33
);
34

35
/// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
36
/// You may use [controller] for added functionalities.
37
class JsonForm extends StatefulWidget {
38
  /// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
39
  /// You may use [controller] for added functionalities.
40
  const JsonForm({
1✔
41
    super.key,
42
    required this.jsonSchema,
43
    required this.onFormDataSaved,
44
    this.controller,
45
    this.uiSchema,
46
    this.uiConfig,
47
    this.fieldValidator,
48
    this.fieldSelectPicker,
49
    this.fieldFilePicker,
50
  });
51

52
  /// The JSON schema to build the form from
53
  final String jsonSchema;
54

55
  /// Callback function to be called when the form is submitted
56
  final void Function(Object) onFormDataSaved;
57

58
  /// The controller to be used for the form.
59
  /// It can be used to set the initial data, get/set the form data,
60
  /// subscribe to changes, etc.
61
  final JsonFormController? controller;
62

63
  /// The UI schema with input configurations for each field
64
  final String? uiSchema;
65

66
  /// The UI configuration with global styles, texts, builders and other Flutter configurations
67
  final JsonFormUiConfig? uiConfig;
68

69
  /// A custom validator for each field.
70
  ///
71
  /// For each field, gives a function that receives the new value and returns an error,
72
  /// or null if the validation was successful and no error was found.
73
  /// If the error is an empty String, it will be considered an error in validation,
74
  /// but no error message will be shown.
75
  final JsonFormValidatorHandler? fieldValidator;
76

77
  /// A custom select picker for enums and one-of fields.
78
  ///
79
  /// For each field, gives a function that receives a map of values (items) to strings (labels)
80
  /// and returns the selected value, or null if none was selected.
81
  final JsonFormSelectPickerHandler? fieldSelectPicker;
82

83
  /// A file picker, required when using the "data-url" string format .
84
  ///
85
  /// For each field, gives a function that returns the files selected by the user.
86
  /// The inner function is called when adding a file for input fields with format "data-url".
87
  final JsonFormFilePickerHandler? fieldFilePicker;
88

89
  @override
1✔
90
  State<JsonForm> createState() => _JsonFormState();
1✔
91
}
92

93
class _JsonFormState extends State<JsonForm> {
94
  late JsonFormController controller;
95
  late Schema mainSchema;
96
  GlobalKey<FormState> get _formKey => controller.formKey!;
3✔
97

98
  @override
1✔
99
  void initState() {
100
    super.initState();
1✔
101
    initMainSchema(controllerChanged: true, schemaChanged: true);
1✔
102
  }
103

104
  void initMainSchema({
1✔
105
    required bool controllerChanged,
106
    required bool schemaChanged,
107
  }) {
108
    if (controllerChanged) {
109
      controller = widget.controller ?? JsonFormController(initialData: {});
5✔
110
      controller.formKey ??= GlobalKey<FormState>();
3✔
111
      if (controller.mainSchema != null &&
2✔
112
          (!schemaChanged || widget.jsonSchema.isEmpty)) {
3✔
113
        return;
114
      }
115
    }
116
    final mainSchema = Schema.fromJson(
1✔
117
      json.decode(widget.jsonSchema) as Map<String, Object?>,
3✔
118
      id: kGenesisIdKey,
119
    );
120
    final map = widget.uiSchema != null
2✔
121
        ? json.decode(widget.uiSchema!) as Map<String, Object?>
3✔
122
        : null;
123
    if (map != null) {
124
      mainSchema.setUiSchema(map, fromOptions: false);
1✔
125
    }
126
    this.mainSchema = mainSchema;
1✔
127
  }
128

129
  @override
1✔
130
  void didUpdateWidget(covariant JsonForm oldWidget) {
131
    super.didUpdateWidget(oldWidget);
1✔
132
    final controllerChanged = oldWidget.controller != widget.controller;
4✔
133
    final schemaChanged = oldWidget.jsonSchema != widget.jsonSchema ||
4✔
134
        oldWidget.uiSchema != widget.uiSchema;
4✔
135
    if (schemaChanged || controllerChanged) {
136
      initMainSchema(
1✔
137
        controllerChanged: controllerChanged,
138
        schemaChanged: schemaChanged,
139
      );
140
    }
141
  }
142

143
  @override
1✔
144
  Widget build(BuildContext context) {
145
    return WidgetBuilderInherited(
1✔
146
      controller: controller,
1✔
147
      jsonForm: widget,
1✔
148
      context: context,
149
      baseConfig: widget.uiConfig,
2✔
150
      child: Builder(
1✔
151
        builder: (context) {
1✔
152
          final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
153
          final uiConfig = widgetBuilderInherited.uiConfig;
1✔
154

155
          final formChild = Column(
1✔
156
            children: <Widget>[
1✔
157
              if (uiConfig.debugMode)
1✔
158
                TextButton(
×
159
                  onPressed: () {
×
160
                    inspect(mainSchema);
×
161
                  },
162
                  child: const Text('INSPECT'),
163
                ),
164
              _buildHeaderTitle(context),
1✔
165
              FormFromSchemaBuilder(
1✔
166
                mainSchema: mainSchema,
1✔
167
                formValue: null,
168
              ),
169
              uiConfig.submitButtonBuilder?.call(onSubmit) ??
2✔
170
                  Padding(
1✔
171
                    padding: const EdgeInsets.symmetric(vertical: 10),
172
                    child: ElevatedButton(
1✔
173
                      key: JsonFormKeys.submitButton,
174
                      onPressed: onSubmit,
1✔
175
                      child: Text(
1✔
176
                        uiConfig.localizedTexts.submit(),
2✔
177
                      ),
178
                    ),
179
                  ),
180
            ],
181
          );
182

183
          return SingleChildScrollView(
1✔
184
            key: JsonFormKeys.scrollView,
185
            child: uiConfig.formBuilder?.call(_formKey, formChild) ??
1✔
186
                Form(
1✔
187
                  key: _formKey,
1✔
188
                  autovalidateMode: uiConfig.autovalidateMode,
1✔
189
                  child: Padding(
1✔
190
                    padding: const EdgeInsets.all(12.0),
191
                    child: formChild,
192
                  ),
193
                ),
194
          );
195
        },
196
      ),
197
    );
198
  }
199

200
  Widget _buildHeaderTitle(BuildContext context) {
1✔
201
    final uiConfig = WidgetBuilderInherited.of(context).uiConfig;
2✔
202
    final custom = uiConfig.titleAndDescriptionBuilder?.call(mainSchema);
1✔
203
    if (custom != null) return custom;
204

205
    return Column(
1✔
206
      crossAxisAlignment: CrossAxisAlignment.start,
207
      children: <Widget>[
1✔
208
        if (mainSchema.title != null)
2✔
209
          SizedBox(
1✔
210
            width: double.infinity,
211
            child: Text(
1✔
212
              mainSchema.title!,
2✔
213
              style: uiConfig.title,
1✔
214
              textAlign: uiConfig.titleAlign,
1✔
215
            ),
216
          ),
217
        const Divider(),
1✔
218
        if (mainSchema.description != null)
2✔
219
          SizedBox(
1✔
220
            width: double.infinity,
221
            child: Text(
1✔
222
              mainSchema.description!,
2✔
223
              style: uiConfig.description,
1✔
224
              textAlign: uiConfig.titleAlign,
1✔
225
            ),
226
          ),
227
      ],
228
    );
229
  }
230

231
  //  Form methods
232
  void onSubmit() {
1✔
233
    final data = controller.submit();
2✔
234
    if (data != null) {
235
      widget.onFormDataSaved(data);
3✔
236
    }
237
  }
238
}
239

240
class FormFromSchemaBuilder extends StatelessWidget {
241
  const FormFromSchemaBuilder({
1✔
242
    super.key,
243
    required this.mainSchema,
244
    required this.formValue,
245
    this.schemaObject,
246
  });
247
  final Schema mainSchema;
248
  final JsonFormValue? formValue;
249
  final SchemaObject? schemaObject;
250

251
  @override
1✔
252
  Widget build(BuildContext context) {
253
    final schema = formValue?.schema ?? mainSchema;
3✔
254
    return JsonFormKeyPath(
1✔
255
      context: context,
256
      id: formValue?.id ?? schema.id,
3✔
257
      child: Builder(
1✔
258
        builder: (context) {
1✔
259
          if (schema.uiSchema.hidden) {
2✔
260
            return const SizedBox.shrink();
261
          }
262
          if (schema is SchemaProperty) {
1✔
263
            return PropertySchemaBuilder(
1✔
264
              mainSchema: mainSchema,
1✔
265
              formValue: formValue!,
1✔
266
            );
267
          }
268
          if (schema is SchemaArray) {
1✔
269
            return ArraySchemaBuilder(
1✔
270
              mainSchema: mainSchema,
1✔
271
              schemaArray: schema,
272
            );
273
          }
274

275
          if (schema is SchemaObject) {
1✔
276
            return ObjectSchemaBuilder(
1✔
277
              mainSchema: mainSchema,
1✔
278
              schemaObject: schema,
279
            );
280
          }
281

282
          return const SizedBox.shrink();
283
        },
284
      ),
285
    );
286
  }
287
}
288

289
class JsonFormKeyPath extends InheritedWidget {
290
  JsonFormKeyPath({
1✔
291
    super.key,
292
    required BuildContext context,
293
    required this.id,
294
    required super.child,
295
  }) : parent = maybeGet(context);
1✔
296

297
  final String id;
298
  final JsonFormKeyPath? parent;
299

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

302
  static String getPath(BuildContext context, {String id = ''}) {
1✔
303
    return JsonFormKeyPath(
1✔
304
      id: id,
305
      context: context,
306
      child: const SizedBox(),
307
    ).path;
1✔
308
  }
309

310
  static String appendId(String? path, String id) {
1✔
311
    return path == null || path.isEmpty || path == kGenesisIdKey
2✔
312
        ? id
313
        : id.isEmpty
1✔
314
            ? path
315
            : '$path.$id';
1✔
316
  }
317

318
  @override
1✔
319
  bool updateShouldNotify(JsonFormKeyPath oldWidget) {
320
    return id != oldWidget.id;
3✔
321
  }
322

323
  static JsonFormKeyPath? maybeOf(BuildContext context) =>
×
324
      context.dependOnInheritedWidgetOfExactType<JsonFormKeyPath>();
×
325

326
  static JsonFormKeyPath of(BuildContext context) {
×
327
    final result = maybeOf(context);
×
328
    assert(result != null, 'No JsonFormKeyPath found in context');
×
329
    return result!;
330
  }
331

332
  static JsonFormKeyPath? maybeGet(BuildContext context) =>
1✔
333
      context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>()?.widget
2✔
334
          as JsonFormKeyPath?;
335

336
  static JsonFormKeyPath get(BuildContext context) {
×
337
    final result =
338
        context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>();
×
339
    assert(result != null, 'No JsonFormKeyPath found in context');
×
340
    return result!.widget as JsonFormKeyPath;
×
341
  }
342
}
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