ember-paper
Version:
The Ember approach to Material Design.
143 lines (128 loc) • 4.93 kB
JavaScript
/* eslint-disable ember/avoid-leaking-state-in-ember-objects, ember/no-get, prettier/prettier */
/**
* @module ember-paper
*/
import Mixin from '@ember/object/mixin';
import { assert, warn } from '@ember/debug';
import { isArray, A } from '@ember/array';
import { get, computed, defineProperty } from '@ember/object';
import { bool, reads, not } from '@ember/object/computed';
import { loc } from '@ember/string';
import { isBlank } from '@ember/utils';
import requiredValidator from 'ember-paper/validators/required';
import minValidator from 'ember-paper/validators/min';
import maxValidator from 'ember-paper/validators/max';
import minlengthValidator from 'ember-paper/validators/minlength';
import maxlengthValidator from 'ember-paper/validators/maxlength';
import { invokeAction } from 'ember-paper/utils/invoke-action';
/**
* In order to make validation generic it is required that components using the validation mixin
* specify what property the validation logic should be based on.
*
* @public
*
* @return computed property that depends on the supplied property name
*/
function buildComputedValidationMessages(property, validations = [], customValidations = []) {
let validationParams = validations.map((v) => get(v, 'param')).filter((v) => !isBlank(v));
let customValidationParams = customValidations.map((v) => get(v, 'param')).filter((v) => !isBlank(v));
return computed(property, 'errors.[]', 'customValidations.[]', ...validationParams, ...customValidationParams, function() {
let validations = A();
let messages = A();
// built-in validations
validations.pushObjects(this.validations());
// custom validations
let customValidations = this.customValidations;
assert('`customValidations` must be an array', isArray(customValidations));
validations.pushObjects(customValidations);
// execute validations
let currentValue = this.get(property);
validations.forEach((validation) => {
assert('validation must include a `validate(value)` function', validation && validation.validate && typeof validation.validate === 'function');
try {
let valParam = get(validation, 'param');
let paramValue = valParam ? this.get(valParam) : undefined;
if (!validation.validate(currentValue, paramValue)) {
let message = this.get(`errorMessages.${valParam}`) || get(validation, 'message');
messages.pushObject({
message: loc(message.string || message, paramValue, currentValue)
});
}
} catch(error) {
warn(`Exception with validation: ${validation} ${error}`, false);
}
});
// error messages array
let errors = this.errors || [];
assert('`errors` must be an array', isArray(errors));
messages.pushObjects(errors.map((e) => {
return get(e, 'message') ? e : { message: e };
}));
return messages;
});
}
/**
* @class ValidationMixin
* @extends Ember.Mixin
*/
export default Mixin.create({
validationErrorMessages: null,
lastIsInvalid: undefined,
validationProperty: null, // property that validation should be based on
init() {
this._super(...arguments);
assert('validationProperty must be set', this.validationProperty);
if (!this.validationErrorMessages) {
let computedValidationMessages = buildComputedValidationMessages(
this.validationProperty,
this.validations(),
this.customValidations
);
defineProperty(this, 'validationErrorMessages', computedValidationMessages);
}
},
hasErrorMessages: bool('validationErrorMessages.length'),
/**
* The result of isInvalid is appropriate for controlling the display of
* validation error messages. It also may be used to distinguish whether
* the input would be considered valid after it is touched.
*
* @public
*
* @return {boolean} Whether the input is or would be invalid.
* false: input is valid (touched or not), or is no longer rendered
* true: input has been touched and is invalid.
*/
isInvalid: reads('hasErrorMessages'),
isValid: not('isInvalid'),
/**
* Return the built-in validations.
*
* May be overridden to provide additional built-in validations. Be sure to
* call this._super() to retrieve the standard validations.
*
* @public
*/
validations() {
return [
requiredValidator,
minValidator,
maxValidator,
minlengthValidator,
maxlengthValidator
];
},
notifyValidityChange() {
let isValid = this.isValid;
let lastIsValid = this.lastIsValid;
let isTouched = this.isTouched;
let lastIsTouched = this.lastIsTouched;
if (lastIsValid !== isValid || lastIsTouched !== isTouched) {
invokeAction(this, 'onValidityChange', isValid);
this.set('lastIsValid', isValid);
this.set('lastIsTouched', isTouched);
}
},
customValidations: [],
errors: []
});