Skip to content

Commit

Permalink
Ensure error changes are added to changes block (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
snewcomer authored Aug 13, 2018
1 parent a949e12 commit c547f08
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 27 deletions.
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,70 @@ if (isChangeset(model)) {
- [`ember-changeset-hofs`](https://github.com/nucleartide/ember-changeset-hofs) - Higher-order validation functions
- [`ember-bootstrap-changeset-validations`](https://github.com/kaliber5/ember-bootstrap-changeset-validations) - Adds support for changeset validations to `ember-bootstrap`

## Tips and Tricks

- General Input Helper with ember-concurrency

```js
export default Component.extend({
classNameBindings: ['hasError:validated-input--error'],

_checkValidity: task(function* (changeset, valuePath, value) {
yield timeout(150);

let snapshot = changeset.snapshot();

// valuePath is the property on the changeset, e.g. firstName
set(changeset, valuePath, value);

if (!changeset.get(`error.${valuePath}`)) {
set(this, 'hasError', false);
} else {
// if error, restore changeset so don't show error in template immediately'
// i.e. wait for onblur action to validate and show error in template
changeset.restore(snapshot);
}
}).restartable(),

actions: {
/**
* @method validateProperty
* @param {Object} changeset
* @param {String} valuePath
* @param {Object} e
*/
validateProperty(changeset, valuePath, e) {
set(changeset, valuePath, e.target.value);

if (changeset.get(`error.${valuePath}`)) {
set(this, 'hasError', true);
} else {
set(this, 'hasError', false);
}
},

/**
* @method checkValidity
* @param {Object} changeset
* @param {String|Integer} value
*/
checkValidity(changeset, value) {
get(this, '_checkValidity').perform(changeset, this.valuePath, value);
}
}
});
```

```hbs
<input
type={{type}}
value={{get model valuePath}}
oninput={{action (action "checkValidity" changeset) value="target.value"}}
onblur={{action "validateProperty" changeset valuePath}}
disabled={{disabled}}
placeholder={{placeholder}}>
```

## Installation

* `git clone` this repository
Expand Down
40 changes: 27 additions & 13 deletions addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,16 +411,26 @@ export function changeset(
*
* @public
* @chainable
* @param {String} key optional key to rollback invalid
* @param {String} key optional key to rollback invalid values
* @return {Changeset}
*/
rollbackInvalid(key /*: string | void */) /*: ChangesetDef */ {
let errorKeys = keys(get(this, ERRORS));

if (key) {
this._notifyVirtualProperties([key]);
this._deleteKey(ERRORS, key);
if (errorKeys.indexOf(key) > -1) {
this._deleteKey(CHANGES, key);
}
} else {
this._notifyVirtualProperties();
set(this, ERRORS, {});

// if on CHANGES hash, rollback those as well
errorKeys.forEach((errKey) => {
this._deleteKey(CHANGES, errKey);
})
}

return this;
Expand Down Expand Up @@ -492,7 +502,6 @@ export function changeset(

// Remove `key` from changes map.
let c = (this /*: ChangesetDef */);
c._deleteKey(CHANGES, key);

// Add `key` to errors map.
let errors /*: Errors */ = get(this, ERRORS);
Expand Down Expand Up @@ -626,10 +635,14 @@ export function changeset(
let validation /*: ValidationResult | Promise<ValidationResult> */ =
c._validate(key, value, oldValue);

let v /*: ValidationResult */ = (validation /*: any */);

c.trigger(BEFORE_VALIDATION_EVENT, key);
let result = c._setProperty(v, { key, value, oldValue });

// TODO: Address case when Promise is rejected.
if (isPromise(validation)) {
c._setIsValidating(key, true);
c.trigger(BEFORE_VALIDATION_EVENT, key);

let v /*: Promise<ValidationResult> */ = (validation /*: any */);
return v.then(resolvedValidation => {
Expand All @@ -639,10 +652,9 @@ export function changeset(
});
}

c.trigger(BEFORE_VALIDATION_EVENT, key);
c.trigger(AFTER_VALIDATION_EVENT, key);
let v /*: ValidationResult */ = (validation /*: any */);
return c._setProperty(v, { key, value, oldValue });

return result;
},

/**
Expand Down Expand Up @@ -689,12 +701,6 @@ export function changeset(
// Shorthand for `this`.
let c /*: ChangesetDef */ = this;

// Error case.
if (!isValid) {
let v /*: ValidationErr */ = (validation /*: any */);
return c.addError(key, { value, validation: v });
}

// Happy path: remove `key` from error map.
c._deleteKey(ERRORS, key);

Expand All @@ -715,6 +721,12 @@ export function changeset(
c.notifyPropertyChange(CHANGES);
c.notifyPropertyChange(key);

// Error case.
if (!isValid) {
let v /*: ValidationErr */ = (validation /*: any */);
return c.addError(key, { value, validation: v });
}

// Return new value.
return value;
},
Expand Down Expand Up @@ -828,7 +840,9 @@ export function changeset(
key /*: string */ = ''
) /*: void */ {
let obj /*: InternalMap */ = get(this, objName);
if (obj.hasOwnProperty(key)) delete obj[key];
if (obj.hasOwnProperty(key)) {
delete obj[key];
}
let c /*: ChangesetDef */ = this;
c.notifyPropertyChange(`${objName}.${key}`);
c.notifyPropertyChange(objName);
Expand Down
7 changes: 4 additions & 3 deletions addon/utils/computed/object-to-array.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// @flow

import Ember from 'ember';
import { computed, get } from '@ember/object';
import { typeOf } from '@ember/utils';
import { assign as EmberAssign } from '@ember/polyfills';
import { merge } from '@ember/polyfills'

const assign = EmberAssign || merge;

/*::
import type Change from 'ember-changeset/-private/change';
import type Err from 'ember-changeset/-private/err';
*/

const { keys } = Object;
// eslint-disable-next-line ember/new-module-imports
const assign = Ember.assign || Ember.merge;

/**
* Compute the array form of an object.
Expand Down
Loading

0 comments on commit c547f08

Please sign in to comment.