1
0
mirror of synced 2024-11-25 14:56:03 +03:00

Adds initial validation rules and support

This commit is contained in:
Justin Schroeder 2019-11-07 17:03:34 -05:00
parent a53edaa342
commit e952e46aad
14 changed files with 1579 additions and 69 deletions

View File

@ -9,10 +9,11 @@ export default {
exports: 'named',
globals: {
'is-plain-object': 'isPlainObject',
'nanoid': 'nanoid'
'nanoid': 'nanoid',
'is-url': 'isUrl'
}
},
external: ['is-plain-object', 'nanoid'],
external: ['is-plain-object', 'nanoid', 'is-url'],
plugins: [
commonjs(),
vue({

340
dist/formulate.esm.js vendored
View File

@ -1,3 +1,4 @@
import isUrl from 'is-url';
import isPlainObject from 'is-plain-object';
import nanoid from 'nanoid';
@ -160,6 +161,291 @@ function arrayify (item) {
return []
}
/**
* Given an array or string return an array of callables.
* @param {array|string} validation
* @param {array} rules and array of functions
* @return {array} an array of functions
*/
function parseRules (validation, rules) {
if (typeof validation === 'string') {
return parseRules(validation.split('|'), rules)
}
if (!Array.isArray(validation)) {
return []
}
return validation.map(function (rule) { return parseRule(rule, rules); }).filter(function (f) { return !!f; })
}
/**
* Given a string or function, parse it and return the an array in the format
* [fn, [...arguments]]
* @param {string|function} rule
*/
function parseRule (rule, rules) {
if (typeof rule === 'function') {
return [rule, []]
}
if (Array.isArray(rule) && rule.length) {
if (typeof rule[0] === 'string' && rules.hasOwnProperty(rule[0])) {
return [rules[rule.shift()], rule]
}
if (typeof rule[0] === 'function') {
return [rule.shift(), rule]
}
}
if (typeof rule === 'string') {
var segments = rule.split(':');
var functionName = segments.shift();
if (rules.hasOwnProperty(functionName)) {
return [rules[functionName], segments.length ? segments.join(':').split(',') : []]
} else {
throw new Error(("Unknown validation rule " + rule))
}
}
return false
}
/**
* Library of rules
*/
var rules = {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
if (!isRequired || ['no', 'false'].includes(isRequired)) {
return true
}
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
})())
},
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) !== undefined)
},
/**
* Rule: Value is not in stack.
*/
not: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) === undefined)
},
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(!!stack.find(function (pattern) {
if (pattern instanceof RegExp) {
return pattern.test(value)
}
return pattern === value
}))
},
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
return Promise.resolve(isUrl(value))
},
/**
* Rule: ensures the value is a date according to Date.parse()
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
},
/**
* Rule: checks if the value is only alpha numeric
*/
alpha: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-z][A-Z]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
return Promise.resolve(!isNaN(value))
},
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z0-9]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
return Promise.resolve((function () {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
}
from = Number(from);
to = Number(to);
if (!isNaN(value)) {
value = Number(value);
return (value > from && value < to)
}
if (typeof value === 'string') {
return value.length > from && value.length < to
}
return false
})())
},
/**
* Rule: tests
*/
email: function (value) {
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
},
/**
* Check the file type is correct.
*/
mime: function (files) {
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
for (var i in files) {
if (!types.includes(files[i].type)) {
return false
}
}
}
return true
})())
},
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value >= minimum
}
if (typeof value === 'string') {
return value.length >= minimum
}
if (Array.isArray(value)) {
return value.length >= minimum
}
return false
})())
},
/**
* Check the minimum value of a particular.
*/
max: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value <= minimum
}
if (typeof value === 'string') {
return value.length <= minimum
}
if (Array.isArray(value)) {
return value.length <= minimum
}
return false
})())
}
};
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -186,7 +472,8 @@ var context = {
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
mergedErrors: mergedErrors,
hasErrors: hasErrors
};
/**
@ -299,6 +586,13 @@ function mergedErrors () {
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Does this computed property have errors.
*/
function hasErrors () {
return !!this.mergedErrors.length
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -397,6 +691,17 @@ var script = {
type: [String, Array, Boolean],
default: false
},
validation: {
type: [String, Boolean, Array],
default: false
},
validationBehavior: {
type: String,
default: 'blur',
validator: function (value) {
return ['blur', 'live'].includes(value)
}
},
error: {
type: [String, Boolean],
default: false
@ -406,7 +711,8 @@ var script = {
return {
defaultId: nanoid(9),
localAttributes: {},
internalModelProxy: this.formulateValue
internalModelProxy: this.formulateValue,
validationErrors: []
}
},
computed: Object.assign({}, context,
@ -425,6 +731,7 @@ var script = {
deep: true
},
internalModelProxy: function internalModelProxy (newValue, oldValue) {
this.performValidation();
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue;
}
@ -440,12 +747,29 @@ var script = {
this.formulateFormRegister(this.nameOrFallback, this);
}
this.updateLocalAttributes(this.$attrs);
this.performValidation();
},
methods: {
updateLocalAttributes: function updateLocalAttributes (value) {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value;
}
},
performValidation: function performValidation () {
var this$1 = this;
var rules = parseRules(this.validation, this.$formulate.rules());
Promise.all(
rules.map(function (ref) {
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
.then(function (res) { return res ? false : 'Validation error!'; })
})
)
.then(function (result) { return result.filter(function (result) { return result; }); })
.then(function (errorMessages) { this$1.validationErrors = errorMessages; });
}
}
};
@ -539,6 +863,7 @@ var __vue_render__ = function() {
staticClass: "formulate-input",
attrs: {
"data-classification": _vm.classification,
"data-has-errors": _vm.hasErrors,
"data-type": _vm.type
}
},
@ -1601,7 +1926,8 @@ var Formulate = function Formulate () {
FormulateInputSelect: FormulateInputSelect,
FormulateInputTextArea: FormulateInputTextArea
},
library: library
library: library,
rules: rules
};
};
@ -1663,6 +1989,14 @@ Formulate.prototype.component = function component (type) {
return false
};
/**
* Get validation rules.
* @return {object} object of validation functions
*/
Formulate.prototype.rules = function rules () {
return this.options.rules
};
var Formulate$1 = new Formulate();
export default Formulate$1;

344
dist/formulate.min.js vendored
View File

@ -1,6 +1,7 @@
var Formulate = (function (exports, isPlainObject, nanoid) {
var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
'use strict';
isUrl = isUrl && isUrl.hasOwnProperty('default') ? isUrl['default'] : isUrl;
isPlainObject = isPlainObject && isPlainObject.hasOwnProperty('default') ? isPlainObject['default'] : isPlainObject;
nanoid = nanoid && nanoid.hasOwnProperty('default') ? nanoid['default'] : nanoid;
@ -163,6 +164,291 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
return []
}
/**
* Given an array or string return an array of callables.
* @param {array|string} validation
* @param {array} rules and array of functions
* @return {array} an array of functions
*/
function parseRules (validation, rules) {
if (typeof validation === 'string') {
return parseRules(validation.split('|'), rules)
}
if (!Array.isArray(validation)) {
return []
}
return validation.map(function (rule) { return parseRule(rule, rules); }).filter(function (f) { return !!f; })
}
/**
* Given a string or function, parse it and return the an array in the format
* [fn, [...arguments]]
* @param {string|function} rule
*/
function parseRule (rule, rules) {
if (typeof rule === 'function') {
return [rule, []]
}
if (Array.isArray(rule) && rule.length) {
if (typeof rule[0] === 'string' && rules.hasOwnProperty(rule[0])) {
return [rules[rule.shift()], rule]
}
if (typeof rule[0] === 'function') {
return [rule.shift(), rule]
}
}
if (typeof rule === 'string') {
var segments = rule.split(':');
var functionName = segments.shift();
if (rules.hasOwnProperty(functionName)) {
return [rules[functionName], segments.length ? segments.join(':').split(',') : []]
} else {
throw new Error(("Unknown validation rule " + rule))
}
}
return false
}
/**
* Library of rules
*/
var rules = {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
if (!isRequired || ['no', 'false'].includes(isRequired)) {
return true
}
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
})())
},
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) !== undefined)
},
/**
* Rule: Value is not in stack.
*/
not: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) === undefined)
},
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(!!stack.find(function (pattern) {
if (pattern instanceof RegExp) {
return pattern.test(value)
}
return pattern === value
}))
},
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
return Promise.resolve(isUrl(value))
},
/**
* Rule: ensures the value is a date according to Date.parse()
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
},
/**
* Rule: checks if the value is only alpha numeric
*/
alpha: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-z][A-Z]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
return Promise.resolve(!isNaN(value))
},
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z0-9]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
return Promise.resolve((function () {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
}
from = Number(from);
to = Number(to);
if (!isNaN(value)) {
value = Number(value);
return (value > from && value < to)
}
if (typeof value === 'string') {
return value.length > from && value.length < to
}
return false
})())
},
/**
* Rule: tests
*/
email: function (value) {
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
},
/**
* Check the file type is correct.
*/
mime: function (files) {
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
for (var i in files) {
if (!types.includes(files[i].type)) {
return false
}
}
}
return true
})())
},
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value >= minimum
}
if (typeof value === 'string') {
return value.length >= minimum
}
if (Array.isArray(value)) {
return value.length >= minimum
}
return false
})())
},
/**
* Check the minimum value of a particular.
*/
max: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value <= minimum
}
if (typeof value === 'string') {
return value.length <= minimum
}
if (Array.isArray(value)) {
return value.length <= minimum
}
return false
})())
}
};
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -189,7 +475,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
mergedErrors: mergedErrors,
hasErrors: hasErrors
};
/**
@ -302,6 +589,13 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Does this computed property have errors.
*/
function hasErrors () {
return !!this.mergedErrors.length
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -400,6 +694,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
type: [String, Array, Boolean],
default: false
},
validation: {
type: [String, Boolean, Array],
default: false
},
validationBehavior: {
type: String,
default: 'blur',
validator: function (value) {
return ['blur', 'live'].includes(value)
}
},
error: {
type: [String, Boolean],
default: false
@ -409,7 +714,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
return {
defaultId: nanoid(9),
localAttributes: {},
internalModelProxy: this.formulateValue
internalModelProxy: this.formulateValue,
validationErrors: []
}
},
computed: Object.assign({}, context,
@ -428,6 +734,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
deep: true
},
internalModelProxy: function internalModelProxy (newValue, oldValue) {
this.performValidation();
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue;
}
@ -443,12 +750,29 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
this.formulateFormRegister(this.nameOrFallback, this);
}
this.updateLocalAttributes(this.$attrs);
this.performValidation();
},
methods: {
updateLocalAttributes: function updateLocalAttributes (value) {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value;
}
},
performValidation: function performValidation () {
var this$1 = this;
var rules = parseRules(this.validation, this.$formulate.rules());
Promise.all(
rules.map(function (ref) {
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
.then(function (res) { return res ? false : 'Validation error!'; })
})
)
.then(function (result) { return result.filter(function (result) { return result; }); })
.then(function (errorMessages) { this$1.validationErrors = errorMessages; });
}
}
};
@ -542,6 +866,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
staticClass: "formulate-input",
attrs: {
"data-classification": _vm.classification,
"data-has-errors": _vm.hasErrors,
"data-type": _vm.type
}
},
@ -1604,7 +1929,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
FormulateInputSelect: FormulateInputSelect,
FormulateInputTextArea: FormulateInputTextArea
},
library: library
library: library,
rules: rules
};
};
@ -1666,10 +1992,18 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
return false
};
/**
* Get validation rules.
* @return {object} object of validation functions
*/
Formulate.prototype.rules = function rules () {
return this.options.rules
};
var Formulate$1 = new Formulate();
exports.default = Formulate$1;
return exports;
}({}, isPlainObject, nanoid));
}({}, isUrl, isPlainObject, nanoid));

348
dist/formulate.umd.js vendored
View File

@ -1,9 +1,10 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('is-plain-object'), require('nanoid')) :
typeof define === 'function' && define.amd ? define(['exports', 'is-plain-object', 'nanoid'], factory) :
(global = global || self, factory(global.Formulate = {}, global.isPlainObject, global.nanoid));
}(this, (function (exports, isPlainObject, nanoid) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('is-url'), require('is-plain-object'), require('nanoid')) :
typeof define === 'function' && define.amd ? define(['exports', 'is-url', 'is-plain-object', 'nanoid'], factory) :
(global = global || self, factory(global.Formulate = {}, global.isUrl, global.isPlainObject, global.nanoid));
}(this, (function (exports, isUrl, isPlainObject, nanoid) { 'use strict';
isUrl = isUrl && isUrl.hasOwnProperty('default') ? isUrl['default'] : isUrl;
isPlainObject = isPlainObject && isPlainObject.hasOwnProperty('default') ? isPlainObject['default'] : isPlainObject;
nanoid = nanoid && nanoid.hasOwnProperty('default') ? nanoid['default'] : nanoid;
@ -166,6 +167,291 @@
return []
}
/**
* Given an array or string return an array of callables.
* @param {array|string} validation
* @param {array} rules and array of functions
* @return {array} an array of functions
*/
function parseRules (validation, rules) {
if (typeof validation === 'string') {
return parseRules(validation.split('|'), rules)
}
if (!Array.isArray(validation)) {
return []
}
return validation.map(function (rule) { return parseRule(rule, rules); }).filter(function (f) { return !!f; })
}
/**
* Given a string or function, parse it and return the an array in the format
* [fn, [...arguments]]
* @param {string|function} rule
*/
function parseRule (rule, rules) {
if (typeof rule === 'function') {
return [rule, []]
}
if (Array.isArray(rule) && rule.length) {
if (typeof rule[0] === 'string' && rules.hasOwnProperty(rule[0])) {
return [rules[rule.shift()], rule]
}
if (typeof rule[0] === 'function') {
return [rule.shift(), rule]
}
}
if (typeof rule === 'string') {
var segments = rule.split(':');
var functionName = segments.shift();
if (rules.hasOwnProperty(functionName)) {
return [rules[functionName], segments.length ? segments.join(':').split(',') : []]
} else {
throw new Error(("Unknown validation rule " + rule))
}
}
return false
}
/**
* Library of rules
*/
var rules = {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
if (!isRequired || ['no', 'false'].includes(isRequired)) {
return true
}
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
})())
},
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) !== undefined)
},
/**
* Rule: Value is not in stack.
*/
not: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(stack.find(function (item) {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) === undefined)
},
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
return Promise.resolve(!!stack.find(function (pattern) {
if (pattern instanceof RegExp) {
return pattern.test(value)
}
return pattern === value
}))
},
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
return Promise.resolve(isUrl(value))
},
/**
* Rule: ensures the value is a date according to Date.parse()
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
var fieldValue = Date.parse(value);
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
},
/**
* Rule: checks if the value is only alpha numeric
*/
alpha: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-z][A-Z]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
return Promise.resolve(!isNaN(value))
},
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
if ( set === void 0 ) set = 'default';
var sets = {
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z0-9]$/
};
var selectedSet = sets.hasOwnProperty(set) ? set : 'default';
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
return Promise.resolve((function () {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
}
from = Number(from);
to = Number(to);
if (!isNaN(value)) {
value = Number(value);
return (value > from && value < to)
}
if (typeof value === 'string') {
return value.length > from && value.length < to
}
return false
})())
},
/**
* Rule: tests
*/
email: function (value) {
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
},
/**
* Check the file type is correct.
*/
mime: function (files) {
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
for (var i in files) {
if (!types.includes(files[i].type)) {
return false
}
}
}
return true
})())
},
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value >= minimum
}
if (typeof value === 'string') {
return value.length >= minimum
}
if (Array.isArray(value)) {
return value.length >= minimum
}
return false
})())
},
/**
* Check the minimum value of a particular.
*/
max: function (value, minimum) {
return Promise.resolve((function () {
minimum = Number(minimum);
if (!isNaN(value)) {
value = Number(value);
return value <= minimum
}
if (typeof value === 'string') {
return value.length <= minimum
}
if (Array.isArray(value)) {
return value.length <= minimum
}
return false
})())
}
};
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -192,7 +478,8 @@
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
mergedErrors: mergedErrors,
hasErrors: hasErrors
};
/**
@ -305,6 +592,13 @@
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Does this computed property have errors.
*/
function hasErrors () {
return !!this.mergedErrors.length
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -403,6 +697,17 @@
type: [String, Array, Boolean],
default: false
},
validation: {
type: [String, Boolean, Array],
default: false
},
validationBehavior: {
type: String,
default: 'blur',
validator: function (value) {
return ['blur', 'live'].includes(value)
}
},
error: {
type: [String, Boolean],
default: false
@ -412,7 +717,8 @@
return {
defaultId: nanoid(9),
localAttributes: {},
internalModelProxy: this.formulateValue
internalModelProxy: this.formulateValue,
validationErrors: []
}
},
computed: Object.assign({}, context,
@ -431,6 +737,7 @@
deep: true
},
internalModelProxy: function internalModelProxy (newValue, oldValue) {
this.performValidation();
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue;
}
@ -446,12 +753,29 @@
this.formulateFormRegister(this.nameOrFallback, this);
}
this.updateLocalAttributes(this.$attrs);
this.performValidation();
},
methods: {
updateLocalAttributes: function updateLocalAttributes (value) {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value;
}
},
performValidation: function performValidation () {
var this$1 = this;
var rules = parseRules(this.validation, this.$formulate.rules());
Promise.all(
rules.map(function (ref) {
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
.then(function (res) { return res ? false : 'Validation error!'; })
})
)
.then(function (result) { return result.filter(function (result) { return result; }); })
.then(function (errorMessages) { this$1.validationErrors = errorMessages; });
}
}
};
@ -545,6 +869,7 @@
staticClass: "formulate-input",
attrs: {
"data-classification": _vm.classification,
"data-has-errors": _vm.hasErrors,
"data-type": _vm.type
}
},
@ -1607,7 +1932,8 @@
FormulateInputSelect: FormulateInputSelect,
FormulateInputTextArea: FormulateInputTextArea
},
library: library
library: library,
rules: rules
};
};
@ -1669,6 +1995,14 @@
return false
};
/**
* Get validation rules.
* @return {object} object of validation functions
*/
Formulate.prototype.rules = function rules () {
return this.options.rules
};
var Formulate$1 = new Formulate();
exports.default = Formulate$1;

2
dist/snow.css vendored
View File

@ -12,7 +12,7 @@
.formulate-input .formulate-input-help {
color: #6d6d6d;
font-size: .7em;
font-weight: 300;
font-weight: 400;
line-height: 1.5;
margin-bottom: .25em; }
.formulate-input .formulate-input-errors {

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

74
package-lock.json generated
View File

@ -1129,7 +1129,8 @@
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz",
"integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==",
"dev": true
"dev": true,
"optional": true
},
"@types/babel__core": {
"version": "7.1.3",
@ -1177,6 +1178,7 @@
"resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
"integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
"dev": true,
"optional": true,
"requires": {
"@types/babel-types": "*"
}
@ -1412,6 +1414,7 @@
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@ -1423,6 +1426,7 @@
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
@ -2441,7 +2445,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
"dev": true,
"optional": true
},
"to-regex-range": {
"version": "5.0.1",
@ -2692,6 +2697,7 @@
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz",
"integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==",
"dev": true,
"optional": true,
"requires": {
"@types/babel-types": "^7.0.0",
"@types/babylon": "^6.16.2",
@ -4226,7 +4232,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -4247,12 +4254,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4267,17 +4276,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -4394,7 +4406,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -4406,6 +4419,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -4420,6 +4434,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -4427,12 +4442,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -4451,6 +4468,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -4531,7 +4549,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -4543,6 +4562,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -4628,7 +4648,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -4664,6 +4685,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -4683,6 +4705,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4726,12 +4749,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -5645,6 +5670,11 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -6241,7 +6271,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
"integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=",
"dev": true
"dev": true,
"optional": true
},
"js-tokens": {
"version": "4.0.0",
@ -6548,7 +6579,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"dev": true
"dev": true,
"optional": true
},
"loose-envify": {
"version": "1.4.0",
@ -7559,7 +7591,8 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.0.tgz",
"integrity": "sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==",
"dev": true
"dev": true,
"optional": true
},
"pify": {
"version": "2.3.0",
@ -8723,7 +8756,8 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz",
"integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==",
"dev": true
"dev": true,
"optional": true
},
"pug-filters": {
"version": "3.1.1",
@ -8843,7 +8877,8 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz",
"integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==",
"dev": true
"dev": true,
"optional": true
},
"pug-strip-comments": {
"version": "1.0.4",
@ -8859,7 +8894,8 @@
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz",
"integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==",
"dev": true
"dev": true,
"optional": true
},
"pump": {
"version": "3.0.0",

View File

@ -77,6 +77,7 @@
},
"dependencies": {
"is-plain-object": "^3.0.0",
"is-url": "^1.2.4",
"nanoid": "^2.1.6"
}
}

View File

@ -1,4 +1,5 @@
import library from './libs/library'
import rules from './libs/rules'
import isPlainObject from 'is-plain-object'
import FormulateInput from './FormulateInput.vue'
import FormulateForm from './FormulateForm.vue'
@ -8,6 +9,7 @@ import FormulateInputBox from './inputs/FormulateInputBox.vue'
import FormulateInputText from './inputs/FormulateInputText.vue'
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
import FormulateInputTextArea from './inputs/FormulateInputTextArea.vue'
/**
* The base formulate library.
*/
@ -27,7 +29,8 @@ class Formulate {
FormulateInputSelect,
FormulateInputTextArea
},
library
library,
rules
}
}
@ -88,6 +91,14 @@ class Formulate {
}
return false
}
/**
* Get validation rules.
* @return {object} object of validation functions
*/
rules () {
return this.options.rules
}
}
export default new Formulate()

View File

@ -48,7 +48,7 @@
<script>
import context from './libs/context'
import { shallowEqualObjects } from './libs/utils'
import { shallowEqualObjects, parseRules } from './libs/utils'
import nanoid from 'nanoid'
export default {
@ -131,7 +131,8 @@ export default {
return {
defaultId: nanoid(9),
localAttributes: {},
internalModelProxy: this.formulateValue
internalModelProxy: this.formulateValue,
validationErrors: []
}
},
computed: {
@ -152,6 +153,7 @@ export default {
deep: true
},
internalModelProxy (newValue, oldValue) {
this.performValidation()
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue
}
@ -167,12 +169,24 @@ export default {
this.formulateFormRegister(this.nameOrFallback, this)
}
this.updateLocalAttributes(this.$attrs)
this.performValidation()
},
methods: {
updateLocalAttributes (value) {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value
}
},
performValidation () {
const rules = parseRules(this.validation, this.$formulate.rules())
Promise.all(
rules.map(([rule, args]) => {
return rule(this.context.model, ...args)
.then(res => res ? false : 'Validation error!')
})
)
.then(result => result.filter(result => result))
.then(errorMessages => { this.validationErrors = errorMessages })
}
}
}

View File

@ -1,5 +1,5 @@
import nanoid from 'nanoid'
import { map, arrayify, parseRules } from './utils'
import { map, arrayify } from './utils'
/**
* For a single instance of an input, export all of the context needed to fully
@ -30,8 +30,7 @@ export default {
logicalLabelPosition,
isVmodeled,
mergedErrors,
hasErrors,
validationRules
hasErrors
}
/**
@ -149,13 +148,6 @@ function hasErrors () {
return !!this.mergedErrors.length
}
/**
* An array of validation rules to pass.
*/
function validationRules () {
return parseRules(this.validation)
}
/**
* Defines the model used throughout the existing context.
* @param {object} context

View File

@ -1,41 +1,220 @@
import isUrl from 'is-url'
import { shallowEqualObjects } from './utils'
/**
* Library of rules
*/
export default {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: must be a value
*/
required: async function (value) {
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
required: function (value, isRequired = true) {
return Promise.resolve((() => {
if (!isRequired || ['no', 'false'].includes(isRequired)) {
return true
}
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
})())
},
/**
* Rule: Value is in an array (stack).
*/
in: async function (value, ...stack) {
return !!stack.find(item => shallowEqualObjects(item, value))
in: function (value, ...stack) {
return Promise.resolve(stack.find(item => {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) !== undefined)
},
/**
* Rule: Value is not in stack.
*/
not: function (value, ...stack) {
return Promise.resolve(stack.find(item => {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
}
return item === value
}) === undefined)
},
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: async function (value, ...stack) {
return !!stack.find(pattern => {
matches: function (value, ...stack) {
return Promise.resolve(!!stack.find(pattern => {
if (pattern instanceof RegExp) {
return pattern.test(value)
}
return pattern === value
})
}))
},
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
return Promise.resolve(isUrl(value))
},
/**
* Rule: ensures the value is a date according to Date.parse()
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare = false) {
const timestamp = Date.parse(compare || new Date())
const fieldValue = Date.parse(value)
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare = false) {
const timestamp = Date.parse(compare || new Date())
const fieldValue = Date.parse(value)
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
},
/**
* Rule: checks if the value is only alpha numeric
*/
alpha: function (value, set = 'default') {
const sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-z][A-Z]$/
}
const selectedSet = sets.hasOwnProperty(set) ? set : 'default'
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
return Promise.resolve(!isNaN(value))
},
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set = 'default') {
const sets = {
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z0-9]$/
}
const selectedSet = sets.hasOwnProperty(set) ? set : 'default'
return Promise.resolve(sets[selectedSet].test(value))
},
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
return Promise.resolve((() => {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
}
from = Number(from)
to = Number(to)
if (!isNaN(value)) {
value = Number(value)
return (value > from && value < to)
}
if (typeof value === 'string') {
return value.length > from && value.length < to
}
return false
})())
},
/**
* Rule: tests
*/
email: function (value) {
// eslint-disable-next-line
const isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
return Promise.resolve(isEmail.test(value))
},
/**
* Check the file type is correct.
*/
mime: function (files, ...types) {
return Promise.resolve((() => {
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
for (const i in files) {
if (!types.includes(files[i].type)) {
return false
}
}
}
return true
})())
},
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum) {
return Promise.resolve((() => {
minimum = Number(minimum)
if (!isNaN(value)) {
value = Number(value)
return value >= minimum
}
if (typeof value === 'string') {
return value.length >= minimum
}
if (Array.isArray(value)) {
return value.length >= minimum
}
return false
})())
},
/**
* Check the minimum value of a particular.
*/
max: function (value, minimum) {
return Promise.resolve((() => {
minimum = Number(minimum)
if (!isNaN(value)) {
value = Number(value)
return value <= minimum
}
if (typeof value === 'string') {
return value.length <= minimum
}
if (Array.isArray(value)) {
return value.length <= minimum
}
return false
})())
}
}

View File

@ -19,6 +19,10 @@ describe('required', () => {
it('passes with a non empty array', async () => expect(await rules.required(['123'])).toBe(true))
it('passes with a non empty object', async () => expect(await rules.required({a: 'b'})).toBe(true))
it('passes with empty value if second argument is false', async () => expect(await rules.required('', false)).toBe(true))
it('passes with empty value if second argument is false string', async () => expect(await rules.required('', 'false')).toBe(true))
})
@ -75,3 +79,273 @@ describe('matches', () => {
expect(await rules.matches('first-fourth', 'second', /^third/, /fourth$/)).toBe(true)
})
})
/**
* Accepted rule
*/
describe('accepted', () => {
it('passes with true', async () => expect(await rules.accepted('yes')).toBe(true))
it('passes with on', async () => expect(await rules.accepted('on')).toBe(true))
it('passes with 1', async () => expect(await rules.accepted('1')).toBe(true))
it('passes with number 1', async () => expect(await rules.accepted(1)).toBe(true))
it('passes with boolean true', async () => expect(await rules.accepted(true)).toBe(true))
it('fail with boolean false', async () => expect(await rules.accepted(false)).toBe(false))
it('fail with "false"', async () => expect(await rules.accepted('false')).toBe(false))
})
/**
* Url rule.
*
* Note: these are just sanity checks because the actual package we use is
* well tested: https://github.com/segmentio/is-url/blob/master/test/index.js
*/
describe('url', () => {
it('passes with http://google.com', async () => expect(await rules.url('http://google.com')).toBe(true))
it('fails with google.com', async () => expect(await rules.url('google.com')).toBe(false))
})
/**
* Determines if the string is a date
*/
describe('date', () => {
it('passes with month day year', async () => expect(await rules.date('December 17, 2020')).toBe(true))
it('passes with month day', async () => expect(await rules.date('December 17')).toBe(true))
it('passes with short month day', async () => expect(await rules.date('Dec 17')).toBe(true))
it('passes with short month day', async () => expect(await rules.date('Dec 17 12:34:15')).toBe(true))
it('passes with out of bounds number', async () => expect(await rules.date('January 77')).toBe(true))
it('passes with only month', async () => expect(await rules.date('January')).toBe(false))
it('fails with only day of week', async () => expect(await rules.date('saturday')).toBe(false))
it('fails with random string', async () => expect(await rules.date('Pepsi 17')).toBe(false))
it('fails with random number', async () => expect(await rules.date('1872301237')).toBe(false))
})
/**
* Checks if a date is after another date
*/
describe('after', () => {
const today = new Date()
const tomorrow = new Date()
const yesterday = new Date()
tomorrow.setDate(today.getDate() + 1)
yesterday.setDate(today.getDate() - 1)
it('passes with tomorrows date object', async () => expect(await rules.after(tomorrow)).toBe(true))
it('passes with future date', async () => expect(await rules.after('January 15, 2999')).toBe(true))
it('passes with long past date', async () => expect(await rules.after(yesterday, 'Jan 15, 2000')).toBe(true))
it('fails with yesterdays date', async () => expect(await rules.after(yesterday)).toBe(false))
it('fails with old date string', async () => expect(await rules.after('January, 2000')).toBe(false))
it('fails with invalid value', async () => expect(await rules.after('')).toBe(false))
})
/**
* Checks if a date is after another date
*/
describe('before', () => {
const today = new Date()
const tomorrow = new Date()
const yesterday = new Date()
tomorrow.setDate(today.getDate() + 1)
yesterday.setDate(today.getDate() - 1)
it('fails with tomorrows date object', async () => expect(await rules.before(tomorrow)).toBe(false))
it('fails with future date', async () => expect(await rules.before('January 15, 2999')).toBe(false))
it('fails with long past date', async () => expect(await rules.before(yesterday, 'Jan 15, 2000')).toBe(false))
it('passes with yesterdays date', async () => expect(await rules.before(yesterday)).toBe(true))
it('passes with old date string', async () => expect(await rules.before('January, 2000')).toBe(true))
it('fails with invalid value', async () => expect(await rules.after('')).toBe(false))
})
/**
* Checks if a date is after another date
*/
describe('alpha', () => {
it('passes with simple string', async () => expect(await rules.alpha('abc')).toBe(true))
it('passes with long string', async () => expect(await rules.alpha('lkashdflaosuihdfaisudgflakjsdbflasidufg')).toBe(true))
it('passes with single character', async () => expect(await rules.alpha('z')).toBe(true))
it('passes with accented character', async () => expect(await rules.alpha('jüstin')).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alpha('àáâäïíôöÆ')).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alpha('àáâäïíôöÆ', 'russian')).toBe(true))
it('fails with lots of accented characters if latin', async () => expect(await rules.alpha('àáâäïíôöÆ', 'latin')).toBe(false))
it('fails with numbers', async () => expect(await rules.alpha('justin83')).toBe(false))
it('fails with symbols', async () => expect(await rules.alpha('-justin')).toBe(false))
})
/**
* Checks if a date is after another date
*/
describe('number', () => {
it('passes with simple number string', async () => expect(await rules.number('123')).toBe(true))
it('passes with simple number', async () => expect(await rules.number(19832461234)).toBe(true))
it('passes with float', async () => expect(await rules.number(198.32464)).toBe(true))
it('passes with decimal in string', async () => expect(await rules.number('567.23')).toBe(true))
it('fails with comma in number string', async () => expect(await rules.number('123,456')).toBe(false))
it('fails with alpha', async () => expect(await rules.number('123sdf')).toBe(false))
})
/**
* Checks if a date alpha and numeric
*/
describe('alphanumeric', () => {
it('passes with simple string', async () => expect(await rules.alphanumeric('567abc')).toBe(true))
it('passes with long string', async () => expect(await rules.alphanumeric('lkashdfla234osuihdfaisudgflakjsdbfla567sidufg')).toBe(true))
it('passes with single character', async () => expect(await rules.alphanumeric('z')).toBe(true))
it('passes with accented character', async () => expect(await rules.alphanumeric('jüst56in')).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alphanumeric('àáâ7567567äïíôöÆ')).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alphanumeric('123123àáâäï67íôöÆ', 'russian')).toBe(true))
it('fails with lots of accented characters if latin', async () => expect(await rules.alphanumeric('àáâäï123123íôöÆ', 'latin')).toBe(false))
it('fails with decimals in', async () => expect(await rules.alphanumeric('abcABC99.123')).toBe(false))
})
/**
* Checks if between
*/
describe('between', () => {
it('passes with simple number', async () => expect(await rules.between(5, 0, 10)).toBe(true))
it('passes with simple number string', async () => expect(await rules.between('5', '0', '10')).toBe(true))
it('passes with decimal number string', async () => expect(await rules.between('0.5', '0', '1')).toBe(true))
it('passes with string length', async () => expect(await rules.between('abc', 2, 4)).toBe(true))
it('fails with string length too long', async () => expect(await rules.between('abcdef', 2, 4)).toBe(false))
it('fails with string length too short', async () => expect(await rules.between('abc', 3, 10)).toBe(false))
it('fails with number to small', async () => expect(await rules.between(0, 3, 10)).toBe(false))
it('fails with number to large', async () => expect(await rules.between(15, 3, 10)).toBe(false))
})
/**
* Checks if email.
*
* Note: testing is light, regular expression used is here: http://jsfiddle.net/ghvj4gy9/embedded/result,js/
*/
describe('email', () => {
it('passes normal email', async () => expect(await rules.email('dev+123@wearebraid.com')).toBe(true))
it('passes numeric email', async () => expect(await rules.email('12345@google.com')).toBe(true))
it('passes unicode email', async () => expect(await rules.email('àlphä@❤️.ly')).toBe(true))
it('passes numeric with new tld', async () => expect(await rules.email('12345@google.photography')).toBe(true))
it('fails string without tld', async () => expect(await rules.email('12345@localhost')).toBe(false))
it('fails string without tld', async () => expect(await rules.email('12345@localhost')).toBe(false))
it('fails string without invalid name', async () => expect(await rules.email('1*(123)2345@localhost')).toBe(false))
})
/**
* Mime types.
*/
describe('mime', () => {
it('passes basic image/jpeg stack', async () => expect(await rules.mime([{type: 'image/jpeg'}], 'image/png', 'image/jpeg')).toBe(true))
it('fails when not in stack', async () => expect(await rules.mime([{type: 'application/json'}], 'image/png', 'image/jpeg')).toBe(false))
})
/**
* Minimum.
*/
describe('min', () => {
it('passes when a number string', async () => expect(await rules.min('5', '5')).toBe(true))
it('passes when a number', async () => expect(await rules.min(6, 5)).toBe(true))
it('passes when a string length', async () => expect(await rules.min('foobar', '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.min(Array(6), '6')).toBe(true))
it('fails when a array length', async () => expect(await rules.min(Array(6), '7')).toBe(false))
it('fails when a string length', async () => expect(await rules.min('bar', 4)).toBe(false))
it('fails when a number', async () => expect(await rules.min(3, '7')).toBe(false))
})
/**
* Maximum.
*/
describe('max', () => {
it('passes when a number string', async () => expect(await rules.max('5', '5')).toBe(true))
it('passes when a number', async () => expect(await rules.max(5, 6)).toBe(true))
it('passes when a string length', async () => expect(await rules.max('foobar', '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.max(Array(6), '6')).toBe(true))
it('fails when a array length', async () => expect(await rules.max(Array(6), '5')).toBe(false))
it('fails when a string length', async () => expect(await rules.max('bar', 2)).toBe(false))
it('fails when a number', async () => expect(await rules.max(10, '7')).toBe(false))
})
/**
* Maximum.
*/
describe('not', () => {
it('passes when a number string', async () => expect(await rules.not('5', '6')).toBe(true))
it('passes when a number', async () => expect(await rules.not(1, 30)).toBe(true))
it('passes when a string', async () => expect(await rules.not('abc', 'def')).toBe(true))
it('fails when a shallow equal array', async () => expect(await rules.not(['abc'], ['abc'])).toBe(false))
it('fails when a shallow equal object', async () => expect(await rules.not({a: 'abc'}, ['123'], {a: 'abc'})).toBe(false))
it('fails when string is in stack', async () => expect(await rules.not('a', 'b', 'c', 'd', 'a', 'f')).toBe(false))
})

View File

@ -20,7 +20,7 @@
.formulate-input-help {
color: $formulate-gray-ddd;
font-size: .7em;
font-weight: 300;
font-weight: 400;
line-height: 1.5;
margin-bottom: .25em;
}