Repository: HenrikJoreteg/Happy.js Branch: master Commit: 2d295bdeb316 Files: 13 Total size: 94.9 KB Directory structure: gitextract_7vyyzr8x/ ├── .jshintignore ├── .jshintrc ├── LICENSE-MIT ├── README.md ├── demo.html ├── happy.js ├── happy.methods.js └── test/ ├── qunit/ │ ├── qunit.css │ └── qunit.js ├── test.html ├── tests.js ├── underscore.js └── zepto.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .jshintignore ================================================ test/zepto-0.4.js test/underscore.js test/jquery-1.4.4.min.js test/qunit ================================================ FILE: .jshintrc ================================================ { "asi": false, "expr": true, "loopfunc": true, "curly": false, "evil": true, "white": true, "undef": true, "browser": true, "trailing": true, "node": true } ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2011 Henrik Joreteg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Happy.js – are your forms happy? Just ask 'em! [http://projects.joreteg.com/Happy.js/](http://projects.joreteg.com/Happy.js/) | [Demo](http://projects.joreteg.com/Happy.js/demo.html) ================================================ FILE: demo.html ================================================ Happy.js | Demo

Simple Form

With config.submitButton

================================================ FILE: happy.js ================================================ /*global $*/ (function happyJS($) { $.fn.isHappy = function isHappy(config) { var fields = [], item; var pauseMessages = false; function isFunction(obj) { return typeof obj === 'function'; } function defaultError(error) { //Default error template var msgErrorClass = config.classes && config.classes.message || 'unhappyMessage'; return $('' + error.message + ''); } function getError(error) { //Generate error html from either config or default if (isFunction(config.errorTemplate)) { return config.errorTemplate(error); } return defaultError(error); } function handleSubmit(e) { var i, l; var errors = false; if (config.testMode) { e.preventDefault(); } for (i = 0, l = fields.length; i < l; i += 1) { if (!fields[i].testValid(true)) { errors = true; } } if (errors) { if (isFunction(config.unHappy)) config.unHappy(e); return false; } else if (config.testMode) { if (window.console) console.warn('would have submitted'); if (isFunction(config.happy)) return config.happy(e); } if (isFunction(config.happy)) return config.happy(e); } function handleMouseUp() { pauseMessages = false; } function handleMouseDown() { pauseMessages = true; } function processField(opts, selector) { var field = $(selector); if (!field.length) return; selector = field.prop('id') || field.prop('name').replace(['[',']'], ''); var error = { message: opts.message || '', id: selector + '_unhappy' }; var errorEl = $(error.id).length > 0 ? $(error.id) : getError(error); var handleBlur = function handleBlur() { if (!pauseMessages) { field.testValid(); } else { $(window).one('mouseup', field.testValid); } }; fields.push(field); field.testValid = function testValid(submit) { var val, temp; var required = field.prop('required') || opts.required; var password = field.attr('type') === 'password'; var arg = isFunction(opts.arg) ? opts.arg() : opts.arg; var errorTarget = (opts.errorTarget && $(opts.errorTarget)) || field; var fieldErrorClass = config.classes && config.classes.field || 'unhappy'; var testResult = errorTarget.hasClass(fieldErrorClass); var oldMessage = error.message; // handle control groups (checkboxes, radio) if (field.length > 1) { val = []; field.each(function(i,obj) { val.push($(obj).val()); }); val = val.join(','); } else { // clean it or trim it if (isFunction(opts.clean)) { val = opts.clean(field.val()); } else if (!password && typeof opts.trim === 'undefined' || opts.trim) { val = $.trim(field.val()); } else { val = field.val(); } // write it back to the field field.val(val); } // check if we've got an error on our hands if (submit === true && required === true) { testResult = !val.length; } if ((val.length > 0 || required === 'sometimes') && opts.test) { if (isFunction(opts.test)) { testResult = opts.test(val, arg); } else if (typeof opts.test === 'object') { $.each(opts.test, function (i, test) { if (isFunction(test)) { testResult = test(val, arg); if (testResult !== true) { return false; } } }); } if (testResult instanceof Error) { error.message = testResult.message; } else { testResult = !testResult; error.message = opts.message || ''; } } // only rebuild the error if necessary if (!oldMessage !== error.message) { temp = getError(error); errorEl.replaceWith(temp); errorEl = temp; } if (testResult) { errorTarget.addClass(fieldErrorClass).after(errorEl); return false; } else { errorEl.remove(); errorTarget.removeClass(fieldErrorClass); return true; } }; field.on(opts.when || config.when || 'blur', handleBlur); } for (item in config.fields) { if (config.fields.hasOwnProperty(item)) { processField(config.fields[item], item); } } $(config.submitButton || this).on('mousedown', handleMouseDown); $(window).on('mouseup', handleMouseUp); if (config.submitButton) { $(config.submitButton).click(handleSubmit); } else { this.on('submit', handleSubmit); } return this; }; })(this.jQuery || this.Zepto); ================================================ FILE: happy.methods.js ================================================ var happy = { USPhone: function (val) { return /^\(?(\d{3})\)?[\- ]?\d{3}[\- ]?\d{4}$/.test(val); }, // matches mm/dd/yyyy (requires leading 0's (which may be a bit silly, what do you think?) date: function (val) { return /^(?:0[1-9]|1[0-2])\/(?:0[1-9]|[12][0-9]|3[01])\/(?:\d{4})/.test(val); }, email: function (val) { return /^(?:\w+\.?\+?)*\w+@(?:\w+\.)+\w+$/.test(val); }, minLength: function (val, length) { return val.length >= length; }, maxLength: function (val, length) { return val.length <= length; }, equal: function (val1, val2) { return (val1 == val2); } }; ================================================ FILE: test/qunit/qunit.css ================================================ /** Font Family and Sizes */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; } #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { margin: 0; padding: 0; } /** Header */ #qunit-header { padding: 0.5em 0 0.5em 1em; color: #fff; text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; background-color: #0d3349; border-radius: 15px 15px 0 0; -moz-border-radius: 15px 15px 0 0; -webkit-border-top-right-radius: 15px; -webkit-border-top-left-radius: 15px; } #qunit-header a { text-decoration: none; color: white; } #qunit-banner { height: 5px; } #qunit-testrunner-toolbar { padding: 0em 0 0.5em 2em; } #qunit-userAgent { padding: 0.5em 0 0.5em 2.5em; background-color: #2b81af; color: #fff; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } /** Tests: Pass/Fail */ #qunit-tests { list-style-position: inside; } #qunit-tests li { padding: 0.4em 0.5em 0.4em 2.5em; border-bottom: 1px solid #fff; list-style-position: inside; } #qunit-tests li strong { cursor: pointer; } #qunit-tests ol { margin-top: 0.5em; padding: 0.5em; background-color: #fff; border-radius: 15px; -moz-border-radius: 15px; -webkit-border-radius: 15px; box-shadow: inset 0px 2px 13px #999; -moz-box-shadow: inset 0px 2px 13px #999; -webkit-box-shadow: inset 0px 2px 13px #999; } /*** Test Counts */ #qunit-tests b.counts { color: black; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { margin: 0.5em; padding: 0.4em 0.5em 0.4em 0.5em; background-color: #fff; border-bottom: none; list-style-position: inside; } /*** Passing Styles */ #qunit-tests li li.pass { color: #5E740B; background-color: #fff; border-left: 26px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, #qunit-tests .pass .test-expected { color: #999999; } #qunit-banner.qunit-pass { background-color: #C6E746; } /*** Failing Styles */ #qunit-tests li li.fail { color: #710909; background-color: #fff; border-left: 26px solid #EE5757; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail .test-name, #qunit-tests .fail .module-name { color: #000000; } #qunit-tests .fail .test-actual { color: #EE5757; } #qunit-tests .fail .test-expected { color: green; } #qunit-banner.qunit-fail, #qunit-testrunner-toolbar { background-color: #EE5757; } /** Footer */ #qunit-testresult { padding: 0.5em 0.5em 0.5em 2.5em; color: #2b81af; background-color: #D2E0E6; border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 15px 15px; -webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-left-radius: 15px; } /** Fixture */ #qunit-fixture { position: absolute; top: -10000px; left: -10000px; } ================================================ FILE: test/qunit/qunit.js ================================================ /* * QUnit - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2009 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. */ (function(window) { var QUnit = { // call on start of module test to prepend name to all tests module: function(name, testEnvironment) { config.currentModule = name; synchronize(function() { if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } config.currentModule = name; config.moduleTestEnvironment = testEnvironment; config.moduleStats = { all: 0, bad: 0 }; QUnit.moduleStart( name, testEnvironment ); }); }, asyncTest: function(testName, expected, callback) { if ( arguments.length === 2 ) { callback = expected; expected = 0; } QUnit.test(testName, expected, callback, true); }, test: function(testName, expected, callback, async) { var name = '' + testName + '', testEnvironment, testEnvironmentArg; if ( arguments.length === 2 ) { callback = expected; expected = null; } // is 2nd argument a testEnvironment? if ( expected && typeof expected === 'object') { testEnvironmentArg = expected; expected = null; } if ( config.currentModule ) { name = '' + config.currentModule + ": " + name; } if ( !validTest(config.currentModule + ": " + testName) ) { return; } synchronize(function() { testEnvironment = extend({ setup: function() {}, teardown: function() {} }, config.moduleTestEnvironment); if (testEnvironmentArg) { extend(testEnvironment,testEnvironmentArg); } QUnit.testStart( testName, testEnvironment ); // allow utility functions to access the current test environment QUnit.current_testEnvironment = testEnvironment; config.assertions = []; config.expected = expected; var tests = id("qunit-tests"); if (tests) { var b = document.createElement("strong"); b.innerHTML = "Running " + name; var li = document.createElement("li"); li.appendChild( b ); li.id = "current-test-output"; tests.appendChild( li ) } try { if ( !config.pollution ) { saveGlobal(); } testEnvironment.setup.call(testEnvironment); } catch(e) { QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); } }); synchronize(function() { if ( async ) { QUnit.stop(); } try { callback.call(testEnvironment); } catch(e) { fail("Test " + name + " died, exception and test follows", e, callback); QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if ( config.blocking ) { start(); } } }); synchronize(function() { try { checkPollution(); testEnvironment.teardown.call(testEnvironment); } catch(e) { QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); } }); synchronize(function() { try { QUnit.reset(); } catch(e) { fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset); } if ( config.expected && config.expected != config.assertions.length ) { QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); } var good = 0, bad = 0, tests = id("qunit-tests"); config.stats.all += config.assertions.length; config.moduleStats.all += config.assertions.length; if ( tests ) { var ol = document.createElement("ol"); for ( var i = 0; i < config.assertions.length; i++ ) { var assertion = config.assertions[i]; var li = document.createElement("li"); li.className = assertion.result ? "pass" : "fail"; li.innerHTML = assertion.message || "(no message)"; ol.appendChild( li ); if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } } if (bad == 0) { ol.style.display = "none"; } var b = document.createElement("strong"); b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; addEvent(b, "click", function() { var next = b.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); addEvent(b, "dblclick", function(e) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); } }); var li = id("current-test-output"); li.id = ""; li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); li.appendChild( b ); li.appendChild( ol ); if ( bad ) { var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "block"; id("qunit-filter-pass").disabled = null; id("qunit-filter-missing").disabled = null; } } } else { for ( var i = 0; i < config.assertions.length; i++ ) { if ( !config.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } } QUnit.testDone( testName, bad, config.assertions.length ); if ( !window.setTimeout && !config.queue.length ) { done(); } }); synchronize( done ); }, /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ expect: function(asserts) { config.expected = asserts; }, /** * Asserts true. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { msg = escapeHtml(msg); QUnit.log(a, msg); config.assertions.push({ result: !!a, message: msg }); }, /** * Checks that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * * Prefered to ok( actual == expected, message ) * * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); * * @param Object actual * @param Object expected * @param String message (optional) */ equal: function(actual, expected, message) { push(expected == actual, actual, expected, message); }, notEqual: function(actual, expected, message) { push(expected != actual, actual, expected, message); }, deepEqual: function(actual, expected, message) { push(QUnit.equiv(actual, expected), actual, expected, message); }, notDeepEqual: function(actual, expected, message) { push(!QUnit.equiv(actual, expected), actual, expected, message); }, strictEqual: function(actual, expected, message) { push(expected === actual, actual, expected, message); }, notStrictEqual: function(actual, expected, message) { push(expected !== actual, actual, expected, message); }, raises: function(fn, message) { try { fn(); ok( false, message ); } catch (e) { ok( true, message ); } }, start: function() { // A slight delay, to avoid any current callbacks if ( window.setTimeout ) { window.setTimeout(function() { if ( config.timeout ) { clearTimeout(config.timeout); } config.blocking = false; process(); }, 13); } else { config.blocking = false; process(); } }, stop: function(timeout) { config.blocking = true; if ( timeout && window.setTimeout ) { config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); QUnit.start(); }, timeout); } } }; // Backwards compatibility, deprecated QUnit.equals = QUnit.equal; QUnit.same = QUnit.deepEqual; // Maintain internal state var config = { // The queue of tests to run queue: [], // block until document ready blocking: true }; // Load paramaters (function() { var location = window.location || { search: "", protocol: "file:" }, GETParams = location.search.slice(1).split('&'); for ( var i = 0; i < GETParams.length; i++ ) { GETParams[i] = decodeURIComponent( GETParams[i] ); if ( GETParams[i] === "noglobals" ) { GETParams.splice( i, 1 ); i--; config.noglobals = true; } else if ( GETParams[i].search('=') > -1 ) { GETParams.splice( i, 1 ); i--; } } // restrict modules/tests by get parameters config.filters = GETParams; // Figure out if we're running the tests from a server or not QUnit.isLocal = !!(location.protocol === 'file:'); })(); // Expose the API as global variables, unless an 'exports' // object exists, in that case we assume we're in CommonJS if ( typeof exports === "undefined" || typeof require === "undefined" ) { extend(window, QUnit); window.QUnit = QUnit; } else { extend(exports, QUnit); exports.QUnit = QUnit; } // define these after exposing globals to keep them in these QUnit namespace only extend(QUnit, { config: config, // Initialize the configuration options init: function() { extend(config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date, updateRate: 1000, blocking: false, autostart: true, autorun: false, assertions: [], filters: [], queue: [] }); var tests = id("qunit-tests"), banner = id("qunit-banner"), result = id("qunit-testresult"); if ( tests ) { tests.innerHTML = ""; } if ( banner ) { banner.className = ""; } if ( result ) { result.parentNode.removeChild( result ); } }, /** * Resets the test setup. Useful for tests that modify the DOM. */ reset: function() { if ( window.jQuery ) { jQuery("#main, #qunit-fixture").html( config.fixture ); } }, /** * Trigger an event on an element. * * @example triggerEvent( document.body, "click" ); * * @param DOMElement elem * @param String type */ triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { event = document.createEvent("MouseEvents"); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { elem.fireEvent("on"+type); } }, // Safe object type checking is: function( type, obj ) { return QUnit.objectType( obj ) == type; }, objectType: function( obj ) { if (typeof obj === "undefined") { return "undefined"; // consider: typeof null === object } if (obj === null) { return "null"; } var type = Object.prototype.toString.call( obj ) .match(/^\[object\s(.*)\]$/)[1] || ''; switch (type) { case 'Number': if (isNaN(obj)) { return "nan"; } else { return "number"; } case 'String': case 'Boolean': case 'Array': case 'Date': case 'RegExp': case 'Function': return type.toLowerCase(); } if (typeof obj === "object") { return "object"; } return undefined; }, // Logging callbacks begin: function() {}, done: function(failures, total) {}, log: function(result, message) {}, testStart: function(name, testEnvironment) {}, testDone: function(name, failures, total) {}, moduleStart: function(name, testEnvironment) {}, moduleDone: function(name, failures, total) {} }); if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } addEvent(window, "load", function() { QUnit.begin(); // Initialize the config, saving the execution queue var oldconfig = extend({}, config); QUnit.init(); extend(config, oldconfig); config.blocking = false; var userAgent = id("qunit-userAgent"); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } var banner = id("qunit-header"); if ( banner ) { banner.innerHTML = '' + banner.innerHTML + ''; } var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "none"; var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; filter.disabled = true; addEvent( filter, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("pass") > -1 ) { li[i].style.display = filter.checked ? "none" : ""; } } }); toolbar.appendChild( filter ); var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); var missing = document.createElement("input"); missing.type = "checkbox"; missing.id = "qunit-filter-missing"; missing.disabled = true; addEvent( missing, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; } } }); toolbar.appendChild( missing ); label = document.createElement("label"); label.setAttribute("for", "qunit-filter-missing"); label.innerHTML = "Hide missing tests (untested code is broken code)"; toolbar.appendChild( label ); } var main = id('main') || id('qunit-fixture'); if ( main ) { config.fixture = main.innerHTML; } if (config.autostart) { QUnit.start(); } }); function done() { if ( config.doneTimer && window.clearTimeout ) { window.clearTimeout( config.doneTimer ); config.doneTimer = null; } if ( config.queue.length ) { config.doneTimer = window.setTimeout(function(){ if ( !config.queue.length ) { done(); } else { synchronize( done ); } }, 13); return; } config.autorun = true; // Log the last module results if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } var banner = id("qunit-banner"), tests = id("qunit-tests"), html = ['Tests completed in ', +new Date - config.started, ' milliseconds.
', '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); } if ( tests ) { var result = id("qunit-testresult"); if ( !result ) { result = document.createElement("p"); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests.nextSibling ); } result.innerHTML = html; } QUnit.done( config.stats.bad, config.stats.all ); } function validTest( name ) { var i = config.filters.length, run = false; if ( !i ) { return true; } while ( i-- ) { var filter = config.filters[i], not = filter.charAt(0) == '!'; if ( not ) { filter = filter.slice(1); } if ( name.indexOf(filter) !== -1 ) { return !not; } if ( not ) { run = true; } } return run; } function escapeHtml(s) { s = s === null ? "" : s + ""; return s.replace(/[\&"<>\\]/g, function(s) { switch(s) { case "&": return "&"; case "\\": return "\\\\"; case '"': return '\"'; case "<": return "<"; case ">": return ">"; default: return s; } }); } function push(result, actual, expected, message) { message = escapeHtml(message) || (result ? "okay" : "failed"); message = '' + message + ""; expected = escapeHtml(QUnit.jsDump.parse(expected)); actual = escapeHtml(QUnit.jsDump.parse(actual)); var output = message + ', expected: ' + expected + ''; if (actual != expected) { output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual); } // can't use ok, as that would double-escape messages QUnit.log(result, output); config.assertions.push({ result: !!result, message: output }); } function synchronize( callback ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { process(); } } function process() { var start = (new Date()).getTime(); while ( config.queue.length && !config.blocking ) { if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { config.queue.shift()(); } else { setTimeout( process, 13 ); break; } } } function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { config.pollution.push( key ); } } } function checkPollution( name ) { var old = config.pollution; saveGlobal(); var newGlobals = diff( old, config.pollution ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); config.expected++; } var deletedGlobals = diff( config.pollution, old ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); config.expected++; } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { var result = a.slice(); for ( var i = 0; i < result.length; i++ ) { for ( var j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice(i, 1); i--; break; } } } return result; } function fail(message, exception, callback) { if ( typeof console !== "undefined" && console.error && console.warn ) { console.error(message); console.error(exception); console.warn(callback.toString()); } else if ( window.opera && opera.postError ) { opera.postError(message, exception, callback.toString); } } function extend(a, b) { for ( var prop in b ) { a[prop] = b[prop]; } return a; } function addEvent(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function id(name) { return !!(typeof document !== "undefined" && document && document.getElementById) && document.getElementById( name ); } // Test for equality any JavaScript type. // Discussions and reference: http://philrathe.com/articles/equiv // Test suites: http://philrathe.com/tests/equiv // Author: Philippe Rathé QUnit.equiv = function () { var innerEquiv; // the real equiv function var callers = []; // stack to decide between skip/abort functions var parents = []; // stack to avoiding loops from circular referencing // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = QUnit.objectType(o); if (prop) { if (QUnit.objectType(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } var callbacks = function () { // for string, boolean, number and null function useStrictEquality(b, a) { if (b instanceof a.constructor || a instanceof b.constructor) { // to catch short annotaion VS 'new' annotation of a declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { "string": useStrictEquality, "boolean": useStrictEquality, "number": useStrictEquality, "null": useStrictEquality, "undefined": useStrictEquality, "nan": function (b) { return isNaN(b); }, "date": function (b, a) { return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp": function (b, a) { return QUnit.objectType(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function": function () { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array": function (b, a) { var i, j, loop; var len; // b could be an object literal here if ( ! (QUnit.objectType(b) === "array")) { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } //track reference to avoid circular references parents.push(a); for (i = 0; i < len; i++) { loop = false; for(j=0;j= 0) { type = "array"; } else { type = typeof obj; } return type; }, separator:function() { return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing if ( !this.multiline ) return ''; var chr = this.indentChar; if ( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ) { this._depth_ += a || 1; }, down:function( a ) { this._depth_ -= a || 1; }, setParser:function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote:quote, literal:literal, join:join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', undefined:'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if ( name ) ret += ' ' + name; ret += '('; ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, this.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map ) { var ret = [ ]; this.up(); for ( var key in map ) ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); this.down(); return join( '{', ret, '}' ); }, node:function( node ) { var open = this.HTML ? '<' : '<', close = this.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for ( var a in this.DOMAttrs ) { var val = node[this.DOMAttrs[a]]; if ( val ) ret += ' ' + a + '=' + this.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function var l = fn.length; if ( !l ) return ''; var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map functionCode:'[code]', //function calls it internally, it's the content of the function attribute:quote, //node calls it internally, it's an html attribute value string:quote, date:quote, regexp:literal, //regex number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName id:'id', name:'name', 'class':'className' }, HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar:' ',//indentation unit multiline:false //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; })(); // from Sizzle.js function getText( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += getText( elem.childNodes ); } } return ret; }; /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) * Modified by Chu Alan "sprite" * * Released under the MIT license. * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ * * Usage: QUnit.diff(expected, actual) * * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { function diff(o, n){ var ns = new Object(); var os = new Object(); for (var i = 0; i < n.length; i++) { if (ns[n[i]] == null) ns[n[i]] = { rows: new Array(), o: null }; ns[n[i]].rows.push(i); } for (var i = 0; i < o.length; i++) { if (os[o[i]] == null) os[o[i]] = { rows: new Array(), n: null }; os[o[i]].rows.push(i); } for (var i in ns) { if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { n[ns[i].rows[0]] = { text: n[ns[i].rows[0]], row: os[i].rows[0] }; o[os[i].rows[0]] = { text: o[os[i].rows[0]], row: ns[i].rows[0] }; } } for (var i = 0; i < n.length - 1; i++) { if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && n[i + 1] == o[n[i].row + 1]) { n[i + 1] = { text: n[i + 1], row: n[i].row + 1 }; o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1 }; } } for (var i = n.length - 1; i > 0; i--) { if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && n[i - 1] == o[n[i].row - 1]) { n[i - 1] = { text: n[i - 1], row: n[i].row - 1 }; o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1 }; } } return { o: o, n: n }; } return function(o, n){ o = o.replace(/\s+$/, ''); n = n.replace(/\s+$/, ''); var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); var str = ""; var oSpace = o.match(/\s+/g); if (oSpace == null) { oSpace = [" "]; } else { oSpace.push(" "); } var nSpace = n.match(/\s+/g); if (nSpace == null) { nSpace = [" "]; } else { nSpace.push(" "); } if (out.n.length == 0) { for (var i = 0; i < out.o.length; i++) { str += '' + out.o[i] + oSpace[i] + ""; } } else { if (out.n[0].text == null) { for (n = 0; n < out.o.length && out.o[n].text == null; n++) { str += '' + out.o[n] + oSpace[n] + ""; } } for (var i = 0; i < out.n.length; i++) { if (out.n[i].text == null) { str += '' + out.n[i] + nSpace[i] + ""; } else { var pre = ""; for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { pre += '' + out.o[n] + oSpace[n] + ""; } str += " " + out.n[i].text + nSpace[i] + pre; } } } return str; } })(); })(this); ================================================ FILE: test/test.html ================================================ Happy Tests

Happy Tests

    ================================================ FILE: test/tests.js ================================================ /*global $, test, equal, happy, ok, equals*/ module('Tiny Validator'); function fixture(html) { return $('#qunit-fixture').html('
    ' + html + '
    ').find('form'); } function submit() { $('form').trigger('submit'); } function fill() { $('#textInput1').val('hjoreteg@gmail.com'); $('#textInput2').val('(909)555-5555'); $('#textarea1').val('something'); $('#password1').val('password'); $('#password2').val('password'); } test('check basic required', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true, message: 'Please enter an email' } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('#textInput1').val('test'); form.trigger('submit'); equal($('.unhappy').length, 0); equal($('.unhappyMessage').length, 0); }); test('check setting required with "required" attribute', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { message: 'Please enter an email' } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('#textInput1').val('test'); form.trigger('submit'); equal($('.unhappy').length, 0); equal($('.unhappyMessage').length, 0); }); test('check email', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true, message: 'Please enter an email', test: happy.email } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('#textInput1').val('test'); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('#textInput1').val('hjoreteg@gmail.com'); form.trigger('submit'); equal($('.unhappy').length, 0); equal($('.unhappyMessage').length, 0); }); test('test password match', function () { var form = fixture(''); form.isHappy({ fields: { '#p1': { required: true, message: 'Please enter a new password', test: function (val1, val2) { return (val1 === val2); }, arg: function () { return $('#p2').val(); } }, '#p2': { required: true, message: 'Please enter your new password again' } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 2); equal($('.unhappyMessage').length, 2); $('#p1').val('test'); form.trigger('submit'); equal($('.unhappy').length, 2); equal($('.unhappyMessage').length, 2); $('#p2').val('test2'); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('#p2').val('test'); form.trigger('submit'); equal($('.unhappy').length, 0); equal($('.unhappyMessage').length, 0); }); test('test trimming fields', function () { var form = fixture('' + '' + '' + ''); form.isHappy({ fields: { '#textInput1': { trim: false }, '#textInput2': { trim: true }, '#textInput3': { message: 'Please enter an email' }, '#passwordInput': { message: 'Please enter a password' } }, testMode: true }); $('#textInput1').val(' a '); $('#textInput2').val(' b '); $('#textInput3').val(' c '); $('#passwordInput').val(' password '); form.trigger('submit'); equal($('#textInput1').val(), ' a '); equal($('#textInput2').val(), 'b'); equal($('#textInput3').val(), 'c'); equal($('#passwordInput').val(), ' password '); }); test('test passed in "clean" method', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { message: 'Please enter an email', clean: function (val) { return val.replace("a", "b"); } } }, testMode: true }); $('#textInput1').val(' a '); form.trigger('submit'); equal($('#textInput1').val(), ' b '); }); test('test passing "sometimes" to required', function () { var form = fixture(''), testFlag = false; form.isHappy({ fields: { '#textInput1': { message: 'Please enter an email', required: 'sometimes', test: function (val) { return testFlag; } } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 1); testFlag = true; form.trigger('submit'); equal($('.unhappy').length, 0); }); test('test required fields should only be tested on submit', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { message: 'test', required: true } }, testMode: true }); $('#textInput1').trigger('blur'); equal($('.unhappy').length, 0); form.trigger('submit'); equal($('.unhappy').length, 1); }); test('test non-required fields still tested on blur', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { message: 'test', test: happy.email } }, testMode: true }); $('#textInput1').val('h@h').trigger('blur'); equal($('.unhappy').length, 1); form.trigger('submit'); equal($('.unhappy').length, 1); }); test('test unHappy callback', function () { var form = fixture(''), myFlag = false; form.isHappy({ fields: { '#textInput1': { message: 'not happy dude' } }, testMode: true, unHappy: function () { myFlag = true; } }); form.trigger('submit'); ok(myFlag); myFlag = false; $('#textInput1').val('test'); form.trigger('submit'); equals(myFlag, false); }); test('test happy callback', function () { var form = fixture(''), myFlag = false; form.isHappy({ fields: { '#textInput1': { message: 'not happy dude' } }, testMode: true, happy: function () { myFlag = true; } }); form.trigger('submit'); equals(myFlag, false); $('#textInput1').val('test'); form.trigger('submit'); ok(myFlag); }); test('test included email validator', function () { var happyEmails = [ 'henrik@andyet.net', 'h.joreteg@gmail.com', '23423.24.2.2342@test.test', '2.3.2.4.a.a.a.ffasaf.a@aol.com' ], sadEmails = [ '.henrik@andyet.net', 'henrik.@andyet.net', 'test@test.afcom.com.', 'h@.h', 'a@a' ], i; for (i = 0; i < happyEmails.length; i++) { ok(happy.email(happyEmails[i])); } for (i = 0; i < sadEmails.length; i++) { ok(!happy.email(sadEmails[i])); } }); test('test included date validator', function () { var happyDates = [ '12/29/1982', '11/02/2099' ], sadDates = [ '123/24/1999', '13/31/2099', '12/32/2099', '1/31/2999' ], i; for (i = 0; i < happyDates.length; i++) { ok(happy.date(happyDates[i])); } for (i = 0; i < sadDates.length; i++) { ok(!happy.date(sadDates[i])); } }); test('test included phone validator', function () { var happyPhones = [ '909-765-3941', '(909) 234-2343', '9999999999', '(909)234-2343', ], sadPhones = [ '12-123-22311', 'asdfasdf', '123-123-12344' ], i; for (i = 0; i < happyPhones.length; i++) { ok(happy.USPhone(happyPhones[i])); } for (i = 0; i < sadPhones.length; i++) { ok(!happy.USPhone(sadPhones[i])); } }); test('check return value', function () { var form = fixture(''); equal(form.isHappy({fields: {}}), form); }); test('test message is empty string when not explicitly set', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true, } }, testMode: true }); form.trigger('submit'); equal($('.unhappyMessage').first().text(), ''); }); test('custom error template', function () { var form = fixture(''); var myTemplate = function (error) { return $('custom ' + error.message + ''); }; form.isHappy({ fields: { '#textInput1': { required: true, } }, testMode: true, errorTemplate: myTemplate }); form.trigger('submit'); equal($('.customUnhappy').length, 1); }); test ('error target', function () { var form = fixture('

    '); form.isHappy({ fields: { '#textInput': { required: true, message: 'nope', errorTarget: '#customError' } }, testMode: true }); form.trigger('submit'); equal($('#customError').length, 1); //Didn't insert after input equal($($('form').children()[1]).text(), 'nope'); //Inserted after

    }); test('test config item when to be set on all fields', function () { var form = fixture(''); var test1ran = false; form.isHappy({ fields: { '#textInput1': { test: function () { test1ran = true; return true; } } }, when: 'keyup', testMode: true }); $('#textInput1').val('asdf').trigger('blur'); equal(test1ran, false); $('#textInput1').val('asdf').trigger('keyup'); equal(test1ran, true); }); test('test config item when to be set on a unique field', function () { var form = fixture(''); var test1ran = false; var test2ran = false; form.isHappy({ fields: { '#textInput1': { test: function () { test1ran = true; return true; } }, '#textInput2': { test: function () { test2ran = true; return true; }, when: 'keyup' } }, testMode: true }); $('#textInput1').val('asdf').trigger('keyup'); equal(test1ran, false); $('#textInput1').val('asdf').trigger('blur'); equal(test1ran, true); $('#textInput2').val('asdf').trigger('keyup'); equal(test2ran, true); }); test('test custom classes', function () { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true }, }, classes: { field: 'error', message: 'errorMsg' }, testMode: true }); form.submit(); equal($('#textInput1').attr('class'), 'error'); equal($('#textInput1_unhappy').attr('class'), 'errorMsg'); $('#textInput1').val('asdf'); form.submit(); equal($('#textInput1').attr('class'), ''); }); test('checkbox values aren\'t clobbered', function () { var form = fixture(''); form.isHappy({ fields: { '.answer': { required: true } }, testMode: true }); form.submit(); equal($('#valueA').attr('value'), 'a'); equal($('#valueB').attr('value'), 'b'); }); test('check name attribute selector', function () { var form = fixture(''); form.isHappy({ fields: { '[name="textInput1"]': { required: true, message: 'Please enter an email' } }, testMode: true }); form.trigger('submit'); equal($('.unhappy').length, 1); equal($('.unhappyMessage').length, 1); $('[name="textInput1"]').val('test'); form.trigger('submit'); equal($('.unhappy').length, 0); equal($('.unhappyMessage').length, 0); }); test('error message persists until valid', function() { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true, message: 'Please enter an email' } }, testMode: true }); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Please enter an email', 'Error message present after submit.'); $('#textInput1').trigger('blur'); equal($('#textInput1_unhappy').text(), 'Please enter an email', 'Error message persists after blur.'); $('#textInput1').val('name@example.com'); form.trigger('submit'); equal($('#textInput1_unhappy').length, 0, 'Error message removed with valid value.'); }); test('custom error messages', function() { var form = fixture(''); form.isHappy({ fields: { '#textInput1': { required: true, test: function(value) { var birthday = new Date(value); var birthdayInt = birthday.getTime(); if (isNaN(birthdayInt)) { return false; } if(birthdayInt > new Date()) { return new Error('Your birthday must have already happened.'); } if (birthday.getDay() === 3) { return new Error('Your birthday cannot have happened on a Wednesday.'); } return true; }, message: 'Please provide a valid birth date.' } }, testMode: true }); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Please provide a valid birth date.', 'Default error message is shown.'); // tomorrow var dateVal = new Date(new Date().getTime() + (1000 * 60 * 60 * 24)).toString(); $('#textInput1').val(dateVal); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Your birthday must have already happened.', 'Future dates are invalid.'); $('#textInput1').val('foo'); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Please provide a valid birth date.', 'Default error message is shown for poorly formatted dates.'); $('#textInput1').val('11/04/2015'); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Your birthday cannot have happened on a Wednesday.', 'Testing for another custom message.'); $('#textInput1').val('01/01/1989'); form.trigger('submit'); equal($('#textInput1_unhappy').length, 0, 'Error message removed with valid value.'); }); test('test support for multiple tests', function() { var form = fixture(''); function minLength(val, arg) { return val.length < arg[0] ? new Error('Your name must be longer.') : true; } function maxLength(val, arg) { return val.length > arg[1] ? new Error('Your name must be shorter.') : true; } form.isHappy({ fields: { '#textInput1': { required: true, message: 'Might we inquire your name', test: [minLength, maxLength], arg: [3, 32] } }, testMode: true }); $('#textInput1').val('xx'); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Your name must be longer.', 'Stop after first test fails.'); $('#textInput1').val((new Array(42)).join('x')); form.trigger('submit'); equal($('#textInput1_unhappy').text(), 'Your name must be shorter.', 'Second test fails.'); $('#textInput1').val('John Smith'); form.trigger('submit'); equal($('#textInput1_unhappy').length, 0, 'All tests pass.'); }); ================================================ FILE: test/underscore.js ================================================ // Underscore.js 1.1.4 // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `global` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Establish the object that gets returned to break out of a loop iteration. var breaker = {}; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype; // Create quick reference variables for speed access to core prototypes. var slice = ArrayProto.slice, unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeForEach = ArrayProto.forEach, nativeMap = ArrayProto.map, nativeReduce = ArrayProto.reduce, nativeReduceRight = ArrayProto.reduceRight, nativeFilter = ArrayProto.filter, nativeEvery = ArrayProto.every, nativeSome = ArrayProto.some, nativeIndexOf = ArrayProto.indexOf, nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; // Export the Underscore object for **CommonJS**, with backwards-compatibility // for the old `require()` API. If we're not in CommonJS, add `_` to the // global object. if (typeof module !== 'undefined' && module.exports) { module.exports = _; _._ = _; } else { root._ = _; } // Current version. _.VERSION = '1.1.4'; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles objects implementing `forEach`, arrays, and raw objects. // Delegates to **ECMAScript 5**'s native `forEach` if available. var each = _.each = _.forEach = function(obj, iterator, context) { var value; if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (_.isNumber(obj.length)) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (hasOwnProperty.call(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; } } } }; // Return the results of applying the iterator to each element. // Delegates to **ECMAScript 5**'s native `map` if available. _.map = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); return results; }; // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { var initial = memo !== void 0; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } each(obj, function(value, index, list) { if (!initial && index === 0) { memo = value; initial = true; } else { memo = iterator.call(context, memo, value, index, list); } }); if (!initial) throw new TypeError("Reduce of empty array with no initial value"); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); return _.reduce(reversed, iterator, memo, context); }; // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, iterator, context) { var result; any(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; return true; } }); return result; }; // Return all the elements that pass a truth test. // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { var results = []; if (obj == null) return results; each(obj, function(value, index, list) { if (!iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // Determine whether all of the elements match a truth test. // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, iterator, context) { iterator = iterator || _.identity; var result = true; if (obj == null) return result; if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); each(obj, function(value, index, list) { if (!(result = result && iterator.call(context, value, index, list))) return breaker; }); return result; }; // Determine if at least one element in the object matches a truth test. // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { iterator = iterator || _.identity; var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); each(obj, function(value, index, list) { if (result = iterator.call(context, value, index, list)) return breaker; }); return result; }; // Determine if a given value is included in the array or object using `===`. // Aliased as `contains`. _.include = _.contains = function(obj, target) { var found = false; if (obj == null) return found; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; any(obj, function(value) { if (found = value === target) return true; }); return found; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); return _.map(obj, function(value) { return (method ? value[method] : value).apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); var result = {computed : -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); var result = {computed : Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index, list) { return { value : value, criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { iterator = iterator || _.identity; var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; } return low; }; // Safely convert anything iterable into a real, live array. _.toArray = function(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); if (_.isArray(iterable)) return iterable; if (_.isArguments(iterable)) return slice.call(iterable); return _.values(iterable); }; // Return the number of elements in an object. _.size = function(obj) { return _.toArray(obj).length; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head`. The **guard** check allows it to work // with `_.map`. _.first = _.head = function(array, n, guard) { return n && !guard ? slice.call(array, 0, n) : array[0]; }; // Returns everything but the first entry of the array. Aliased as `tail`. // Especially useful on the arguments object. Passing an **index** will return // the rest of the values in the array from that index onward. The **guard** // check allows it to work with `_.map`. _.rest = _.tail = function(array, index, guard) { return slice.call(array, _.isUndefined(index) || guard ? 1 : index); }; // Get the last element of an array. _.last = function(array) { return array[array.length - 1]; }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, function(value){ return !!value; }); }; // Return a completely flattened version of an array. _.flatten = function(array) { return _.reduce(array, function(memo, value) { if (_.isArray(value)) return memo.concat(_.flatten(value)); memo[memo.length] = value; return memo; }, []); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { var values = slice.call(arguments, 1); return _.filter(array, function(value){ return !_.include(values, value); }); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted) { return _.reduce(array, function(memo, el, i) { if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; return memo; }, []); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersect = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { return _.indexOf(other, item) >= 0; }); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { var args = slice.call(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); return results; }; // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // we need this function. Return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. // Delegates to **ECMAScript 5**'s native `indexOf` if available. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { if (array == null) return -1; if (isSorted) { var i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; return -1; }; // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item) { if (array == null) return -1; if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); var i = array.length; while (i--) if (array[i] === item) return i; return -1; }; // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { var args = slice.call(arguments), solo = args.length <= 1, start = solo ? 0 : args[0], stop = solo ? args[0] : args[1], step = args[2] || 1, len = Math.max(Math.ceil((stop - start) / step), 0), idx = 0, range = new Array(len); while (idx < len) { range[idx++] = start; start += step; } return range; }; // Function (ahem) Functions // ------------------ // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Binding with arguments is also known as `curry`. _.bind = function(func, obj) { var args = slice.call(arguments, 2); return function() { return func.apply(obj || {}, args.concat(slice.call(arguments))); }; }; // Bind all of an object's methods to that object. Useful for ensuring that // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); if (funcs.length == 0) funcs = _.functions(obj); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memo = {}; hasher = hasher || _.identity; return function() { var key = hasher.apply(this, arguments); return key in memo ? memo[key] : (memo[key] = func.apply(this, arguments)); }; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(func, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; // Internal function used to implement `_.throttle` and `_.debounce`. var limit = function(func, wait, debounce) { var timeout; return function() { var context = this, args = arguments; var throttler = function() { timeout = null; func.apply(context, args); }; if (debounce) clearTimeout(timeout); if (debounce || !timeout) timeout = setTimeout(throttler, wait); }; }; // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { return limit(func, wait, false); }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. _.debounce = function(func, wait) { return limit(func, wait, true); }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { var args = [func].concat(slice.call(arguments)); return wrapper.apply(this, args); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var funcs = slice.call(arguments); return function() { var args = slice.call(arguments); for (var i=funcs.length-1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } return args[0]; }; }; // Object Functions // ---------------- // Retrieve the names of an object's properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = nativeKeys || function(obj) { if (_.isArray(obj)) return _.range(0, obj.length); var keys = []; for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { return _.map(obj, _.identity); }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { for (var prop in source) obj[prop] = source[prop]; }); return obj; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { // Check object identity. if (a === b) return true; // Different types? var atype = typeof(a), btype = typeof(b); if (atype != btype) return false; // Basic equality test (watch out for coercions). if (a == b) return true; // One is falsy and the other truthy. if ((!a && b) || (a && !b)) return false; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); // Check dates' integer values. if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); // Both are NaN? if (_.isNaN(a) && _.isNaN(b)) return false; // Compare regular expressions. if (_.isRegExp(a) && _.isRegExp(b)) return a.source === b.source && a.global === b.global && a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; // If a is not an object by this point, we can't handle it. if (atype !== 'object') return false; // Check for different array lengths before comparing contents. if (a.length && (a.length !== b.length)) return false; // Nothing else worked, deep compare the contents. var aKeys = _.keys(a), bKeys = _.keys(b); // Different object sizes? if (aKeys.length != bKeys.length) return false; // Recursive comparison of contents. for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; return true; }; // Is a given array or object empty? _.isEmpty = function(obj) { if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; return true; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType == 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Is a given variable an arguments object? _.isArguments = function(obj) { return !!(obj && hasOwnProperty.call(obj, 'callee')); }; // Is a given value a function? _.isFunction = function(obj) { return !!(obj && obj.constructor && obj.call && obj.apply); }; // Is a given value a string? _.isString = function(obj) { return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); }; // Is a given value a number? _.isNumber = function(obj) { return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); }; // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript // that does not equal itself. _.isNaN = function(obj) { return obj !== obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false; }; // Is a given value a date? _.isDate = function(obj) { return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); }; // Is the given value a regular expression? _.isRegExp = function(obj) { return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iterators. _.identity = function(value) { return value; }; // Run a function **n** times. _.times = function (n, iterator, context) { for (var i = 0; i < n; i++) iterator.call(context, i); }; // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { each(_.functions(obj), function(name){ addToWrapper(name, _[name] = obj[name]); }); }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = idCounter++; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(str, data) { var c = _.templateSettings; var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(c.interpolate, function(match, code) { return "'," + code.replace(/\\'/g, "'") + ",'"; }) .replace(c.evaluate || null, function(match, code) { return "');" + code.replace(/\\'/g, "'") .replace(/[\r\n\t]/g, ' ') + "__p.push('"; }) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + "');}return __p.join('');"; var func = new Function('obj', tmpl); return data ? func(data) : func; }; // The OOP Wrapper // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. var wrapper = function(obj) { this._wrapped = obj; }; // Expose `wrapper.prototype` as `_.prototype` _.prototype = wrapper.prototype; // Helper function to continue chaining intermediate results. var result = function(obj, chain) { return chain ? _(obj).chain() : obj; }; // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { var args = slice.call(arguments); unshift.call(args, this._wrapped); return result(func.apply(_, args), this._chain); }; }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { method.apply(this._wrapped, arguments); return result(this._wrapped, this._chain); }; }); // Add all accessor Array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { return result(method.apply(this._wrapped, arguments), this._chain); }; }); // Start chaining a wrapped Underscore object. wrapper.prototype.chain = function() { this._chain = true; return this; }; // Extracts the result from a wrapped and chained object. wrapper.prototype.value = function() { return this._wrapped; }; })(); ================================================ FILE: test/zepto.html ================================================ Happy Zepto Tests

    Happy Zepto Tests