diff --git a/addon/validations/error.js b/addon/validations/error.js new file mode 100644 index 00000000..60fea659 --- /dev/null +++ b/addon/validations/error.js @@ -0,0 +1,6 @@ +import Ember from 'ember'; + +export default Ember.Object.extend({ + attribute: null, + message: null +}); diff --git a/addon/validations/factory.js b/addon/validations/factory.js index 91919ddc..fe61adb8 100644 --- a/addon/validations/factory.js +++ b/addon/validations/factory.js @@ -127,6 +127,23 @@ function createGlobalValidationProps(validatableAttrs) { return get(this, 'messages.0'); })); + + props.errors = computed(...validatableAttrs.map((attr) => `attrs.${attr}.@each.errors`), function() { + var errors = []; + validatableAttrs.forEach((attr) => { + var validation = get(this, `attrs.${attr}`); + if (validation) { + errors.push(get(validation, 'errors')); + } + }); + + return emberArray(flatten(errors)).compact(); + }); + + props.error = computed('errors.[]', cycleBreaker(function() { + return get(this, 'errors.0'); + })); + return props; } @@ -138,10 +155,10 @@ function createGlobalValidationProps(validatableAttrs) { function createMixin(GlobalValidations, AttrValidations) { return Ember.Mixin.create({ validate() { - return this.get('validations').validate(...arguments); + return get(this, 'validations').validate(...arguments); }, validateSync() { - return this.get('validations').validateSync(...arguments); + return get(this, 'validations').validateSync(...arguments); }, validations: computed(function() { return GlobalValidations.create({ diff --git a/addon/validations/result-collection.js b/addon/validations/result-collection.js index a1cf3181..f54754be 100644 --- a/addon/validations/result-collection.js +++ b/addon/validations/result-collection.js @@ -72,6 +72,23 @@ export default Ember.Object.extend({ return uniq(compact(messageArray)); })), + message: computed('messages.[]', cycleBreaker(function() { + return get(this, 'messages.0'); + })), + + errors: computed('content.@each.{error,errors}', cycleBreaker(function() { + let errors = [ + get(this, 'content').getEach('error'), + get(this, 'content').getEach('errors') + ]; + let errorArray = flatten(errors); + return uniq(compact(errorArray)); + })), + + error: computed('errors.[]', cycleBreaker(function() { + return get(this, 'errors.0'); + })), + _promise: computed('content.@each._promise', cycleBreaker(function() { var promises = get(this, 'content').getEach('_promise'); if (!isEmpty(promises)) { @@ -79,10 +96,6 @@ export default Ember.Object.extend({ } })), - message: computed('messages.[]', cycleBreaker(function() { - return get(this, 'messages.0'); - })), - value: computed('isAsync', cycleBreaker(function() { var isAsync = get(this, 'isAsync'); var promise = get(this, '_promise'); diff --git a/addon/validations/result.js b/addon/validations/result.js index 4f98f4d7..97fc3598 100644 --- a/addon/validations/result.js +++ b/addon/validations/result.js @@ -5,6 +5,7 @@ import Ember from 'ember'; import ValidationResultCollection from './result-collection'; +import ValidationError from './error'; import { hasEmberData } from '../utils/utils'; const { @@ -68,6 +69,17 @@ var ValidationsObject = Ember.Object.extend({ } } return !isNone(attrValue); + }), + + error: computed('message', 'isInvalid', 'attribute', function() { + if (get(this, 'isInvalid')) { + return ValidationError.create({ + message: get(this, 'message'), + attribute: get(this, 'attribute') + }); + } else { + return null; + } }) }); @@ -84,6 +96,8 @@ export default Ember.Object.extend({ isDirty: computed.oneWay('_validations.isDirty'), message: computed.oneWay('_validations.message'), messages: computed.oneWay('_validations.messages'), + error: computed.oneWay('_validations.error'), + errors: computed.oneWay('_validations.errors'), // This hold all the logic for the above CPs. We do this so we can easily switch this object out with a different validations object _validations: computed('model', 'attribute', '_promise', function() { diff --git a/docs/docs/validating.md b/docs/docs/validating.md index 3779e66e..90989d8f 100644 --- a/docs/docs/validating.md +++ b/docs/docs/validating.md @@ -34,7 +34,7 @@ model.validate({ ``` # Inspecting Validations -All validations can be accessed via the `validations` object created on your model/object. Each attribute also has its own validation which has the same properties. An attribute validation can be accessed via `validations.attrs.`. If you want to use [Ember Data's Errors API](http://emberjs.com/api/data/classes/DS.Errors.html), check out their docs on how to access everything you might need. +All validations can be accessed via the `validations` object created on your model/object. Each attribute also has its own validation which has the same properties. An attribute validation can be accessed via `validations.attrs.`. If you want to use [Ember Data's Errors API](http://emberjs.com/api/data/classes/DS.Errors.html), check out their docs on how to access everything you might need. **isValid** ```javascript @@ -110,3 +110,30 @@ An alias to the first message in the messages collection. get(user, 'validations.message') get(user, 'validations.attrs.username.message') ``` + +**errors** + +A collection of all errors on the object in question. Each error object includes the error message and it's associated attribute name. + +```javascript +// Example +get(user, 'validations.errors') +/* [ + * { + * attribute: 'email' + * messages: "Can't be blank" + * }, + * { + * ... + * } + * ] + */ +``` + +**error** +An alias to the first error in the errors collection. + +```javascript +// Example +get(user, 'validations.error') +``` diff --git a/tests/unit/validations/factory-general-test.js b/tests/unit/validations/factory-general-test.js index 8be2f38b..48dbf4ef 100644 --- a/tests/unit/validations/factory-general-test.js +++ b/tests/unit/validations/factory-general-test.js @@ -55,6 +55,12 @@ test("basic sync validation – invalid", function(assert) { assert.equal(object.get('validations.attrs.lastName.isValidating'), false); assert.equal(object.get('validations.attrs.lastName.message'), 'lastName should be present'); + assert.equal(object.get('validations.errors.length'), 2, 'errors length was expected to be 2'); + assert.ok(object.get('validations.errors').indexOf(object.get('validations.attrs.firstName.errors.0')) > -1, 'errors was expected to contain firstName error'); + assert.ok(object.get('validations.errors').indexOf(object.get('validations.attrs.lastName.errors.0')) > -1, 'errors was expected to contain lastName error'); + assert.equal(object.get('validations.errors.0.attribute'), 'firstName', 'error object was expected to have attribute "firstName"'); + assert.equal(object.get('validations.errors.1.attribute'), 'lastName', 'error object was expected to have attribute "lastName"'); + object.set('firstName', 'stef'); object.set('lastName', 'penner'); @@ -69,6 +75,8 @@ test("basic sync validation – invalid", function(assert) { assert.equal(object.get('validations.attrs.lastName.isValid'), true); assert.equal(object.get('validations.attrs.lastName.isValidating'), false); assert.equal(object.get('validations.attrs.lastName.message'), null); + + assert.equal(object.get('validations.errors.length'), 0, 'errors length was expected to be 0'); }); test("basic sync validation - valid", function(assert) {