Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onDepedencyChange #50

Merged
merged 9 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.16.5",
"dart.lineLength": 120
"dart.lineLength": 120,
"yaml.schemaStore.enable": false
}
5 changes: 5 additions & 0 deletions glade_forms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
## 2.0.0
- **[Breaking]**: TextEditingController is no more created automatically. When TextEditingController is used, input's behavior is slightly changed. See README.md for full info.
- **[Breaking]**: GladeInput's controller is now private. Use factory constructors to create input.
- **[Breaking]**: `Extra` parameter removed
- **[Breaking]**: `dependencies` are no longer passed into `onChange` and in validator.
- **[Breaking]**: GladeInput is no longer ChangeNotifier
- **[Add]**: onDependencyChange - callback is called when any (or multiple with groupEdit()) dependency was udpated.
- **Improvement**: GladeModelDebugInfo now colorize String values to visualize whitespace.

## 1.6.0
- **Improvement**: GladeModelDebugInfo is more colorful and polished.
Expand Down
96 changes: 42 additions & 54 deletions glade_forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ A universal way to define form validators with support of translations.
- [Inputs](#inputs)
- [Flutter widgets](#flutter-widgets)
- [Edit multiple inputs at once](#edit-multiple-inputs-at-once)
- [Dependencies (WIP)](#dependencies-wip)
- [Dependencies](#dependencies)
- [Controlling other inputs](#controlling-other-inputs)
- [Translation](#translation)
- [Default translations](#default-translations)
Expand Down Expand Up @@ -179,9 +179,7 @@ ageInput = GladeInput.create(
validator: (v) => (v
..notNull()
..satisfy(
(value, extra, dependencies) {
return value >= 18;
},
(value) => value >= 18,
devError: (_, __) => 'Value must be greater or equal to 18',
key: _ErrorKeys.ageRestriction,
))
Expand Down Expand Up @@ -267,66 +265,35 @@ class FormModel extends GladeModel {

After that listener will contain `lastUpdatedKeys` with keys of `age` and `name` inputs.

### Dependencies (WIP)
**NOTICE** - in future versions, dependencies will be used only for dependent listenner. Use your variables directly without need to lookup dependencies.
### Dependencies
An input can depend on other inputs to enable updates based on those dependencies. To define these dependencies, use the dependencies attribute. It's essential to specify inputKey on any inputs that are intended to serve as dependencies.

Input can have dependencies on other inputs to allow dependent validation. Define input's dependencies with `dependencies`.

`inputKey` must be specified on inputs to be used as dependencies.

In validation, translation or in `onChange()`, just call `dependencies.byKey()` to get dependent input.

Note that `byKey()` will throw if no input is found. This is by design to provide immediate indication of error.

For example, we want to restrict "Age input" to be at least 18 when "VIP Content" is checked.
For instance, consider a scenario where we want the "VIP Content" option to be automatically selected when the 'ageInput' is updated and its value exceeds 18.

```dart
ageInput = GladeInput.create(
validator: (v) => (v
..notNull()
..satisfy(
(value, extra, dependencies) {
final vipContentInput = dependencies.byKey<bool>('vip-input');

if (!vipContentInput.value) {
return true;
}

return value >= 18;
},
devError: (_, __) => 'When VIP enabled you must be at least 18 years old.',
key: _ErrorKeys.ageRestriction,
))
.build(),
value: 0,
dependencies: () => [vipInput], // <--- Dependency
valueConverter: GladeTypeConverters.intConverter,
inputKey: 'age-input',
translateError: (error, key, devMessage, dependencies) {
if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr();

if (error.isConversionError) return LocaleKeys.ageRestriction_ageFormat.tr();

return devMessage;
},
);

vipInput = GladeInput.create(
validator: (v) => (v..notNull()).build(),
value: false,
inputKey: 'vip-input',
);
ageInput = GladeInput.create(
value: 0,
valueConverter: GladeTypeConverters.intConverter,
inputKey: 'age-input',
);
vipInput = GladeInput.create(
inputKey: 'vip-input',
dependencies: () => [ageInput],
onDependencyChange: (keys) {
if (keys.contains('age-input')) {
vipInput.value = ageInput.value >= 18;
}
},
);
```

![dependent-validation](https://raw.githubusercontent.com/netglade/glade_forms/main/glade_forms/doc/depend-validation.gif)

### Controlling other inputs

Sometimes, it can be handy to update some input's value based on the changed value of another input.
Sometimes, it can be handy to update some input's value based on the changed value of another input. As developer you have two options.

Each input has `onChange()` callback where these reactions can be created.

An example could be automatically update `Age` value based on checked `VIP Content` input (checkbox).
You can listen for `onChange()` callback and update other inputs based on input's changed value. An example could be automatically updating the `Age` value based on checked `VIP Content` input (checkbox).

```dart
// In vipContent input
Expand All @@ -339,6 +306,27 @@ onChange: (info, dependencies) {

![two-way-inputs-example](https://raw.githubusercontent.com/netglade/glade_forms/main/glade_forms/doc/two-way-dependencies.gif)

The second approach is to use `dependencies` and `onDependencyChange` callback and react when different dependencies are changed. Note that it works with groupEdit() as well. In that case, onDependencyChange is called once for every changed dependency.

In this example, when age-input update its value (dependency), checkbox's value (vipInput) is updated.

```dart
vipInput = GladeInput.create(
inputKey: 'vip-input',
dependencies: () => [ageInput],
onChange: (info) {
if (info.value && ageInput.value < 18) {
ageInput.value = 18;
}
},
onDependencyChange: (key) {
if (key.contains('age-input')) {
vipInput.value = ageInput.value >= 18;
}
},
);
```

### Translation

Each validation error (and conversion error if any) can be translated. Provide `translateError` function which accepts:
Expand Down
Binary file modified glade_forms/doc/depend-validation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion glade_forms/lib/src/core/changes_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:glade_forms/src/validator/validator_result.dart';

class ChangesInfo<T> extends Equatable {
final String inputKey;
final T? initialValue;
final T? previousValue;

Expand All @@ -10,8 +11,10 @@ class ChangesInfo<T> extends Equatable {
final ValidatorResult<T>? validatorResult;

@override
List<Object?> get props => [initialValue, previousValue, value, validatorResult];
List<Object?> get props => [initialValue, previousValue, value, validatorResult, inputKey];

const ChangesInfo({
required this.inputKey,
required this.previousValue,
required this.value,
required this.validatorResult,
Expand Down
6 changes: 3 additions & 3 deletions glade_forms/lib/src/core/convert_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:glade_forms/src/core/glade_input_error.dart';

/// Before validation when converer from string to prpoer type failed.
typedef OnConvertError = String Function(String? rawInput, {Object? extra, Object? key});
typedef OnConvertError = String Function(String? rawInput, {Object? key});

class ConvertError<T> extends GladeInputError<T> with EquatableMixin implements Exception {
// ignore: prefer-correct-callback-field-name, more suitable name
Expand All @@ -20,7 +20,7 @@ class ConvertError<T> extends GladeInputError<T> with EquatableMixin implements

String get targetType => T.runtimeType.toString();

String get devErrorMessage => devError(input, extra: error, key: key);
String get devErrorMessage => devError(input, key: key);

@override
// ignore: no-object-declaration, error can be any object.
Expand All @@ -32,7 +32,7 @@ class ConvertError<T> extends GladeInputError<T> with EquatableMixin implements
super.key,
OnConvertError? formatError,
}) : _convertError = error,
devError = formatError ?? ((rawValue, {extra, key}) => 'Conversion error: $error');
devError = formatError ?? ((rawValue, {key}) => 'Conversion error: $error');

@override
String toString() => devError(input, key: key);
Expand Down
Loading
Loading