castjs
Version:
Validation and conversion library
378 lines (318 loc) • 9.94 kB
JavaScript
/*!
* cast - validation and conversion library
* https://github.com/jessehouchins/castjs
* copyright Jesse Houchins
* MIT License
*/
/*
1. Date min, max, within range
2. Number min, max, within range round (places)
- Integer
3. String trim, min, max, limit chars
- Email custom validation for email
4.
*/
!function (name, definition) {
if (typeof module != 'undefined') module.exports = definition()
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition)
else this[name] = definition()
}('cast', function () {
// ### EXPOSED CONSTRUCTOR
var cast = function(obj, prop){ return new Caster(obj, prop) }
cast.VERSION = '0.0.3'
// ### `Caster`
//
// Packages arguments to be converted by first chained method call
function Caster(obj, prop){
this.obj = obj
this.prop = prop
this.is = this
this.as = this
this.a = this
this.an = this
}
Caster.prototype = {}
// ### `Base`
//
// The base module that gets extended through `cast.register`.
// All registered modules will contain these methods.
// Do not overload these or your modules will misbehave.
Base = function(methods){
var aliases = {}
for (var name in methods) {
var method = methods[name]
if (typeof method == 'string') {
aliases[name] = method
continue
}
(function(module, method, name){
module[name] = function(){
if (this.valid) {
var args = Array.prototype.slice.call(arguments)
args.unshift(this.value)
method.apply(this, args)
}
return this
}
})(this, method, name)
}
for (var alias in aliases) {
var method = aliases[alias]
this[alias] = this[method]
}
}
Base.prototype = {
set: function(x){
// set value
this.value = x
if (this.prop) this.obj[this.prop] = x
// track valid state
if (this.valid) {
this.valid = this.blank || this.isValid(x)
this.invalid = !this.valid
}
},
fail: function(){
this.set(null)
},
rangeError: function(x){
this.outOfRange = true
this.fail()
},
// Expose usefull helpers for user modules
makeString: makeString,
makeNumber: makeNumber,
realTypeof: realTypeof,
trimString: trimString,
isBlank: isBlank,
isValid: isValid,
// handle errors
if: function(handlers){
if (this.blank && handlers.blank) handlers.blank()
else if (this.outOfRange && handlers.outOfRange) handlers.outOfRange(this.value)
else if (this.valid && handlers.valid) handlers.valid(this.value)
else if (this.invalid && handlers.invalid) handlers.invalid(this.original)
return this
}
}
// ### `cast.register`
//
// Register a converter as a cast module. Supports (method, fn) or object syntax.
// When using object syntax, the `initialize` method will be used as the constructor.
// All other methods will be prototype methods on the converter.
cast.register = function(type, fn) {
// Support string/fn or object syntax
var obj = fn ? {} : type
if (fn) obj[type] = {initialize: fn}
var aliases = {}
for (var type in obj) {
var module = obj[type]
if (typeof module === 'string') {
aliases[type] = module
continue
}
var fn = module.initialize
if (!fn) continue
;delete module.initialize; // yay for semicolons :(
(function(fn, type){
function Casting(obj, prop){
setup(this, obj, prop, type)
var args = Array.prototype.slice.call(arguments, 2)
args.unshift(this.value)
fn.apply(this, args)
}
Casting.prototype = new Base(module)
Caster.prototype[type] = function(opts){
return new Casting(this.obj, this.prop, opts)
}
Caster.prototype[type].prototype = module
})(fn, type)
}
for (var alias in aliases) {
var method = aliases[alias]
Caster.prototype[alias] = Caster.prototype[method]
}
}
// Used when testing
cast.unregister = function() {
for (var i=0; i<arguments.length; i++){
delete Caster.prototype[arguments[i]]
}
}
/*//// CAST MODULES ///////////////////////*/
var numeric = {
lessThan: function(x, max){
if (x >= this.makeNumber(max)) this.rangeError()
},
greaterThan: function(x, min){
if (x <= this.makeNumber(min)) this.rangeError()
},
max: function(x, max){
if (x > this.makeNumber(max)) this.rangeError()
},
min: function(x, min){
if (x < this.makeNumber(min)) this.rangeError()
},
"moreThan": "greaterThan",
"noMoreThan": "max",
"atLeast": "min",
"noLessThan": "min",
"noGreaterThan": "max"
}
var modules = {
date: {
initialize: function(x){
if (isNaN(Date.parse(x))) return this.fail()
this.set(new Date(x))
},
beginningOfDay: function(x){
if (this.realTypeof(x) !== 'date') return this.fail()
this.set(x.setSeconds(0) && x.setMinutes(0) && x.setHours(0) && x)
},
endOfDay: function(x){
if (this.realTypeof(x) !== 'date') return this.fail()
this.set(x.setSeconds(59) && x.setMinutes(59) && x.setHours(23) && x)
},
before: function(x, limit){
if (Date.parse(x) >= Date.parse(limit)) this.rangeError()
},
after: function(x, limit){
if (Date.parse(x) <= Date.parse(limit)) this.rangeError()
},
format: function(x, str){
var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']
var months = ['January','February','March','April','May','June','July','August','September','October','November','December']
var UTC = str.replace(/\s*UTC$/,'')
UTC = (str != UTC) ? (str = UTC) && 'UTC' : ''
var day = x['get'+UTC+'Day']()
var month = x['get'+UTC+'Month']()
var date = x['get'+UTC+'Date']()
var year = x['get'+UTC+'FullYear']()
var hour = x['get'+UTC+'Hours']()
var min = x['get'+UTC+'Minutes']()
var sec = x['get'+UTC+'Seconds']()
var str = str.split(/(day|DAY|dy|DY|D|month|MONTH|mon|MON|mm|m|yyyy|yy|dd|d|HH|H|MM|M|SS|S)/)
var tokens = {
day: days[day],
DAY: days[day].toUpperCase(),
dy: days[day].substring(0,3),
DY: days[day].substring(0,3).toUpperCase(),
D: days[day].substring(0,1),
month: months[month],
MONTH: months[month].toUpperCase(),
mon: months[month].substring(0,3),
MON: months[month].substring(0,3).toUpperCase(),
mm: month > 9 ? month+1 : "0"+(month+1),
m: month+1,
yyyy: year,
yy: year.toString().substring(2),
dd: date > 9 ? date : "0"+date,
d: date,
HH: hour > 9 ? hour : "0"+hour,
H: hour,
MM: min > 9 ? min : "0"+min,
M: min,
SS: sec > 9 ? sec : "0"+sec,
S: sec
}
for ( var i=0; i<str.length; i++) {
var token = str[i]
if (tokens[token]) str[i] = tokens[token]
}
this.set(str.join(''))
}
},
string: {
initialize: function(x){
this.set(this.makeString(x))
},
matches: function(x, regex){
if (!x.match(regex)) this.fail()
},
doesNotmatch: function(x, regex){
if (x.match(regex)) this.fail()
},
min: function(x, min){
if (x.length < min) this.rangeError()
},
max: function(x, max){
if (x.length > max) this.rangeError()
}
},
email: {
initialize: function(x){
x = this.makeString(x)
if (!x.match(/.+@.+\..+/)) return this.fail()
this.set(x)
}
},
float: defaults({
initialize: function(x, d){
this.set(this.makeNumber(x, d))
}
}, numeric),
integer: defaults({
initialize: function(x){
this.set(this.makeNumber(x, 0))
}
}, numeric),
// Aliased modules
'text': 'string',
'int': 'integer',
'number': 'float'
}
cast.register(modules)
// ### PRIVATE HELPER METHODS
function setup(self, obj, prop, type){
var x = obj
if (typeof prop === 'string') {
x = obj[prop]
self.obj = obj
self.prop = prop
}
self.valid = true
self.invalid = false
self.original = trimString(x)
self.blank = isBlank(self.original)
self.set(clone(x))
self.makeType = function(x){
var madule = cast(x)[type]
return module ? module().value : x
}
}
function isBlank(x){
return x === undefined || x === null || x === ''
}
function isValid(x){
return !(x === undefined || x === null || (x !== x) || (x.getTime && isNaN(x.getTime())))
}
function makeString(x){
if (isBlank(x)) return ''
var str = x.toString && x.toString() || ''
return (str.match(/\n|\f|\r/)) ? str : trimString(str)
}
function makeNumber(x, d){
if (makeString(x).match(/[^\d|\s|\$|\.|,]/g)) return NaN
var num = parseFloat(x, 10)
return d !== undefined ? parseFloat(num.toFixed(d)) : num
}
function trimString(x){
return (typeof x === 'string') ? x.replace(/^\s+|\s+$/g, '') : x
}
function realTypeof(x){
return typeof x === 'object' ? Object.prototype.toString.call(x).replace(/^\[object |\]$/g,'').toLowerCase() : typeof x
}
function clone(x){
if (x instanceof Array) return [].concat(x)
if (x instanceof Date) return new Date(x)
if (x instanceof Object) JSON.parse(JSON.stringify(x))
return x
}
function defaults(obj, src){
for (var method in src) {
if (obj[method] === undefined) obj[method] = src[method]
}
return obj
}
return cast
})