2017-01-31 13:13:06 +00:00
|
|
|
import {module} from './module';
|
2017-02-01 17:55:02 +00:00
|
|
|
import {loopbackValidations} from 'vendor';
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2017-02-01 17:55:02 +00:00
|
|
|
directive.$inject = ['$interpolate', '$compile', '$window']
|
|
|
|
export function directive(interpolate, compile, $window) {
|
2017-01-31 13:13:06 +00:00
|
|
|
return {
|
|
|
|
restrict: 'A',
|
|
|
|
require: ['ngModel', '^^form'],
|
|
|
|
link: function (scope, element, attrs, ctrl) {
|
2017-02-01 17:55:02 +00:00
|
|
|
let vnValidations = $window.validations;
|
|
|
|
|
|
|
|
if(!attrs['vnValidation'])
|
|
|
|
return;
|
|
|
|
|
|
|
|
let split = attrs['vnValidation'].split('.');
|
|
|
|
|
|
|
|
if(split.length != 2)
|
|
|
|
throw new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`);
|
|
|
|
|
|
|
|
let entityName = firstUpper(split[0]);
|
|
|
|
let fieldName = split[1];
|
|
|
|
|
|
|
|
let entity = vnValidations[entityName];
|
|
|
|
|
|
|
|
if(!entity)
|
|
|
|
throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`);
|
|
|
|
|
|
|
|
let validations = entity.validations[fieldName];
|
|
|
|
|
|
|
|
if(!validations)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let input = ctrl[0],
|
2017-01-31 13:13:06 +00:00
|
|
|
form = ctrl[1],
|
|
|
|
messages = [],
|
|
|
|
customValidator = {},
|
|
|
|
parentMessage;
|
|
|
|
|
2017-02-01 17:55:02 +00:00
|
|
|
let i = 0;
|
|
|
|
for(let conf of validations){
|
|
|
|
let key = `v${i++}`;
|
|
|
|
|
|
|
|
let messageNode = createMessage(form.$name, input.$name, key);
|
|
|
|
customValidator[key] = messageNode;
|
|
|
|
messages.push(messageNode);
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2017-02-01 17:55:02 +00:00
|
|
|
input.$validators[key] = function(value) {
|
|
|
|
return isValid(value, conf, messageNode);
|
|
|
|
}
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
|
|
|
|
if(messages.length > 0) {
|
|
|
|
parentMessage = angular.element(createMessages(form.$name, input.$name));
|
|
|
|
messages.forEach(function (item) {
|
|
|
|
parentMessage.append(item);
|
|
|
|
});
|
|
|
|
messages = null;
|
|
|
|
element.after(compile(parentMessage)(scope));
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
|
|
|
|
scope.$on('destroy', function() {
|
|
|
|
customValidator = null;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function firstUpper(str) {
|
|
|
|
return str.charAt(0).toUpperCase() + str.substr(1);
|
|
|
|
}
|
|
|
|
function createMessage(form, input, key) {
|
|
|
|
var template = '<span class="mdl-textfield__error" ng-show="{{form}}.$dirty && {{form}}.{{input}}.$error.{{key}}"></span>';
|
|
|
|
var span = interpolate(template)({ form: form, input: input, key: key });
|
|
|
|
var element = angular.element(span);
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
function createMessages(form, input) {
|
|
|
|
var template = '<div ng-show="{{form}}.$dirty && {{form}}.{{input}}.$invalid"></div>';
|
|
|
|
return interpolate(template)({ form: form, input: input });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loopback code with modifications
|
|
|
|
|
|
|
|
function isValid(value, conf, messageNode) {
|
|
|
|
let valid = true;
|
|
|
|
let inst = {value: value};
|
|
|
|
let validator = validators[conf.validation];
|
|
|
|
|
|
|
|
if(!validator)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
validator.call(inst, 'value', conf, err);
|
|
|
|
|
|
|
|
function err(kind) {
|
|
|
|
var message, code = conf.code || conf.validation;
|
|
|
|
if (conf.message) {
|
|
|
|
message = conf.message;
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
if (!message && defaultMessages[conf.validation]) {
|
|
|
|
message = defaultMessages[conf.validation];
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
if (!message) {
|
|
|
|
message = 'is invalid';
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
if (kind) {
|
|
|
|
code += '.' + kind;
|
|
|
|
if (message[kind]) {
|
|
|
|
message = message[kind];
|
|
|
|
} else if (defaultMessages.common[kind]) {
|
|
|
|
message = defaultMessages.common[kind];
|
|
|
|
} else {
|
|
|
|
message = 'is invalid';
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
messageNode.text(message); // code
|
|
|
|
valid = false;
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
module.directive('vnValidation', directive);
|
|
|
|
|
|
|
|
// Code portion of 'lib/validations.js' from 'loopback-datasource-juggler' package
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Presence validator
|
|
|
|
*/
|
|
|
|
function validatePresence(attr, conf, err, options) {
|
|
|
|
if (blank(this[attr])) {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Absence validator
|
|
|
|
*/
|
|
|
|
function validateAbsence(attr, conf, err, options) {
|
|
|
|
if (!blank(this[attr])) {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Length validator
|
|
|
|
*/
|
|
|
|
function validateLength(attr, conf, err, options) {
|
|
|
|
if (nullCheck.call(this, attr, conf, err)) return;
|
|
|
|
|
|
|
|
var len = this[attr].length;
|
|
|
|
if (conf.min && len < conf.min) {
|
|
|
|
err('min');
|
|
|
|
}
|
|
|
|
if (conf.max && len > conf.max) {
|
|
|
|
err('max');
|
|
|
|
}
|
|
|
|
if (conf.is && len !== conf.is) {
|
|
|
|
err('is');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Numericality validator
|
|
|
|
*/
|
|
|
|
function validateNumericality(attr, conf, err, options) {
|
|
|
|
if (nullCheck.call(this, attr, conf, err)) return;
|
|
|
|
|
|
|
|
if (typeof this[attr] !== 'number' || isNaN(this[attr])) {
|
|
|
|
return err('number');
|
|
|
|
}
|
|
|
|
if (conf.int && this[attr] !== Math.round(this[attr])) {
|
|
|
|
return err('int');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Inclusion validator
|
|
|
|
*/
|
|
|
|
function validateInclusion(attr, conf, err, options) {
|
|
|
|
if (nullCheck.call(this, attr, conf, err)) return;
|
|
|
|
|
|
|
|
if (!~conf.in.indexOf(this[attr])) {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Exclusion validator
|
|
|
|
*/
|
|
|
|
function validateExclusion(attr, conf, err, options) {
|
|
|
|
if (nullCheck.call(this, attr, conf, err)) return;
|
|
|
|
|
|
|
|
if (~conf.in.indexOf(this[attr])) {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Format validator
|
|
|
|
*/
|
|
|
|
function validateFormat(attr, conf, err, options) {
|
|
|
|
if (nullCheck.call(this, attr, conf, err)) return;
|
|
|
|
|
|
|
|
if (typeof this[attr] === 'string') {
|
|
|
|
if (!this[attr].match(conf['with'])) {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Custom validator
|
|
|
|
*/
|
|
|
|
function validateCustom(attr, conf, err, options, done) {
|
|
|
|
if (typeof options === 'function') {
|
|
|
|
done = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
conf.customValidator.call(this, err, done);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Uniqueness validator
|
|
|
|
*/
|
|
|
|
function validateUniqueness(attr, conf, err, options, done) {
|
|
|
|
if (typeof options === 'function') {
|
|
|
|
done = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
if (blank(this[attr])) {
|
|
|
|
return process.nextTick(done);
|
|
|
|
}
|
|
|
|
var cond = {where: {}};
|
|
|
|
cond.where[attr] = this[attr];
|
|
|
|
|
|
|
|
if (conf && conf.scopedTo) {
|
|
|
|
conf.scopedTo.forEach(function(k) {
|
|
|
|
var val = this[k];
|
|
|
|
if (val !== undefined)
|
|
|
|
cond.where[k] = this[k];
|
|
|
|
}, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
var idName = this.constructor.definition.idName();
|
|
|
|
var isNewRecord = this.isNewRecord();
|
|
|
|
this.constructor.find(cond, options, function(error, found) {
|
|
|
|
if (error) {
|
|
|
|
err(error);
|
|
|
|
} else if (found.length > 1) {
|
|
|
|
err();
|
|
|
|
} else if (found.length === 1 && idName === attr && isNewRecord) {
|
|
|
|
err();
|
|
|
|
} else if (found.length === 1 && (
|
|
|
|
!this.id || !found[0].id || found[0].id.toString() != this.id.toString()
|
|
|
|
)) {
|
|
|
|
err();
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2017-02-01 17:55:02 +00:00
|
|
|
done();
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
var validators = {
|
|
|
|
presence: validatePresence,
|
|
|
|
absence: validateAbsence,
|
|
|
|
length: validateLength,
|
|
|
|
numericality: validateNumericality,
|
|
|
|
inclusion: validateInclusion,
|
|
|
|
exclusion: validateExclusion,
|
|
|
|
format: validateFormat,
|
|
|
|
custom: validateCustom,
|
|
|
|
uniqueness: validateUniqueness,
|
|
|
|
};
|
|
|
|
|
|
|
|
var defaultMessages = {
|
|
|
|
presence: 'can\'t be blank',
|
|
|
|
absence: 'can\'t be set',
|
|
|
|
'unknown-property': 'is not defined in the model',
|
|
|
|
length: {
|
|
|
|
min: 'too short',
|
|
|
|
max: 'too long',
|
|
|
|
is: 'length is wrong',
|
|
|
|
},
|
|
|
|
common: {
|
|
|
|
blank: 'is blank',
|
|
|
|
'null': 'is null',
|
|
|
|
},
|
|
|
|
numericality: {
|
|
|
|
'int': 'is not an integer',
|
|
|
|
'number': 'is not a number',
|
|
|
|
},
|
|
|
|
inclusion: 'is not included in the list',
|
|
|
|
exclusion: 'is reserved',
|
|
|
|
uniqueness: 'is not unique',
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if attribute is undefined or null. Calls err function with 'blank' or 'null'.
|
|
|
|
* See defaultMessages. You can affect this behaviour with conf.allowBlank and conf.allowNull.
|
|
|
|
* @param {String} attr Property name of attribute
|
|
|
|
* @param {Object} conf conf object for validator
|
|
|
|
* @param {Function} err
|
|
|
|
* @return {Boolean} returns true if attribute is null or blank
|
|
|
|
*/
|
|
|
|
function nullCheck(attr, conf, err) {
|
|
|
|
// First determine if attribute is defined
|
|
|
|
if (typeof this[attr] === 'undefined') {
|
|
|
|
if (!conf.allowBlank) {
|
|
|
|
err('blank');
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// Now check if attribute is null
|
|
|
|
if (this[attr] === null) {
|
|
|
|
if (!conf.allowNull) {
|
|
|
|
err('null');
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Return true when v is undefined, blank array, null or empty string
|
|
|
|
* otherwise returns false
|
|
|
|
*
|
|
|
|
* @param {Mix} v
|
|
|
|
* Returns true if `v` is blank.
|
|
|
|
*/
|
|
|
|
function blank(v) {
|
|
|
|
if (typeof v === 'undefined') return true;
|
|
|
|
if (v instanceof Array && v.length === 0) return true;
|
|
|
|
if (v === null) return true;
|
|
|
|
if (typeof v === 'number' && isNaN(v)) return true;
|
|
|
|
if (typeof v == 'string' && v === '') return true;
|
|
|
|
return false;
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|