Valib.js
A validation library for the browser and Node.js.
Whether you need to check data that has been just entered into a form or ensure that your models have been given the allowed data, you'll need a validation library. Valib.js want to be your reference library when it's time to validate something, be it a form in a browser or a Backbone model, perhaps in Node.js.
You can read through the annotated source code or run the tests.
Tests pass on IE >= 6, Firefox >= 3.6, Chrome, Opera, Safari, iPhone and more.
Rationale
Read below why this library was built.
Installation
- old style just download the source, zero dependencies.
- npm npm install valib
- bower bower install valib
API
Note: for brevity the examples suppose that valib has been assigned to the variable v.
Type
isNumber v.Type.isNumber(value)
Test if value is of type Number. Note that a string containing a number is not of type Number: use valib.String.isNumeric() for that kind of test.
v.Type.isNumber(2.8)
=> true
v.Type.isNumber('2.8')
=> false
isString v.Type.isString(value)
Test if value is of type String.
v.Type.isString('hello world')
=> true
v.Type.isString(null)
=> false
isBoolean v.Type.isBoolean(value)
Test if value is of type Boolean.
v.Type.isBoolean(true)
=> true
v.Type.isBoolean(1)
=> false
isArray v.Type.isArray(value)
Test if value is an instance of Array.
v.Type.isArray([1, 2, 3])
=> true
v.Type.isArray(null)
=> false
isObject v.Type.isObject(value)
Test if value is of type Object. Note that arrays do not pass this test (while being of type object in regular javascript).
v.Type.isObject({x: 1})
=> true
v.Type.isObject([1, 2, 3])
=> false
isFunction v.Type.isFunction(value)
Test if value is of type Function.
v.Type.isFunction(map)
=> true
isDate v.Type.isDate(value)
Test if value is of type Date.
v.Type.isDate(new Date())
=> true
v.Type.isDate(Date()) // Date() return a string
=> false
isRegExp v.Type.isRegExp(value)
Test if value is of type RegExp.
v.Type.isRegExp(new RegExp('[Vv]alib'))
=> true
v.Type.isRegExp(/[Vv]alib/)
=> true
isUndefined v.Type.isUndefined(value)
Test if value is of type undefined.
v.Type.isUndefined({}.foo)
=> true
v.Type.isUndefined(null)
=> false
isNull v.Type.isNull(value)
Test if value is null.
v.Type.isNull(null)
=> true
v.Type.isNull(undefined)
=> false
isNaN v.Type.isNaN(value)
Test if value is NaN. Note that this is different from the builtin isNaN() (which checks if a value is not coercible to a number, but NaN itself is of type... Number).
v.Type.isNaN(NaN)
=> true
v.Type.isNaN('foo')
=> false
isNaN(NaN)
=> true
isNaN('foo')
=> true
Number
isInteger v.Number.isInteger(n)
Test if n does not have a decimal part.
v.Number.isInteger(1)
=> true
v.Number.isInteger(1.0) // 1.0 === 1
=> true
v.Number.isInteger(1.2)
=> false
isFloat v.Number.isFloat(n)
Test if n has a decimal part.
v.Number.isFloat(1)
=> false
v.Number.isFloat(1.0) // 1.0 === 1
=> false
v.Number.isFloat(1.2)
=> true
isInfinity v.Number.isInfinity(n)
Test if n is +/- Infinity.
v.Number.isInfinity(1 / 0)
=> true
isZero v.Number.isZero(n)
Test if n is zero.
v.Number.isZero(0)
=> true
v.Number.isZero(null)
=> false
inRange v.Number.inRange(n, min, max[, options])
Test if n is between min and max. By default both bounds are included: you can exclude either bound via the options object, {l_exc: true} means "left bound excluded" while {r_exc: true} means "right bound excluded".
v.Number.inRange(5, 0, 10)
=> true
v.Number.inRange(0, 0, 10)
=> true
v.Number.inRange(0, 0, 10, {l_exc: true})
=> false
lt v.Number.lt(n, max)
Test if n is lesser than max.
v.Number.lt(3, 10)
=> true
v.Number.lt(10, 10)
=> false
lte v.Number.lte(n, max)
Test if n is less than or equal to max.
v.Number.lte(3, 10)
=> true
v.Number.lte(10, 10)
=> true
gt v.Number.gt(n, min)
Test if n is greater than min.
v.Number.gt(15, 10)
=> true
v.Number.gt(15, 15)
=> false
gte v.Number.gte(n, min)
Test if n is greater than or equal to min.
v.Number.gte(15, 10)
=> true
v.Number.gte(15, 15)
=> true
String
isNumeric v.String.isNumeric(str[, options])
Test if str represents a number. options is an optional object and can contain the following fields:
- canBeSigned, boolean, defaults true
- simple, boolean, defaults false
When the simple option is true then only decimal notation is accepted (so 2e3, 0x7, etc. are not allowed).
// decimals
v.String.isNumeric('120.3')
=> true
// hexadecimals
v.String.isNumeric('-0x10')
=> true
// octals
v.String.isNumeric('010')
=> true
// scientific notation
v.String.isNumeric('2e3')
=> true
// you may accept only integers and decimals
v.String.isNumeric('-0x10', {simple: true})
=> false
// or just unsigned numbers
v.String.isNumeric('-2', {canBeSigned: false})
=> false
toNumber v.String.toNumber(str[, options])
Convert a string to a number. null is returned if the string cannot be converted.
options is optional and has the same format of String.isNumeric().
v.String.toNumber('2.5')
=> 2.5
v.String.toNumber('-0x10')
=> -16
v.String.toNumber('-2', {canBeSigned: false})
=> null
trim v.String.trim(str)
A cross browser version of String.trim() that takes into account any unicode whitespace character.
v.String.trim(' hello world \u00A0 ') === 'hello world'
=> true
startsWith v.String.startsWith(str, prefix)
Test if str starts with prefix
v.String.startsWith('hello world', 'hello')
=> true
endsWith v.String.endsWith(str, suffix)
Test if str ends with suffix
v.String.endsWith('hello world', 'world')
=> true
isEmpty v.String.isEmpty(str)
Test if str is of length 0.
v.String.isEmpty(' ')
=> false
match v.String.match(str, regOrString[, options])
If regOrString is a string tests if str is equal to regOrString, else it expects regOrString to be a regular expression to be tested against.
options is an object with options. The only supported option right now is trim: when enabled str is trimmed before the matching occurs.
v.String.match('hello', ' hello ', {trim: true})
=> true
v.String.match('hello', /hell./)
=> true
isEmailLike v.String.isEmailLike(str)
Test if str is a plausible e-mail. This isn't a strict check following the most up-to-date RFC about e-mail addresses, but a lax control. If it is something with a '@' and no spaces, it resembles an e-mail, and it passes. Checking against the RFC is pointless when the World doesn't follow the standard.
v.String.isEmailLike('foo@bar.com')
=> true
v.String.isEmailLike('admin@localhost')
=> true
v.String.isEmailLike('john smith@smith@whatever')
=> false
isMD5 v.String.isMD5(str)
Test if str is a plausible MD5 checksum.
v.String.isMD5('5b717e31e77094e7bae24c2f0ca43ae0')
=> true
isSHA1 v.String.isSHA1(str)
Test if str is a plausible SHA1 checksum.
v.String.isSHA1('23dea0f9d56cfab1380f31edb14d77ccc1fa8d26')
=> true
isUrl v.String.isUrl(str)
Test if str is a well formed http(s)/ftp URL.
v.String.isUrl('http://www.sideralis.org/valib')
=> true
v.String.isUrl('http://user:password@host.com/foo?bar=1')
=> true
length.eq v.String.length.eq(str, n)
length.gt v.String.length.gt(str, n)
length.gte v.String.length.gte(str, n)
length.lt v.String.length.lt(str, n)
length.lte v.String.length.lte(str, n)
Test if str length is within some constraints (lesser/greater/equal to n).
v.String.length.eq('abc', 3)
=> true
v.String.length.gt('abc', 1)
=> true
v.String.length.gte('abc', 4)
=> false
v.String.length.lt('abc', 3)
=> false
v.String.length.lte('abc', 3)
=> true
Array
indexOf v.Array.indexOf(array, value[, fromIndex])
Returns the first index at which value can be found in array, or -1 if it is not present. If the optional fromIndex is passed, the search will start from there (both positive and negative indexes are allowed).
v.Array.indexOf(['a', 'b', 'c'], 'c')
=> 2
v.Array.indexOf(['a', 'b', 'c'], 'd')
=> -1
v.Array.indexOf(['a', 'b', 'c'], 'b', 2)
=> -1
has v.Array.has(array, value[, fromIndex])
Test if array contains value. Starts to search from index 0 unless the optional fromIndex is present (it can be either positive or negative).
v.Array.has(['a', 'b', 'c'], 'c')
=> true
v.Array.has(['a', 'b', 'c'], 'd')
=> false
v.Array.has(['a', 'b', 'c'], 'b', -1)
=> false
isEmpty v.Array.isEmpty(array)
Test if array does not contain any value.
v.Array.isEmpty([])
=> true
v.Array.isEmpty(['a', 'b', 'c'])
=> false
length.eq v.Array.length.eq(array, n)
length.gt v.Array.length.gt(array, n)
length.gte v.Array.length.gte(array, n)
length.lt v.Array.length.lt(array, n)
length.lte v.Array.length.lte(array, n)
Test if array length is within some constraints (lesser/greater/equal to n).
v.Array.length.eq(['a', 'b', 'c'], 3)
=> true
v.Array.length.gt(['a', 'b', 'c'], 1)
=> true
v.Array.length.gte(['a', 'b', 'c'], 4)
=> false
v.Array.length.lt(['a', 'b', 'c'], 3)
=> false
v.Array.length.lte(['a', 'b', 'c'], 3)
=> true
Object
hasKey v.Object.hasKey(object, key)
Test if object has a (not inherited) property named key.
v.Object.hasKey({'x': 1}, 'x')
=> true
hasValue v.Object.hasValue(object, value)
Test if object has a (not inherited) property whose value is value.
v.Object.hasValue({'x': 1}, 1)
=> true
isEmpty v.Object.isEmpty(object)
Test if object has any property at all (skipping inherited properties).
v.Object.isEmpty({})
=> true
v.Object.isEmpty({'x': 1})
=> false
countKeys v.Object.countKeys(object)
Return the number of properties in object (skipping inherited properties).
v.Object.countKeys({})
=> 0
v.Object.countKeys({'x': 10, 'y': 5})
=> 2
Date
Dates are complicated and these functions have to be used with care.
If a function accept multiple dates they must be in the same timezone.
You are also limited to the browser's default timezone, so do not use dates that have been created/modified elsewhere.
This is a big known limitation, you can't set a timezone: if a function must use the current time to detect something,
it will use the current local time (e.g. isToday(date) will compare the current LOCAL datetime to the date provided).
isToday v.Date.isToday(d)
Test if d match the current day (in localtime).
v.Date.isToday(new Date())
=> true
isTomorrow v.Date.isTomorrow(d)
Test if d match tomorrow's day (in localtime).
// assuming today is 2020/01/06
v.Date.isTomorrow(new Date('2020/01/07'))
=> true
v.Date.isTomorrow(new Date())
=> false
isYesterday v.Date.isYesterday(d)
Test if d match yesterday's day (in localtime).
// assuming today is 2020/01/06
v.Date.isYesterday(new Date('2020/01/05'))
=> true
v.Date.isYesterday(new Date())
=> false
today v.Date.today()
Return today's date (in localtime) at 00:00:00.
// assuming today is 2020/01/06
v.Date.today()
=> Date {Mon Jan 06 2020 00:00:00 GMT+0100 (CET)} // timezone may vary
tomorrow v.Date.tomorrow()
Return tomorrow's date (in localtime) at 00:00:00.
// assuming today is 2020/01/06
v.Date.tomorrow()
=> Date {Tue Jan 07 2020 00:00:00 GMT+0100 (CET)} // timezone may vary
yesterday v.Date.yesterday()
Test if d matches yesterday's day (in localtime).
// assuming today is 2020/01/06
v.Date.yesterday()
=> Date {Sun Jan 05 2020 00:00:00 GMT+0100 (CET)} // timezone may vary
isNextDay v.Date.isNextDay(d, future)
Test if future is the day after d.
v.Date.isNextDay(new Date('2020/01/07'),
new Date('2020/01/08'))
=> true
isPreviousDay v.Date.isPreviousDay(d, past)
Test if past is the day before d.
v.Date.isPreviousDay(new Date('2020/01/07'),
new Date('2020/01/06'))
=> true
isSameDay v.Date.isSameDay(d1, d2)
Test if d1 has the same year/month/day as d2.
v.Date.isSameDay(new Date('2020/01/07 12:30:46'),
new Date('2020/01/07 23:02:18'))
=> true
isSameMonth v.Date.isSameMonth(d1, d2)
Test if d1 has the same year/month as d2.
v.Date.isSameMonth(new Date('2020/01/07 12:30:46'),
new Date('2020/01/23 23:02:18'))
=> true
isSameWeek v.Date.isSameWeek(d1, d2[, weekStartsAtSunday=true])
Test if d1 is in the same week of the year as d2. If the optional weekStartsAtSunday is set to false weeks will start at Monday.
// 2020/01/05 is Sunday
v.Date.isSameWeek(new Date('2020/01/05'),
new Date('2020/01/06'))
=> true
v.Date.isSameWeek(new Date('2020/01/05'),
new Date('2020/01/06'),
false)
=> false
clone v.Date.clone(d)
Return a copy of the date d.
v.Date.clone(new Date('2020/01/07 12:30:46'))
=> Date {Tue Jan 07 2020 12:30:46 GMT+0100 (CET)}
isEqual v.Date.isEqual(d1, d2)
Test if d1 and d2 represent the exact same datetime.
var dateStr = '2020/01/07 12:30:46',
d1 = new Date(dateStr),
d2 = new Date(dateStr);
v.Date.isEqual(d1,d2)
=> true
elapsedDays v.Date.elapsedDays(d1, d2)
Return the absolute difference in number of (calendar) days between two dates.
v.Date.elapsedDays(
new Date('2020/01/07 00:00:00'),
new Date('2020/01/08 18:00:00')
)
=> 1
nDaysFromDate v.Date.nDaysFromDate(n[, d])
Return the nth day after/before d.
n is the (positive or negative) number of days to move from d.
If d is missing the current date is used.
// assuming today is 2020/01/06
v.Date.nDaysFromDate(2)
=> Date {Wed Jan 08 2020 00:00:00 GMT+0100 (CET)}
// assuming today is 2020/01/06
v.Date.nDaysFromDate(-2)
=> Date {Sat Jan 04 2020 00:00:00 GMT+0100 (CET)}
v.Date.nDaysFromDate(2, new Date('2020/12/03'))
=> Date {Sat Dec 05 2020 00:00:00 GMT+0100 (CET)}
isWithinDays v.Date.isWithinDays(d, nDays[, startFrom])
Test if d will occur at most nDays after startFrom.
If startFrom is missing the current date is used.
If d is an earlier date than startFrom the function will always return false.
// assuming today is 2020/01/06
v.Date.isWithinDays(new Date('2020/01/07'), 1)
=> true
// assuming today is 2020/01/06
v.Date.isWithinDays(new Date('2020/01/07'), 5)
=> true
v.Date.isWithinDays(new Date('2020/12/05 01:00:00'),
4,
new Date('2020/12/01 23:00:00'))
=> true
toStartOfTheDay v.Date.toStartOfTheDay(d)
Return a new date at the same year/month/day of d but with the time set at 00:00:00 (and 0 milliseconds).
var d1 = new Date('2020/01/07 12:30:46'),
d2 = null;
d2 = v.Date.toStartOfTheDay(d1)
=> Date {Tue Jan 07 2020 00:00:00 GMT+0100 (CET)}
d1
=> Date {Tue Jan 07 2020 12:30:46 GMT+0100 (CET)}
Rationale
When I needed it, I couldn't find a library matching my requirements.
The library I was searching for:
- doesn't do automatic conversions: strings are strings, numbers are numbers, etc...
- works both on a browser and Node.js.
- uses a strict set of whitespaces for trim() (native String.trim() gives different results across browsers/versions).
- supports negative hexadecimals (0 + (new Number("-0x42")) may surprise).
- considers scientific notation an option: unless I'm writing an application used by scientists, `2e3` is a typo, not `1000`.
- is part of a decent package management system, with meaningful versions (download from "master" is wrong).
- has an AMD API (so require.js and others are supported).
- is very well tested.
- supports old browsers.
- has good documentation.
How to contribute
Fork & pull request. Don't forget about tests.
If you plan to add a feature please create an issue before.
Attributions
The layout is inspired by Backbone.js.
LICENSE
Copyright 2014 Riccardo Attilio Galli riccardo@sideralis.org [http://www.sideralis.org]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.