├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── dist ├── ive.js └── ive.min.js ├── index.js ├── lib ├── form.js ├── only.js └── validate.js ├── package.json └── test ├── index.html └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | components 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.2.4 / 2015-01-11 3 | ================== 4 | 5 | * clear attributes immediately 6 | 7 | 0.2.3 / 2015-01-09 8 | ================== 9 | 10 | * bump squares 11 | 12 | 0.1.1 / 2015-01-08 13 | ================== 14 | 15 | * remove todo 16 | * support nested attributes with keys 17 | 18 | 0.1.0 / 2015-01-08 19 | ================== 20 | 21 | * rework form API 22 | 23 | 0.0.3 / 2015-01-07 24 | ================== 25 | 26 | * add valid attribute 27 | 28 | 0.0.2 / 2015-01-07 29 | ================== 30 | 31 | * return => continue 32 | * update readme 33 | * update readme 34 | * update readme 35 | * update readme 36 | * fix readme 37 | * readme cleanup 38 | 39 | 0.0.1 / 2015-01-07 40 | ================== 41 | 42 | * Initial release 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @node_modules/.bin/mocha 3 | 4 | browser: 5 | @node_modules/.bin/duo-serve -h test/index.html -g Schema index.js 6 | 7 | dist: components dist-build dist-minify 8 | 9 | dist-build: 10 | @mkdir -p dist/ 11 | @duo -g Ive < index.js > dist/ive.js 12 | 13 | dist-minify: dist/ive.js 14 | @curl -s \ 15 | -d compilation_level=SIMPLE_OPTIMIZATIONS \ 16 | -d output_format=text \ 17 | -d output_info=compiled_code \ 18 | --data-urlencode "js_code@$<" \ 19 | http://marijnhaverbeke.nl/uglifyjs \ 20 | > $<.tmp 21 | @mv $<.tmp dist/ive.min.js 22 | 23 | clean: 24 | @rm -rf node_modules 25 | @rm -rf components 26 | 27 | .PHONY: test browser dist 28 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # ive 3 | 4 | Isomorphic validation. Validate your forms in the browser and the form data on the server using the same schemas. 5 | 6 | ## Installation 7 | 8 | **Server:** 9 | 10 | ```bash 11 | npm install ive 12 | ``` 13 | 14 | **Browser (duo):** 15 | 16 | ```js 17 | var Ive = require('ive'); 18 | ``` 19 | 20 | **Browser (standalone):** 21 | 22 | - [ive.js](dist/ive.js) 23 | - [ive.min.js](dist/ive.min.js) 24 | 25 | **Browser (browserify):** 26 | 27 | - accepting PRs 28 | 29 | ## Features 30 | 31 | - Node & browser support 32 | - Generator-friendly 33 | - Thoughtful form validation 34 | - Data cleansing, formatting & casting 35 | - Asynchronous & custom validation 36 | - Validate against partial schemas 37 | - Informative errors 38 | - Composable schemas 39 | 40 | ## Example 41 | 42 | **user-schema.js** 43 | 44 | ```js 45 | var ive = module.exports = Ive(); 46 | 47 | ive.attr('name') 48 | .type(String) 49 | .between(2, 30) 50 | .required(true); 51 | 52 | ive.attr('phone') 53 | .assert(/^\d{10}$/) 54 | .format(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3') 55 | .required(true) 56 | 57 | ive.attr('email') 58 | .type(String) 59 | .assert(/\w+\@\w+\.\w+/) 60 | .required(true); 61 | 62 | ive.attr('age') 63 | .cast(Number) 64 | .between(18, 110) 65 | .required(true); 66 | ``` 67 | 68 | ### Browser 69 | 70 | **index.html** 71 | 72 | ```html 73 |
74 | 75 | 76 | 77 | 80 |
81 | ``` 82 | 83 | **index.js** 84 | 85 | ```js 86 | var schema = require('./user-schema'); 87 | schema(document.querySelector('.create-user')); 88 | // there is no next step! form looks for `[validate]` and 89 | // then validates the `input[name]` against Ive's schema 90 | ``` 91 | 92 | ### Server 93 | 94 | #### Express 95 | 96 | **server.js** 97 | 98 | ```js 99 | var schema = require('./user-schema'); 100 | 101 | app.post('/users', function(req, res, next) { 102 | var body = req.body; 103 | 104 | // validate 105 | schema(body, function(err, obj) { 106 | if (err) return res.send(400, { error: err }); 107 | user.create(obj, next); 108 | }); 109 | }); 110 | ``` 111 | 112 | #### Koa 113 | 114 | **server.js** 115 | 116 | ```js 117 | var schema = require('./user-schema'); 118 | 119 | app.use(_.post('/users', function *(next) { 120 | var body = this.request.body; 121 | 122 | // validate 123 | var obj = yield schema(body); 124 | yield user.create(obj); 125 | 126 | yield next; 127 | })); 128 | ``` 129 | 130 | ## API 131 | 132 | ### `Ive([attrs])` 133 | 134 | Initialize an `Ive` instance with an optional set of `attrs`. 135 | 136 | ### `ive([str], obj, [fn])` 137 | 138 | Validate `obj` against the schema, calling `fn` when the validation is complete. `fn` has the signature 139 | `function(error, val) { ... }`. `val` is the new object that may be cleansed, formatted, and cast. 140 | 141 | ```js 142 | var ive = Ive({ 143 | name: rube().type(String), 144 | email: rube().assert(/@/).type(String), 145 | age: rube().cast(Number).type(Number) 146 | }); 147 | 148 | ive({ 149 | name: 'matt', 150 | email: 'matt@lapwinglabs.com', 151 | age: '25' 152 | }, function(err, v) { 153 | assert(!err); 154 | assert('matt' == v.name); 155 | assert('matt@lapwinglabs.com' == v.email); 156 | assert(25 === v.age); 157 | done(); 158 | }); 159 | ``` 160 | 161 | If you're working with generators you can omit `fn` and Ive will return a thunk: 162 | 163 | ```js 164 | var ive = Ive({ 165 | name: rube().type(String), 166 | email: rube().assert(/@/).type(String), 167 | age: rube().cast(Number).type(Number) 168 | }); 169 | 170 | var val = yield ive({ 171 | name: 'matt', 172 | email: 'matt@lapwinglabs.com', 173 | age: '25' 174 | }); 175 | ``` 176 | 177 | You can also choose to validate against certain properties. This is useful if you're making updates 178 | to an existing document and you don't have all the properties: 179 | 180 | ```js 181 | var ive = Ive({ 182 | name: rube().type(String).required(true), 183 | email: rube().assert(/@/).type(String).required(true), 184 | age: rube().cast(Number).type(Number).required(true) 185 | }); 186 | 187 | // only validate on name and email 188 | ive('name email', { 189 | name: 'matt', 190 | email: 'matt@lapwinglabs.com' 191 | }, fn); 192 | ``` 193 | 194 | ### `ive.attr(name|ive|obj, [rube])` 195 | 196 | Add an attribute, ive instance, or object to `ive`. Optionally you may pass a `rube` instance as the key. 197 | 198 | ```js 199 | ive.attr('name', rube().required(true).type(String)); 200 | ``` 201 | 202 | If you just specify a name, ive returns a [rube](https://github.com/lapwinglabs/rube) instance. 203 | 204 | ```js 205 | ive.attr('phone') 206 | .assert(/^\d{10}$/) 207 | .format(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3') 208 | .required(true) 209 | ``` 210 | 211 | Ive schemas are composable: 212 | 213 | ```js 214 | var basic = Schema(); 215 | 216 | basic.attr('name') 217 | .type(String) 218 | .between(2, 30) 219 | .required(true); 220 | 221 | basic.attr('email') 222 | .type(String) 223 | .assert(/\w+\@\w+\.\w+/) 224 | .required(true); 225 | 226 | var admin = Schema(basic); 227 | 228 | admin.attr('password') 229 | .type(String) 230 | .between(8, 10) 231 | .assert(/[0-9]/) 232 | .required(true); 233 | ``` 234 | 235 | ## Differences to other libraries 236 | 237 | ### parsley.js 238 | 239 | - Nice for simple form validation, but it's declarative nature tends to get verbose very quickly. 240 | - No server-side support 241 | - jQuery dependency 242 | 243 | ## TODO 244 | 245 | * Better support for different form elements (radio, checkbox, etc.) 246 | * Customize the error formatting based on the environment 247 | * Subclass the generic Error 248 | 249 | ## Test 250 | 251 | Server: 252 | 253 | make test 254 | 255 | Browser: 256 | 257 | make browser 258 | 259 | ## License 260 | 261 | (The MIT License) 262 | 263 | Copyright (c) 2014 Matthew Mueller <matt@lapwinglabs.com> 264 | 265 | Permission is hereby granted, free of charge, to any person obtaining 266 | a copy of this software and associated documentation files (the 267 | 'Software'), to deal in the Software without restriction, including 268 | without limitation the rights to use, copy, modify, merge, publish, 269 | distribute, sublicense, and/or sell copies of the Software, and to 270 | permit persons to whom the Software is furnished to do so, subject to 271 | the following conditions: 272 | 273 | The above copyright notice and this permission notice shall be 274 | included in all copies or substantial portions of the Software. 275 | 276 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 277 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 278 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 279 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 280 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 281 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 282 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 283 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ive", 3 | "version": "0.2.4", 4 | "description": "isomorphic validation", 5 | "keywords": [ 6 | "schema", 7 | "object", 8 | "rube" 9 | ], 10 | "dependencies": { 11 | "tj/batch": "^0.5.1", 12 | "lapwinglabs/rube": "0.0.x", 13 | "matthewmueller/extend.js": "0.0.x", 14 | "dominicbarnes/form": "0.2.x", 15 | "component/event": "0.1.x", 16 | "component/value": "1.1.0", 17 | "matthewmueller/squares": "0.2.x" 18 | } 19 | } -------------------------------------------------------------------------------- /dist/ive.js: -------------------------------------------------------------------------------- 1 | (function outer(modules, cache, entries){ 2 | 3 | /** 4 | * Global 5 | */ 6 | 7 | var global = (function(){ return this; })(); 8 | 9 | /** 10 | * Require `name`. 11 | * 12 | * @param {String} name 13 | * @param {Boolean} jumped 14 | * @api public 15 | */ 16 | 17 | function require(name, jumped){ 18 | if (cache[name]) return cache[name].exports; 19 | if (modules[name]) return call(name, require); 20 | throw new Error('cannot find module "' + name + '"'); 21 | } 22 | 23 | /** 24 | * Call module `id` and cache it. 25 | * 26 | * @param {Number} id 27 | * @param {Function} require 28 | * @return {Function} 29 | * @api private 30 | */ 31 | 32 | function call(id, require){ 33 | var m = cache[id] = { exports: {} }; 34 | var mod = modules[id]; 35 | var name = mod[2]; 36 | var fn = mod[0]; 37 | 38 | fn.call(m.exports, function(req){ 39 | var dep = modules[id][1][req]; 40 | return require(dep ? dep : req); 41 | }, m, m.exports, outer, modules, cache, entries); 42 | 43 | // expose as `name`. 44 | if (name) cache[name] = cache[id]; 45 | 46 | return cache[id].exports; 47 | } 48 | 49 | /** 50 | * Require all entries exposing them on global if needed. 51 | */ 52 | 53 | for (var id in entries) { 54 | if (entries[id]) { 55 | global[entries[id]] = require(id); 56 | } else { 57 | require(id); 58 | } 59 | } 60 | 61 | /** 62 | * Duo flag. 63 | */ 64 | 65 | require.duo = true; 66 | 67 | /** 68 | * Expose cache. 69 | */ 70 | 71 | require.cache = cache; 72 | 73 | /** 74 | * Expose modules 75 | */ 76 | 77 | require.modules = modules; 78 | 79 | /** 80 | * Return newest require. 81 | */ 82 | 83 | return require; 84 | })({ 85 | 1: [function(require, module, exports) { 86 | /** 87 | * Module Dependencies 88 | */ 89 | 90 | var validate = require('./lib/validate'); 91 | var extend = require('extend.js'); 92 | var form = require('./lib/form'); 93 | var Batch = require('batch'); 94 | var isArray = Array.isArray; 95 | var Rube = require('rube'); 96 | 97 | /** 98 | * Export `Ive` 99 | */ 100 | 101 | module.exports = Ive; 102 | 103 | /** 104 | * Validate an object against a schema 105 | * 106 | * @return {Function} 107 | * @api public 108 | */ 109 | 110 | function Ive(props) { 111 | if (!(this instanceof Ive)) return new Ive(props); 112 | 113 | /** 114 | * Create a ive instance 115 | * 116 | * @param {Object|String|FormElement|Array|NodeList} 117 | * @param {Function} fn 118 | * @return {Ive} self 119 | */ 120 | 121 | function ive(obj, fn) { 122 | if ('string' == typeof obj) return filter(obj, ive.attrs); 123 | 124 | // Browser element 125 | if (obj.nodeName) { 126 | form(obj, ive); 127 | } else if (isArray(obj) || isNodeList(obj)) { 128 | for (var i = 0, el; el = obj[i]; i++) form(el, ive); 129 | } else if (fn) { 130 | // validate 131 | validate(obj, ive.attrs, fn); 132 | } else { 133 | // thunkify 134 | return function (done) { 135 | validate(obj, ive.attrs, done); 136 | } 137 | } 138 | 139 | return ive; 140 | } 141 | 142 | ive.attrs = {}; 143 | 144 | // add the methods 145 | for (var k in Ive.prototype) { 146 | ive[k] = Ive.prototype[k]; 147 | } 148 | 149 | // add the attributes 150 | ive.attr(props); 151 | 152 | return ive; 153 | } 154 | 155 | /** 156 | * Add an attribute 157 | * 158 | * @param {String} attr 159 | * @param {Rube} rube (optional) 160 | */ 161 | 162 | Ive.prototype.attr = function(key, rube) { 163 | if (!key) { 164 | return this.attrs; 165 | } else if ('ive' == key.name || 'object' == typeof key) { 166 | this.attrs = extend(this.attrs, key.attrs || key) 167 | return this; 168 | } else if (rube) { 169 | if (!this.attrs[key]) this.attrs[key] = Rube(); 170 | this.attrs[key].use(rube); 171 | return this; 172 | } else { 173 | return this.attrs[key] || (this.attrs[key] = Rube()); 174 | } 175 | }; 176 | 177 | /** 178 | * Filter out fields 179 | * 180 | * @param {String|Array} fields 181 | * @param {Object} attrs 182 | * @return {Function} 183 | */ 184 | 185 | function filter(fields, attrs) { 186 | return function(obj, fn) { 187 | return validate(obj, only(attrs, fields), fn); 188 | } 189 | } 190 | 191 | /** 192 | * Check if the element is a nodelist 193 | * 194 | * @param {Mixed} el 195 | * @return {Boolean} 196 | */ 197 | 198 | function isNodeList(el) { 199 | return typeof el.length == 'number' 200 | && typeof el.item == 'function' 201 | && typeof el.nextNode == 'function' 202 | && typeof el.reset == 'function' 203 | ? true 204 | : false; 205 | } 206 | 207 | /** 208 | * Filter `obj` by `keys` 209 | * 210 | * @param {Object} obj 211 | * @return {Object} 212 | */ 213 | 214 | function only(obj, keys){ 215 | obj = obj || {}; 216 | if ('string' == typeof keys) keys = keys.split(/ +/); 217 | return keys.reduce(function(ret, key){ 218 | if (null == obj[key]) return ret; 219 | ret[key] = obj[key]; 220 | return ret; 221 | }, {}); 222 | }; 223 | 224 | }, {"./lib/validate":2,"extend.js":3,"./lib/form":4,"batch":5,"rube":6}], 225 | 2: [function(require, module, exports) { 226 | /** 227 | * Module Dependencies 228 | */ 229 | 230 | var Batch = require('batch'); 231 | var keys = Object.keys; 232 | 233 | /** 234 | * Export `validate` 235 | */ 236 | 237 | module.exports = validate; 238 | 239 | /** 240 | * Validate 241 | * 242 | * @param {Object} obj 243 | * @param {Object} schema 244 | * @return {Function} fn 245 | */ 246 | 247 | function validate(obj, schema, fn) { 248 | var batch = Batch().throws(false); 249 | var attrs = keys(schema); 250 | var errors = {}; 251 | var values = {}; 252 | 253 | // loop through each of the schema attributes 254 | attrs.forEach(function(attr) { 255 | batch.push(function (next) { 256 | schema[attr](obj[attr], function(err, v) { 257 | if (err) { 258 | errors[attr] = err; 259 | return next(err); 260 | } else { 261 | values[attr] = v; 262 | return next(); 263 | } 264 | }); 265 | }); 266 | }); 267 | 268 | batch.end(function() { 269 | return keys(errors).length 270 | ? fn(format(errors, obj)) 271 | : fn(null, values); 272 | }) 273 | }; 274 | 275 | /** 276 | * Format the errors into a single error 277 | * 278 | * TODO: create a custom error 279 | * 280 | * @param {Array} arr 281 | * @return {Error} 282 | */ 283 | 284 | function format(errors, actual) { 285 | // format the object 286 | actual = JSON.stringify(actual, true, 2).split('\n').map(function(line) { 287 | return ' | ' + line; 288 | }).join('\n'); 289 | 290 | // format the errors 291 | var msg = keys(errors).map(function(error, i) { 292 | return ' | ' + (i + 1) + '. ' + error + ': ' + errors[error].message; 293 | }).join('\n'); 294 | 295 | var err = new Error('\n |\n | Rube Schema Validation Error\n |\n' + actual + '\n |\n' + msg + '\n |\n'); 296 | err.fields = errors; 297 | return err; 298 | } 299 | 300 | }, {"batch":5}], 301 | 5: [function(require, module, exports) { 302 | /** 303 | * Module dependencies. 304 | */ 305 | 306 | try { 307 | var EventEmitter = require('events').EventEmitter; 308 | } catch (err) { 309 | var Emitter = require('emitter'); 310 | } 311 | 312 | /** 313 | * Noop. 314 | */ 315 | 316 | function noop(){} 317 | 318 | /** 319 | * Expose `Batch`. 320 | */ 321 | 322 | module.exports = Batch; 323 | 324 | /** 325 | * Create a new Batch. 326 | */ 327 | 328 | function Batch() { 329 | if (!(this instanceof Batch)) return new Batch; 330 | this.fns = []; 331 | this.concurrency(Infinity); 332 | this.throws(true); 333 | for (var i = 0, len = arguments.length; i < len; ++i) { 334 | this.push(arguments[i]); 335 | } 336 | } 337 | 338 | /** 339 | * Inherit from `EventEmitter.prototype`. 340 | */ 341 | 342 | if (EventEmitter) { 343 | Batch.prototype.__proto__ = EventEmitter.prototype; 344 | } else { 345 | Emitter(Batch.prototype); 346 | } 347 | 348 | /** 349 | * Set concurrency to `n`. 350 | * 351 | * @param {Number} n 352 | * @return {Batch} 353 | * @api public 354 | */ 355 | 356 | Batch.prototype.concurrency = function(n){ 357 | this.n = n; 358 | return this; 359 | }; 360 | 361 | /** 362 | * Queue a function. 363 | * 364 | * @param {Function} fn 365 | * @return {Batch} 366 | * @api public 367 | */ 368 | 369 | Batch.prototype.push = function(fn){ 370 | this.fns.push(fn); 371 | return this; 372 | }; 373 | 374 | /** 375 | * Set wether Batch will or will not throw up. 376 | * 377 | * @param {Boolean} throws 378 | * @return {Batch} 379 | * @api public 380 | */ 381 | Batch.prototype.throws = function(throws) { 382 | this.e = !!throws; 383 | return this; 384 | }; 385 | 386 | /** 387 | * Execute all queued functions in parallel, 388 | * executing `cb(err, results)`. 389 | * 390 | * @param {Function} cb 391 | * @return {Batch} 392 | * @api public 393 | */ 394 | 395 | Batch.prototype.end = function(cb){ 396 | var self = this 397 | , total = this.fns.length 398 | , pending = total 399 | , results = [] 400 | , errors = [] 401 | , cb = cb || noop 402 | , fns = this.fns 403 | , max = this.n 404 | , throws = this.e 405 | , index = 0 406 | , done; 407 | 408 | // empty 409 | if (!fns.length) return cb(null, results); 410 | 411 | // process 412 | function next() { 413 | var i = index++; 414 | var fn = fns[i]; 415 | if (!fn) return; 416 | var start = new Date; 417 | 418 | try { 419 | fn(callback); 420 | } catch (err) { 421 | callback(err); 422 | } 423 | 424 | function callback(err, res){ 425 | if (done) return; 426 | if (err && throws) return done = true, cb(err); 427 | var complete = total - pending + 1; 428 | var end = new Date; 429 | 430 | results[i] = res; 431 | errors[i] = err; 432 | 433 | self.emit('progress', { 434 | index: i, 435 | value: res, 436 | error: err, 437 | pending: pending, 438 | total: total, 439 | complete: complete, 440 | percent: complete / total * 100 | 0, 441 | start: start, 442 | end: end, 443 | duration: end - start 444 | }); 445 | 446 | if (--pending) next(); 447 | else if(!throws) cb(errors, results); 448 | else cb(null, results); 449 | } 450 | } 451 | 452 | // concurrency 453 | for (var i = 0; i < fns.length; i++) { 454 | if (i == max) break; 455 | next(); 456 | } 457 | 458 | return this; 459 | }; 460 | 461 | }, {"emitter":7}], 462 | 7: [function(require, module, exports) { 463 | 464 | /** 465 | * Expose `Emitter`. 466 | */ 467 | 468 | module.exports = Emitter; 469 | 470 | /** 471 | * Initialize a new `Emitter`. 472 | * 473 | * @api public 474 | */ 475 | 476 | function Emitter(obj) { 477 | if (obj) return mixin(obj); 478 | }; 479 | 480 | /** 481 | * Mixin the emitter properties. 482 | * 483 | * @param {Object} obj 484 | * @return {Object} 485 | * @api private 486 | */ 487 | 488 | function mixin(obj) { 489 | for (var key in Emitter.prototype) { 490 | obj[key] = Emitter.prototype[key]; 491 | } 492 | return obj; 493 | } 494 | 495 | /** 496 | * Listen on the given `event` with `fn`. 497 | * 498 | * @param {String} event 499 | * @param {Function} fn 500 | * @return {Emitter} 501 | * @api public 502 | */ 503 | 504 | Emitter.prototype.on = 505 | Emitter.prototype.addEventListener = function(event, fn){ 506 | this._callbacks = this._callbacks || {}; 507 | (this._callbacks[event] = this._callbacks[event] || []) 508 | .push(fn); 509 | return this; 510 | }; 511 | 512 | /** 513 | * Adds an `event` listener that will be invoked a single 514 | * time then automatically removed. 515 | * 516 | * @param {String} event 517 | * @param {Function} fn 518 | * @return {Emitter} 519 | * @api public 520 | */ 521 | 522 | Emitter.prototype.once = function(event, fn){ 523 | var self = this; 524 | this._callbacks = this._callbacks || {}; 525 | 526 | function on() { 527 | self.off(event, on); 528 | fn.apply(this, arguments); 529 | } 530 | 531 | on.fn = fn; 532 | this.on(event, on); 533 | return this; 534 | }; 535 | 536 | /** 537 | * Remove the given callback for `event` or all 538 | * registered callbacks. 539 | * 540 | * @param {String} event 541 | * @param {Function} fn 542 | * @return {Emitter} 543 | * @api public 544 | */ 545 | 546 | Emitter.prototype.off = 547 | Emitter.prototype.removeListener = 548 | Emitter.prototype.removeAllListeners = 549 | Emitter.prototype.removeEventListener = function(event, fn){ 550 | this._callbacks = this._callbacks || {}; 551 | 552 | // all 553 | if (0 == arguments.length) { 554 | this._callbacks = {}; 555 | return this; 556 | } 557 | 558 | // specific event 559 | var callbacks = this._callbacks[event]; 560 | if (!callbacks) return this; 561 | 562 | // remove all handlers 563 | if (1 == arguments.length) { 564 | delete this._callbacks[event]; 565 | return this; 566 | } 567 | 568 | // remove specific handler 569 | var cb; 570 | for (var i = 0; i < callbacks.length; i++) { 571 | cb = callbacks[i]; 572 | if (cb === fn || cb.fn === fn) { 573 | callbacks.splice(i, 1); 574 | break; 575 | } 576 | } 577 | return this; 578 | }; 579 | 580 | /** 581 | * Emit `event` with the given args. 582 | * 583 | * @param {String} event 584 | * @param {Mixed} ... 585 | * @return {Emitter} 586 | */ 587 | 588 | Emitter.prototype.emit = function(event){ 589 | this._callbacks = this._callbacks || {}; 590 | var args = [].slice.call(arguments, 1) 591 | , callbacks = this._callbacks[event]; 592 | 593 | if (callbacks) { 594 | callbacks = callbacks.slice(0); 595 | for (var i = 0, len = callbacks.length; i < len; ++i) { 596 | callbacks[i].apply(this, args); 597 | } 598 | } 599 | 600 | return this; 601 | }; 602 | 603 | /** 604 | * Return array of callbacks for `event`. 605 | * 606 | * @param {String} event 607 | * @return {Array} 608 | * @api public 609 | */ 610 | 611 | Emitter.prototype.listeners = function(event){ 612 | this._callbacks = this._callbacks || {}; 613 | return this._callbacks[event] || []; 614 | }; 615 | 616 | /** 617 | * Check if this emitter has `event` handlers. 618 | * 619 | * @param {String} event 620 | * @return {Boolean} 621 | * @api public 622 | */ 623 | 624 | Emitter.prototype.hasListeners = function(event){ 625 | return !! this.listeners(event).length; 626 | }; 627 | 628 | }, {}], 629 | 3: [function(require, module, exports) { 630 | /** 631 | * Extend an object with another. 632 | * 633 | * @param {Object, ...} src, ... 634 | * @return {Object} merged 635 | * @api private 636 | */ 637 | 638 | module.exports = function(src) { 639 | var objs = [].slice.call(arguments, 1), obj; 640 | 641 | for (var i = 0, len = objs.length; i < len; i++) { 642 | obj = objs[i]; 643 | for (var prop in obj) { 644 | src[prop] = obj[prop]; 645 | } 646 | } 647 | 648 | return src; 649 | } 650 | 651 | }, {}], 652 | 4: [function(require, module, exports) { 653 | /** 654 | * Module Dependencies 655 | */ 656 | 657 | var validate = require('./validate'); 658 | var assert = require('assert'); 659 | 660 | /** 661 | * Export `form` 662 | */ 663 | 664 | module.exports = form; 665 | 666 | /** 667 | * Exclude 668 | */ 669 | 670 | var exclude = /^(button|submit|reset|hidden)$/; 671 | 672 | /** 673 | * Initialize `form` 674 | * 675 | * @param {HTMLForm} el 676 | */ 677 | 678 | function form(el, schema) { 679 | assert(el.nodeName == 'FORM'); 680 | 681 | // browser-only modules 682 | var event = require('event'); 683 | 684 | // bind onto the form 685 | var submit = el.querySelector('input[type="submit"]'); 686 | event.bind(el, 'submit', onsubmit(el, submit, schema)); 687 | 688 | var inputs = el.querySelectorAll('input, textarea'); 689 | for (var i = 0, input; input = inputs[i]; i++) { 690 | if (exclude.test(input.type)) return; 691 | event.bind(input, 'blur', onevent(input, schema.attrs, field)); 692 | } 693 | } 694 | 695 | /** 696 | * Listen to submit events 697 | */ 698 | 699 | function onsubmit(form, button, schema) { 700 | var Form = require('form'); 701 | 702 | return function submit(e) { 703 | if (form.getAttribute('submitting')) return true; 704 | 705 | e.preventDefault(); 706 | e.stopImmediatePropagation(); 707 | 708 | var json = Form(form).serialize(); 709 | schema(json, function(err, v) { 710 | if (err) { 711 | form.setAttribute('invalid', err.message); 712 | for (var name in err.fields) { 713 | field(form.querySelector('[name="' + name + '"]'))(err.fields[name]); 714 | } 715 | } else { 716 | form.setAttribute('submitting', 'submitting'); 717 | form.removeAttribute('invalid'); 718 | submitForm(form); 719 | } 720 | }); 721 | }; 722 | } 723 | 724 | /** 725 | * Listen for blur events 726 | * 727 | * @param {InputElement} inputs 728 | * @param {Object} attrs 729 | * @param {Function} fn 730 | * @return {Function} 731 | */ 732 | 733 | function onevent(input, attrs, fn) { 734 | var name = input.getAttribute('name'); 735 | return function event(e) { 736 | var value = input.value; 737 | if ('' === value) return; 738 | attrs[name](value, fn(input)); 739 | }; 740 | } 741 | 742 | /** 743 | * Check validation on a field 744 | * 745 | * @param {InputElement} input 746 | * @return {Function} 747 | */ 748 | 749 | function field(input) { 750 | return function check(err, v) { 751 | if (err) { 752 | input.setAttribute('invalid', err.message); 753 | } else { 754 | input.removeAttribute('invalid'); 755 | if (v) input.value = v; 756 | } 757 | } 758 | } 759 | 760 | /** 761 | * Submit a `form` programmatically, 762 | * triggering submit handlers. 763 | * 764 | * @param {Element} form 765 | */ 766 | 767 | function submitForm(form) { 768 | var trigger = require('trigger-event'); 769 | var button = document.createElement('button'); 770 | button.style.display = 'none'; 771 | form.appendChild(button); 772 | trigger(button, 'click', { clientX: 0, clientY: 0}); 773 | form.removeChild(button); 774 | } 775 | 776 | 777 | }, {"./validate":2,"assert":8,"event":9,"form":10,"trigger-event":11}], 778 | 8: [function(require, module, exports) { 779 | 780 | /** 781 | * Module dependencies. 782 | */ 783 | 784 | var equals = require('equals'); 785 | var fmt = require('fmt'); 786 | var stack = require('stack'); 787 | 788 | /** 789 | * Assert `expr` with optional failure `msg`. 790 | * 791 | * @param {Mixed} expr 792 | * @param {String} [msg] 793 | * @api public 794 | */ 795 | 796 | module.exports = exports = function (expr, msg) { 797 | if (expr) return; 798 | throw error(msg || message()); 799 | }; 800 | 801 | /** 802 | * Assert `actual` is weak equal to `expected`. 803 | * 804 | * @param {Mixed} actual 805 | * @param {Mixed} expected 806 | * @param {String} [msg] 807 | * @api public 808 | */ 809 | 810 | exports.equal = function (actual, expected, msg) { 811 | if (actual == expected) return; 812 | throw error(msg || fmt('Expected %o to equal %o.', actual, expected), actual, expected); 813 | }; 814 | 815 | /** 816 | * Assert `actual` is not weak equal to `expected`. 817 | * 818 | * @param {Mixed} actual 819 | * @param {Mixed} expected 820 | * @param {String} [msg] 821 | * @api public 822 | */ 823 | 824 | exports.notEqual = function (actual, expected, msg) { 825 | if (actual != expected) return; 826 | throw error(msg || fmt('Expected %o not to equal %o.', actual, expected)); 827 | }; 828 | 829 | /** 830 | * Assert `actual` is deep equal to `expected`. 831 | * 832 | * @param {Mixed} actual 833 | * @param {Mixed} expected 834 | * @param {String} [msg] 835 | * @api public 836 | */ 837 | 838 | exports.deepEqual = function (actual, expected, msg) { 839 | if (equals(actual, expected)) return; 840 | throw error(msg || fmt('Expected %o to deeply equal %o.', actual, expected), actual, expected); 841 | }; 842 | 843 | /** 844 | * Assert `actual` is not deep equal to `expected`. 845 | * 846 | * @param {Mixed} actual 847 | * @param {Mixed} expected 848 | * @param {String} [msg] 849 | * @api public 850 | */ 851 | 852 | exports.notDeepEqual = function (actual, expected, msg) { 853 | if (!equals(actual, expected)) return; 854 | throw error(msg || fmt('Expected %o not to deeply equal %o.', actual, expected)); 855 | }; 856 | 857 | /** 858 | * Assert `actual` is strict equal to `expected`. 859 | * 860 | * @param {Mixed} actual 861 | * @param {Mixed} expected 862 | * @param {String} [msg] 863 | * @api public 864 | */ 865 | 866 | exports.strictEqual = function (actual, expected, msg) { 867 | if (actual === expected) return; 868 | throw error(msg || fmt('Expected %o to strictly equal %o.', actual, expected), actual, expected); 869 | }; 870 | 871 | /** 872 | * Assert `actual` is not strict equal to `expected`. 873 | * 874 | * @param {Mixed} actual 875 | * @param {Mixed} expected 876 | * @param {String} [msg] 877 | * @api public 878 | */ 879 | 880 | exports.notStrictEqual = function (actual, expected, msg) { 881 | if (actual !== expected) return; 882 | throw error(msg || fmt('Expected %o not to strictly equal %o.', actual, expected)); 883 | }; 884 | 885 | /** 886 | * Assert `block` throws an `error`. 887 | * 888 | * @param {Function} block 889 | * @param {Function} [error] 890 | * @param {String} [msg] 891 | * @api public 892 | */ 893 | 894 | exports.throws = function (block, err, msg) { 895 | var threw; 896 | try { 897 | block(); 898 | } catch (e) { 899 | threw = e; 900 | } 901 | 902 | if (!threw) throw error(msg || fmt('Expected %s to throw an error.', block.toString())); 903 | if (err && !(threw instanceof err)) { 904 | throw error(msg || fmt('Expected %s to throw an %o.', block.toString(), err)); 905 | } 906 | }; 907 | 908 | /** 909 | * Assert `block` doesn't throw an `error`. 910 | * 911 | * @param {Function} block 912 | * @param {Function} [error] 913 | * @param {String} [msg] 914 | * @api public 915 | */ 916 | 917 | exports.doesNotThrow = function (block, err, msg) { 918 | var threw; 919 | try { 920 | block(); 921 | } catch (e) { 922 | threw = e; 923 | } 924 | 925 | if (threw) throw error(msg || fmt('Expected %s not to throw an error.', block.toString())); 926 | if (err && (threw instanceof err)) { 927 | throw error(msg || fmt('Expected %s not to throw an %o.', block.toString(), err)); 928 | } 929 | }; 930 | 931 | /** 932 | * Create a message from the call stack. 933 | * 934 | * @return {String} 935 | * @api private 936 | */ 937 | 938 | function message() { 939 | if (!Error.captureStackTrace) return 'assertion failed'; 940 | var callsite = stack()[2]; 941 | var fn = callsite.getFunctionName(); 942 | var file = callsite.getFileName(); 943 | var line = callsite.getLineNumber() - 1; 944 | var col = callsite.getColumnNumber() - 1; 945 | var src = get(file); 946 | line = src.split('\n')[line].slice(col); 947 | var m = line.match(/assert\((.*)\)/); 948 | return m && m[1].trim(); 949 | } 950 | 951 | /** 952 | * Load contents of `script`. 953 | * 954 | * @param {String} script 955 | * @return {String} 956 | * @api private 957 | */ 958 | 959 | function get(script) { 960 | var xhr = new XMLHttpRequest; 961 | xhr.open('GET', script, false); 962 | xhr.send(null); 963 | return xhr.responseText; 964 | } 965 | 966 | /** 967 | * Error with `msg`, `actual` and `expected`. 968 | * 969 | * @param {String} msg 970 | * @param {Mixed} actual 971 | * @param {Mixed} expected 972 | * @return {Error} 973 | */ 974 | 975 | function error(msg, actual, expected){ 976 | var err = new Error(msg); 977 | err.showDiff = 3 == arguments.length; 978 | err.actual = actual; 979 | err.expected = expected; 980 | return err; 981 | } 982 | 983 | }, {"equals":12,"fmt":13,"stack":14}], 984 | 12: [function(require, module, exports) { 985 | var type = require('type') 986 | 987 | // (any, any, [array]) -> boolean 988 | function equal(a, b, memos){ 989 | // All identical values are equivalent 990 | if (a === b) return true 991 | var fnA = types[type(a)] 992 | var fnB = types[type(b)] 993 | return fnA && fnA === fnB 994 | ? fnA(a, b, memos) 995 | : false 996 | } 997 | 998 | var types = {} 999 | 1000 | // (Number) -> boolean 1001 | types.number = function(a, b){ 1002 | return a !== a && b !== b/*Nan check*/ 1003 | } 1004 | 1005 | // (function, function, array) -> boolean 1006 | types['function'] = function(a, b, memos){ 1007 | return a.toString() === b.toString() 1008 | // Functions can act as objects 1009 | && types.object(a, b, memos) 1010 | && equal(a.prototype, b.prototype) 1011 | } 1012 | 1013 | // (date, date) -> boolean 1014 | types.date = function(a, b){ 1015 | return +a === +b 1016 | } 1017 | 1018 | // (regexp, regexp) -> boolean 1019 | types.regexp = function(a, b){ 1020 | return a.toString() === b.toString() 1021 | } 1022 | 1023 | // (DOMElement, DOMElement) -> boolean 1024 | types.element = function(a, b){ 1025 | return a.outerHTML === b.outerHTML 1026 | } 1027 | 1028 | // (textnode, textnode) -> boolean 1029 | types.textnode = function(a, b){ 1030 | return a.textContent === b.textContent 1031 | } 1032 | 1033 | // decorate `fn` to prevent it re-checking objects 1034 | // (function) -> function 1035 | function memoGaurd(fn){ 1036 | return function(a, b, memos){ 1037 | if (!memos) return fn(a, b, []) 1038 | var i = memos.length, memo 1039 | while (memo = memos[--i]) { 1040 | if (memo[0] === a && memo[1] === b) return true 1041 | } 1042 | return fn(a, b, memos) 1043 | } 1044 | } 1045 | 1046 | types['arguments'] = 1047 | types.array = memoGaurd(arrayEqual) 1048 | 1049 | // (array, array, array) -> boolean 1050 | function arrayEqual(a, b, memos){ 1051 | var i = a.length 1052 | if (i !== b.length) return false 1053 | memos.push([a, b]) 1054 | while (i--) { 1055 | if (!equal(a[i], b[i], memos)) return false 1056 | } 1057 | return true 1058 | } 1059 | 1060 | types.object = memoGaurd(objectEqual) 1061 | 1062 | // (object, object, array) -> boolean 1063 | function objectEqual(a, b, memos) { 1064 | if (typeof a.equal == 'function') { 1065 | memos.push([a, b]) 1066 | return a.equal(b, memos) 1067 | } 1068 | var ka = getEnumerableProperties(a) 1069 | var kb = getEnumerableProperties(b) 1070 | var i = ka.length 1071 | 1072 | // same number of properties 1073 | if (i !== kb.length) return false 1074 | 1075 | // although not necessarily the same order 1076 | ka.sort() 1077 | kb.sort() 1078 | 1079 | // cheap key test 1080 | while (i--) if (ka[i] !== kb[i]) return false 1081 | 1082 | // remember 1083 | memos.push([a, b]) 1084 | 1085 | // iterate again this time doing a thorough check 1086 | i = ka.length 1087 | while (i--) { 1088 | var key = ka[i] 1089 | if (!equal(a[key], b[key], memos)) return false 1090 | } 1091 | 1092 | return true 1093 | } 1094 | 1095 | // (object) -> array 1096 | function getEnumerableProperties (object) { 1097 | var result = [] 1098 | for (var k in object) if (k !== 'constructor') { 1099 | result.push(k) 1100 | } 1101 | return result 1102 | } 1103 | 1104 | module.exports = equal 1105 | 1106 | }, {"type":15}], 1107 | 15: [function(require, module, exports) { 1108 | 1109 | var toString = {}.toString 1110 | var DomNode = typeof window != 'undefined' 1111 | ? window.Node 1112 | : Function 1113 | 1114 | /** 1115 | * Return the type of `val`. 1116 | * 1117 | * @param {Mixed} val 1118 | * @return {String} 1119 | * @api public 1120 | */ 1121 | 1122 | module.exports = exports = function(x){ 1123 | var type = typeof x 1124 | if (type != 'object') return type 1125 | type = types[toString.call(x)] 1126 | if (type) return type 1127 | if (x instanceof DomNode) switch (x.nodeType) { 1128 | case 1: return 'element' 1129 | case 3: return 'text-node' 1130 | case 9: return 'document' 1131 | case 11: return 'document-fragment' 1132 | default: return 'dom-node' 1133 | } 1134 | } 1135 | 1136 | var types = exports.types = { 1137 | '[object Function]': 'function', 1138 | '[object Date]': 'date', 1139 | '[object RegExp]': 'regexp', 1140 | '[object Arguments]': 'arguments', 1141 | '[object Array]': 'array', 1142 | '[object String]': 'string', 1143 | '[object Null]': 'null', 1144 | '[object Undefined]': 'undefined', 1145 | '[object Number]': 'number', 1146 | '[object Boolean]': 'boolean', 1147 | '[object Object]': 'object', 1148 | '[object Text]': 'text-node', 1149 | '[object Uint8Array]': 'bit-array', 1150 | '[object Uint16Array]': 'bit-array', 1151 | '[object Uint32Array]': 'bit-array', 1152 | '[object Uint8ClampedArray]': 'bit-array', 1153 | '[object Error]': 'error', 1154 | '[object FormData]': 'form-data', 1155 | '[object File]': 'file', 1156 | '[object Blob]': 'blob' 1157 | } 1158 | 1159 | }, {}], 1160 | 13: [function(require, module, exports) { 1161 | 1162 | /** 1163 | * Export `fmt` 1164 | */ 1165 | 1166 | module.exports = fmt; 1167 | 1168 | /** 1169 | * Formatters 1170 | */ 1171 | 1172 | fmt.o = JSON.stringify; 1173 | fmt.s = String; 1174 | fmt.d = parseInt; 1175 | 1176 | /** 1177 | * Format the given `str`. 1178 | * 1179 | * @param {String} str 1180 | * @param {...} args 1181 | * @return {String} 1182 | * @api public 1183 | */ 1184 | 1185 | function fmt(str){ 1186 | var args = [].slice.call(arguments, 1); 1187 | var j = 0; 1188 | 1189 | return str.replace(/%([a-z])/gi, function(_, f){ 1190 | return fmt[f] 1191 | ? fmt[f](args[j++]) 1192 | : _ + f; 1193 | }); 1194 | } 1195 | 1196 | }, {}], 1197 | 14: [function(require, module, exports) { 1198 | 1199 | /** 1200 | * Expose `stack()`. 1201 | */ 1202 | 1203 | module.exports = stack; 1204 | 1205 | /** 1206 | * Return the stack. 1207 | * 1208 | * @return {Array} 1209 | * @api public 1210 | */ 1211 | 1212 | function stack() { 1213 | var orig = Error.prepareStackTrace; 1214 | Error.prepareStackTrace = function(_, stack){ return stack; }; 1215 | var err = new Error; 1216 | Error.captureStackTrace(err, arguments.callee); 1217 | var stack = err.stack; 1218 | Error.prepareStackTrace = orig; 1219 | return stack; 1220 | } 1221 | }, {}], 1222 | 9: [function(require, module, exports) { 1223 | var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', 1224 | unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', 1225 | prefix = bind !== 'addEventListener' ? 'on' : ''; 1226 | 1227 | /** 1228 | * Bind `el` event `type` to `fn`. 1229 | * 1230 | * @param {Element} el 1231 | * @param {String} type 1232 | * @param {Function} fn 1233 | * @param {Boolean} capture 1234 | * @return {Function} 1235 | * @api public 1236 | */ 1237 | 1238 | exports.bind = function(el, type, fn, capture){ 1239 | el[bind](prefix + type, fn, capture || false); 1240 | return fn; 1241 | }; 1242 | 1243 | /** 1244 | * Unbind `el` event `type`'s callback `fn`. 1245 | * 1246 | * @param {Element} el 1247 | * @param {String} type 1248 | * @param {Function} fn 1249 | * @param {Boolean} capture 1250 | * @return {Function} 1251 | * @api public 1252 | */ 1253 | 1254 | exports.unbind = function(el, type, fn, capture){ 1255 | el[unbind](prefix + type, fn, capture || false); 1256 | return fn; 1257 | }; 1258 | }, {}], 1259 | 10: [function(require, module, exports) { 1260 | // dependencies 1261 | var classes = require("classes"); 1262 | var formElement = require("form-element"); 1263 | var serialize = require("form-serialize"); 1264 | var value = require("value"); 1265 | 1266 | 1267 | // single export 1268 | module.exports = Form; 1269 | 1270 | 1271 | /** 1272 | * A helper for working with HTML forms 1273 | * 1274 | * @constructor 1275 | * @param {HTMLFormElement} el 1276 | */ 1277 | function Form(el) { 1278 | if (!(this instanceof Form)) { 1279 | return new Form(el); 1280 | } 1281 | 1282 | this.element = el; 1283 | this.classes = classes(this.element); 1284 | } 1285 | 1286 | 1287 | /** 1288 | * Retrieves an input from the form by name. If 2 arguments are passed, 1289 | * the first is assumed to be the name of a `
`. (which allows 1290 | * only retrieving from a specific subset of elements) 1291 | * 1292 | * @see http://github.com/dominicbarnes/form-element 1293 | * 1294 | * @param {String} [fieldset] Only search for controls in the named fieldset 1295 | * @param {String} name 1296 | * @returns {HTMLElement} 1297 | */ 1298 | Form.prototype.input = function (fieldset, name) { 1299 | if (!name) { 1300 | name = fieldset; 1301 | fieldset = null; 1302 | } 1303 | 1304 | var el = fieldset ? formElement(this.element, fieldset) : this.element; 1305 | return formElement(el, name); 1306 | }; 1307 | 1308 | 1309 | /** 1310 | * Gets/sets the value for a form input (found by name) 1311 | * 1312 | * @see https://github.com/component/value 1313 | * 1314 | * @param {String} name 1315 | * @param {Mixed} val 1316 | * @returns {Mixed} 1317 | */ 1318 | Form.prototype.value = function (name, val) { 1319 | var args = [ this.input(name) ]; 1320 | if (typeof val !== "undefined") args.push(val); 1321 | return value.apply(null, args); 1322 | }; 1323 | 1324 | 1325 | /** 1326 | * Serializes all the inputs of a form into a single JS object 1327 | * 1328 | * @see https://github.com/dominicbarnes/form-serialize 1329 | * 1330 | * @param {String} [fieldset] Only serialize the controls in the named fieldset 1331 | * @param {Function} [transformer] Apply an interceptor function to the serializer 1332 | * @returns {Object} 1333 | */ 1334 | Form.prototype.serialize = function (fieldset, transformer) { 1335 | if (typeof fieldset === "function") { 1336 | transformer = fieldset; 1337 | fieldset = null; 1338 | } 1339 | 1340 | var el = fieldset ? formElement(this.element, fieldset) : this.element; 1341 | 1342 | return serialize(el, transformer); 1343 | }; 1344 | 1345 | }, {"classes":16,"form-element":17,"form-serialize":18,"value":19}], 1346 | 16: [function(require, module, exports) { 1347 | /** 1348 | * Module dependencies. 1349 | */ 1350 | 1351 | var index = require('indexof'); 1352 | 1353 | /** 1354 | * Whitespace regexp. 1355 | */ 1356 | 1357 | var re = /\s+/; 1358 | 1359 | /** 1360 | * toString reference. 1361 | */ 1362 | 1363 | var toString = Object.prototype.toString; 1364 | 1365 | /** 1366 | * Wrap `el` in a `ClassList`. 1367 | * 1368 | * @param {Element} el 1369 | * @return {ClassList} 1370 | * @api public 1371 | */ 1372 | 1373 | module.exports = function(el){ 1374 | return new ClassList(el); 1375 | }; 1376 | 1377 | /** 1378 | * Initialize a new ClassList for `el`. 1379 | * 1380 | * @param {Element} el 1381 | * @api private 1382 | */ 1383 | 1384 | function ClassList(el) { 1385 | if (!el || !el.nodeType) { 1386 | throw new Error('A DOM element reference is required'); 1387 | } 1388 | this.el = el; 1389 | this.list = el.classList; 1390 | } 1391 | 1392 | /** 1393 | * Add class `name` if not already present. 1394 | * 1395 | * @param {String} name 1396 | * @return {ClassList} 1397 | * @api public 1398 | */ 1399 | 1400 | ClassList.prototype.add = function(name){ 1401 | // classList 1402 | if (this.list) { 1403 | this.list.add(name); 1404 | return this; 1405 | } 1406 | 1407 | // fallback 1408 | var arr = this.array(); 1409 | var i = index(arr, name); 1410 | if (!~i) arr.push(name); 1411 | this.el.className = arr.join(' '); 1412 | return this; 1413 | }; 1414 | 1415 | /** 1416 | * Remove class `name` when present, or 1417 | * pass a regular expression to remove 1418 | * any which match. 1419 | * 1420 | * @param {String|RegExp} name 1421 | * @return {ClassList} 1422 | * @api public 1423 | */ 1424 | 1425 | ClassList.prototype.remove = function(name){ 1426 | if ('[object RegExp]' == toString.call(name)) { 1427 | return this.removeMatching(name); 1428 | } 1429 | 1430 | // classList 1431 | if (this.list) { 1432 | this.list.remove(name); 1433 | return this; 1434 | } 1435 | 1436 | // fallback 1437 | var arr = this.array(); 1438 | var i = index(arr, name); 1439 | if (~i) arr.splice(i, 1); 1440 | this.el.className = arr.join(' '); 1441 | return this; 1442 | }; 1443 | 1444 | /** 1445 | * Remove all classes matching `re`. 1446 | * 1447 | * @param {RegExp} re 1448 | * @return {ClassList} 1449 | * @api private 1450 | */ 1451 | 1452 | ClassList.prototype.removeMatching = function(re){ 1453 | var arr = this.array(); 1454 | for (var i = 0; i < arr.length; i++) { 1455 | if (re.test(arr[i])) { 1456 | this.remove(arr[i]); 1457 | } 1458 | } 1459 | return this; 1460 | }; 1461 | 1462 | /** 1463 | * Toggle class `name`, can force state via `force`. 1464 | * 1465 | * For browsers that support classList, but do not support `force` yet, 1466 | * the mistake will be detected and corrected. 1467 | * 1468 | * @param {String} name 1469 | * @param {Boolean} force 1470 | * @return {ClassList} 1471 | * @api public 1472 | */ 1473 | 1474 | ClassList.prototype.toggle = function(name, force){ 1475 | // classList 1476 | if (this.list) { 1477 | if ("undefined" !== typeof force) { 1478 | if (force !== this.list.toggle(name, force)) { 1479 | this.list.toggle(name); // toggle again to correct 1480 | } 1481 | } else { 1482 | this.list.toggle(name); 1483 | } 1484 | return this; 1485 | } 1486 | 1487 | // fallback 1488 | if ("undefined" !== typeof force) { 1489 | if (!force) { 1490 | this.remove(name); 1491 | } else { 1492 | this.add(name); 1493 | } 1494 | } else { 1495 | if (this.has(name)) { 1496 | this.remove(name); 1497 | } else { 1498 | this.add(name); 1499 | } 1500 | } 1501 | 1502 | return this; 1503 | }; 1504 | 1505 | /** 1506 | * Return an array of classes. 1507 | * 1508 | * @return {Array} 1509 | * @api public 1510 | */ 1511 | 1512 | ClassList.prototype.array = function(){ 1513 | var str = this.el.className.replace(/^\s+|\s+$/g, ''); 1514 | var arr = str.split(re); 1515 | if ('' === arr[0]) arr.shift(); 1516 | return arr; 1517 | }; 1518 | 1519 | /** 1520 | * Check if class `name` is present. 1521 | * 1522 | * @param {String} name 1523 | * @return {ClassList} 1524 | * @api public 1525 | */ 1526 | 1527 | ClassList.prototype.has = 1528 | ClassList.prototype.contains = function(name){ 1529 | return this.list 1530 | ? this.list.contains(name) 1531 | : !! ~index(this.array(), name); 1532 | }; 1533 | 1534 | }, {"indexof":20}], 1535 | 20: [function(require, module, exports) { 1536 | module.exports = function(arr, obj){ 1537 | if (arr.indexOf) return arr.indexOf(obj); 1538 | for (var i = 0; i < arr.length; ++i) { 1539 | if (arr[i] === obj) return i; 1540 | } 1541 | return -1; 1542 | }; 1543 | }, {}], 1544 | 17: [function(require, module, exports) { 1545 | 1546 | /** 1547 | * Retrieves a form control from the given root element. The ideal use-case is for 1548 | * a `
` or `
`, (via their `elements` API) but arbitrary DOM elements 1549 | * work as well. (via `querySelectorAll`) 1550 | * 1551 | * @param {HTMLElement} root 1552 | * @param {String} name 1553 | * 1554 | * @returns {Mixed} 1555 | */ 1556 | 1557 | module.exports = function (root, name) { 1558 | if (!(root instanceof HTMLElement)) { 1559 | throw new TypeError("a root element is required"); 1560 | } 1561 | 1562 | if ("namedItem" in root) { 1563 | // the short-circuit here is because IE won't find things like
even 1564 | // when they have an assigned name 1565 | return normalize(root.namedItem(name) || bruteForce(root, name)); 1566 | } else if (root.elements) { 1567 | return normalize(root.elements.namedItem(name)); 1568 | } else { 1569 | return normalize(bruteForce(root, name)); 1570 | } 1571 | }; 1572 | 1573 | 1574 | /** 1575 | * When searching an arbitrary element (or for browsers that don't support 1576 | * the elements list properly) 1577 | * 1578 | * @param {HTMLElement} root 1579 | * @param {String} name 1580 | * 1581 | * @return {NodeList} 1582 | */ 1583 | function bruteForce(root, name) { 1584 | return root.querySelectorAll("[name='" + name + "']"); 1585 | } 1586 | 1587 | /** 1588 | * Normalizes the value returned by the API: 1589 | * - when empty, return `null` 1590 | * - when only a single node, return that node directly 1591 | * - when a `NodeList`, return an `Array` instead 1592 | * 1593 | * @param {Mixed} ret 1594 | * 1595 | * @return {Mixed} 1596 | */ 1597 | function normalize(ret) { 1598 | if (!ret) { 1599 | return null; 1600 | } else if (ret instanceof HTMLElement) { 1601 | return ret; 1602 | } else if (ret.length === 0) { 1603 | return null; 1604 | } else if (ret.length === 1) { 1605 | return ret[0]; 1606 | } else { 1607 | return [].slice.call(ret); 1608 | } 1609 | } 1610 | 1611 | }, {}], 1612 | 18: [function(require, module, exports) { 1613 | // dependencies 1614 | var controls = require("form-controls"); 1615 | var reduce = require("reduce"); 1616 | var square = require("square"); 1617 | var value = require("value"); 1618 | var submittable = require("submittable"); 1619 | 1620 | 1621 | /** 1622 | * Retrieves a single JS object representing the values filled in 1623 | * the `form` element's controls. 1624 | * 1625 | * @param {HTMLElement} form @see dominicbarnes/form-controls 1626 | * @param {Function} transformer Allows intercepting and transforming values 1627 | * @return {Object} 1628 | */ 1629 | 1630 | module.exports = function (form, transformer) { 1631 | return reduce(controls(form), function (acc, el) { 1632 | if (!submittable(el)) return acc; 1633 | var val = transformer ? transformer.call(form, el.name, value(el), el) : value(el); 1634 | return square.set(acc, el.name, val); 1635 | }, {}); 1636 | }; 1637 | 1638 | }, {"form-controls":21,"reduce":22,"square":23,"value":19,"submittable":24}], 1639 | 21: [function(require, module, exports) { 1640 | // dependencies 1641 | var toArray = require("to-array"); 1642 | 1643 | var bruteForce = [ 1644 | "button", 1645 | "fieldset", 1646 | "input", 1647 | "keygen", 1648 | "object", 1649 | "output", 1650 | "select", 1651 | "textarea" 1652 | ].join(","); 1653 | 1654 | 1655 | /** 1656 | * Retrieves a list of valid controls from a given root element. 1657 | * 1658 | * If the `root` element is not either a `` or `
`, or does not 1659 | * expose a `HTMLFormControlCollection` interface, then a "brute-force" search 1660 | * retrieves the valid "listed controls" is used. 1661 | * 1662 | * @see http://www.w3.org/TR/html5/forms.html#category-listed 1663 | * 1664 | * @param {HTMLElement} root 1665 | * @returns {Array:HTMLElement} 1666 | */ 1667 | 1668 | module.exports = function (root) { 1669 | if (!root) { 1670 | throw new TypeError("a root element is required"); 1671 | } 1672 | 1673 | return toArray(root.elements || root.querySelectorAll(bruteForce)); 1674 | }; 1675 | 1676 | }, {"to-array":25}], 1677 | 25: [function(require, module, exports) { 1678 | /** 1679 | * Convert an array-like object into an `Array`. 1680 | * If `collection` is already an `Array`, then will return a clone of `collection`. 1681 | * 1682 | * @param {Array | Mixed} collection An `Array` or array-like object to convert e.g. `arguments` or `NodeList` 1683 | * @return {Array} Naive conversion of `collection` to a new `Array`. 1684 | * @api public 1685 | */ 1686 | 1687 | module.exports = function toArray(collection) { 1688 | if (typeof collection === 'undefined') return [] 1689 | if (collection === null) return [null] 1690 | if (collection === window) return [window] 1691 | if (typeof collection === 'string') return [collection] 1692 | if (isArray(collection)) return collection 1693 | if (typeof collection.length != 'number') return [collection] 1694 | if (typeof collection === 'function' && collection instanceof Function) return [collection] 1695 | 1696 | var arr = [] 1697 | for (var i = 0; i < collection.length; i++) { 1698 | if (Object.prototype.hasOwnProperty.call(collection, i) || i in collection) { 1699 | arr.push(collection[i]) 1700 | } 1701 | } 1702 | if (!arr.length) return [] 1703 | return arr 1704 | } 1705 | 1706 | function isArray(arr) { 1707 | return Object.prototype.toString.call(arr) === "[object Array]"; 1708 | } 1709 | 1710 | }, {}], 1711 | 22: [function(require, module, exports) { 1712 | 1713 | /** 1714 | * dependencies 1715 | */ 1716 | 1717 | var each = require('each'); 1718 | 1719 | /** 1720 | * Export `reduce` 1721 | */ 1722 | 1723 | module.exports = reduce; 1724 | 1725 | /** 1726 | * Reduce `obj` with `fn`. 1727 | * 1728 | * @param {Mixed} obj 1729 | * @param {Function} fn 1730 | * @param {Mixed} val 1731 | * @api public 1732 | */ 1733 | 1734 | function reduce(obj, fn, val){ 1735 | each(obj, function(a, b){ 1736 | val = fn.apply(null, [val, a, b]); 1737 | }); 1738 | return val; 1739 | } 1740 | 1741 | }, {"each":26}], 1742 | 26: [function(require, module, exports) { 1743 | 1744 | /** 1745 | * Module dependencies. 1746 | */ 1747 | 1748 | try { 1749 | var type = require('type'); 1750 | } catch (err) { 1751 | var type = require('component-type'); 1752 | } 1753 | 1754 | var toFunction = require('to-function'); 1755 | 1756 | /** 1757 | * HOP reference. 1758 | */ 1759 | 1760 | var has = Object.prototype.hasOwnProperty; 1761 | 1762 | /** 1763 | * Iterate the given `obj` and invoke `fn(val, i)` 1764 | * in optional context `ctx`. 1765 | * 1766 | * @param {String|Array|Object} obj 1767 | * @param {Function} fn 1768 | * @param {Object} [ctx] 1769 | * @api public 1770 | */ 1771 | 1772 | module.exports = function(obj, fn, ctx){ 1773 | fn = toFunction(fn); 1774 | ctx = ctx || this; 1775 | switch (type(obj)) { 1776 | case 'array': 1777 | return array(obj, fn, ctx); 1778 | case 'object': 1779 | if ('number' == typeof obj.length) return array(obj, fn, ctx); 1780 | return object(obj, fn, ctx); 1781 | case 'string': 1782 | return string(obj, fn, ctx); 1783 | } 1784 | }; 1785 | 1786 | /** 1787 | * Iterate string chars. 1788 | * 1789 | * @param {String} obj 1790 | * @param {Function} fn 1791 | * @param {Object} ctx 1792 | * @api private 1793 | */ 1794 | 1795 | function string(obj, fn, ctx) { 1796 | for (var i = 0; i < obj.length; ++i) { 1797 | fn.call(ctx, obj.charAt(i), i); 1798 | } 1799 | } 1800 | 1801 | /** 1802 | * Iterate object keys. 1803 | * 1804 | * @param {Object} obj 1805 | * @param {Function} fn 1806 | * @param {Object} ctx 1807 | * @api private 1808 | */ 1809 | 1810 | function object(obj, fn, ctx) { 1811 | for (var key in obj) { 1812 | if (has.call(obj, key)) { 1813 | fn.call(ctx, key, obj[key]); 1814 | } 1815 | } 1816 | } 1817 | 1818 | /** 1819 | * Iterate array-ish. 1820 | * 1821 | * @param {Array|Object} obj 1822 | * @param {Function} fn 1823 | * @param {Object} ctx 1824 | * @api private 1825 | */ 1826 | 1827 | function array(obj, fn, ctx) { 1828 | for (var i = 0; i < obj.length; ++i) { 1829 | fn.call(ctx, obj[i], i); 1830 | } 1831 | } 1832 | 1833 | }, {"type":27,"component-type":27,"to-function":28}], 1834 | 27: [function(require, module, exports) { 1835 | 1836 | /** 1837 | * toString ref. 1838 | */ 1839 | 1840 | var toString = Object.prototype.toString; 1841 | 1842 | /** 1843 | * Return the type of `val`. 1844 | * 1845 | * @param {Mixed} val 1846 | * @return {String} 1847 | * @api public 1848 | */ 1849 | 1850 | module.exports = function(val){ 1851 | switch (toString.call(val)) { 1852 | case '[object Function]': return 'function'; 1853 | case '[object Date]': return 'date'; 1854 | case '[object RegExp]': return 'regexp'; 1855 | case '[object Arguments]': return 'arguments'; 1856 | case '[object Array]': return 'array'; 1857 | case '[object String]': return 'string'; 1858 | } 1859 | 1860 | if (val === null) return 'null'; 1861 | if (val === undefined) return 'undefined'; 1862 | if (val && val.nodeType === 1) return 'element'; 1863 | if (val === Object(val)) return 'object'; 1864 | 1865 | return typeof val; 1866 | }; 1867 | 1868 | }, {}], 1869 | 28: [function(require, module, exports) { 1870 | 1871 | /** 1872 | * Module Dependencies 1873 | */ 1874 | 1875 | var expr; 1876 | try { 1877 | expr = require('props'); 1878 | } catch(e) { 1879 | expr = require('component-props'); 1880 | } 1881 | 1882 | /** 1883 | * Expose `toFunction()`. 1884 | */ 1885 | 1886 | module.exports = toFunction; 1887 | 1888 | /** 1889 | * Convert `obj` to a `Function`. 1890 | * 1891 | * @param {Mixed} obj 1892 | * @return {Function} 1893 | * @api private 1894 | */ 1895 | 1896 | function toFunction(obj) { 1897 | switch ({}.toString.call(obj)) { 1898 | case '[object Object]': 1899 | return objectToFunction(obj); 1900 | case '[object Function]': 1901 | return obj; 1902 | case '[object String]': 1903 | return stringToFunction(obj); 1904 | case '[object RegExp]': 1905 | return regexpToFunction(obj); 1906 | default: 1907 | return defaultToFunction(obj); 1908 | } 1909 | } 1910 | 1911 | /** 1912 | * Default to strict equality. 1913 | * 1914 | * @param {Mixed} val 1915 | * @return {Function} 1916 | * @api private 1917 | */ 1918 | 1919 | function defaultToFunction(val) { 1920 | return function(obj){ 1921 | return val === obj; 1922 | }; 1923 | } 1924 | 1925 | /** 1926 | * Convert `re` to a function. 1927 | * 1928 | * @param {RegExp} re 1929 | * @return {Function} 1930 | * @api private 1931 | */ 1932 | 1933 | function regexpToFunction(re) { 1934 | return function(obj){ 1935 | return re.test(obj); 1936 | }; 1937 | } 1938 | 1939 | /** 1940 | * Convert property `str` to a function. 1941 | * 1942 | * @param {String} str 1943 | * @return {Function} 1944 | * @api private 1945 | */ 1946 | 1947 | function stringToFunction(str) { 1948 | // immediate such as "> 20" 1949 | if (/^ *\W+/.test(str)) return new Function('_', 'return _ ' + str); 1950 | 1951 | // properties such as "name.first" or "age > 18" or "age > 18 && age < 36" 1952 | return new Function('_', 'return ' + get(str)); 1953 | } 1954 | 1955 | /** 1956 | * Convert `object` to a function. 1957 | * 1958 | * @param {Object} object 1959 | * @return {Function} 1960 | * @api private 1961 | */ 1962 | 1963 | function objectToFunction(obj) { 1964 | var match = {}; 1965 | for (var key in obj) { 1966 | match[key] = typeof obj[key] === 'string' 1967 | ? defaultToFunction(obj[key]) 1968 | : toFunction(obj[key]); 1969 | } 1970 | return function(val){ 1971 | if (typeof val !== 'object') return false; 1972 | for (var key in match) { 1973 | if (!(key in val)) return false; 1974 | if (!match[key](val[key])) return false; 1975 | } 1976 | return true; 1977 | }; 1978 | } 1979 | 1980 | /** 1981 | * Built the getter function. Supports getter style functions 1982 | * 1983 | * @param {String} str 1984 | * @return {String} 1985 | * @api private 1986 | */ 1987 | 1988 | function get(str) { 1989 | var props = expr(str); 1990 | if (!props.length) return '_.' + str; 1991 | 1992 | var val, i, prop; 1993 | for (i = 0; i < props.length; i++) { 1994 | prop = props[i]; 1995 | val = '_.' + prop; 1996 | val = "('function' == typeof " + val + " ? " + val + "() : " + val + ")"; 1997 | 1998 | // mimic negative lookbehind to avoid problems with nested properties 1999 | str = stripNested(prop, str, val); 2000 | } 2001 | 2002 | return str; 2003 | } 2004 | 2005 | /** 2006 | * Mimic negative lookbehind to avoid problems with nested properties. 2007 | * 2008 | * See: http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript 2009 | * 2010 | * @param {String} prop 2011 | * @param {String} str 2012 | * @param {String} val 2013 | * @return {String} 2014 | * @api private 2015 | */ 2016 | 2017 | function stripNested (prop, str, val) { 2018 | return str.replace(new RegExp('(\\.)?' + prop, 'g'), function($0, $1) { 2019 | return $1 ? $0 : val; 2020 | }); 2021 | } 2022 | 2023 | }, {"props":29,"component-props":29}], 2024 | 29: [function(require, module, exports) { 2025 | /** 2026 | * Global Names 2027 | */ 2028 | 2029 | var globals = /\b(this|Array|Date|Object|Math|JSON)\b/g; 2030 | 2031 | /** 2032 | * Return immediate identifiers parsed from `str`. 2033 | * 2034 | * @param {String} str 2035 | * @param {String|Function} map function or prefix 2036 | * @return {Array} 2037 | * @api public 2038 | */ 2039 | 2040 | module.exports = function(str, fn){ 2041 | var p = unique(props(str)); 2042 | if (fn && 'string' == typeof fn) fn = prefixed(fn); 2043 | if (fn) return map(str, p, fn); 2044 | return p; 2045 | }; 2046 | 2047 | /** 2048 | * Return immediate identifiers in `str`. 2049 | * 2050 | * @param {String} str 2051 | * @return {Array} 2052 | * @api private 2053 | */ 2054 | 2055 | function props(str) { 2056 | return str 2057 | .replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') 2058 | .replace(globals, '') 2059 | .match(/[$a-zA-Z_]\w*/g) 2060 | || []; 2061 | } 2062 | 2063 | /** 2064 | * Return `str` with `props` mapped with `fn`. 2065 | * 2066 | * @param {String} str 2067 | * @param {Array} props 2068 | * @param {Function} fn 2069 | * @return {String} 2070 | * @api private 2071 | */ 2072 | 2073 | function map(str, props, fn) { 2074 | var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g; 2075 | return str.replace(re, function(_){ 2076 | if ('(' == _[_.length - 1]) return fn(_); 2077 | if (!~props.indexOf(_)) return _; 2078 | return fn(_); 2079 | }); 2080 | } 2081 | 2082 | /** 2083 | * Return unique array. 2084 | * 2085 | * @param {Array} arr 2086 | * @return {Array} 2087 | * @api private 2088 | */ 2089 | 2090 | function unique(arr) { 2091 | var ret = []; 2092 | 2093 | for (var i = 0; i < arr.length; i++) { 2094 | if (~ret.indexOf(arr[i])) continue; 2095 | ret.push(arr[i]); 2096 | } 2097 | 2098 | return ret; 2099 | } 2100 | 2101 | /** 2102 | * Map with prefix `str`. 2103 | */ 2104 | 2105 | function prefixed(str) { 2106 | return function(_){ 2107 | return str + _; 2108 | }; 2109 | } 2110 | 2111 | }, {}], 2112 | 23: [function(require, module, exports) { 2113 | 2114 | exports.parse = function (name) { 2115 | return name.split("[").map(function (part) { 2116 | // strip the "]" from the string, it's not needed after the split 2117 | part = part.replace(/\]$/, ""); 2118 | 2119 | // if the string is now empty, we're dealing with an array 2120 | if (!part) return false; 2121 | 2122 | // if the key is just numbers, parse it 2123 | if (/^\d+$/.test(part)) return parseInt(part, 10); 2124 | 2125 | // otherwise, return string key name 2126 | return part; 2127 | }); 2128 | }; 2129 | 2130 | exports.set = function (o, key, value) { 2131 | if (!o) o = {}; // create an empty object if needed 2132 | 2133 | return exports.parse(key).reduce(function (acc, branch, x, branches) { 2134 | // while we are setting the various branches on our object 2135 | if (x + 1 < branches.length) { 2136 | // we need to see what key is coming next 2137 | var nextKey = branches[x + 1]; 2138 | 2139 | // when working with an array 2140 | if (branch === false) { 2141 | // first inspect the last item on the array 2142 | var temp = acc[acc.length - 1]; 2143 | 2144 | if (!temp || branchesExist(temp, branches.slice(x + 1))) { 2145 | temp = {}; 2146 | acc.push(temp); 2147 | } 2148 | 2149 | return temp; 2150 | } else { 2151 | // when the branch does not already exist 2152 | if (!(branch in acc)) { 2153 | // depending on nextKey, we may be setting an array or an object 2154 | acc[branch] = (nextKey === false || typeof nextKey === "number") ? [] : {}; 2155 | } 2156 | 2157 | return acc[branch]; 2158 | } 2159 | // the last iteration just sets a simple property / appends to an array 2160 | } else { 2161 | if (branch === false) { 2162 | acc.push(value); 2163 | } else { 2164 | acc[branch] = value; 2165 | } 2166 | 2167 | return o; 2168 | } 2169 | }, o); 2170 | }; 2171 | 2172 | function branchesExist(o, branches) { 2173 | var current = o; 2174 | return branches.every(function (branch) { 2175 | if (branch in current) { 2176 | current = current[branch]; 2177 | return true; 2178 | } else { 2179 | return false; 2180 | } 2181 | }); 2182 | } 2183 | 2184 | }, {}], 2185 | 19: [function(require, module, exports) { 2186 | 2187 | /** 2188 | * Module dependencies. 2189 | */ 2190 | 2191 | var typeOf = require('type'); 2192 | 2193 | /** 2194 | * Set or get `el`'s' value. 2195 | * 2196 | * @param {Element} el 2197 | * @param {Mixed} val 2198 | * @return {Mixed} 2199 | * @api public 2200 | */ 2201 | 2202 | module.exports = function(el, val){ 2203 | if (2 == arguments.length) return set(el, val); 2204 | return get(el); 2205 | }; 2206 | 2207 | /** 2208 | * Get `el`'s value. 2209 | */ 2210 | 2211 | function get(el) { 2212 | switch (type(el)) { 2213 | case 'checkbox': 2214 | case 'radio': 2215 | if (el.checked) { 2216 | var attr = el.getAttribute('value'); 2217 | return null == attr ? true : attr; 2218 | } else { 2219 | return false; 2220 | } 2221 | case 'radiogroup': 2222 | for (var i = 0, radio; radio = el[i]; i++) { 2223 | if (radio.checked) return radio.value; 2224 | } 2225 | break; 2226 | case 'select': 2227 | for (var i = 0, option; option = el.options[i]; i++) { 2228 | if (option.selected) return option.value; 2229 | } 2230 | break; 2231 | default: 2232 | return el.value; 2233 | } 2234 | } 2235 | 2236 | /** 2237 | * Set `el`'s value. 2238 | */ 2239 | 2240 | function set(el, val) { 2241 | switch (type(el)) { 2242 | case 'checkbox': 2243 | case 'radio': 2244 | if (val) { 2245 | el.checked = true; 2246 | } else { 2247 | el.checked = false; 2248 | } 2249 | break; 2250 | case 'radiogroup': 2251 | for (var i = 0, radio; radio = el[i]; i++) { 2252 | radio.checked = radio.value === val; 2253 | } 2254 | break; 2255 | case 'select': 2256 | for (var i = 0, option; option = el.options[i]; i++) { 2257 | option.selected = option.value === val; 2258 | } 2259 | break; 2260 | default: 2261 | el.value = val; 2262 | } 2263 | } 2264 | 2265 | /** 2266 | * Element type. 2267 | */ 2268 | 2269 | function type(el) { 2270 | var group = 'array' == typeOf(el) || 'object' == typeOf(el); 2271 | if (group) el = el[0]; 2272 | var name = el.nodeName.toLowerCase(); 2273 | var type = el.getAttribute('type'); 2274 | 2275 | if (group && type && 'radio' == type.toLowerCase()) return 'radiogroup'; 2276 | if ('input' == name && type && 'checkbox' == type.toLowerCase()) return 'checkbox'; 2277 | if ('input' == name && type && 'radio' == type.toLowerCase()) return 'radio'; 2278 | if ('select' == name) return 'select'; 2279 | return name; 2280 | } 2281 | 2282 | }, {"type":30}], 2283 | 30: [function(require, module, exports) { 2284 | /** 2285 | * toString ref. 2286 | */ 2287 | 2288 | var toString = Object.prototype.toString; 2289 | 2290 | /** 2291 | * Return the type of `val`. 2292 | * 2293 | * @param {Mixed} val 2294 | * @return {String} 2295 | * @api public 2296 | */ 2297 | 2298 | module.exports = function(val){ 2299 | switch (toString.call(val)) { 2300 | case '[object Date]': return 'date'; 2301 | case '[object RegExp]': return 'regexp'; 2302 | case '[object Arguments]': return 'arguments'; 2303 | case '[object Array]': return 'array'; 2304 | case '[object Error]': return 'error'; 2305 | } 2306 | 2307 | if (val === null) return 'null'; 2308 | if (val === undefined) return 'undefined'; 2309 | if (val !== val) return 'nan'; 2310 | if (val && val.nodeType === 1) return 'element'; 2311 | 2312 | val = val.valueOf 2313 | ? val.valueOf() 2314 | : Object.prototype.valueOf.apply(val) 2315 | 2316 | return typeof val; 2317 | }; 2318 | 2319 | }, {}], 2320 | 24: [function(require, module, exports) { 2321 | 2322 | /** 2323 | * Check if the given `el` is submittable. 2324 | * 2325 | * @param {Element} 2326 | * @return {Boolean} 2327 | */ 2328 | 2329 | module.exports = function(el){ 2330 | return ! el.disabled 2331 | && el.name 2332 | && ! rtype.test(el.type) 2333 | && rname.test(el.nodeName) 2334 | && (!rcheck.test(el.type) 2335 | || el.checked); 2336 | }; 2337 | 2338 | /** 2339 | * expr's 2340 | */ 2341 | 2342 | var rtype = /^(?:submit|button|image|reset|file)$/i; 2343 | var rname = /^(?:input|select|textarea|keygen)$/i; 2344 | var rcheck = /^(?:checkbox|radio)$/i; 2345 | 2346 | }, {}], 2347 | 11: [function(require, module, exports) { 2348 | 2349 | var create = require('create-event'); 2350 | 2351 | 2352 | /** 2353 | * Expose `trigger`. 2354 | */ 2355 | 2356 | module.exports = trigger; 2357 | 2358 | 2359 | /** 2360 | * Trigger an event of `type` on an `el` with `options`. 2361 | * 2362 | * @param {Element} el 2363 | * @param {String} type 2364 | * @param {Object} options 2365 | */ 2366 | 2367 | function trigger (el, type, options) { 2368 | 2369 | // default el is `document` 2370 | if ('string' === typeof el) { 2371 | options = type; 2372 | type = el; 2373 | el = document; 2374 | } 2375 | 2376 | var e = create(type, options); 2377 | el.dispatchEvent 2378 | ? el.dispatchEvent(e) 2379 | : el.fireEvent(ieify(type), e); 2380 | } 2381 | 2382 | 2383 | /** 2384 | * Convert a type into an IE-specific type. 2385 | * 2386 | * @param {String} type 2387 | * @return {String} 2388 | */ 2389 | 2390 | function ieify (type) { 2391 | return 'on' + type[0].toUpperCase() + type.slice(1); 2392 | } 2393 | }, {"create-event":31}], 2394 | 31: [function(require, module, exports) { 2395 | 2396 | var extend = require('extend') 2397 | , keycode = require('keycode'); 2398 | 2399 | 2400 | /** 2401 | * Expose `createEvent`. 2402 | */ 2403 | 2404 | module.exports = !!document.createEvent 2405 | ? createEvent 2406 | : createIeEvent; 2407 | 2408 | 2409 | /** 2410 | * Default options. 2411 | */ 2412 | 2413 | var defaults = { 2414 | alt : false, 2415 | bubbles : true, 2416 | button : 0, 2417 | cancelable : true, 2418 | clientX : 0, 2419 | clientY : 0, 2420 | ctrl : false, 2421 | detail : 1, 2422 | key : 0, 2423 | meta : false, 2424 | relatedTarget : null, 2425 | screenX : 0, 2426 | screenY : 0, 2427 | shift : false, 2428 | view : window 2429 | }; 2430 | 2431 | 2432 | /** 2433 | * Create a non-IE event object. 2434 | * 2435 | * @param {String} type 2436 | * @param {Object} options 2437 | */ 2438 | 2439 | function createEvent (type, options) { 2440 | switch (type) { 2441 | case 'dblclick': 2442 | case 'click': 2443 | return createMouseEvent(type, options); 2444 | case 'keydown': 2445 | case 'keyup': 2446 | return createKeyboardEvent(type, options); 2447 | } 2448 | } 2449 | 2450 | 2451 | /** 2452 | * Create a non-IE mouse event. 2453 | * 2454 | * @param {String} type 2455 | * @param {Object} options 2456 | */ 2457 | 2458 | function createMouseEvent (type, options) { 2459 | options = clean(type, options); 2460 | var e = document.createEvent('MouseEvent'); 2461 | e.initMouseEvent( 2462 | type, 2463 | options.bubbles, // bubbles? 2464 | options.cancelable, // cancelable? 2465 | options.view, // view 2466 | options.detail, // detail 2467 | options.screenX, // screenX 2468 | options.screenY, // screenY 2469 | options.clientX , // clientX 2470 | options.clientY, // clientY 2471 | options.ctrl, // ctrlKey 2472 | options.alt, // altKey 2473 | options.shift, // shiftKey 2474 | options.meta, // metaKey 2475 | options.button, // button 2476 | options.relatedTarget // relatedTarget 2477 | ); 2478 | return e; 2479 | } 2480 | 2481 | 2482 | /** 2483 | * Create a non-IE keyboard event. 2484 | * 2485 | * @param {String} type 2486 | * @param {Object} options 2487 | */ 2488 | 2489 | function createKeyboardEvent (type, options) { 2490 | options = clean(type, options); 2491 | var e = document.createEvent('KeyboardEvent'); 2492 | (e.initKeyEvent || e.initKeyboardEvent).call( 2493 | e, 2494 | type, 2495 | options.bubbles, // bubbles? 2496 | options.cancelable, // cancelable? 2497 | options.view, // view 2498 | options.ctrl, // ctrlKey 2499 | options.alt, // altKey 2500 | options.shift, // shiftKey 2501 | options.meta, // metaKey 2502 | options.key, // keyCode 2503 | options.key // charCode 2504 | ); 2505 | 2506 | // super hack: http://stackoverflow.com/questions/10455626/keydown-simulation-in-chrome-fires-normally-but-not-the-correct-key/10520017#10520017 2507 | if (e.keyCode !== options.key) { 2508 | Object.defineProperty(e, 'keyCode', { 2509 | get: function () { return options.key; } 2510 | }); 2511 | Object.defineProperty(e, 'charCode', { 2512 | get: function () { return options.key; } 2513 | }); 2514 | Object.defineProperty(e, 'which', { 2515 | get: function () { return options.key; } 2516 | }); 2517 | Object.defineProperty(e, 'shiftKey', { 2518 | get: function () { return options.shift; } 2519 | }); 2520 | } 2521 | 2522 | return e; 2523 | } 2524 | 2525 | 2526 | /** 2527 | * Create an IE event. Surprisingly nicer API, eh? 2528 | * 2529 | * @param {String} type 2530 | * @param {Object} options 2531 | */ 2532 | 2533 | function createIeEvent (type, options) { 2534 | options = clean(type, options); 2535 | var e = document.createEventObject(); 2536 | e.altKey = options.alt; 2537 | e.bubbles = options.bubbles; 2538 | e.button = options.button; 2539 | e.cancelable = options.cancelable; 2540 | e.clientX = options.clientX; 2541 | e.clientY = options.clientY; 2542 | e.ctrlKey = options.ctrl; 2543 | e.detail = options.detail; 2544 | e.metaKey = options.meta; 2545 | e.screenX = options.screenX; 2546 | e.screenY = options.screenY; 2547 | e.shiftKey = options.shift; 2548 | e.keyCode = options.key; 2549 | e.charCode = options.key; 2550 | e.view = options.view; 2551 | return e; 2552 | } 2553 | 2554 | 2555 | /** 2556 | * Back an `options` object by defaults, and convert some convenience features. 2557 | * 2558 | * @param {String} type 2559 | * @param {Object} options 2560 | * @return {Object} [description] 2561 | */ 2562 | 2563 | function clean (type, options) { 2564 | options = extend({}, defaults, options); 2565 | if ('dblclick' === type) options.detail = 2; 2566 | if ('string' === typeof options.key) options.key = keycode(options.key); 2567 | return options; 2568 | } 2569 | 2570 | }, {"extend":32,"keycode":33}], 2571 | 32: [function(require, module, exports) { 2572 | 2573 | module.exports = function extend (object) { 2574 | // Takes an unlimited number of extenders. 2575 | var args = Array.prototype.slice.call(arguments, 1); 2576 | 2577 | // For each extender, copy their properties on our object. 2578 | for (var i = 0, source; source = args[i]; i++) { 2579 | if (!source) continue; 2580 | for (var property in source) { 2581 | object[property] = source[property]; 2582 | } 2583 | } 2584 | 2585 | return object; 2586 | }; 2587 | }, {}], 2588 | 33: [function(require, module, exports) { 2589 | // Source: http://jsfiddle.net/vWx8V/ 2590 | // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 2591 | 2592 | 2593 | 2594 | /** 2595 | * Conenience method returns corresponding value for given keyName or keyCode. 2596 | * 2597 | * @param {Mixed} keyCode {Number} or keyName {String} 2598 | * @return {Mixed} 2599 | * @api public 2600 | */ 2601 | 2602 | exports = module.exports = function(searchInput) { 2603 | // Keyboard Events 2604 | if (searchInput && 'object' === typeof searchInput) { 2605 | var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode 2606 | if (hasKeyCode) searchInput = hasKeyCode 2607 | } 2608 | 2609 | // Numbers 2610 | if ('number' === typeof searchInput) return names[searchInput] 2611 | 2612 | // Everything else (cast to string) 2613 | var search = String(searchInput) 2614 | 2615 | // check codes 2616 | var foundNamedKey = codes[search.toLowerCase()] 2617 | if (foundNamedKey) return foundNamedKey 2618 | 2619 | // check aliases 2620 | var foundNamedKey = aliases[search.toLowerCase()] 2621 | if (foundNamedKey) return foundNamedKey 2622 | 2623 | // weird character? 2624 | if (search.length === 1) return search.charCodeAt(0) 2625 | 2626 | return undefined 2627 | } 2628 | 2629 | /** 2630 | * Get by name 2631 | * 2632 | * exports.code['enter'] // => 13 2633 | */ 2634 | 2635 | var codes = exports.code = exports.codes = { 2636 | 'backspace': 8, 2637 | 'tab': 9, 2638 | 'enter': 13, 2639 | 'shift': 16, 2640 | 'ctrl': 17, 2641 | 'alt': 18, 2642 | 'pause/break': 19, 2643 | 'caps lock': 20, 2644 | 'esc': 27, 2645 | 'space': 32, 2646 | 'page up': 33, 2647 | 'page down': 34, 2648 | 'end': 35, 2649 | 'home': 36, 2650 | 'left': 37, 2651 | 'up': 38, 2652 | 'right': 39, 2653 | 'down': 40, 2654 | 'insert': 45, 2655 | 'delete': 46, 2656 | 'windows': 91, 2657 | 'command': 91, 2658 | 'right click': 93, 2659 | 'numpad *': 106, 2660 | 'numpad +': 107, 2661 | 'numpad -': 109, 2662 | 'numpad .': 110, 2663 | 'numpad /': 111, 2664 | 'num lock': 144, 2665 | 'scroll lock': 145, 2666 | 'my computer': 182, 2667 | 'my calculator': 183, 2668 | ';': 186, 2669 | '=': 187, 2670 | ',': 188, 2671 | '-': 189, 2672 | '.': 190, 2673 | '/': 191, 2674 | '`': 192, 2675 | '[': 219, 2676 | '\\': 220, 2677 | ']': 221, 2678 | "'": 222, 2679 | '⇧': 16, 2680 | '⌥': 18, 2681 | '⌃': 17, 2682 | '⌘': 91, 2683 | } 2684 | 2685 | // Helper aliases 2686 | 2687 | var aliases = exports.aliases = { 2688 | 'shift': 16, 2689 | 'ctl': 17, 2690 | 'ctrl': 17, 2691 | 'control': 17, 2692 | 'alt': 18, 2693 | 'option': 18, 2694 | 'pause': 19, 2695 | 'break': 19, 2696 | 'caps': 20, 2697 | 'escape': 27, 2698 | 'spc': 32, 2699 | 'pgup': 33, 2700 | 'pgdn': 33, 2701 | 'ins': 45, 2702 | 'del': 46, 2703 | 'cmd': 91 2704 | } 2705 | 2706 | 2707 | /*! 2708 | * Programatically add the following 2709 | */ 2710 | 2711 | // lower case chars 2712 | for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32 2713 | 2714 | // numbers 2715 | for (var i = 48; i < 58; i++) codes[i - 48] = i 2716 | 2717 | // function keys 2718 | for (i = 1; i < 13; i++) codes['f'+i] = i + 111 2719 | 2720 | // numpad keys 2721 | for (i = 0; i < 10; i++) codes['numpad '+i] = i + 96 2722 | 2723 | /** 2724 | * Get by code 2725 | * 2726 | * exports.name[13] // => 'Enter' 2727 | */ 2728 | 2729 | var names = exports.names = exports.title = {} // title for backward compat 2730 | 2731 | // Create reverse mapping 2732 | for (i in codes) names[codes[i]] = i 2733 | 2734 | // Add aliases 2735 | for (var alias in aliases) { 2736 | codes[alias] = aliases[alias] 2737 | } 2738 | 2739 | }, {}], 2740 | 6: [function(require, module, exports) { 2741 | /** 2742 | * Module dependencies 2743 | */ 2744 | 2745 | var Step = require('step.js'); 2746 | var noop = function() {}; 2747 | 2748 | /** 2749 | * Export `Rube` 2750 | */ 2751 | 2752 | module.exports = Rube; 2753 | 2754 | /** 2755 | * Initialize `Rube` 2756 | * 2757 | * @return {Rube} 2758 | * @api public 2759 | */ 2760 | 2761 | function Rube() { 2762 | if (!(this instanceof Rube)) return new Rube(); 2763 | var pipeline = []; 2764 | 2765 | function rube(actual, fn) { 2766 | Step(pipeline).run(actual, function(err, v) { 2767 | return err 2768 | ? fn(rube._message(err)) 2769 | : fn(null, v); 2770 | }); 2771 | } 2772 | 2773 | rube._pipeline = pipeline; 2774 | rube._message = function(err) { return err; } 2775 | 2776 | // add the methods 2777 | for (var k in Rube.prototype) { 2778 | rube[k] = Rube.prototype[k]; 2779 | } 2780 | 2781 | return rube; 2782 | } 2783 | 2784 | /** 2785 | * Attach a custom method to Rube instances 2786 | * 2787 | * @param {String} name 2788 | * @param {Function} fn 2789 | * @return {Rube} 2790 | * @api public 2791 | */ 2792 | 2793 | Rube.plugin = function(name, fn) { 2794 | if (arguments.length == 1) { 2795 | fn = name; 2796 | name = fn.name; 2797 | } 2798 | 2799 | if (!name) throw new Error('Rube.plugin(name, fn) requires a name'); 2800 | 2801 | // add the method 2802 | this.prototype[name.toLowerCase()] = function() { 2803 | var ret = fn.apply(null, arguments); 2804 | this._pipeline.push(ret || noop); 2805 | return this; 2806 | }; 2807 | 2808 | return this; 2809 | } 2810 | 2811 | /** 2812 | * Add a plugin a rube instance 2813 | * 2814 | * @param {Function} fn 2815 | * @return {Rube} 2816 | * @api public 2817 | */ 2818 | 2819 | Rube.prototype.use = function(fn) { 2820 | this._pipeline.push(fn); 2821 | return this; 2822 | }; 2823 | 2824 | /** 2825 | * Add a custom error message 2826 | * 2827 | * @param {Mixed} msg 2828 | * @return {Rube} 2829 | */ 2830 | 2831 | Rube.prototype.message = function(msg) { 2832 | this._message = 'string' == typeof msg 2833 | ? function() { return new TypeError(msg); } 2834 | : msg instanceof Error 2835 | ? function() { return msg; } 2836 | : msg; 2837 | 2838 | return this; 2839 | }; 2840 | 2841 | /** 2842 | * Bundled plugins 2843 | */ 2844 | 2845 | Rube.plugin('default', require('./lib/default.js')); 2846 | Rube.plugin('required', require('./lib/required.js')); 2847 | Rube.plugin('between', require('./lib/between.js')); 2848 | Rube.plugin('format', require('./lib/format.js')); 2849 | Rube.plugin('assert', require('./lib/assert.js')); 2850 | Rube.plugin('cast', require('./lib/cast.js')); 2851 | Rube.plugin('type', require('./lib/type.js')); 2852 | 2853 | }, {"step.js":34,"./lib/default.js":35,"./lib/required.js":36,"./lib/between.js":37,"./lib/format.js":38,"./lib/assert.js":39,"./lib/cast.js":40,"./lib/type.js":41}], 2854 | 34: [function(require, module, exports) { 2855 | /** 2856 | * Module Dependencies 2857 | */ 2858 | 2859 | var slice = Array.prototype.slice; 2860 | var noop = function() {}; 2861 | var co = require('co'); 2862 | 2863 | /** 2864 | * Export `Step` 2865 | */ 2866 | 2867 | module.exports = Step; 2868 | 2869 | /** 2870 | * Initialize `Step` 2871 | * 2872 | * @param {Mixed} fn (optional) 2873 | * @return {Step} 2874 | * @api public 2875 | */ 2876 | 2877 | function Step(fn) { 2878 | if (!(this instanceof Step)) return new Step(fn); 2879 | this.fns = []; 2880 | this.length = 0; 2881 | fn && this.use(fn); 2882 | } 2883 | 2884 | /** 2885 | * Add a step 2886 | * 2887 | * @param {Function|Generator|Array} fn 2888 | * @return {Step} 2889 | * @api public 2890 | */ 2891 | 2892 | Step.prototype.use = function(fn) { 2893 | if (fn instanceof Step) this.fns = this.fns.concat(fn.fns); 2894 | else if (fn instanceof Array) this.fns = this.fns.concat(fn); 2895 | else this.fns.push(fn); 2896 | this.length = this.fns.length; 2897 | return this; 2898 | }; 2899 | 2900 | /** 2901 | * Run the steps 2902 | * 2903 | * @param {Args...} args 2904 | * @param {Function} fn 2905 | * @api public 2906 | */ 2907 | 2908 | Step.prototype.run = function() { 2909 | var args = slice.call(arguments); 2910 | var fns = slice.call(this.fns); 2911 | var len = args.length; 2912 | var ctx = this; 2913 | 2914 | // callback or noop 2915 | var done = 'function' == typeof args[len - 1] 2916 | ? args.pop() 2917 | : noop; 2918 | 2919 | // kick us off 2920 | // next tick to ensure we're async (no double callbacks) 2921 | setTimeout(function() { 2922 | call(fns.shift(), args); 2923 | }, 0); 2924 | 2925 | // next 2926 | function next(err) { 2927 | if (err) return done(err); 2928 | var arr = slice.call(arguments, 1); 2929 | args = extend(args, arr); 2930 | var fn = fns.shift(); 2931 | call(fn, args); 2932 | } 2933 | 2934 | // call 2935 | function call(fn, args) { 2936 | if (!fn) { 2937 | return done.apply(done, [null].concat(args)); 2938 | } else if (fn.length > args.length) { 2939 | fn.apply(ctx, args.concat(next)); 2940 | } else if (generator(fn)) { 2941 | co(fn).apply(ctx, args.concat(next)); 2942 | } else { 2943 | var ret = fn.apply(ctx, args); 2944 | ret instanceof Error ? next(ret) : next(null, ret); 2945 | } 2946 | } 2947 | }; 2948 | 2949 | /** 2950 | * Pull values from another array 2951 | * @param {Array} a 2952 | * @param {Array} b 2953 | * @return {Array} 2954 | */ 2955 | 2956 | function extend(a, b) { 2957 | var len = a.length; 2958 | var out = []; 2959 | 2960 | for (var i = 0; i < len; i++) { 2961 | out[i] = undefined === b[i] ? a[i] : b[i]; 2962 | } 2963 | 2964 | return out; 2965 | } 2966 | 2967 | /** 2968 | * Is generator? 2969 | * 2970 | * @param {Mixed} value 2971 | * @return {Boolean} 2972 | * @api private 2973 | */ 2974 | 2975 | function generator(value){ 2976 | return value 2977 | && value.constructor 2978 | && 'GeneratorFunction' == value.constructor.name; 2979 | } 2980 | 2981 | }, {"co":42}], 2982 | 42: [function(require, module, exports) { 2983 | 2984 | /** 2985 | * slice() reference. 2986 | */ 2987 | 2988 | var slice = Array.prototype.slice; 2989 | 2990 | /** 2991 | * Expose `co`. 2992 | */ 2993 | 2994 | module.exports = co['default'] = co.co = co; 2995 | 2996 | /** 2997 | * Wrap the given generator `fn` into a 2998 | * function that returns a promise. 2999 | * This is a separate function so that 3000 | * every `co()` call doesn't create a new, 3001 | * unnecessary closure. 3002 | * 3003 | * @param {GeneratorFunction} fn 3004 | * @return {Function} 3005 | * @api public 3006 | */ 3007 | 3008 | co.wrap = function (fn) { 3009 | return function () { 3010 | return co.call(this, fn.apply(this, arguments)); 3011 | }; 3012 | }; 3013 | 3014 | /** 3015 | * Execute the generator function or a generator 3016 | * and return a promise. 3017 | * 3018 | * @param {Function} fn 3019 | * @return {Function} 3020 | * @api public 3021 | */ 3022 | 3023 | function co(gen) { 3024 | var ctx = this; 3025 | if (typeof gen === 'function') gen = gen.call(this); 3026 | return Promise.resolve(onFulfilled()); 3027 | 3028 | /** 3029 | * @param {Mixed} res 3030 | * @return {Promise} 3031 | * @api private 3032 | */ 3033 | 3034 | function onFulfilled(res) { 3035 | var ret; 3036 | try { 3037 | ret = gen.next(res); 3038 | } catch (e) { 3039 | return Promise.reject(e); 3040 | } 3041 | return next(ret); 3042 | } 3043 | 3044 | /** 3045 | * @param {Error} err 3046 | * @return {Promise} 3047 | * @api private 3048 | */ 3049 | 3050 | function onRejected(err) { 3051 | var ret; 3052 | try { 3053 | ret = gen.throw(err); 3054 | } catch (e) { 3055 | return Promise.reject(e); 3056 | } 3057 | return next(ret); 3058 | } 3059 | 3060 | /** 3061 | * Get the next value in the generator, 3062 | * return a promise. 3063 | * 3064 | * @param {Object} ret 3065 | * @return {Promise} 3066 | * @api private 3067 | */ 3068 | 3069 | function next(ret) { 3070 | if (ret.done) return Promise.resolve(ret.value); 3071 | var value = toPromise.call(ctx, ret.value); 3072 | if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 3073 | return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' 3074 | + 'but the following object was passed: "' + String(ret.value) + '"')); 3075 | } 3076 | } 3077 | 3078 | /** 3079 | * Convert a `yield`ed value into a promise. 3080 | * 3081 | * @param {Mixed} obj 3082 | * @return {Promise} 3083 | * @api private 3084 | */ 3085 | 3086 | function toPromise(obj) { 3087 | if (!obj) return obj; 3088 | if (isPromise(obj)) return obj; 3089 | if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); 3090 | if ('function' == typeof obj) return thunkToPromise.call(this, obj); 3091 | if (Array.isArray(obj)) return arrayToPromise.call(this, obj); 3092 | if (isObject(obj)) return objectToPromise.call(this, obj); 3093 | return obj; 3094 | } 3095 | 3096 | /** 3097 | * Convert a thunk to a promise. 3098 | * 3099 | * @param {Function} 3100 | * @return {Promise} 3101 | * @api private 3102 | */ 3103 | 3104 | function thunkToPromise(fn) { 3105 | var ctx = this; 3106 | return new Promise(function (resolve, reject) { 3107 | fn.call(ctx, function (err, res) { 3108 | if (err) return reject(err); 3109 | if (arguments.length > 2) res = slice.call(arguments, 1); 3110 | resolve(res); 3111 | }); 3112 | }); 3113 | } 3114 | 3115 | /** 3116 | * Convert an array of "yieldables" to a promise. 3117 | * Uses `Promise.all()` internally. 3118 | * 3119 | * @param {Array} obj 3120 | * @return {Promise} 3121 | * @api private 3122 | */ 3123 | 3124 | function arrayToPromise(obj) { 3125 | return Promise.all(obj.map(toPromise, this)); 3126 | } 3127 | 3128 | /** 3129 | * Convert an object of "yieldables" to a promise. 3130 | * Uses `Promise.all()` internally. 3131 | * 3132 | * @param {Object} obj 3133 | * @return {Promise} 3134 | * @api private 3135 | */ 3136 | 3137 | function objectToPromise(obj){ 3138 | var results = new obj.constructor(); 3139 | var keys = Object.keys(obj); 3140 | var promises = []; 3141 | for (var i = 0; i < keys.length; i++) { 3142 | var key = keys[i]; 3143 | var promise = toPromise.call(this, obj[key]); 3144 | if (promise && isPromise(promise)) defer(promise, key); 3145 | else results[key] = obj[key]; 3146 | } 3147 | return Promise.all(promises).then(function () { 3148 | return results; 3149 | }); 3150 | 3151 | function defer(promise, key) { 3152 | // predefine the key in the result 3153 | results[key] = undefined; 3154 | promises.push(promise.then(function (res) { 3155 | results[key] = res; 3156 | })); 3157 | } 3158 | } 3159 | 3160 | /** 3161 | * Check if `obj` is a promise. 3162 | * 3163 | * @param {Object} obj 3164 | * @return {Boolean} 3165 | * @api private 3166 | */ 3167 | 3168 | function isPromise(obj) { 3169 | return 'function' == typeof obj.then; 3170 | } 3171 | 3172 | /** 3173 | * Check if `obj` is a generator. 3174 | * 3175 | * @param {Mixed} obj 3176 | * @return {Boolean} 3177 | * @api private 3178 | */ 3179 | 3180 | function isGenerator(obj) { 3181 | return 'function' == typeof obj.next && 'function' == typeof obj.throw; 3182 | } 3183 | 3184 | /** 3185 | * Check if `obj` is a generator function. 3186 | * 3187 | * @param {Mixed} obj 3188 | * @return {Boolean} 3189 | * @api private 3190 | */ 3191 | 3192 | function isGeneratorFunction(obj) { 3193 | var constructor = obj.constructor; 3194 | return constructor && 'GeneratorFunction' == constructor.name; 3195 | } 3196 | 3197 | /** 3198 | * Check for plain object. 3199 | * 3200 | * @param {Mixed} val 3201 | * @return {Boolean} 3202 | * @api private 3203 | */ 3204 | 3205 | function isObject(val) { 3206 | return Object == val.constructor; 3207 | } 3208 | 3209 | }, {}], 3210 | 35: [function(require, module, exports) { 3211 | /** 3212 | * Module Dependencies 3213 | */ 3214 | 3215 | var type = require('./utils/type'); 3216 | 3217 | /** 3218 | * Export `Default` 3219 | */ 3220 | 3221 | module.exports = Default; 3222 | 3223 | /** 3224 | * Set the value if `undefined` 3225 | */ 3226 | 3227 | function Default(def) { 3228 | return function(value) { 3229 | switch(type(value)) { 3230 | case 'undefined': 3231 | return def; 3232 | default: 3233 | return value; 3234 | } 3235 | }; 3236 | } 3237 | 3238 | }, {"./utils/type":43}], 3239 | 43: [function(require, module, exports) { 3240 | try { 3241 | var t = require('type'); 3242 | } catch (e) { 3243 | var t = require('type-component'); 3244 | } 3245 | 3246 | /** 3247 | * Expose `type` 3248 | */ 3249 | 3250 | module.exports = type; 3251 | 3252 | /** 3253 | * Get the type 3254 | * 3255 | * @param {Mixed} val 3256 | * @return {String} type 3257 | */ 3258 | 3259 | function type(val) { 3260 | switch(val) { 3261 | case undefined: return 'undefined'; 3262 | case Function: return 'function'; 3263 | case Boolean: return 'boolean'; 3264 | case Number: return 'number'; 3265 | case String: return 'string'; 3266 | case RegExp: return 'regexp'; 3267 | case Object: return 'object'; 3268 | case Array: return 'Array'; 3269 | case Date: return 'date'; 3270 | case null: return 'null'; 3271 | case NaN: return 'nan'; 3272 | default: return t(val); 3273 | } 3274 | } 3275 | 3276 | }, {"type":27}], 3277 | 36: [function(require, module, exports) { 3278 | /** 3279 | * Module dependencies 3280 | */ 3281 | 3282 | var type = require('./utils/type'); 3283 | 3284 | /** 3285 | * Export `Require` 3286 | */ 3287 | 3288 | module.exports = Required; 3289 | 3290 | /** 3291 | * Require a value. Cannot be `undefined`. 3292 | */ 3293 | 3294 | function Required() { 3295 | return function(value) { 3296 | switch(type(value)) { 3297 | case 'undefined': return new TypeError('value must be defined'); 3298 | case 'string': return value.length == 0 3299 | ? new TypeError('value cannot be blank') 3300 | : value; 3301 | default: return value; 3302 | } 3303 | } 3304 | } 3305 | 3306 | }, {"./utils/type":43}], 3307 | 37: [function(require, module, exports) { 3308 | /** 3309 | * Module dependencies 3310 | */ 3311 | 3312 | var fmt = require('./utils/format'); 3313 | var type = require('./utils/type'); 3314 | 3315 | /** 3316 | * Errors 3317 | */ 3318 | 3319 | var minmsg = 'length must be greater than or equal to %s'; 3320 | var maxmsg = 'length must be less than or equal to %s'; 3321 | 3322 | /** 3323 | * Export `Between` 3324 | */ 3325 | 3326 | module.exports = Between; 3327 | 3328 | /** 3329 | * Between a value. Cannot be `undefined`. 3330 | */ 3331 | 3332 | function Between(min, max) { 3333 | return function(value) { 3334 | var len = value.length === undefined ? value : value.length; 3335 | 3336 | return len < min 3337 | ? new RangeError(fmt(minmsg, min)) 3338 | : len > max 3339 | ? new RangeError(fmt(maxmsg, max)) 3340 | : value; 3341 | } 3342 | } 3343 | 3344 | }, {"./utils/format":44,"./utils/type":43}], 3345 | 44: [function(require, module, exports) { 3346 | try { 3347 | module.exports = require('fmt'); 3348 | } catch (e) { 3349 | module.exports = require('util').format; 3350 | } 3351 | 3352 | }, {"fmt":13}], 3353 | 38: [function(require, module, exports) { 3354 | /** 3355 | * Module dependencies 3356 | */ 3357 | 3358 | var type = require('./utils/type.js'); 3359 | var rrep = /(\$(`|&|'|\d+))/g; 3360 | var noop = function() {}; 3361 | var slice = [].slice; 3362 | 3363 | /** 3364 | * Export `Format` 3365 | */ 3366 | 3367 | module.exports = Format; 3368 | 3369 | /** 3370 | * Initialize `Format` 3371 | * 3372 | * @param {RegExp|Function} formatter 3373 | * @param {String|Function} format 3374 | */ 3375 | 3376 | function Format(formatter, format) { 3377 | return 1 == arguments.length 3378 | ? func(formatter) 3379 | : regex(formatter, format); 3380 | } 3381 | 3382 | /** 3383 | * Regular format function 3384 | * 3385 | * @param {Function} fn 3386 | * @return {Function} 3387 | */ 3388 | 3389 | function func(fn) { 3390 | return function(value) { 3391 | return fn(value); 3392 | } 3393 | } 3394 | 3395 | /** 3396 | * Regex based formatting 3397 | * 3398 | * @param {Regexp} regex 3399 | * @param {String|Function} rep 3400 | */ 3401 | 3402 | function regex(regex, rep) { 3403 | var global = !!regex.global; 3404 | 3405 | rep = arguments.length > 1 ? rep : noop; 3406 | rep = 'function' == typeof rep ? rep : compile(rep); 3407 | 3408 | return function(value) { 3409 | return (value + '').replace(regex, function() { 3410 | var m = slice.call(arguments); 3411 | var i = 1; 3412 | 3413 | // remove extra stuff if not global 3414 | if (!global) { 3415 | while(m[i] && 'string' == type(m[i])) i++; 3416 | m = m.slice(0, i); 3417 | } 3418 | 3419 | return rep(m); 3420 | }); 3421 | } 3422 | } 3423 | 3424 | /** 3425 | * Compile the replacer 3426 | * 3427 | * @param {String} str 3428 | * @return {String} 3429 | */ 3430 | 3431 | function compile(str) { 3432 | var expr = str.replace(rrep, function(m) { 3433 | var out = '\' + ($['; 3434 | out += '&' == m[1] ? 0 : m[1]; 3435 | out += '] || \'\') + \''; 3436 | return out; 3437 | }) 3438 | 3439 | expr = '\'' + expr + '\''; 3440 | return new Function('$', 'return ' + expr); 3441 | } 3442 | 3443 | }, {"./utils/type.js":43}], 3444 | 39: [function(require, module, exports) { 3445 | /** 3446 | * Module dependencies 3447 | */ 3448 | 3449 | var fmt = require('./utils/format'); 3450 | var type = require('./utils/type'); 3451 | var assert = require('assert'); 3452 | var wrap = require('wrap-fn'); 3453 | 3454 | /** 3455 | * Export `Assert` 3456 | */ 3457 | 3458 | module.exports = Assert; 3459 | 3460 | /** 3461 | * Assert a value. Cannot be `undefined`. 3462 | */ 3463 | 3464 | function Assert(expected, msg) { 3465 | if ('function' == typeof expected) return func(expected); 3466 | 3467 | var fn = compile(expected, msg); 3468 | 3469 | return function(value) { 3470 | try { 3471 | fn(value); 3472 | } catch (e) { 3473 | return e; 3474 | } 3475 | } 3476 | } 3477 | 3478 | /** 3479 | * Compile the assertion 3480 | */ 3481 | 3482 | function compile(expected, msg) { 3483 | switch(type(expected)) { 3484 | case 'regexp': return regex(expected, msg); 3485 | case 'object': 3486 | case 'array': 3487 | return object(expected, msg) 3488 | default: 3489 | return misc(expected, msg); 3490 | } 3491 | } 3492 | 3493 | function func(fn) { 3494 | return function(value, done) { 3495 | wrap(fn, function(err, v) { 3496 | try { 3497 | if (err) throw err; 3498 | assert(v); 3499 | done() 3500 | } catch (e) { 3501 | done(e); 3502 | } 3503 | })(value); 3504 | } 3505 | } 3506 | 3507 | /** 3508 | * Regex assertion 3509 | */ 3510 | 3511 | function regex(expected, msg) { 3512 | return function(value) { 3513 | msg = msg || fmt('"%s" does not match "%s"', value, expected); 3514 | assert(expected.test(value), msg); 3515 | } 3516 | } 3517 | 3518 | /** 3519 | * Deep equality on objects and arrays 3520 | */ 3521 | 3522 | function object(expected, msg) { 3523 | return function(value) { 3524 | assert.deepEqual(value, expected, msg); 3525 | } 3526 | } 3527 | 3528 | /** 3529 | * Equality on everything else 3530 | */ 3531 | 3532 | function misc(expected, msg) { 3533 | return function(value) { 3534 | assert.equal(value, expected, msg); 3535 | } 3536 | } 3537 | 3538 | }, {"./utils/format":44,"./utils/type":43,"assert":8,"wrap-fn":45}], 3539 | 45: [function(require, module, exports) { 3540 | /** 3541 | * Module Dependencies 3542 | */ 3543 | 3544 | var slice = [].slice; 3545 | var co = require('co'); 3546 | var noop = function(){}; 3547 | 3548 | /** 3549 | * Export `wrap-fn` 3550 | */ 3551 | 3552 | module.exports = wrap; 3553 | 3554 | /** 3555 | * Wrap a function to support 3556 | * sync, async, and gen functions. 3557 | * 3558 | * @param {Function} fn 3559 | * @param {Function} done 3560 | * @return {Function} 3561 | * @api public 3562 | */ 3563 | 3564 | function wrap(fn, done) { 3565 | done = done || noop; 3566 | 3567 | return function() { 3568 | var args = slice.call(arguments); 3569 | var ctx = this; 3570 | 3571 | // done 3572 | if (!fn) { 3573 | return done.apply(ctx, [null].concat(args)); 3574 | } 3575 | 3576 | // async 3577 | if (fn.length > args.length) { 3578 | return fn.apply(ctx, args.concat(done)); 3579 | } 3580 | 3581 | // generator 3582 | if (generator(fn)) { 3583 | return co(fn).apply(ctx, args.concat(done)); 3584 | } 3585 | 3586 | // sync 3587 | return sync(fn, done).apply(ctx, args); 3588 | } 3589 | } 3590 | 3591 | /** 3592 | * Wrap a synchronous function execution. 3593 | * 3594 | * @param {Function} fn 3595 | * @param {Function} done 3596 | * @return {Function} 3597 | * @api private 3598 | */ 3599 | 3600 | function sync(fn, done) { 3601 | return function () { 3602 | var ret; 3603 | 3604 | try { 3605 | ret = fn.apply(this, arguments); 3606 | } catch (err) { 3607 | return done(err); 3608 | } 3609 | 3610 | if (promise(ret)) { 3611 | ret.then(function (value) { done(null, value); }, done); 3612 | } else { 3613 | ret instanceof Error ? done(ret) : done(null, ret); 3614 | } 3615 | } 3616 | } 3617 | 3618 | /** 3619 | * Is `value` a generator? 3620 | * 3621 | * @param {Mixed} value 3622 | * @return {Boolean} 3623 | * @api private 3624 | */ 3625 | 3626 | function generator(value) { 3627 | return value 3628 | && value.constructor 3629 | && 'GeneratorFunction' == value.constructor.name; 3630 | } 3631 | 3632 | 3633 | /** 3634 | * Is `value` a promise? 3635 | * 3636 | * @param {Mixed} value 3637 | * @return {Boolean} 3638 | * @api private 3639 | */ 3640 | 3641 | function promise(value) { 3642 | return value && 'function' == typeof value.then; 3643 | } 3644 | 3645 | }, {"co":46}], 3646 | 46: [function(require, module, exports) { 3647 | 3648 | /** 3649 | * slice() reference. 3650 | */ 3651 | 3652 | var slice = Array.prototype.slice; 3653 | 3654 | /** 3655 | * Expose `co`. 3656 | */ 3657 | 3658 | module.exports = co; 3659 | 3660 | /** 3661 | * Wrap the given generator `fn` and 3662 | * return a thunk. 3663 | * 3664 | * @param {Function} fn 3665 | * @return {Function} 3666 | * @api public 3667 | */ 3668 | 3669 | function co(fn) { 3670 | var isGenFun = isGeneratorFunction(fn); 3671 | 3672 | return function (done) { 3673 | var ctx = this; 3674 | 3675 | // in toThunk() below we invoke co() 3676 | // with a generator, so optimize for 3677 | // this case 3678 | var gen = fn; 3679 | 3680 | // we only need to parse the arguments 3681 | // if gen is a generator function. 3682 | if (isGenFun) { 3683 | var args = slice.call(arguments), len = args.length; 3684 | var hasCallback = len && 'function' == typeof args[len - 1]; 3685 | done = hasCallback ? args.pop() : error; 3686 | gen = fn.apply(this, args); 3687 | } else { 3688 | done = done || error; 3689 | } 3690 | 3691 | next(); 3692 | 3693 | // #92 3694 | // wrap the callback in a setImmediate 3695 | // so that any of its errors aren't caught by `co` 3696 | function exit(err, res) { 3697 | setImmediate(function(){ 3698 | done.call(ctx, err, res); 3699 | }); 3700 | } 3701 | 3702 | function next(err, res) { 3703 | var ret; 3704 | 3705 | // multiple args 3706 | if (arguments.length > 2) res = slice.call(arguments, 1); 3707 | 3708 | // error 3709 | if (err) { 3710 | try { 3711 | ret = gen.throw(err); 3712 | } catch (e) { 3713 | return exit(e); 3714 | } 3715 | } 3716 | 3717 | // ok 3718 | if (!err) { 3719 | try { 3720 | ret = gen.next(res); 3721 | } catch (e) { 3722 | return exit(e); 3723 | } 3724 | } 3725 | 3726 | // done 3727 | if (ret.done) return exit(null, ret.value); 3728 | 3729 | // normalize 3730 | ret.value = toThunk(ret.value, ctx); 3731 | 3732 | // run 3733 | if ('function' == typeof ret.value) { 3734 | var called = false; 3735 | try { 3736 | ret.value.call(ctx, function(){ 3737 | if (called) return; 3738 | called = true; 3739 | next.apply(ctx, arguments); 3740 | }); 3741 | } catch (e) { 3742 | setImmediate(function(){ 3743 | if (called) return; 3744 | called = true; 3745 | next(e); 3746 | }); 3747 | } 3748 | return; 3749 | } 3750 | 3751 | // invalid 3752 | next(new TypeError('You may only yield a function, promise, generator, array, or object, ' 3753 | + 'but the following was passed: "' + String(ret.value) + '"')); 3754 | } 3755 | } 3756 | } 3757 | 3758 | /** 3759 | * Convert `obj` into a normalized thunk. 3760 | * 3761 | * @param {Mixed} obj 3762 | * @param {Mixed} ctx 3763 | * @return {Function} 3764 | * @api private 3765 | */ 3766 | 3767 | function toThunk(obj, ctx) { 3768 | 3769 | if (isGeneratorFunction(obj)) { 3770 | return co(obj.call(ctx)); 3771 | } 3772 | 3773 | if (isGenerator(obj)) { 3774 | return co(obj); 3775 | } 3776 | 3777 | if (isPromise(obj)) { 3778 | return promiseToThunk(obj); 3779 | } 3780 | 3781 | if ('function' == typeof obj) { 3782 | return obj; 3783 | } 3784 | 3785 | if (isObject(obj) || Array.isArray(obj)) { 3786 | return objectToThunk.call(ctx, obj); 3787 | } 3788 | 3789 | return obj; 3790 | } 3791 | 3792 | /** 3793 | * Convert an object of yieldables to a thunk. 3794 | * 3795 | * @param {Object} obj 3796 | * @return {Function} 3797 | * @api private 3798 | */ 3799 | 3800 | function objectToThunk(obj){ 3801 | var ctx = this; 3802 | var isArray = Array.isArray(obj); 3803 | 3804 | return function(done){ 3805 | var keys = Object.keys(obj); 3806 | var pending = keys.length; 3807 | var results = isArray 3808 | ? new Array(pending) // predefine the array length 3809 | : new obj.constructor(); 3810 | var finished; 3811 | 3812 | if (!pending) { 3813 | setImmediate(function(){ 3814 | done(null, results) 3815 | }); 3816 | return; 3817 | } 3818 | 3819 | // prepopulate object keys to preserve key ordering 3820 | if (!isArray) { 3821 | for (var i = 0; i < pending; i++) { 3822 | results[keys[i]] = undefined; 3823 | } 3824 | } 3825 | 3826 | for (var i = 0; i < keys.length; i++) { 3827 | run(obj[keys[i]], keys[i]); 3828 | } 3829 | 3830 | function run(fn, key) { 3831 | if (finished) return; 3832 | try { 3833 | fn = toThunk(fn, ctx); 3834 | 3835 | if ('function' != typeof fn) { 3836 | results[key] = fn; 3837 | return --pending || done(null, results); 3838 | } 3839 | 3840 | fn.call(ctx, function(err, res){ 3841 | if (finished) return; 3842 | 3843 | if (err) { 3844 | finished = true; 3845 | return done(err); 3846 | } 3847 | 3848 | results[key] = res; 3849 | --pending || done(null, results); 3850 | }); 3851 | } catch (err) { 3852 | finished = true; 3853 | done(err); 3854 | } 3855 | } 3856 | } 3857 | } 3858 | 3859 | /** 3860 | * Convert `promise` to a thunk. 3861 | * 3862 | * @param {Object} promise 3863 | * @return {Function} 3864 | * @api private 3865 | */ 3866 | 3867 | function promiseToThunk(promise) { 3868 | return function(fn){ 3869 | promise.then(function(res) { 3870 | fn(null, res); 3871 | }, fn); 3872 | } 3873 | } 3874 | 3875 | /** 3876 | * Check if `obj` is a promise. 3877 | * 3878 | * @param {Object} obj 3879 | * @return {Boolean} 3880 | * @api private 3881 | */ 3882 | 3883 | function isPromise(obj) { 3884 | return obj && 'function' == typeof obj.then; 3885 | } 3886 | 3887 | /** 3888 | * Check if `obj` is a generator. 3889 | * 3890 | * @param {Mixed} obj 3891 | * @return {Boolean} 3892 | * @api private 3893 | */ 3894 | 3895 | function isGenerator(obj) { 3896 | return obj && 'function' == typeof obj.next && 'function' == typeof obj.throw; 3897 | } 3898 | 3899 | /** 3900 | * Check if `obj` is a generator function. 3901 | * 3902 | * @param {Mixed} obj 3903 | * @return {Boolean} 3904 | * @api private 3905 | */ 3906 | 3907 | function isGeneratorFunction(obj) { 3908 | return obj && obj.constructor && 'GeneratorFunction' == obj.constructor.name; 3909 | } 3910 | 3911 | /** 3912 | * Check for plain object. 3913 | * 3914 | * @param {Mixed} val 3915 | * @return {Boolean} 3916 | * @api private 3917 | */ 3918 | 3919 | function isObject(val) { 3920 | return val && Object == val.constructor; 3921 | } 3922 | 3923 | /** 3924 | * Throw `err` in a new stack. 3925 | * 3926 | * This is used when co() is invoked 3927 | * without supplying a callback, which 3928 | * should only be for demonstrational 3929 | * purposes. 3930 | * 3931 | * @param {Error} err 3932 | * @api private 3933 | */ 3934 | 3935 | function error(err) { 3936 | if (!err) return; 3937 | setImmediate(function(){ 3938 | throw err; 3939 | }); 3940 | } 3941 | 3942 | }, {}], 3943 | 40: [function(require, module, exports) { 3944 | /** 3945 | * Module Dependencies 3946 | */ 3947 | 3948 | var type = require('./utils/type.js'); 3949 | var typecast = require('typecast'); 3950 | 3951 | /** 3952 | * Export `cast` 3953 | */ 3954 | 3955 | module.exports = cast; 3956 | 3957 | /** 3958 | * Initialize `cast` 3959 | * 3960 | * @param {Mixed} from (optional) 3961 | * @param {Mixed} to 3962 | */ 3963 | 3964 | function cast(from, to) { 3965 | if (1 == arguments.length) { 3966 | to = type(from); 3967 | from = false; 3968 | } else { 3969 | from = type(from); 3970 | to = type(to); 3971 | } 3972 | 3973 | return function(value) { 3974 | return !from || type(value) == from 3975 | ? typecast(value, to) 3976 | : value; 3977 | } 3978 | } 3979 | 3980 | }, {"./utils/type.js":43,"typecast":47}], 3981 | 47: [function(require, module, exports) { 3982 | module.exports = typecast; 3983 | 3984 | /** 3985 | * Cast given `val` to `type` 3986 | * 3987 | * @param {Mixed} val 3988 | * @param {String} type 3989 | * @api public 3990 | */ 3991 | 3992 | function typecast (val, type) { 3993 | var fn = typecast[type]; 3994 | if (typeof fn != 'function') throw new Error('cannot cast to ' + type); 3995 | return fn(val); 3996 | } 3997 | 3998 | /** 3999 | * Cast `val` to `String` 4000 | * 4001 | * @param {Mixed} val 4002 | * @api public 4003 | */ 4004 | 4005 | typecast.string = function (val) { 4006 | if (null == val) return ''; 4007 | return val.toString(); 4008 | }; 4009 | 4010 | /** 4011 | * Cast `val` to `Number` 4012 | * 4013 | * @param {Mixed} val 4014 | * @api public 4015 | */ 4016 | 4017 | typecast.number = function (val) { 4018 | var num = parseFloat(val); 4019 | return isNaN(num) 4020 | ? 0 4021 | : num; 4022 | }; 4023 | 4024 | /** 4025 | * Cast `val` to a`Date` 4026 | * 4027 | * @param {Mixed} val 4028 | * @api public 4029 | */ 4030 | 4031 | typecast.date = function (val) { 4032 | var date = new Date(val); 4033 | return isNaN(date.valueOf()) 4034 | ? new Date(0) 4035 | : date; 4036 | }; 4037 | 4038 | /** 4039 | * Cast `val` to `Array` 4040 | * 4041 | * @param {Mixed} val 4042 | * @api public 4043 | */ 4044 | 4045 | typecast.array = function (val) { 4046 | if (val == null) return []; 4047 | if (val instanceof Array) return val; 4048 | if (typeof val != 'string') return [val]; 4049 | 4050 | var arr = val.split(','); 4051 | for (var i = 0; i < arr.length; i++) { 4052 | arr[i] = arr[i].trim(); 4053 | } 4054 | 4055 | return arr; 4056 | }; 4057 | 4058 | /** 4059 | * Cast `val` to `Boolean` 4060 | * 4061 | * @param {Mixed} val 4062 | * @api public 4063 | */ 4064 | 4065 | typecast.boolean = function (val) { 4066 | return !! val && val !== 'false' && val !== '0'; 4067 | }; 4068 | }, {}], 4069 | 41: [function(require, module, exports) { 4070 | /** 4071 | * Module dependencies 4072 | */ 4073 | 4074 | var invalid = require('invalid'); 4075 | 4076 | /** 4077 | * Export `Type` 4078 | */ 4079 | 4080 | module.exports = Type; 4081 | 4082 | /** 4083 | * Initialize `Type` 4084 | */ 4085 | 4086 | function Type(type) { 4087 | return function(value) { 4088 | var err = invalid(value, type); 4089 | return err || value; 4090 | } 4091 | } 4092 | 4093 | }, {"invalid":48}], 4094 | 48: [function(require, module, exports) { 4095 | /** 4096 | * Type 4097 | */ 4098 | 4099 | try { 4100 | var type = require('type'); 4101 | } catch (e) { 4102 | var type = require('component-type'); 4103 | } 4104 | 4105 | /** 4106 | * Export `invalid` 4107 | */ 4108 | 4109 | module.exports = invalid; 4110 | 4111 | /** 4112 | * Initialize `invalid` 4113 | * 4114 | * @param {Mixed} obj 4115 | * @param {Mixed} schema 4116 | * @return {Boolean|TypeError} 4117 | * @api public 4118 | */ 4119 | 4120 | function invalid(obj, schema) { 4121 | return 1 == arguments.length 4122 | ? function (o) { return check(valid(o, obj)); } 4123 | : check(valid(obj, schema)); 4124 | 4125 | // pass the errors through 4126 | function check(errs) { 4127 | return errs.length 4128 | ? new TypeError(errs.join(', ')) 4129 | : false 4130 | } 4131 | } 4132 | 4133 | /** 4134 | * Cast the string 4135 | */ 4136 | 4137 | var cast = { 4138 | 'undefined': undefined, 4139 | 'function': Function, 4140 | 'boolean': Boolean, 4141 | 'number': Number, 4142 | 'string': String, 4143 | 'regexp': RegExp, 4144 | 'object': Object, 4145 | 'array': Array, 4146 | 'date': Date, 4147 | 'null': null, 4148 | 'nan': NaN 4149 | }; 4150 | 4151 | /** 4152 | * Get the type 4153 | * 4154 | * @param {Mixed} val 4155 | * @return {String} 4156 | */ 4157 | 4158 | function typecheck(val) { 4159 | switch(val) { 4160 | case undefined: return 'undefined'; 4161 | case Function: return 'function'; 4162 | case Boolean: return 'boolean'; 4163 | case Number: return 'number'; 4164 | case String: return 'string'; 4165 | case RegExp: return 'regexp'; 4166 | case Object: return 'object'; 4167 | case Array: return 'Array'; 4168 | case Date: return 'date'; 4169 | case null: return 'null'; 4170 | case NaN: return 'nan'; 4171 | default: return type(val); 4172 | } 4173 | } 4174 | 4175 | /** 4176 | * Validate `actual` against `expected` 4177 | * 4178 | * @param {Mixed} actual 4179 | * @param {Mixed} expected 4180 | * @param {String} key (private) 4181 | * @return {Array} errors 4182 | * @api public 4183 | */ 4184 | 4185 | function valid(actual, expected, key) { 4186 | key = key || ''; 4187 | 4188 | var et = type(expected); 4189 | var t = type(actual); 4190 | var errs = []; 4191 | 4192 | if ('object' == et && t == et) { 4193 | for (var k in actual) 4194 | errs = errs.concat(valid(actual[k], expected[k], key ? key + '.' + k : k)); 4195 | } else if ('array' == et && t == et) { 4196 | for (var i = 0, v; v = actual[i]; i++) 4197 | errs = errs.concat(valid(v, expected[0], key ? key + '[' + i + ']': i)); 4198 | } else if ('regexp' == et && expected instanceof RegExp) { 4199 | !expected.test(actual) && errs.push(error(t, key, actual, expected)); 4200 | } else if (cast[t] != expected) { 4201 | errs.push(error(t, key, actual, expected)); 4202 | } 4203 | 4204 | return errs; 4205 | } 4206 | 4207 | /** 4208 | * Format an error 4209 | * 4210 | * @param {String} type 4211 | * @param {String} key (optional) 4212 | * @param {Mixed} actual 4213 | * @param {Mixed} expected 4214 | * @return {String} 4215 | * @api private 4216 | */ 4217 | 4218 | function error(type, key, actual, expected) { 4219 | var msg = key ? key + ': ' : ''; 4220 | if (expected instanceof RegExp) { 4221 | msg += fmt(type, actual) + ' does not match regexp ' + expected; 4222 | } else { 4223 | msg += fmt(type, actual) + ' is not a ' + typecheck(expected); 4224 | } 4225 | return msg; 4226 | } 4227 | 4228 | /** 4229 | * Format based on type 4230 | * 4231 | * @param {String} type 4232 | * @param {Mixed} actual 4233 | * @return {String} 4234 | * @api private 4235 | */ 4236 | 4237 | function fmt(type, actual) { 4238 | switch(type) { 4239 | case 'string': return actual = '"' + actual + '"'; 4240 | case 'object': return JSON.stringify(actual); 4241 | case 'undefined': 4242 | case 'number': 4243 | case 'null': 4244 | return actual; 4245 | case 'function': 4246 | return actual.toString().replace(/\{[^\}]+/, '{ ... '); 4247 | default: 4248 | return actual.toString(); 4249 | } 4250 | } 4251 | 4252 | }, {"type":27,"component-type":27}]}, {}, {"1":"Ive"}) 4253 | -------------------------------------------------------------------------------- /dist/ive.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function f(a){if(c[a])return c[a].exports;if(b[a])return g(a,f);throw new Error('cannot find module "'+a+'"')}function g(e,f){var g=c[e]={exports:{}},h=b[e],i=h[2],j=h[0];return j.call(g.exports,function(a){var c=b[e][1][a];return f(c?c:a)},g,g.exports,a,b,c,d),i&&(c[i]=c[e]),c[e].exports}var e=function(){return this}();for(var h in d)d[h]?e[d[h]]=f(h):f(h);return f.duo=!0,f.cache=c,f.modules=b,f}({1:[function(a,b){function j(a){function b(a,c){if("string"==typeof a)return k(a,b.attrs);if(a.nodeName)f(a,b);else if(h(a)||l(a))for(var g,e=0;g=a[e];e++)f(g,b);else{if(!c)return function(c){d(a,b.attrs,c)};d(a,b.attrs,c)}return b}if(!(this instanceof j))return new j(a);b.attrs={};for(var c in j.prototype)b[c]=j.prototype[c];return b.attr(a),b}function k(a,b){return function(c,e){return d(c,m(b,a),e)}}function l(a){return"number"==typeof a.length&&"function"==typeof a.item&&"function"==typeof a.nextNode&&"function"==typeof a.reset?!0:!1}function m(a,b){return a=a||{},"string"==typeof b&&(b=b.split(/ +/)),b.reduce(function(b,c){return null==a[c]?b:(b[c]=a[c],b)},{})}var d=a("./lib/validate"),e=a("extend.js"),f=a("./lib/form");a("batch");var h=Array.isArray,i=a("rube");b.exports=j,j.prototype.attr=function(a,b){return a?"ive"==a.name||"object"==typeof a?(this.attrs=e(this.attrs,a.attrs||a),this):b?(this.attrs[a]||(this.attrs[a]=i()),this.attrs[a].use(b),this):this.attrs[a]||(this.attrs[a]=i()):this.attrs}},{"./lib/validate":2,"extend.js":3,"./lib/form":4,batch:5,rube:6}],2:[function(a,b){function f(a,b,c){var f=d().throws(!1),h=e(b),i={},j={};h.forEach(function(c){f.push(function(d){b[c](a[c],function(a,b){return a?(i[c]=a,d(a)):(j[c]=b,d())})})}),f.end(function(){return e(i).length?c(g(i,a)):c(null,j)})}function g(a,b){b=JSON.stringify(b,!0,2).split("\n").map(function(a){return" | "+a}).join("\n");var c=e(a).map(function(b,c){return" | "+(c+1)+". "+b+": "+a[b].message}).join("\n"),d=new Error("\n |\n | Rube Schema Validation Error\n |\n"+b+"\n |\n"+c+"\n |\n");return d.fields=a,d}var d=a("batch"),e=Object.keys;b.exports=f},{batch:5}],5:[function(a,b){function g(){}function h(){if(!(this instanceof h))return new h;this.fns=[],this.concurrency(1/0),this.throws(!0);for(var a=0,b=arguments.length;b>a;++a)this.push(arguments[a])}try{var d=a("events").EventEmitter}catch(e){var f=a("emitter")}b.exports=h,d?h.prototype.__proto__=d.prototype:f(h.prototype),h.prototype.concurrency=function(a){return this.n=a,this},h.prototype.push=function(a){return this.fns.push(a),this},h.prototype.throws=function(a){return this.e=!!a,this},h.prototype.end=function(a){function m(){function p(h,i){if(!l){if(h&&j)return l=!0,a(h);var k=c-d+1,o=new Date;e[g]=i,f[g]=h,b.emit("progress",{index:g,value:i,error:h,pending:d,total:c,complete:k,percent:0|100*(k/c),start:n,end:o,duration:o-n}),--d?m():j?a(null,e):a(f,e)}}var g=k++,i=h[g];if(i){var n=new Date;try{i(p)}catch(o){p(o)}}}var l,b=this,c=this.fns.length,d=c,e=[],f=[],a=a||g,h=this.fns,i=this.n,j=this.e,k=0;if(!h.length)return a(null,e);for(var n=0;nd;++d)c[d].apply(this,b)}return this},d.prototype.listeners=function(a){return this._callbacks=this._callbacks||{},this._callbacks[a]||[]},d.prototype.hasListeners=function(a){return!!this.listeners(a).length}},{}],3:[function(a,b){b.exports=function(a){for(var c,b=[].slice.call(arguments,1),d=0,e=b.length;e>d;d++){c=b[d];for(var f in c)a[f]=c[f]}return a}},{}],4:[function(a,b){function g(b,c){e("FORM"==b.nodeName);var d=a("event"),g=b.querySelector('input[type="submit"]');d.bind(b,"submit",h(b,g,c));for(var m,k=b.querySelectorAll("input, textarea"),l=0;m=k[l];l++){if(f.test(m.type))return;d.bind(m,"blur",i(m,c.attrs,j))}}function h(b,c,d){var e=a("form");return function(a){if(b.getAttribute("submitting"))return!0;a.preventDefault(),a.stopImmediatePropagation();var c=e(b).serialize();d(c,function(a){if(a){b.setAttribute("invalid",a.message);for(var d in a.fields)j(b.querySelector('[name="'+d+'"]'))(a.fields[d])}else b.setAttribute("submitting","submitting"),b.removeAttribute("invalid"),k(b)})}}function i(a,b,c){var d=a.getAttribute("name");return function(){var f=a.value;""!==f&&b[d](f,c(a))}}function j(a){return function(b,c){b?a.setAttribute("invalid",b.message):(a.removeAttribute("invalid"),c&&(a.value=c))}}function k(b){var c=a("trigger-event"),d=document.createElement("button");d.style.display="none",b.appendChild(d),c(d,"click",{clientX:0,clientY:0}),b.removeChild(d)}a("./validate");var e=a("assert");b.exports=g;var f=/^(button|submit|reset|hidden)$/},{"./validate":2,assert:8,event:9,form:10,"trigger-event":11}],8:[function(a,b,c){function g(){if(!Error.captureStackTrace)return"assertion failed";var a=f()[2];a.getFunctionName();var c=a.getFileName(),d=a.getLineNumber()-1,e=a.getColumnNumber()-1,g=h(c);d=g.split("\n")[d].slice(e);var i=d.match(/assert\((.*)\)/);return i&&i[1].trim()}function h(a){var b=new XMLHttpRequest;return b.open("GET",a,!1),b.send(null),b.responseText}function i(a,b,c){var d=new Error(a);return d.showDiff=3==arguments.length,d.actual=b,d.expected=c,d}var d=a("equals"),e=a("fmt"),f=a("stack");b.exports=c=function(a,b){if(!a)throw i(b||g())},c.equal=function(a,b,c){if(a!=b)throw i(c||e("Expected %o to equal %o.",a,b),a,b)},c.notEqual=function(a,b,c){if(a==b)throw i(c||e("Expected %o not to equal %o.",a,b))},c.deepEqual=function(a,b,c){if(!d(a,b))throw i(c||e("Expected %o to deeply equal %o.",a,b),a,b)},c.notDeepEqual=function(a,b,c){if(d(a,b))throw i(c||e("Expected %o not to deeply equal %o.",a,b))},c.strictEqual=function(a,b,c){if(a!==b)throw i(c||e("Expected %o to strictly equal %o.",a,b),a,b)},c.notStrictEqual=function(a,b,c){if(a===b)throw i(c||e("Expected %o not to strictly equal %o.",a,b))},c.throws=function(a,b,c){var d;try{a()}catch(f){d=f}if(!d)throw i(c||e("Expected %s to throw an error.",a.toString()));if(b&&!(d instanceof b))throw i(c||e("Expected %s to throw an %o.",a.toString(),b))},c.doesNotThrow=function(a,b,c){var d;try{a()}catch(f){d=f}if(d)throw i(c||e("Expected %s not to throw an error.",a.toString()));if(b&&d instanceof b)throw i(c||e("Expected %s not to throw an %o.",a.toString(),b))}},{equals:12,fmt:13,stack:14}],12:[function(a,b){function e(a,b,c){if(a===b)return!0;var e=f[d(a)],g=f[d(b)];return e&&e===g?e(a,b,c):!1}function g(a){return function(b,c,d){if(!d)return a(b,c,[]);for(var f,e=d.length;f=d[--e];)if(f[0]===b&&f[1]===c)return!0;return a(b,c,d)}}function h(a,b,c){var d=a.length;if(d!==b.length)return!1;for(c.push([a,b]);d--;)if(!e(a[d],b[d],c))return!1;return!0}function i(a,b,c){if("function"==typeof a.equal)return c.push([a,b]),a.equal(b,c);var d=j(a),f=j(b),g=d.length;if(g!==f.length)return!1;for(d.sort(),f.sort();g--;)if(d[g]!==f[g])return!1;for(c.push([a,b]),g=d.length;g--;){var h=d[g];if(!e(a[h],b[h],c))return!1}return!0}function j(a){var b=[];for(var c in a)"constructor"!==c&&b.push(c);return b}var d=a("type"),f={};f.number=function(a,b){return a!==a&&b!==b},f["function"]=function(a,b,c){return a.toString()===b.toString()&&f.object(a,b,c)&&e(a.prototype,b.prototype)},f.date=function(a,b){return+a===+b},f.regexp=function(a,b){return a.toString()===b.toString()},f.element=function(a,b){return a.outerHTML===b.outerHTML},f.textnode=function(a,b){return a.textContent===b.textContent},f.arguments=f.array=g(h),f.object=g(i),b.exports=e},{type:15}],15:[function(a,b,c){var d={}.toString,e="undefined"!=typeof window?window.Node:Function;b.exports=c=function(a){var b=typeof a;if("object"!=b)return b;if(b=f[d.call(a)])return b;if(a instanceof e)switch(a.nodeType){case 1:return"element";case 3:return"text-node";case 9:return"document";case 11:return"document-fragment";default:return"dom-node"}};var f=c.types={"[object Function]":"function","[object Date]":"date","[object RegExp]":"regexp","[object Arguments]":"arguments","[object Array]":"array","[object String]":"string","[object Null]":"null","[object Undefined]":"undefined","[object Number]":"number","[object Boolean]":"boolean","[object Object]":"object","[object Text]":"text-node","[object Uint8Array]":"bit-array","[object Uint16Array]":"bit-array","[object Uint32Array]":"bit-array","[object Uint8ClampedArray]":"bit-array","[object Error]":"error","[object FormData]":"form-data","[object File]":"file","[object Blob]":"blob"}},{}],13:[function(a,b){function d(a){var b=[].slice.call(arguments,1),c=0;return a.replace(/%([a-z])/gi,function(a,e){return d[e]?d[e](b[c++]):a+e})}b.exports=d,d.o=JSON.stringify,d.s=String,d.d=parseInt},{}],14:[function(a,b){function d(){var a=Error.prepareStackTrace;Error.prepareStackTrace=function(a,b){return b};var b=new Error;Error.captureStackTrace(b,arguments.callee);var c=b.stack;return Error.prepareStackTrace=a,c}b.exports=d},{}],9:[function(a,b,c){var d=window.addEventListener?"addEventListener":"attachEvent",e=window.removeEventListener?"removeEventListener":"detachEvent",f="addEventListener"!==d?"on":"";c.bind=function(a,b,c,e){return a[d](f+b,c,e||!1),c},c.unbind=function(a,b,c,d){return a[e](f+b,c,d||!1),c}},{}],10:[function(a,b){function h(a){return this instanceof h?(this.element=a,this.classes=d(this.element),void 0):new h(a)}var d=a("classes"),e=a("form-element"),f=a("form-serialize"),g=a("value");b.exports=h,h.prototype.input=function(a,b){b||(b=a,a=null);var c=a?e(this.element,a):this.element;return e(c,b)},h.prototype.value=function(a,b){var c=[this.input(a)];return"undefined"!=typeof b&&c.push(b),g.apply(null,c)},h.prototype.serialize=function(a,b){"function"==typeof a&&(b=a,a=null);var c=a?e(this.element,a):this.element;return f(c,b)}},{classes:16,"form-element":17,"form-serialize":18,value:19}],16:[function(a,b){function g(a){if(!a||!a.nodeType)throw new Error("A DOM element reference is required");this.el=a,this.list=a.classList}var d=a("indexof"),e=/\s+/,f=Object.prototype.toString;b.exports=function(a){return new g(a)},g.prototype.add=function(a){if(this.list)return this.list.add(a),this;var b=this.array(),c=d(b,a);return~c||b.push(a),this.el.className=b.join(" "),this},g.prototype.remove=function(a){if("[object RegExp]"==f.call(a))return this.removeMatching(a);if(this.list)return this.list.remove(a),this;var b=this.array(),c=d(b,a);return~c&&b.splice(c,1),this.el.className=b.join(" "),this},g.prototype.removeMatching=function(a){for(var b=this.array(),c=0;cf;f++)d[String.fromCharCode(f)]=f-32;for(var f=48;58>f;f++)d[f-48]=f;for(f=1;13>f;f++)d["f"+f]=f+111;for(f=0;10>f;f++)d["numpad "+f]=f+96;var g=c.names=c.title={};for(f in d)g[d[f]]=f;for(var h in e)d[h]=e[h]},{}],6:[function(a,b){function f(){function b(c,e){d(a).run(c,function(a,c){return a?e(b._message(a)):e(null,c)})}if(!(this instanceof f))return new f;var a=[];b._pipeline=a,b._message=function(a){return a};for(var c in f.prototype)b[c]=f.prototype[c];return b}var d=a("step.js"),e=function(){};b.exports=f,f.plugin=function(a,b){if(1==arguments.length&&(b=a,a=b.name),!a)throw new Error("Rube.plugin(name, fn) requires a name");return this.prototype[a.toLowerCase()]=function(){var a=b.apply(null,arguments);return this._pipeline.push(a||e),this},this},f.prototype.use=function(a){return this._pipeline.push(a),this},f.prototype.message=function(a){return this._message="string"==typeof a?function(){return new TypeError(a)}:a instanceof Error?function(){return a}:a,this},f.plugin("default",a("./lib/default.js")),f.plugin("required",a("./lib/required.js")),f.plugin("between",a("./lib/between.js")),f.plugin("format",a("./lib/format.js")),f.plugin("assert",a("./lib/assert.js")),f.plugin("cast",a("./lib/cast.js")),f.plugin("type",a("./lib/type.js"))},{"step.js":34,"./lib/default.js":35,"./lib/required.js":36,"./lib/between.js":37,"./lib/format.js":38,"./lib/assert.js":39,"./lib/cast.js":40,"./lib/type.js":41}],34:[function(a,b){function g(a){return this instanceof g?(this.fns=[],this.length=0,a&&this.use(a),void 0):new g(a)}function h(a,b){for(var c=a.length,d=[],e=0;c>e;e++)d[e]=void 0===b[e]?a[e]:b[e];return d}function i(a){return a&&a.constructor&&"GeneratorFunction"==a.constructor.name}var d=Array.prototype.slice,e=function(){},f=a("co");b.exports=g,g.prototype.use=function(a){return a instanceof g?this.fns=this.fns.concat(a.fns):a instanceof Array?this.fns=this.fns.concat(a):this.fns.push(a),this.length=this.fns.length,this},g.prototype.run=function(){function k(c){if(c)return j(c);var e=d.call(arguments,1);a=h(a,e);var f=b.shift();l(f,a)}function l(a,b){if(!a)return j.apply(j,[null].concat(b));if(a.length>b.length)a.apply(g,b.concat(k));else if(i(a))f(a).apply(g,b.concat(k));else{var c=a.apply(g,b);c instanceof Error?k(c):k(null,c)}}var a=d.call(arguments),b=d.call(this.fns),c=a.length,g=this,j="function"==typeof a[c-1]?a.pop():e;setTimeout(function(){l(b.shift(),a)},0)}},{co:42}],42:[function(a,b){function e(a){function c(b){var c;try{c=a.next(b)}catch(d){return Promise.reject(d)}return e(c)}function d(b){var c;try{c=a.throw(b)}catch(d){return Promise.reject(d)}return e(c)}function e(a){if(a.done)return Promise.resolve(a.value);var e=f.call(b,a.value);return e&&j(e)?e.then(c,d):d(new TypeError('You may only yield a function, promise, generator, array, or object, but the following object was passed: "'+String(a.value)+'"'))}var b=this;return"function"==typeof a&&(a=a.call(this)),Promise.resolve(c())}function f(a){return a?j(a)?a:l(a)||k(a)?e.call(this,a):"function"==typeof a?g.call(this,a):Array.isArray(a)?h.call(this,a):m(a)?i.call(this,a):a:a}function g(a){var b=this;return new Promise(function(c,e){a.call(b,function(a,b){return a?e(a):(arguments.length>2&&(b=d.call(arguments,1)),c(b),void 0)})})}function h(a){return Promise.all(a.map(f,this))}function i(a){function i(a,c){b[c]=void 0,d.push(a.then(function(a){b[c]=a}))}for(var b=new a.constructor,c=Object.keys(a),d=[],e=0;ee?new RangeError(d(f,a)):e>b?new RangeError(d(g,b)):c}}var d=a("./utils/format");a("./utils/type");var f="length must be greater than or equal to %s",g="length must be less than or equal to %s";b.exports=h},{"./utils/format":44,"./utils/type":43}],44:[function(a,b){try{b.exports=a("fmt")}catch(d){b.exports=a("util").format}},{fmt:13}],38:[function(a,b){function h(a,b){return 1==arguments.length?i(a):j(a,b)}function i(a){return function(b){return a(b)}}function j(a,b){var c=!!a.global;return b=arguments.length>1?b:f,b="function"==typeof b?b:k(b),function(e){return(e+"").replace(a,function(){var a=g.call(arguments),e=1;if(!c){for(;a[e]&&"string"==d(a[e]);)e++;a=a.slice(0,e)}return b(a)})}}function k(a){var b=a.replace(e,function(a){var b="' + ($[";return b+="&"==a[1]?0:a[1],b+="] || '') + '"});return b="'"+b+"'",new Function("$","return "+b)}var d=a("./utils/type.js"),e=/(\$(`|&|'|\d+))/g,f=function(){},g=[].slice;b.exports=h},{"./utils/type.js":43}],39:[function(a,b){function h(a,b){if("function"==typeof a)return j(a);var c=i(a,b);return function(a){try{c(a)}catch(b){return b}}}function i(a,b){switch(e(a)){case"regexp":return k(a,b);case"object":case"array":return l(a,b);default:return m(a,b)}}function j(a){return function(b,c){g(a,function(a,b){try{if(a)throw a;f(b),c()}catch(d){c(d)}})(b)}}function k(a,b){return function(c){b=b||d('"%s" does not match "%s"',c,a),f(a.test(c),b)}}function l(a,b){return function(c){f.deepEqual(c,a,b)}}function m(a,b){return function(c){f.equal(c,a,b)}}var d=a("./utils/format"),e=a("./utils/type"),f=a("assert"),g=a("wrap-fn");b.exports=h},{"./utils/format":44,"./utils/type":43,assert:8,"wrap-fn":45}],45:[function(a,b){function g(a,b){return b=b||f,function(){var c=d.call(arguments),f=this;return a?a.length>c.length?a.apply(f,c.concat(b)):i(a)?e(a).apply(f,c.concat(b)):h(a,b).apply(f,c):b.apply(f,[null].concat(c))}}function h(a,b){return function(){var c;try{c=a.apply(this,arguments)}catch(d){return b(d)}j(c)?c.then(function(a){b(null,a)},b):c instanceof Error?b(c):b(null,c)}}function i(a){return a&&a.constructor&&"GeneratorFunction"==a.constructor.name}function j(a){return a&&"function"==typeof a.then}var d=[].slice,e=a("co"),f=function(){};b.exports=g},{co:46}],46:[function(a,b){function e(a){var b=k(a);return function(c){function k(a,b){setImmediate(function(){c.call(e,a,b)})}function l(a,b){var c;if(arguments.length>2&&(b=d.call(arguments,1)),a)try{c=g.throw(a)}catch(h){return k(h)}if(!a)try{c=g.next(b)}catch(h){return k(h)}if(c.done)return k(null,c.value);if(c.value=f(c.value,e),"function"!=typeof c.value)l(new TypeError('You may only yield a function, promise, generator, array, or object, but the following was passed: "'+String(c.value)+'"'));else{var i=!1;try{c.value.call(e,function(){i||(i=!0,l.apply(e,arguments))})}catch(h){setImmediate(function(){i||(i=!0,l(h))})}}}var e=this,g=a;if(b){var h=d.call(arguments),i=h.length,j=i&&"function"==typeof h[i-1];c=j?h.pop():m,g=a.apply(this,h)}else c=c||m;l()}}function f(a,b){return k(a)?e(a.call(b)):j(a)?e(a):i(a)?h(a):"function"==typeof a?a:l(a)||Array.isArray(a)?g.call(b,a):a}function g(a){var b=this,c=Array.isArray(a);return function(d){function k(a,c){if(!i)try{if(a=f(a,b),"function"!=typeof a)return h[c]=a,--g||d(null,h);a.call(b,function(a,b){if(!i){if(a)return i=!0,d(a);h[c]=b,--g||d(null,h)}})}catch(e){i=!0,d(e)}}var i,e=Object.keys(a),g=e.length,h=c?new Array(g):new a.constructor;if(!g)return setImmediate(function(){d(null,h)}),void 0;if(!c)for(var j=0;g>j;j++)h[e[j]]=void 0;for(var j=0;j element'); 31 | 32 | var fields = slice.call(form.querySelectorAll('[validate]')); 33 | var submit = form.querySelector('input[type="submit"]'); 34 | var value = require('value'); 35 | var event = require('event'); 36 | var Form = require('form'); 37 | 38 | // "submit": validate all fields 39 | event.bind(form, 'submit', function(e) { 40 | var names = fieldnames(fields); 41 | var enable = disable(submit); 42 | 43 | e.stopImmediatePropagation(); 44 | e.preventDefault(); 45 | 46 | form.setAttribute('submitting', 'submitting'); 47 | 48 | var json = Form(form).serialize(); 49 | 50 | // validate 51 | validate(json, only(schema, names), function(err, obj) { 52 | if (err) { 53 | form.removeAttribute('submitting'); 54 | fields.forEach(function(field) { 55 | var input = fetch(field); 56 | if (!input) return; 57 | var name = input.name; 58 | var e = err.fields[name]; 59 | if (e) setAttr(field, e); 60 | }); 61 | 62 | fn && fn(err); 63 | enable(); 64 | } else { 65 | fn ? fn(null, obj) : form.submit(); 66 | } 67 | }); 68 | }); 69 | 70 | // "blur" or "change": validate field 71 | fields.forEach(function(field) { 72 | var input = fetch(field); 73 | if (!input) return; 74 | var name = input.name; 75 | 76 | event.bind(input, 'change', check); 77 | event.bind(input, 'blur', check); 78 | 79 | function check(e) { 80 | var val = value(input); 81 | clearAttrs(field) 82 | if ('' === val || input.disabled) return 83 | schema[name](val, function(err, v) { 84 | setAttr(field, err, v); 85 | }); 86 | } 87 | }); 88 | } 89 | 90 | /** 91 | * Only validate on these fields 92 | * 93 | * @param {NodeList} fields 94 | * @return {String} 95 | */ 96 | 97 | function fieldnames(fields) { 98 | var names = []; 99 | for (var i = 0, field; field = fields[i]; i++) { 100 | var input = fetch(field); 101 | if (!input || !input.name || input.disabled) continue; 102 | names.push(input.name); 103 | } 104 | 105 | return names.join(' '); 106 | } 107 | 108 | /** 109 | * Get the inputs from the fields 110 | * 111 | * @param {NodeList} fields 112 | * @return {Boolean|InputElement} 113 | */ 114 | 115 | function fetch(field) { 116 | var input = nodenames.test(field.nodeName) ? field : field.querySelector('input,textarea'); 117 | if (exclude.test(input.type)) return false; 118 | return input; 119 | } 120 | 121 | /** 122 | * Set the element's attributes 123 | * 124 | * @param {Element} el 125 | * @param {Error} err 126 | * @param {Mixed} val 127 | */ 128 | 129 | function setAttr(el, err, val) { 130 | if (err) { 131 | el.setAttribute('invalid', err.message); 132 | el.removeAttribute('valid'); 133 | } else { 134 | el.removeAttribute('invalid'); 135 | if (val) el.setAttribute('valid', val); 136 | } 137 | } 138 | 139 | /** 140 | * Clear attributes 141 | * 142 | * @param {Element} el 143 | */ 144 | 145 | function clearAttrs(el) { 146 | el.removeAttribute('valid'); 147 | el.removeAttribute('invalid'); 148 | } 149 | 150 | /** 151 | * Disable submit button 152 | * 153 | * @param {Element} btn 154 | * @return {Function} 155 | */ 156 | 157 | function disable(btn) { 158 | if (btn) btn.disabled = true; 159 | return function() { 160 | if (btn) btn.disabled = false; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/only.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | module.exports = only; 6 | 7 | /** 8 | * Filter `obj` by `keys` 9 | * 10 | * @param {Object} obj 11 | * @return {Object} 12 | */ 13 | 14 | function only(obj, keys){ 15 | obj = obj || {}; 16 | if ('string' == typeof keys) keys = keys.split(/ +/); 17 | return keys.reduce(function(ret, key){ 18 | if (null == obj[key]) return ret; 19 | ret[key] = obj[key]; 20 | return ret; 21 | }, {}); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var squares = require('squares'); 6 | var Batch = require('batch'); 7 | var isArray = Array.isArray; 8 | var keys = Object.keys; 9 | 10 | /** 11 | * Export `validate` 12 | */ 13 | 14 | module.exports = validate; 15 | 16 | /** 17 | * Validate 18 | * 19 | * @param {Object} obj 20 | * @param {Object} schema 21 | * @return {Function} fn 22 | */ 23 | 24 | function validate(obj, schema, fn) { 25 | var batch = Batch().throws(false); 26 | var attrs = keys(schema); 27 | var errors = {}; 28 | var values = {}; 29 | 30 | // loop through each of the schema attributes 31 | attrs.forEach(function(attr) { 32 | var vals = squares.get(obj, attr); 33 | vals = isArray(vals) ? vals : [vals]; 34 | vals.forEach(function(val) { 35 | batch.push(function (next) { 36 | schema[attr](val, function(err, v) { 37 | if (err) { 38 | errors[attr] = err; 39 | return next(err); 40 | } else { 41 | squares.set(values, attr, v); 42 | return next(); 43 | } 44 | }); 45 | }); 46 | }) 47 | }); 48 | 49 | batch.end(function() { 50 | return keys(errors).length 51 | ? fn(format(errors, obj)) 52 | : fn(null, values); 53 | }) 54 | }; 55 | 56 | /** 57 | * Format the errors into a single error 58 | * 59 | * TODO: create a custom error 60 | * 61 | * @param {Array} arr 62 | * @return {Error} 63 | */ 64 | 65 | function format(errors, actual) { 66 | // format the object 67 | actual = JSON.stringify(actual, true, 2).split('\n').map(function(line) { 68 | return ' | ' + line; 69 | }).join('\n'); 70 | 71 | // format the errors 72 | var msg = keys(errors).map(function(error, i) { 73 | return ' | ' + (i + 1) + '. ' + error + ': ' + errors[error].message; 74 | }).join('\n'); 75 | 76 | var err = new Error('\n |\n | Rube Schema Validation Error\n |\n' + actual + '\n |\n' + msg + '\n |\n'); 77 | err.fields = errors; 78 | return err; 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ive", 3 | "version": "0.2.4", 4 | "description": "isomorphic validation", 5 | "keywords": [ 6 | "schema", 7 | "object", 8 | "rube", 9 | "validation", 10 | "isomorphic", 11 | "browser" 12 | ], 13 | "author": "Matthew Mueller ", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/lapwinglabs/ive.git" 17 | }, 18 | "dependencies": { 19 | "batch": "^0.5.1", 20 | "extend.js": "0.0.x", 21 | "rube": "0.0.x", 22 | "squares": "^0.2.x" 23 | }, 24 | "devDependencies": { 25 | "duo-serve": "^0.6.1", 26 | "mocha": "^2.1.0" 27 | }, 28 | "main": "index" 29 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rube Schema 5 | 31 | 32 | 33 | 34 | 35 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 47 |
48 | Male 49 | Female 50 |
51 |
52 | 53 | 54 | 55 | 56 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var assert = require('assert'); 6 | var Schema = require('../'); 7 | var rube = require('rube'); 8 | 9 | describe('Schema', function() { 10 | 11 | it('should validate schema', function(done) { 12 | var schema = Schema(); 13 | 14 | schema.attr('name').type(String) 15 | schema.attr('email').type(String) 16 | schema.attr('age').cast(Number).type(Number); 17 | 18 | schema({ 19 | name: 'matt', 20 | email: 'matt@lapwinglabs.com', 21 | age: '25' 22 | }, function(err, v) { 23 | assert(!err); 24 | assert('matt' == v.name); 25 | assert('matt@lapwinglabs.com' == v.email); 26 | assert(25 === v.age); 27 | done(); 28 | }) 29 | }); 30 | 31 | it('should work by passing in an object', function(done) { 32 | var schema = Schema({ 33 | name: rube().type(String), 34 | email: rube().assert(/@/).type(String), 35 | age: rube().cast(Number).type(Number) 36 | }); 37 | 38 | schema({ 39 | name: 'matt', 40 | email: 'matt@lapwinglabs.com', 41 | age: '25' 42 | }, function(err, v) { 43 | assert(!err); 44 | assert('matt' == v.name); 45 | assert('matt@lapwinglabs.com' == v.email); 46 | assert(25 === v.age); 47 | done(); 48 | }) 49 | }); 50 | 51 | it('should support composing objects', function(done) { 52 | var name = Schema().attr('name', rube().type(String)); 53 | var email = Schema().attr('email', rube().assert(/@/).type(String)); 54 | var age = Schema().attr('age', rube().cast(Number).type(Number)); 55 | 56 | var schema = Schema() 57 | .attr(name) 58 | .attr(email) 59 | .attr(age) 60 | 61 | schema({ 62 | name: 'matt', 63 | email: 'matt@lapwinglabs.com', 64 | age: '25' 65 | }, function(err, v) { 66 | assert(!err); 67 | assert('matt' == v.name); 68 | assert('matt@lapwinglabs.com' == v.email); 69 | assert(25 === v.age); 70 | done(); 71 | }); 72 | 73 | }); 74 | 75 | it('should support passing objects through `schema.attr`', function(done) { 76 | var schema = Schema() 77 | .attr({ 78 | name: rube().type(String), 79 | email: rube().assert(/@/).type(String), 80 | age: rube().cast(Number).type(Number) 81 | }) 82 | 83 | schema({ 84 | name: 'matt', 85 | email: 'matt@lapwinglabs.com', 86 | age: '25' 87 | }, function(err, v) { 88 | assert(!err); 89 | assert('matt' == v.name); 90 | assert('matt@lapwinglabs.com' == v.email); 91 | assert(25 === v.age); 92 | done(); 93 | }); 94 | }) 95 | 96 | it('should remove properties that arent part of the schema', function(done) { 97 | var schema = Schema(); 98 | schema.attr('name').type(String) 99 | schema.attr('email').type(String) 100 | schema.attr('age').cast(Number).type(Number); 101 | 102 | schema({ 103 | name: 'matt', 104 | email: 'matt@lapwinglabs.com', 105 | age: '25', 106 | pets: [] 107 | }, function(err, v) { 108 | assert(!err); 109 | assert('matt' == v.name); 110 | assert('matt@lapwinglabs.com' == v.email); 111 | assert(25 === v.age); 112 | assert(v.pets == undefined); 113 | done(); 114 | }) 115 | }) 116 | 117 | it('should error out on missing required properties', function(done) { 118 | var schema = Schema(); 119 | schema.attr('name').type(String).required(true); 120 | schema.attr('email').type(String).required(true); 121 | schema.attr('age').cast(Number).type(Number).required(true); 122 | 123 | schema({ name: 'matt', email: 'lapwinglabs@gmail.com' }, function(err) { 124 | assert(err); 125 | assert(err.message.indexOf('age')); 126 | done(); 127 | }); 128 | }) 129 | 130 | describe('validate certain fields', function() { 131 | it('should support space-separated strings', function(done) { 132 | var schema = Schema(); 133 | schema.attr('name').type(String).required(true); 134 | schema.attr('email').type(String).required(true); 135 | schema.attr('age').cast(Number).type(Number).required(true); 136 | 137 | schema('name email', { name: 'matt', email: 'lapwinglabs@gmail.com' }, function(err, v) { 138 | assert(!err); 139 | assert('matt' == v.name); 140 | assert('lapwinglabs@gmail.com' == v.email); 141 | done(); 142 | }) 143 | }) 144 | }); 145 | 146 | it('ive should be composable', function(done) { 147 | var basic = Schema(); 148 | 149 | basic.attr('name') 150 | .type(String) 151 | .between(2, 30) 152 | .required(true); 153 | 154 | basic.attr('email') 155 | .type(String) 156 | .assert(/\w+\@\w+\.\w+/) 157 | .required(true); 158 | 159 | var admin = Schema(basic) 160 | 161 | admin.attr('password') 162 | .type(String) 163 | .between(5, 10) 164 | .assert(/[0-9]/) 165 | .required(true); 166 | 167 | admin({ 168 | name: 'matt', 169 | email: 'lapwinglabs@gmail.com', 170 | password: '0abcde' 171 | }, function(err, v) { 172 | assert(!err); 173 | assert('matt' == v.name); 174 | assert('lapwinglabs@gmail.com' == v.email); 175 | assert('0abcde' == v.password); 176 | done(); 177 | }); 178 | }) 179 | 180 | it('should support nested properties', function() { 181 | var schema = Schema(); 182 | schema.attr('gateways.paypal').required(true).type(String).assert(/\w+\@\w+\.\w+/); 183 | 184 | schema({ 185 | gateways: { 186 | paypal: 'lapwing@lapwinglabs.com' 187 | } 188 | }, function(err, v) { 189 | assert(!err); 190 | assert('lapwing@lapwinglabs.com' == v.gateways.paypal); 191 | }); 192 | }) 193 | 194 | it('should validate arrays', function() { 195 | var schema = Schema(); 196 | schema.attr('accounts[].name').required(true).type(String).between(2, 10); 197 | schema.attr('accounts[].password').required(true).type(Number).between(100, Infinity) 198 | 199 | schema({ 200 | accounts: [ 201 | { 202 | name: 'twitter', 203 | password: 123 204 | }, 205 | { 206 | name: 'facebook', 207 | password: 456 208 | } 209 | ] 210 | }, function(err, v) { 211 | assert(!err); 212 | assert.deepEqual(v, { 213 | accounts: [ 214 | { 215 | name: 'twitter', 216 | password: 123 217 | }, 218 | { 219 | name: 'facebook', 220 | password: 456 221 | } 222 | ] 223 | }); 224 | }); 225 | }) 226 | }) 227 | --------------------------------------------------------------------------------