-
-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3e950cc
commit 117b1dd
Showing
1 changed file
with
65 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,21 +24,28 @@ ember install ember-changeset | |
## Updates | ||
|
||
We have released `v3.0.0`. See the CHANGELOG [here](https://github.com/adopted-ember-addons/ember-changeset/blob/master/CHANGELOG.md). This requires Ember >= 3.13 as the use of `@tracked` will help us monitor and propagate changes to the UI layer. If your app is < 3.13 or you need to support IE11, then you can install the 2.0 series `ember install [email protected]`. | ||
We have released `v3.0.0`. See the CHANGELOG [here](https://github.com/adopted-ember-addons/ember-changeset/blob/master/CHANGELOG.md). | ||
This requires Ember >= 3.13 as the use of `@tracked` will help us monitor and propagate changes to the UI layer. | ||
If your app is < 3.13 or you need to support IE11, then you can install the 2.0 series `ember install [email protected]`. | ||
|
||
Support for IE11 was dropped with the `v3.0.0` release given our ubiquitous use of Proxy. | ||
|
||
The base library for this addon is [validated-changeset](https://github.com/validated-changeset/validated-changeset/). As a result, this functionality is available outside of Ember as well! | ||
The base library for this addon is [validated-changeset](https://github.com/adopted-ember-addons/validated-changeset/). | ||
As a result, this functionality is available outside of Ember as well! | ||
|
||
## Philosophy | ||
|
||
The idea behind a changeset is simple: it represents a set of valid changes to be applied onto any Object (`Ember.Object`, `DS.Model`, POJOs, etc). Each change is tested against an optional validation, and if valid, the change is stored and applied when executed. | ||
The idea behind a changeset is simple: it represents a set of valid changes to be applied onto any Object (`Ember.Object`, `DS.Model`, POJOs, etc). | ||
Each change is tested against an optional validation, and if valid, the change is stored and applied when executed. | ||
|
||
Assuming a Data Down, Actions Up (DDAU) approach, a changeset is more appropriate compared to implicit 2 way bindings. Other validation libraries only validate a property _after_ it is set on an Object, which means that your Object can enter an invalid state. | ||
Assuming a Data Down, Actions Up (DDAU) approach, a changeset is more appropriate compared to implicit 2 way bindings. | ||
Other validation libraries only validate a property _after_ it is set on an Object, which means that your Object can enter an invalid state. | ||
|
||
`ember-changeset` only allows valid changes to be set, so your Objects will never become invalid (assuming you have 100% validation coverage). Additionally, this addon is designed to be un-opinionated about your choice of form and/or validation library, so you can easily integrate it into an existing solution. | ||
`ember-changeset` only allows valid changes to be set, so your Objects will never become invalid (assuming you have 100% validation coverage). | ||
Additionally, this addon is designed to be un-opinionated about your choice of form and/or validation library, so you can easily integrate it into an existing solution. | ||
|
||
The simplest way to incorporate validations is to use [`ember-changeset-validations`](https://github.com/adopted-ember-addons/ember-changeset-validations/), a companion addon to this one. It has a simple mental model, and there are no Observers or CPs involved – just pure functions. | ||
The simplest way to incorporate validations is to use [`ember-changeset-validations`](https://github.com/adopted-ember-addons/ember-changeset-validations/), a companion addon to this one. | ||
It has a simple mental model, and there are no Observers or CPs involved – just pure functions. | ||
|
||
See also the [plugins](#plugins) section for addons that extend `ember-changeset`. | ||
|
||
|
@@ -62,22 +69,22 @@ function validatorFn({ key, newValue, oldValue, changes, content }) { | |
} | ||
|
||
let changeset = Changeset(user, validatorFn); | ||
user.get('firstName'); // "Michael" | ||
user.get('lastName'); // "Bolton" | ||
user.firstName; // "Michael" | ||
user.lastName; // "Bolton" | ||
|
||
changeset.set('firstName', 'Jim'); | ||
changeset.set('lastName', 'B'); | ||
changeset.get('isInvalid'); // true | ||
changeset.isInvalid; // true | ||
changeset.get('errors'); // [{ key: 'lastName', validation: 'too short', value: 'B' }] | ||
changeset.set('lastName', 'Bob'); | ||
changeset.get('isValid'); // true | ||
changeset.isValid; // true | ||
|
||
user.get('firstName'); // "Michael" | ||
user.get('lastName'); // "Bolton" | ||
user.firstName; // "Michael" | ||
user.lastName; // "Bolton" | ||
|
||
changeset.save(); // sets and saves valid changes on the user | ||
user.get('firstName'); // "Jim" | ||
user.get('lastName'); // "Bob" | ||
user.firstName; // "Jim" | ||
user.lastName; // "Bob" | ||
``` | ||
|
||
## Usage | ||
|
@@ -86,29 +93,31 @@ First, create a new `Changeset` using the `changeset` helper or through JavaScri | |
|
||
```hbs | ||
{{! application/template.hbs}} | ||
{{#let (changeset model this.validate) as |changesetObj|}} | ||
{{#let (changeset this.model this.validate) as |changesetObj|}} | ||
<DummyForm | ||
@changeset={{changesetObj}} | ||
@submit={{this.submit}} | ||
@rollback={{this.rollback}} /> | ||
@changeset={{changesetObj}} | ||
@submit={{this.submit}} | ||
@rollback={{this.rollback}} | ||
/> | ||
{{/let}} | ||
``` | ||
|
||
```js | ||
import Component from '@ember/component'; | ||
import Component from '@glimmer/component'; | ||
import { cached } from '@glimmer/tracking'; | ||
import { Changeset } from 'ember-changeset'; | ||
|
||
export default FormComponent extends Component { | ||
init(...args) { | ||
super.init(...args) | ||
|
||
@cached | ||
get changeset() { | ||
let validator = this.validate; | ||
this.changeset = Changeset(this.model, validator); | ||
return Changeset(this.model, validator); | ||
} | ||
} | ||
``` | ||
|
||
The helper receives any Object (including `DS.Model`, `Ember.Object`, or even POJOs) and an optional `validator` action. If a `validator` is passed into the helper, the changeset will attempt to call that function when a value changes. | ||
The helper receives any Object (including `DS.Model`, `Ember.Object`, or even POJOs) and an optional `validator` action. | ||
If a `validator` is passed into the helper, the changeset will attempt to call that function when a value changes. | ||
|
||
```js | ||
// application/controller.js | ||
|
@@ -126,6 +135,11 @@ export default class FormController extends Controller { | |
return changeset.rollback(); | ||
} | ||
|
||
@action | ||
setChangesetProperty(changeset, path, evt) { | ||
return changeset.set(path, evt.target.value); | ||
} | ||
|
||
@action | ||
validate({ key, newValue, oldValue, changes, content }) { | ||
// lookup a validator function on your favorite validation library | ||
|
@@ -139,15 +153,16 @@ Then, in your favorite form library, simply pass in the `changeset` in place of | |
```hbs | ||
{{! dummy-form/template.hbs}} | ||
<form> | ||
<Input @value={{changeset.firstName}} /> | ||
<Input @value={{changeset.lastName}} /> | ||
<input value={{changeset.firstName}} {{on "change" (fn this.setChangesetProperty changeset "firstName")}} /> | ||
<input value={{changeset.lastName}} {{on "change" (fn this.setChangesetProperty changeset "lastName")}} /> | ||
<button {{on "click" this.submit changeset}}>Submit</button> | ||
<button {{on "click" this.rollback changeset}}>Cancel</button> | ||
</form> | ||
``` | ||
|
||
In the above example, when the input changes, only the changeset's internal values are updated. When the submit button is clicked, the changes are only executed if _all changes_ are valid. | ||
In the above example, when the input changes, only the changeset's internal values are updated. | ||
When the submit button is clicked, the changes are only executed if _all changes_ are valid. | ||
|
||
On rollback, all changes are dropped and the underlying Object is left untouched. | ||
|
||
|
@@ -168,9 +183,13 @@ let changeset = Changeset(user, validatorFn, validationMap, { changeset: MyChang | |
|
||
## Changeset template helpers | ||
|
||
`ember-changeset` overrides `set` and `get` in order to handle deeply nested setters. `mut` is simply an alias for `Ember.set(changeset, ...)`, thus we provide a `changeset-set` template helper if you are dealing with nested setters. | ||
`ember-changeset` overrides `set` and `get` in order to handle deeply nested setters. | ||
`mut` is simply an alias for `Ember.set(changeset, ...)`, thus we provide a `changeset-set` template helper if you are dealing with nested setters. | ||
|
||
`changeset-get` is necessary for nested getters to easily retrieve leaf keys without error. Ember's templating layer will ask us for the first key it comes across as it traverses down the object (`user.firstName`). We keep track of the changes, but to also keep track of unchanged values and properly merge them in the changeset is difficult. If you are only accessing keys in an object that is only one level deep, you do not need this helper. | ||
`changeset-get` is necessary for nested getters to easily retrieve leaf keys without error. | ||
Ember's templating layer will ask us for the first key it comes across as it traverses down the object (`user.firstName`). | ||
We keep track of the changes, but to also keep track of unchanged values and properly merge them in the changeset is difficult. | ||
If you are only accessing keys in an object that is only one level deep, you do not need this helper. | ||
|
||
```hbs | ||
<form> | ||
|
@@ -192,14 +211,15 @@ let changeset = Changeset(model, validatorFn, validationMap, { changesetKeys: [' | |
|
||
## Disabling Automatic Validation | ||
|
||
The default behavior of `Changeset` is to automatically validate a field when it is set. Automatic validation can be disabled by passing `skipValidate` as an option when creating a changeset. | ||
The default behavior of `Changeset` is to automatically validate a field when it is set. | ||
Automatic validation can be disabled by passing `skipValidate` as an option when creating a changeset. | ||
|
||
```js | ||
let changeset = Changeset(model, validatorFn, validationMap, { skipValidate: true }); | ||
``` | ||
|
||
```hbs | ||
{{#let (changeset model this.validate skipValidate=true) as |changesetObj|}} | ||
{{#let (changeset this.model this.validate skipValidate=true) as |changesetObj|}} | ||
... | ||
{{/let}} | ||
``` | ||
|
@@ -210,7 +230,7 @@ Be sure to call `validate()` on the `changeset` before saving or committing chan | |
|
||
```ts | ||
import Component from '@glimmer/component'; | ||
import { BufferedChangeset } from 'ember-changeset/types'; | ||
import type { BufferedChangeset } from 'ember-changeset/types'; | ||
import { Changeset } from 'ember-changeset'; | ||
|
||
interface Args { | ||
|
@@ -233,20 +253,21 @@ export default class Foo extends Component<Args> { | |
Other available types include the following. Please put in a PR if you need more types or access directly in `validated-changeset`! | ||
|
||
```js | ||
import { ValidationResult, ValidatorMapFunc, ValidatorAction } from 'ember-changeset/types'; | ||
import type { ValidationResult, ValidatorMapFunc, ValidatorAction } from 'ember-changeset/types'; | ||
``` | ||
|
||
## Alternative Changeset | ||
|
||
Enabled in 4.1.0. Experimental and subject to changes until 5.0. | ||
|
||
We now ship a ValidatedChangeset that is a proposed new API we would like to introduce and see if it jives with users. The goal of this new feature is to remove confusing APIs and externalize validations. | ||
We now ship a `ValidatedChangeset` that is a proposed new API we would like to introduce and see if it jives with users. | ||
The goal of this new feature is to remove confusing APIs and externalize validations. | ||
|
||
- ✂️ `save` | ||
- ✂️ `cast` | ||
- ✂️ `merge` | ||
- `errors` are required to be added to the Changeset manually after `validate` | ||
- `validate` takes a callback with the sum of changes and original content to be applied to your externalized validation. In user land you will call `changeset.validate((changes) => yupSchema.validate(changes))` | ||
- `validate` takes a callback with the sum of changes and original content to be applied to your externalized validation. In user land you will call `changeset.validate((changes) => yupSchema.validate(changes))` | ||
|
||
```js | ||
import Component from '@glimmer/component'; | ||
|
@@ -502,7 +523,7 @@ changeset.get('pendingData'); // { name: 'Zoe', age: 21, address: { zipCode: '10 | |
Returns a Boolean value of the changeset's validity. | ||
|
||
```js | ||
changeset.get('isValid'); // true | ||
changeset.isValid; // true | ||
``` | ||
|
||
You can use this property in the template: | ||
|
@@ -520,7 +541,7 @@ You can use this property in the template: | |
Returns a Boolean value of the changeset's (in)validity. | ||
|
||
```js | ||
changeset.get('isInvalid'); // true | ||
changeset.isInvalid; // true | ||
``` | ||
|
||
You can use this property in the template: | ||
|
@@ -538,7 +559,7 @@ You can use this property in the template: | |
Returns a Boolean value of the changeset's state. A pristine changeset is one with no changes. | ||
|
||
```js | ||
changeset.get('isPristine'); // true | ||
changeset.isPristine; // true | ||
``` | ||
|
||
If changes present on the changeset are equal to the content's, this will return `true`. However, note that key/value pairs in the list of changes must all be present and equal on the content, but not necessarily vice versa: | ||
|
@@ -547,13 +568,13 @@ If changes present on the changeset are equal to the content's, this will return | |
let user = { name: 'Bobby', age: 21, address: { zipCode: '10001' } }; | ||
|
||
changeset.set('name', 'Bobby'); | ||
changeset.get('isPristine'); // true | ||
changeset.isPristine; // true | ||
|
||
changeset.set('address.zipCode', '10001'); | ||
changeset.get('isPristine'); // true | ||
changeset.isPristine; // true | ||
|
||
changeset.set('foo', 'bar'); | ||
changeset.get('isPristine'); // false | ||
changeset.isPristine; // false | ||
``` | ||
|
||
**[⬆️ back to top](#api)** | ||
|
@@ -563,7 +584,7 @@ changeset.get('isPristine'); // false | |
Returns a Boolean value of the changeset's state. A dirty changeset is one with changes. | ||
|
||
```js | ||
changeset.get('isDirty'); // true | ||
changeset.isDirty; // true | ||
``` | ||
|
||
**[⬆️ back to top](#api)** | ||
|
@@ -886,7 +907,8 @@ changeset.get('address.country'); // "United States" | |
changeset.get('another.unwantedProp'); // undefined | ||
``` | ||
|
||
For example, this method can be used to only allow specified changes through prior to saving. This is especially useful if you also setup a `schema` object for your model (using Ember Data), which can then be exported and used as a list of allowed keys: | ||
For example, this method can be used to only allow specified changes through prior to saving. | ||
This is especially useful if you also setup a `schema` object for your model (using Ember Data), which can then be exported and used as a list of allowed keys: | ||
|
||
```js | ||
// models/user.js | ||
|