├── app ├── img │ └── .gitkeep ├── modules │ └── .gitkeep ├── templates │ └── .gitkeep ├── styles │ └── index.css ├── router.js ├── config.js ├── main.js └── app.js ├── .gitignore ├── favicon.ico ├── test ├── jasmine │ ├── vendor │ │ ├── jasmine_favicon.png │ │ ├── MIT.LICENSE │ │ ├── jasmine.css │ │ └── jasmine-html.js │ ├── index.html │ └── spec │ │ └── example.spec.js └── qunit │ ├── tests │ └── example.js │ ├── index.html │ └── vendor │ ├── qunit.css │ └── qunit.js ├── package.json ├── vendor ├── jam │ ├── underscore │ │ ├── package.json │ │ └── underscore.js │ ├── jquery │ │ ├── src │ │ │ └── sizzle │ │ │ │ ├── package.json │ │ │ │ └── speed │ │ │ │ └── benchmark.js │ │ │ │ └── package.json │ │ ├── test │ │ │ └── qunit │ │ │ │ └── package.json │ │ └── package.json │ ├── backbone │ │ └── package.json │ ├── lodash │ │ └── package.json │ ├── backbone.layoutmanager │ │ ├── package.json │ │ └── backbone.layoutmanager.js │ └── require.config.js ├── h5bp │ └── css │ │ ├── main.css │ │ └── normalize.css └── js │ └── libs │ └── almond.js ├── LICENSE ├── index.html ├── readme.md ├── contributing.md └── grunt.js /app/img/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /app/modules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchase/backbone-boilerplate/master/favicon.ico -------------------------------------------------------------------------------- /test/jasmine/vendor/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchase/backbone-boilerplate/master/test/jasmine/vendor/jasmine_favicon.png -------------------------------------------------------------------------------- /app/styles/index.css: -------------------------------------------------------------------------------- 1 | /*-- HTML5 Boilerplate. -----------------------------------------------------*/ 2 | 3 | @import "../../vendor/h5bp/css/normalize.css"; 4 | @import "../../vendor/h5bp/css/main.css"; 5 | 6 | /*-- Application stylesheets. -----------------------------------------------*/ 7 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | define([ 2 | // Application. 3 | "app" 4 | ], 5 | 6 | function(app) { 7 | 8 | // Defining the application router, you can attach sub routers here. 9 | var Router = Backbone.Router.extend({ 10 | routes: { 11 | "": "index" 12 | }, 13 | 14 | index: function() { 15 | 16 | } 17 | }); 18 | 19 | return Router; 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone-boilerplate", 3 | "version": "0.1.0", 4 | 5 | "dependencies": { 6 | "grunt": "~0.4", 7 | "grunt-bbb-server": "0.1.0-alpha" 8 | }, 9 | 10 | "jam": { 11 | "packageDir": "./vendor/jam", 12 | "baseUrl": "./app", 13 | 14 | "dependencies": { 15 | "backbone": "*", 16 | "jquery": "*", 17 | "lodash": "*", 18 | "backbone.layoutmanager": "*" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vendor/jam/underscore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "underscore", 3 | "description" : "JavaScript's functional programming helper library.", 4 | "homepage" : "http://underscorejs.org", 5 | "keywords" : ["util", "functional", "server", "client", "browser"], 6 | "author" : "Jeremy Ashkenas ", 7 | "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"}, 8 | "main" : "underscore.js", 9 | "version" : "1.4.3", 10 | "jam": { 11 | "main": "underscore.js", 12 | "include": ["underscore.js"], 13 | "shim": { 14 | "exports": "_" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | // Set the require.js configuration for your application. 2 | require.config({ 3 | 4 | // Initialize the application with the main application file and the JamJS 5 | // generated configuration file. 6 | deps: ["../vendor/jam/require.config", "main"], 7 | 8 | paths: { 9 | // Use the underscore build of Lo-Dash to minimize incompatibilities. 10 | "lodash": "../vendor/jam/lodash/dist/lodash.underscore" 11 | 12 | // Put additional paths here. 13 | }, 14 | 15 | map: { 16 | // Ensure Lo-Dash is used instead of underscore. 17 | "*": { "underscore": "lodash" } 18 | 19 | // Put additional maps here. 20 | }, 21 | 22 | shim: { 23 | // Put shims here. 24 | } 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /vendor/jam/jquery/src/sizzle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sizzle", 3 | "title": "Sizzle", 4 | "description": "A pure-JavaScript, bottom-up CSS selector engine designed to be easily dropped in to a host library.", 5 | "version": "1.8.2pre", 6 | "homepage": "http://sizzlejs.com", 7 | "author": { 8 | "name": "jQuery Foundation and other contributors", 9 | "url": "https://github.com/jquery/sizzle/blob/master/AUTHORS.txt" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jquery/sizzle.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/jquery/sizzle/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt" 22 | } 23 | ], 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "grunt-git-authors": ">=1.0.0", 27 | "grunt": "~0.3.17", 28 | "testswarm": "0.2.2" 29 | }, 30 | "keywords": [ "sizzle", "selector", "jquery" ] 31 | } 32 | -------------------------------------------------------------------------------- /test/qunit/tests/example.js: -------------------------------------------------------------------------------- 1 | test("one tautology", function() { 2 | ok(true); 3 | }); 4 | 5 | module("simple tests"); 6 | 7 | test("increments", function() { 8 | var mike = 0; 9 | 10 | ok(mike++ === 0); 11 | ok(mike === 1); 12 | }); 13 | 14 | test("increments (improved)", function() { 15 | var mike = 0; 16 | 17 | equal(mike++, 0); 18 | equal(mike, 1); 19 | }); 20 | 21 | 22 | module("setUp/tearDown", { 23 | setup: function() { 24 | //console.log("Before"); 25 | }, 26 | 27 | teardown: function() { 28 | //console.log("After"); 29 | } 30 | }); 31 | 32 | test("example", function() { 33 | //console.log("During"); 34 | }); 35 | 36 | module("async"); 37 | 38 | test("multiple async", function() { 39 | expect(2); 40 | 41 | stop(); 42 | 43 | setTimeout( function( ) { 44 | ok(true, "async operation completed"); 45 | start(); 46 | }, 500); 47 | 48 | stop(); 49 | 50 | setTimeout(function() { 51 | ok(true, "async operation completed"); 52 | start(); 53 | }, 500); 54 | }); 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tim Branyen (@tbranyen) 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /vendor/jam/backbone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "backbone", 3 | "description" : "Give your JS App some Backbone with Models, Views, Collections, and Events.", 4 | "url" : "http://backbonejs.org", 5 | "keywords" : ["model", "view", "controller", "router", "server", "client", "browser"], 6 | "author" : "Jeremy Ashkenas ", 7 | "dependencies" : { 8 | "underscore" : ">=1.4.3" 9 | }, 10 | "devDependencies": { 11 | "phantomjs": "1.8.1-3", 12 | "docco": "0.6.1", 13 | "coffee-script": "1.6.1" 14 | }, 15 | "scripts": { 16 | "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true && coffee test/model.coffee" 17 | }, 18 | "main" : "backbone.js", 19 | "version" : "1.0.0", 20 | "jam": { 21 | "main": "backbone.js", 22 | "include": ["backbone.js"], 23 | "dependencies": { 24 | "underscore": "~1.4", 25 | "jquery": ">=1.6" 26 | }, 27 | "shim": { 28 | "deps": ["underscore", "jquery"], 29 | "exports": "Backbone" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vendor/jam/jquery/test/qunit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qunitjs", 3 | "title": "QUnit", 4 | "description": "An easy-to-use JavaScript Unit Testing framework.", 5 | "version": "1.11.0", 6 | "author": { 7 | "name": "jQuery Foundation and other contributors", 8 | "url": "https://github.com/jquery/qunit/blob/master/AUTHORS.txt" 9 | }, 10 | "contributors": [ 11 | "John Resig (http://ejohn.org/)", 12 | "Jörn Zaefferer (http://bassistance.de/)" 13 | ], 14 | "homepage": "http://qunitjs.com", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/jquery/qunit.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/jquery/qunit/issues" 21 | }, 22 | "license": { 23 | "name": "MIT", 24 | "url": "http://www.opensource.org/licenses/mit-license.php" 25 | }, 26 | "keywords": [ 27 | "testing", 28 | "unit", 29 | "jquery" 30 | ], 31 | "main": "qunit/qunit.js", 32 | "devDependencies": { 33 | "grunt": "0.3.x", 34 | "grunt-git-authors": "1.0.0", 35 | "testswarm": "1.0.0-alpha" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/jasmine/vendor/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone Boilerplate 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /vendor/jam/jquery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "title": "jQuery", 4 | "description": "JavaScript library for DOM operations", 5 | "version": "1.9.1", 6 | "homepage": "http://jquery.com", 7 | "author": { 8 | "name": "jQuery Foundation and other contributors", 9 | "url": "https://github.com/jquery/jquery/blob/master/AUTHORS.txt" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jquery/jquery.git" 14 | }, 15 | "bugs": { 16 | "url": "http://bugs.jquery.com" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt" 22 | } 23 | ], 24 | "scripts": { 25 | "test": "./node_modules/.bin/grunt" 26 | }, 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "grunt-compare-size": "~0.3.0", 30 | "grunt-git-authors": "~1.1.0", 31 | "grunt-update-submodules": "~0.2.0", 32 | "grunt-contrib-watch": "~0.1.1", 33 | "grunt-contrib-jshint": "~0.1.1", 34 | "grunt-contrib-uglify": "~0.1.1", 35 | "grunt": "~0.4.0", 36 | "testswarm": "0.2.2" 37 | }, 38 | "keywords": [], 39 | "jam": { 40 | "include": ["dist/jquery.js"], 41 | "main": "dist/jquery.js" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/qunit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone Boilerplate QUnit Test Suite 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

Backbone Boilerplate QUnit Test Suite

18 |

19 |

20 |
    21 |
    22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/jasmine/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone Boilerplate Jasmine Test Suite 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /vendor/jam/lodash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash", 3 | "version": "1.0.1", 4 | "description": "An alternative to Underscore.js, delivering consistency, customization, performance, and extra features.", 5 | "homepage": "http://lodash.com", 6 | "main": "./dist/lodash", 7 | "keywords": [ 8 | "browser", 9 | "client", 10 | "functional", 11 | "performance", 12 | "server", 13 | "speed", 14 | "util" 15 | ], 16 | "licenses": [ 17 | { 18 | "type": "MIT", 19 | "url": "http://lodash.com/license" 20 | } 21 | ], 22 | "author": { 23 | "name": "John-David Dalton", 24 | "email": "john.david.dalton@gmail.com", 25 | "web": "http://allyoucanleet.com/" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/bestiejs/lodash/issues" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/bestiejs/lodash.git" 33 | }, 34 | "bin": { 35 | "lodash": "./build.js" 36 | }, 37 | "directories": { 38 | "doc": "./doc", 39 | "test": "./test" 40 | }, 41 | "engines": [ 42 | "node", 43 | "rhino" 44 | ], 45 | "jam": { 46 | "main": "./lodash.js" 47 | }, 48 | "scripts": { 49 | "build": "node ./build.js", 50 | "test": "node ./test/test.js ./dist/lodash.js && node ./test/test.js ./dist/lodash.min.js && node ./test/test-build.js" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/jam/backbone.layoutmanager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Tim Branyen (@tbranyen)", 3 | "name": "backbone.layoutmanager", 4 | "description": "A layout and template manager for Backbone.js applications.", 5 | "version": "0.8.6", 6 | "homepage": "http://tbranyen.github.com/backbone.layoutmanager/", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/tbranyen/backbone.layoutmanager.git" 10 | }, 11 | "main": "node/index", 12 | "engines": { 13 | "node": ">=0.8" 14 | }, 15 | "dependencies": { 16 | "backbone": "~1.0", 17 | "underscore.deferred": "~0.4", 18 | "cheerio": "~0.10.7", 19 | "underscore": "~1.4" 20 | }, 21 | "devDependencies": { 22 | "grunt": "~0.4", 23 | "grunt-cli": "~0.1", 24 | "grunt-contrib-jshint": "0.1.1rc5", 25 | "grunt-contrib-qunit": "0.1.1rc5", 26 | "grunt-nodequnit": "~0.1" 27 | }, 28 | "scripts": { 29 | "test": "grunt" 30 | }, 31 | "jam": { 32 | "dependencies": { 33 | "underscore": "~1.4", 34 | "backbone": "~1.0", 35 | "jquery": ">=1.6" 36 | }, 37 | "include": [ 38 | "backbone.layoutmanager.js" 39 | ], 40 | "main": "backbone.layoutmanager.js", 41 | "shim": { 42 | "deps": [ 43 | "jquery", 44 | "backbone", 45 | "underscore" 46 | ], 47 | "exports": "Backbone.Layout" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/jam/jquery/src/sizzle/speed/benchmark.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "version": "1.0.0", 4 | "description": "A benchmarking library that works on nearly all JavaScript platforms, supports high-resolution timers, and returns statistically significant results.", 5 | "homepage": "http://benchmarkjs.com/", 6 | "main": "benchmark", 7 | "keywords": [ 8 | "benchmark", 9 | "narwhal", 10 | "node", 11 | "performance", 12 | "ringo", 13 | "speed" 14 | ], 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "http://mths.be/mit" 19 | } 20 | ], 21 | "author": { 22 | "name": "Mathias Bynens", 23 | "email": "mathias@benchmarkjs.com", 24 | "web": "http://mathiasbynens.be/" 25 | }, 26 | "maintainers": [ 27 | { 28 | "name": "John-David Dalton", 29 | "email": "john.david.dalton@gmail.com", 30 | "web": "http://allyoucanleet.com/" 31 | }, 32 | { 33 | "name": "Mathias Bynens", 34 | "email": "mathias@benchmarkjs.com", 35 | "web": "http://mathiasbynens.be/" 36 | } 37 | ], 38 | "bugs": { 39 | "email": "bugs@benchmarkjs.com", 40 | "url": "https://github.com/bestiejs/benchmark.js/issues" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/bestiejs/benchmark.js.git" 45 | }, 46 | "engines": [ 47 | "node", 48 | "rhino" 49 | ], 50 | "directories": { 51 | "doc": "./doc", 52 | "test": "./test" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | require([ 2 | // Application. 3 | "app", 4 | 5 | // Main Router. 6 | "router" 7 | ], 8 | 9 | function(app, Router) { 10 | 11 | // Define your master router on the application namespace and trigger all 12 | // navigation from this instance. 13 | app.router = new Router(); 14 | 15 | // Trigger the initial route and enable HTML5 History API support, set the 16 | // root folder to '/' by default. Change in app.js. 17 | Backbone.history.start({ pushState: true, root: app.root }); 18 | 19 | // All navigation that is relative should be passed through the navigate 20 | // method, to be processed by the router. If the link has a `data-bypass` 21 | // attribute, bypass the delegation completely. 22 | $(document).on("click", "a[href]:not([data-bypass])", function(evt) { 23 | // Get the absolute anchor href. 24 | var href = { prop: $(this).prop("href"), attr: $(this).attr("href") }; 25 | // Get the absolute root. 26 | var root = location.protocol + "//" + location.host + app.root; 27 | 28 | // Ensure the root is part of the anchor href, meaning it's relative. 29 | if (href.prop.slice(0, root.length) === root) { 30 | // Stop the default event to ensure the link will not cause a page 31 | // refresh. 32 | evt.preventDefault(); 33 | 34 | // `Backbone.history.navigate` is sufficient for all Routers and will 35 | // trigger the correct events. The Router's internal `navigate` method 36 | // calls this anyways. The fragment is sliced from the root. 37 | Backbone.history.navigate(href.attr, true); 38 | } 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/jasmine/spec/example.spec.js: -------------------------------------------------------------------------------- 1 | describe("one tautology", function() { 2 | it("is a tautology", function() { 3 | expect(true).toBeTruthy(); 4 | }); 5 | 6 | describe("is awesome", function() { 7 | it("is awesome", function() { 8 | expect(1).toBe(1); 9 | }); 10 | }); 11 | }); 12 | 13 | describe("simple tests", function() { 14 | it("increments", function() { 15 | var mike = 0; 16 | 17 | expect(mike++ === 0).toBeTruthy(); 18 | expect(mike === 1).toBeTruthy(); 19 | }); 20 | 21 | it("increments (improved)", function() { 22 | var mike = 0; 23 | 24 | expect(mike++).toBe(0); 25 | expect(mike).toBe(1); 26 | }); 27 | }); 28 | 29 | describe("setUp/tearDown", function() { 30 | beforeEach(function() { 31 | // console.log("Before"); 32 | }); 33 | 34 | afterEach(function() { 35 | // console.log("After"); 36 | }); 37 | 38 | it("example", function() { 39 | // console.log("During"); 40 | }); 41 | 42 | describe("setUp/tearDown", function() { 43 | beforeEach(function() { 44 | // console.log("Before2"); 45 | }); 46 | 47 | afterEach(function() { 48 | // console.log("After2"); 49 | }); 50 | 51 | it("example", function() { 52 | // console.log("During Nested"); 53 | }); 54 | }); 55 | }); 56 | 57 | describe("async", function() { 58 | it("multiple async", function() { 59 | var semaphore = 2; 60 | 61 | setTimeout(function() { 62 | expect(true).toBeTruthy(); 63 | semaphore--; 64 | }, 500); 65 | 66 | setTimeout(function() { 67 | expect(true).toBeTruthy(); 68 | semaphore--; 69 | }, 500); 70 | 71 | waitsFor(function() { return semaphore === 0 }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Boilerplate](https://github.com/tbranyen/backbone-boilerplate/raw/assets/header.png) 2 | 3 | Backbone Boilerplate 4 | ==================== 5 | 6 | This boilerplate is the product of much research and frustration. Existing 7 | boilerplates freely modify Backbone core, lack a build process, and are very 8 | prescriptive; Backbone Boilerplate changes that. 9 | 10 | Organize your application with a logical file structure, develop your 11 | Models/Collections/Views/Routers inside modules, and build knowing you have 12 | efficient code that will not bottleneck your users. 13 | 14 | Thanks to our 15 | [Contributors](https://github.com/tbranyen/backbone-boilerplate/contributors)! 16 | 17 | Special Thanks to: [cowboy](http://github.com/cowboy), 18 | [iros](http://github.com/iros), [nimbupani](http://github.com/nimbupani), 19 | [wookiehangover](http://github.com/wookiehangover), and 20 | [jugglinmike](http://github.com/jugglinmike) for helping me create this project. 21 | 22 | Extra Special Thanks to: [Paul Guinan](http://bigredhair.com/work/paul.html) 23 | for giving me usage rights to his fantastic Boilerplate character. 24 | 25 | ## Getting started ## 26 | 27 | The easiest way to get started is to install Git and clone the repository: 28 | 29 | ``` bash 30 | # Create a new project directory and enter it. 31 | mkdir myproject && cd myproject 32 | 33 | # Using Git, fetch only the last few commits. You don't need the full history 34 | # for your project. 35 | git clone --q --depth 0 git@github.com:tbranyen/backbone-boilerplate.git . 36 | ``` 37 | 38 | ## Documentation ## 39 | 40 | View the Backbone Boilerplate documentation here: 41 | 42 | [GitHub Wiki](https://github.com/tbranyen/backbone-boilerplate/wiki) 43 | 44 | ## Build process ## 45 | 46 | To use the new and improved build process, please visit the 47 | [grunt-bbb](https://github.com/backbone-boilerplate/grunt-bbb) 48 | plugin repo and follow the instructions to install. Basing your project off 49 | this repo will allow the `bbb` commands to work out-of-the-box. 50 | 51 | 52 | ## License 53 | Copyright (c) 2013 Tim Branyen (@tbranyen) 54 | Licensed under the MIT license. 55 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "backbone.layoutmanager" 3 | 4 | // Include additional libraries installed with JamJS or placed in the 5 | // `vendor/js` directory, here. 6 | ], 7 | 8 | function(LayoutManager) { 9 | 10 | // Provide a global location to place configuration settings and module 11 | // creation. 12 | var app = { 13 | // The root path to run the application. 14 | root: "/" 15 | }; 16 | 17 | // Localize or create a new JavaScript Template object. 18 | var JST = window.JST = window.JST || {}; 19 | 20 | // Configure LayoutManager with Backbone Boilerplate defaults. 21 | LayoutManager.configure({ 22 | // Allow LayoutManager to augment Backbone.View.prototype. 23 | manage: true, 24 | 25 | prefix: "app/templates/", 26 | 27 | fetch: function(path) { 28 | // Concatenate the file extension. 29 | path = path + ".html"; 30 | 31 | // If cached, use the compiled template. 32 | if (JST[path]) { 33 | return JST[path]; 34 | } 35 | 36 | // Put fetch into `async-mode`. 37 | var done = this.async(); 38 | 39 | // Seek out the template asynchronously. 40 | $.get(app.root + path, function(contents) { 41 | done(_.template(contents)); 42 | }, "text"); 43 | } 44 | }); 45 | 46 | // Mix Backbone.Events, modules, and layout management into the app object. 47 | return _.extend(app, { 48 | // Create a custom object with a nested Views object. 49 | module: function(additionalProps) { 50 | return _.extend({ Views: {} }, additionalProps); 51 | }, 52 | 53 | // Helper for using layouts. 54 | useLayout: function(name, options) { 55 | // Enable variable arity by allowing the first argument to be the options 56 | // object and omitting the name argument. 57 | if (_.isObject(name)) { 58 | options = name; 59 | } 60 | 61 | // Ensure options is an object. 62 | options = options || {}; 63 | 64 | // If a name property was specified use that as the template. 65 | if (_.isString(name)) { 66 | options.template = name; 67 | } 68 | 69 | // Check if a layout already exists, if so, update the template. 70 | if (this.layout) { 71 | this.layout.template = options.template; 72 | } else { 73 | // Create a new Layout with options. 74 | this.layout = new Backbone.Layout(_.extend({ 75 | el: "main" 76 | }, options)); 77 | } 78 | 79 | // Cache the reference. 80 | return this.layout; 81 | } 82 | }, Backbone.Events); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Backbone Boilerplate ## 2 | 3 | ### Filing an issue ### 4 | 5 | You're using Backbone Boilerplate and something doesn't appear to function the 6 | way it's described or you're thinking something could be done better. Please 7 | let us know. Issues are how the project moves forward; by letting us know 8 | what's bothering you. 9 | 10 | * Search the [issues 11 | list](https://github.com/tbranyen/backbone-boilerplate/issues) 12 | for existing similar issues. Consider adding to an existing issue if you 13 | find one. 14 | * Choose an applicable title. This will be used for a corresponding unit test. 15 | * Provide a unit test or snippet that illustrates the problem. This small 16 | gesture goes a long way in a speedy patch. 17 | 18 | ### Sending a pull request ### 19 | 20 | If you feel confident that you can correct a bug or add a new feature, feel 21 | free to submit a pull request with your changes. Everyone helping makes the 22 | project community instead of a sole proprietorship. 23 | 24 | * Link to an existing issue if applicable, create one to start discussion to 25 | ensure everyone is on board with the change and so you don't spend time 26 | writing a fantastic patch we cannot accept. 27 | * Provide a description of what the patch is/does. 28 | 29 | ### Code style ### 30 | 31 | I am very sensitive to maintaining a holistic codebase and that includes a 32 | single code style. Pull requests may be rejected or modified to ensure this 33 | code style is maintained. 34 | 35 | * Follow close to [BSD KNF 36 | style](http://en.wikipedia.org/wiki/Indent_style#BSD_KNF_style). 37 | * Use two space, expanded/soft tabs. Use `\t` if you need a tab character in a 38 | string. 39 | * No trailing whitespace, except in markdown files where a linebreak must be 40 | forced. 41 | * Don't go overboard with the whitespace. 42 | * No more than one assignment per var statement. 43 | * Delimit strings with double-quotes ", not single-quotes '. 44 | * Comments must always contain proper punctuation and end with a correct 45 | sentence terminator. Put before the line of code, not at the end of the 46 | line. 47 | * Prefer if and else to "clever" uses of ? : conditional or ||, && logical 48 | operators. 49 | * When in doubt, follow the conventions you see used in the source already. 50 | 51 | ### Documentation ### 52 | 53 | We strive to make the project as easy to consume and use as possible which 54 | means having consistent and up-to-date documentation. If you notice something 55 | is outdated or incorrect, please file an issue, pull request, or make the 56 | change yourself in the Wiki. 57 | 58 | * Add documentation to the 59 | [Wiki](https://github.com/tbranyen/backbone-boilerplate/wiki) if 60 | applicable. 61 | * Release notes are added to the 62 | [readme.md](https://github.com/tbranyen/backbone-boilerplate/blob/master/readme.md) 63 | if applicable. 64 | -------------------------------------------------------------------------------- /test/jasmine/vendor/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /vendor/jam/require.config.js: -------------------------------------------------------------------------------- 1 | var jam = { 2 | "packages": [ 3 | { 4 | "name": "backbone", 5 | "location": "../vendor/jam/backbone", 6 | "main": "backbone.js" 7 | }, 8 | { 9 | "name": "backbone.layoutmanager", 10 | "location": "../vendor/jam/backbone.layoutmanager", 11 | "main": "backbone.layoutmanager.js" 12 | }, 13 | { 14 | "name": "jquery", 15 | "location": "../vendor/jam/jquery", 16 | "main": "dist/jquery.js" 17 | }, 18 | { 19 | "name": "lodash", 20 | "location": "../vendor/jam/lodash", 21 | "main": "./lodash.js" 22 | }, 23 | { 24 | "name": "underscore", 25 | "location": "../vendor/jam/underscore", 26 | "main": "underscore.js" 27 | } 28 | ], 29 | "version": "0.2.15", 30 | "shim": { 31 | "backbone": { 32 | "deps": [ 33 | "underscore", 34 | "jquery" 35 | ], 36 | "exports": "Backbone" 37 | }, 38 | "backbone.layoutmanager": { 39 | "deps": [ 40 | "jquery", 41 | "backbone", 42 | "underscore" 43 | ], 44 | "exports": "Backbone.Layout" 45 | }, 46 | "underscore": { 47 | "exports": "_" 48 | } 49 | } 50 | }; 51 | 52 | if (typeof require !== "undefined" && require.config) { 53 | require.config({ 54 | "packages": [ 55 | { 56 | "name": "backbone", 57 | "location": "../vendor/jam/backbone", 58 | "main": "backbone.js" 59 | }, 60 | { 61 | "name": "backbone.layoutmanager", 62 | "location": "../vendor/jam/backbone.layoutmanager", 63 | "main": "backbone.layoutmanager.js" 64 | }, 65 | { 66 | "name": "jquery", 67 | "location": "../vendor/jam/jquery", 68 | "main": "dist/jquery.js" 69 | }, 70 | { 71 | "name": "lodash", 72 | "location": "../vendor/jam/lodash", 73 | "main": "./lodash.js" 74 | }, 75 | { 76 | "name": "underscore", 77 | "location": "../vendor/jam/underscore", 78 | "main": "underscore.js" 79 | } 80 | ], 81 | "shim": { 82 | "backbone": { 83 | "deps": [ 84 | "underscore", 85 | "jquery" 86 | ], 87 | "exports": "Backbone" 88 | }, 89 | "backbone.layoutmanager": { 90 | "deps": [ 91 | "jquery", 92 | "backbone", 93 | "underscore" 94 | ], 95 | "exports": "Backbone.Layout" 96 | }, 97 | "underscore": { 98 | "exports": "_" 99 | } 100 | } 101 | }); 102 | } 103 | else { 104 | var require = { 105 | "packages": [ 106 | { 107 | "name": "backbone", 108 | "location": "../vendor/jam/backbone", 109 | "main": "backbone.js" 110 | }, 111 | { 112 | "name": "backbone.layoutmanager", 113 | "location": "../vendor/jam/backbone.layoutmanager", 114 | "main": "backbone.layoutmanager.js" 115 | }, 116 | { 117 | "name": "jquery", 118 | "location": "../vendor/jam/jquery", 119 | "main": "dist/jquery.js" 120 | }, 121 | { 122 | "name": "lodash", 123 | "location": "../vendor/jam/lodash", 124 | "main": "./lodash.js" 125 | }, 126 | { 127 | "name": "underscore", 128 | "location": "../vendor/jam/underscore", 129 | "main": "underscore.js" 130 | } 131 | ], 132 | "shim": { 133 | "backbone": { 134 | "deps": [ 135 | "underscore", 136 | "jquery" 137 | ], 138 | "exports": "Backbone" 139 | }, 140 | "backbone.layoutmanager": { 141 | "deps": [ 142 | "jquery", 143 | "backbone", 144 | "underscore" 145 | ], 146 | "exports": "Backbone.Layout" 147 | }, 148 | "underscore": { 149 | "exports": "_" 150 | } 151 | } 152 | }; 153 | } 154 | 155 | if (typeof exports !== "undefined" && typeof module !== "undefined") { 156 | module.exports = jam; 157 | } -------------------------------------------------------------------------------- /test/qunit/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit 1.2.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Mon Oct 31 14:00:02 UTC 2011 10 | * Last Commit: ee156923cdb01820e35e6bb579d5cf6bf55736d4 11 | */ 12 | 13 | /** Font Family and Sizes */ 14 | 15 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 16 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 20 | #qunit-tests { font-size: smaller; } 21 | 22 | 23 | /** Resets */ 24 | 25 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | 31 | /** Header */ 32 | 33 | #qunit-header { 34 | padding: 0.5em 0 0.5em 1em; 35 | 36 | color: #8699a4; 37 | background-color: #0d3349; 38 | 39 | font-size: 1.5em; 40 | line-height: 1em; 41 | font-weight: normal; 42 | 43 | border-radius: 15px 15px 0 0; 44 | -moz-border-radius: 15px 15px 0 0; 45 | -webkit-border-top-right-radius: 15px; 46 | -webkit-border-top-left-radius: 15px; 47 | } 48 | 49 | #qunit-header a { 50 | text-decoration: none; 51 | color: #c2ccd1; 52 | } 53 | 54 | #qunit-header a:hover, 55 | #qunit-header a:focus { 56 | color: #fff; 57 | } 58 | 59 | #qunit-banner { 60 | height: 5px; 61 | } 62 | 63 | #qunit-testrunner-toolbar { 64 | padding: 0.5em 0 0.5em 2em; 65 | color: #5E740B; 66 | background-color: #eee; 67 | } 68 | 69 | #qunit-userAgent { 70 | padding: 0.5em 0 0.5em 2.5em; 71 | background-color: #2b81af; 72 | color: #fff; 73 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 74 | } 75 | 76 | 77 | /** Tests: Pass/Fail */ 78 | 79 | #qunit-tests { 80 | list-style-position: inside; 81 | } 82 | 83 | #qunit-tests li { 84 | padding: 0.4em 0.5em 0.4em 2.5em; 85 | border-bottom: 1px solid #fff; 86 | list-style-position: inside; 87 | } 88 | 89 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 90 | display: none; 91 | } 92 | 93 | #qunit-tests li strong { 94 | cursor: pointer; 95 | } 96 | 97 | #qunit-tests li a { 98 | padding: 0.5em; 99 | color: #c2ccd1; 100 | text-decoration: none; 101 | } 102 | #qunit-tests li a:hover, 103 | #qunit-tests li a:focus { 104 | color: #000; 105 | } 106 | 107 | #qunit-tests ol { 108 | margin-top: 0.5em; 109 | padding: 0.5em; 110 | 111 | background-color: #fff; 112 | 113 | border-radius: 15px; 114 | -moz-border-radius: 15px; 115 | -webkit-border-radius: 15px; 116 | 117 | box-shadow: inset 0px 2px 13px #999; 118 | -moz-box-shadow: inset 0px 2px 13px #999; 119 | -webkit-box-shadow: inset 0px 2px 13px #999; 120 | } 121 | 122 | #qunit-tests table { 123 | border-collapse: collapse; 124 | margin-top: .2em; 125 | } 126 | 127 | #qunit-tests th { 128 | text-align: right; 129 | vertical-align: top; 130 | padding: 0 .5em 0 0; 131 | } 132 | 133 | #qunit-tests td { 134 | vertical-align: top; 135 | } 136 | 137 | #qunit-tests pre { 138 | margin: 0; 139 | white-space: pre-wrap; 140 | word-wrap: break-word; 141 | } 142 | 143 | #qunit-tests del { 144 | background-color: #e0f2be; 145 | color: #374e0c; 146 | text-decoration: none; 147 | } 148 | 149 | #qunit-tests ins { 150 | background-color: #ffcaca; 151 | color: #500; 152 | text-decoration: none; 153 | } 154 | 155 | /*** Test Counts */ 156 | 157 | #qunit-tests b.counts { color: black; } 158 | #qunit-tests b.passed { color: #5E740B; } 159 | #qunit-tests b.failed { color: #710909; } 160 | 161 | #qunit-tests li li { 162 | margin: 0.5em; 163 | padding: 0.4em 0.5em 0.4em 0.5em; 164 | background-color: #fff; 165 | border-bottom: none; 166 | list-style-position: inside; 167 | } 168 | 169 | /*** Passing Styles */ 170 | 171 | #qunit-tests li li.pass { 172 | color: #5E740B; 173 | background-color: #fff; 174 | border-left: 26px solid #C6E746; 175 | } 176 | 177 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 178 | #qunit-tests .pass .test-name { color: #366097; } 179 | 180 | #qunit-tests .pass .test-actual, 181 | #qunit-tests .pass .test-expected { color: #999999; } 182 | 183 | #qunit-banner.qunit-pass { background-color: #C6E746; } 184 | 185 | /*** Failing Styles */ 186 | 187 | #qunit-tests li li.fail { 188 | color: #710909; 189 | background-color: #fff; 190 | border-left: 26px solid #EE5757; 191 | white-space: pre; 192 | } 193 | 194 | #qunit-tests > li:last-child { 195 | border-radius: 0 0 15px 15px; 196 | -moz-border-radius: 0 0 15px 15px; 197 | -webkit-border-bottom-right-radius: 15px; 198 | -webkit-border-bottom-left-radius: 15px; 199 | } 200 | 201 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 202 | #qunit-tests .fail .test-name, 203 | #qunit-tests .fail .module-name { color: #000000; } 204 | 205 | #qunit-tests .fail .test-actual { color: #EE5757; } 206 | #qunit-tests .fail .test-expected { color: green; } 207 | 208 | #qunit-banner.qunit-fail { background-color: #EE5757; } 209 | 210 | 211 | /** Result */ 212 | 213 | #qunit-testresult { 214 | padding: 0.5em 0.5em 0.5em 2.5em; 215 | 216 | color: #2b81af; 217 | background-color: #D2E0E6; 218 | 219 | border-bottom: 1px solid white; 220 | } 221 | 222 | /** Fixture */ 223 | 224 | #qunit-fixture { 225 | position: absolute; 226 | top: -10000px; 227 | left: -10000px; 228 | } 229 | -------------------------------------------------------------------------------- /vendor/h5bp/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | body { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | /* 27 | * Remove text-shadow in selection highlight: h5bp.com/i 28 | * These selection declarations have to be separate. 29 | * Customize the background color to match your design. 30 | */ 31 | 32 | ::-moz-selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | ::selection { 38 | background: #b3d4fc; 39 | text-shadow: none; 40 | } 41 | 42 | /* 43 | * A better looking default horizontal rule 44 | */ 45 | 46 | hr { 47 | display: block; 48 | height: 1px; 49 | border: 0; 50 | border-top: 1px solid #ccc; 51 | margin: 1em 0; 52 | padding: 0; 53 | } 54 | 55 | /* 56 | * Remove the gap between images and the bottom of their containers: h5bp.com/i/440 57 | */ 58 | 59 | img { 60 | vertical-align: middle; 61 | } 62 | 63 | /* 64 | * Remove default fieldset styles. 65 | */ 66 | 67 | fieldset { 68 | border: 0; 69 | margin: 0; 70 | padding: 0; 71 | } 72 | 73 | /* 74 | * Allow only vertical resizing of textareas. 75 | */ 76 | 77 | textarea { 78 | resize: vertical; 79 | } 80 | 81 | /* ========================================================================== 82 | Chrome Frame prompt 83 | ========================================================================== */ 84 | 85 | .chromeframe { 86 | margin: 0.2em 0; 87 | background: #ccc; 88 | color: #000; 89 | padding: 0.2em 0; 90 | } 91 | 92 | /* ========================================================================== 93 | Author's custom styles 94 | ========================================================================== */ 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | /* ========================================================================== 113 | Helper classes 114 | ========================================================================== */ 115 | 116 | /* 117 | * Image replacement 118 | */ 119 | 120 | .ir { 121 | background-color: transparent; 122 | border: 0; 123 | overflow: hidden; 124 | /* IE 6/7 fallback */ 125 | *text-indent: -9999px; 126 | } 127 | 128 | .ir:before { 129 | content: ""; 130 | display: block; 131 | width: 0; 132 | height: 100%; 133 | } 134 | 135 | /* 136 | * Hide from both screenreaders and browsers: h5bp.com/u 137 | */ 138 | 139 | .hidden { 140 | display: none !important; 141 | visibility: hidden; 142 | } 143 | 144 | /* 145 | * Hide only visually, but have it available for screenreaders: h5bp.com/v 146 | */ 147 | 148 | .visuallyhidden { 149 | border: 0; 150 | clip: rect(0 0 0 0); 151 | height: 1px; 152 | margin: -1px; 153 | overflow: hidden; 154 | padding: 0; 155 | position: absolute; 156 | width: 1px; 157 | } 158 | 159 | /* 160 | * Extends the .visuallyhidden class to allow the element to be focusable 161 | * when navigated to via the keyboard: h5bp.com/p 162 | */ 163 | 164 | .visuallyhidden.focusable:active, 165 | .visuallyhidden.focusable:focus { 166 | clip: auto; 167 | height: auto; 168 | margin: 0; 169 | overflow: visible; 170 | position: static; 171 | width: auto; 172 | } 173 | 174 | /* 175 | * Hide visually and from screenreaders, but maintain layout 176 | */ 177 | 178 | .invisible { 179 | visibility: hidden; 180 | } 181 | 182 | /* 183 | * Clearfix: contain floats 184 | * 185 | * For modern browsers 186 | * 1. The space content is one way to avoid an Opera bug when the 187 | * `contenteditable` attribute is included anywhere else in the document. 188 | * Otherwise it causes space to appear at the top and bottom of elements 189 | * that receive the `clearfix` class. 190 | * 2. The use of `table` rather than `block` is only necessary if using 191 | * `:before` to contain the top-margins of child elements. 192 | */ 193 | 194 | .clearfix:before, 195 | .clearfix:after { 196 | content: " "; /* 1 */ 197 | display: table; /* 2 */ 198 | } 199 | 200 | .clearfix:after { 201 | clear: both; 202 | } 203 | 204 | /* 205 | * For IE 6/7 only 206 | * Include this rule to trigger hasLayout and contain floats. 207 | */ 208 | 209 | .clearfix { 210 | *zoom: 1; 211 | } 212 | 213 | /* ========================================================================== 214 | EXAMPLE Media Queries for Responsive Design. 215 | Theses examples override the primary ('mobile first') styles. 216 | Modify as content requires. 217 | ========================================================================== */ 218 | 219 | @media only screen and (min-width: 35em) { 220 | /* Style adjustments for viewports that meet the condition */ 221 | } 222 | 223 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 224 | only screen and (min-resolution: 144dpi) { 225 | /* Style adjustments for high resolution devices */ 226 | } 227 | 228 | /* ========================================================================== 229 | Print styles. 230 | Inlined to avoid required HTTP connection: h5bp.com/r 231 | ========================================================================== */ 232 | 233 | @media print { 234 | * { 235 | background: transparent !important; 236 | color: #000 !important; /* Black prints faster: h5bp.com/s */ 237 | box-shadow: none !important; 238 | text-shadow: none !important; 239 | } 240 | 241 | a, 242 | a:visited { 243 | text-decoration: underline; 244 | } 245 | 246 | a[href]:after { 247 | content: " (" attr(href) ")"; 248 | } 249 | 250 | abbr[title]:after { 251 | content: " (" attr(title) ")"; 252 | } 253 | 254 | /* 255 | * Don't show links for images, or javascript/internal links 256 | */ 257 | 258 | .ir a:after, 259 | a[href^="javascript:"]:after, 260 | a[href^="#"]:after { 261 | content: ""; 262 | } 263 | 264 | pre, 265 | blockquote { 266 | border: 1px solid #999; 267 | page-break-inside: avoid; 268 | } 269 | 270 | thead { 271 | display: table-header-group; /* h5bp.com/t */ 272 | } 273 | 274 | tr, 275 | img { 276 | page-break-inside: avoid; 277 | } 278 | 279 | img { 280 | max-width: 100% !important; 281 | } 282 | 283 | @page { 284 | margin: 0.5cm; 285 | } 286 | 287 | p, 288 | h2, 289 | h3 { 290 | orphans: 3; 291 | widows: 3; 292 | } 293 | 294 | h2, 295 | h3 { 296 | page-break-after: avoid; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /test/jasmine/vendor/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('span', { className: 'title' }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | // This is the main application configuration file. It is a Grunt 2 | // configuration file, which you can learn more about here: 3 | // https://github.com/cowboy/grunt/blob/master/docs/configuring.md 4 | module.exports = function(grunt) { 5 | 6 | grunt.initConfig({ 7 | 8 | // The lint task will run the build configuration and the application 9 | // JavaScript through JSHint and report any errors. You can change the 10 | // options for this task, by reading this: 11 | // https://github.com/cowboy/grunt/blob/master/docs/task_lint.md 12 | lint: { 13 | files: [ 14 | "build/config.js", "app/**/*.js" 15 | ] 16 | }, 17 | 18 | // The jshint option for scripturl is set to lax, because the anchor 19 | // override inside main.js needs to test for them so as to not accidentally 20 | // route. 21 | jshint: { 22 | options: { 23 | scripturl: true 24 | } 25 | }, 26 | 27 | // The jst task compiles all application templates into JavaScript 28 | // functions with the underscore.js template function from 1.2.4. You can 29 | // change the namespace and the template options, by reading this: 30 | // https://github.com/gruntjs/grunt-contrib/blob/master/docs/jst.md 31 | // 32 | // The concat task depends on this file to exist, so if you decide to 33 | // remove this, ensure concat is updated accordingly. 34 | jst: { 35 | "dist/debug/templates.js": [ 36 | "app/templates/**/*.html" 37 | ] 38 | }, 39 | 40 | // This task simplifies working with CSS inside Backbone Boilerplate 41 | // projects. Instead of manually specifying your stylesheets inside the 42 | // configuration, you can use `@imports` and this task will concatenate 43 | // only those paths. 44 | styles: { 45 | // Out the concatenated contents of the following styles into the below 46 | // development file path. 47 | "dist/debug/index.css": { 48 | // Point this to where your `index.css` file is location. 49 | src: "app/styles/index.css", 50 | 51 | // The relative path to use for the @imports. 52 | paths: ["app/styles"], 53 | 54 | // Point to where styles live. 55 | prefix: "app/styles/", 56 | 57 | // Additional production-only stylesheets here. 58 | additional: [] 59 | } 60 | }, 61 | 62 | // This task uses James Burke's excellent r.js AMD build tool. In the 63 | // future other builders may be contributed as drop-in alternatives. 64 | requirejs: { 65 | // Include the main configuration file. 66 | mainConfigFile: "app/config.js", 67 | 68 | // Also include the JamJS configuration file. 69 | jamConfig: "/vendor/jam/require.config.js", 70 | 71 | // Output file. 72 | out: "dist/debug/require.js", 73 | 74 | // Root application module. 75 | name: "config", 76 | 77 | // Do not wrap everything in an IIFE. 78 | wrap: false 79 | }, 80 | 81 | // The concatenate task is used here to merge the almond require/define 82 | // shim and the templates into the application code. It's named 83 | // dist/debug/require.js, because we want to only load one script file in 84 | // index.html. 85 | concat: { 86 | dist: { 87 | src: [ 88 | "vendor/js/libs/almond.js", 89 | "dist/debug/templates.js", 90 | "dist/debug/require.js" 91 | ], 92 | 93 | dest: "dist/debug/require.js", 94 | 95 | separator: ";" 96 | } 97 | }, 98 | 99 | // This task uses the MinCSS Node.js project to take all your CSS files in 100 | // order and concatenate them into a single CSS file named index.css. It 101 | // also minifies all the CSS as well. This is named index.css, because we 102 | // only want to load one stylesheet in index.html. 103 | mincss: { 104 | "dist/release/index.css": [ 105 | "dist/debug/index.css" 106 | ] 107 | }, 108 | 109 | // Takes the built require.js file and minifies it for filesize benefits. 110 | min: { 111 | "dist/release/require.js": [ 112 | "dist/debug/require.js" 113 | ] 114 | }, 115 | 116 | // Running the server without specifying an action will run the defaults, 117 | // port: 8000 and host: 127.0.0.1. If you would like to change these 118 | // defaults, simply add in the properties `port` and `host` respectively. 119 | // Alternatively you can omit the port and host properties and the server 120 | // task will instead default to process.env.PORT or process.env.HOST. 121 | // 122 | // Changing the defaults might look something like this: 123 | // 124 | // server: { 125 | // host: "127.0.0.1", port: 9001 126 | // debug: { ... can set host and port here too ... 127 | // } 128 | // 129 | // To learn more about using the server task, please refer to the code 130 | // until documentation has been written. 131 | server: { 132 | // Ensure the favicon is mapped correctly. 133 | files: { "favicon.ico": "favicon.ico" }, 134 | 135 | // For styles. 136 | prefix: "app/styles/", 137 | 138 | debug: { 139 | // Ensure the favicon is mapped correctly. 140 | files: "", 141 | 142 | // Map `server:debug` to `debug` folders. 143 | folders: { 144 | "app": "dist/debug", 145 | "vendor/js/libs": "dist/debug", 146 | "app/styles": "dist/debug" 147 | } 148 | }, 149 | 150 | release: { 151 | // This makes it easier for deploying, by defaulting to any IP. 152 | host: "0.0.0.0", 153 | 154 | // Ensure the favicon is mapped correctly. 155 | files: "", 156 | 157 | // Map `server:release` to `release` folders. 158 | folders: { 159 | "app": "dist/release", 160 | "vendor/js/libs": "dist/release", 161 | "app/styles": "dist/release" 162 | } 163 | } 164 | }, 165 | 166 | // The headless QUnit testing environment is provided for "free" by Grunt. 167 | // Simply point the configuration to your test directory. 168 | qunit: { 169 | all: ["test/qunit/*.html"] 170 | }, 171 | 172 | // The headless Jasmine testing is provided by grunt-jasmine-task. Simply 173 | // point the configuration to your test directory. 174 | jasmine: { 175 | all: ["test/jasmine/*.html"] 176 | }, 177 | 178 | // The watch task can be used to monitor the filesystem and execute 179 | // specific tasks when files are modified. By default, the watch task is 180 | // available to compile CSS if you are unable to use the runtime compiler 181 | // (use if you have a custom server, PhoneGap, Adobe Air, etc.) 182 | watch: { 183 | files: ["grunt.js", "vendor/**/*", "app/**/*"], 184 | tasks: "styles" 185 | }, 186 | 187 | // The clean task ensures all files are removed from the dist/ directory so 188 | // that no files linger from previous builds. 189 | clean: ["dist/"], 190 | 191 | // If you want to generate targeted `index.html` builds into the `dist/` 192 | // folders, uncomment the following configuration block and use the 193 | // conditionals inside `index.html`. 194 | //targethtml: { 195 | // debug: { 196 | // src: "index.html", 197 | // dest: "dist/debug/index.html" 198 | // }, 199 | // 200 | // release: { 201 | // src: "index.html", 202 | // dest: "dist/release/index.html" 203 | // } 204 | //}, 205 | 206 | // This task will copy assets into your build directory, 207 | // automatically. This makes an entirely encapsulated build into 208 | // each directory. 209 | //copy: { 210 | // debug: { 211 | // files: { 212 | // "dist/debug/app/": "app/**", 213 | // "dist/debug/vendor/": "vendor/**" 214 | // } 215 | // }, 216 | 217 | // release: { 218 | // files: { 219 | // "dist/release/app/": "app/**", 220 | // "dist/release/vendor/": "vendor/**" 221 | // } 222 | // } 223 | //} 224 | 225 | }); 226 | 227 | // The debug task will remove all contents inside the dist/ folder, lint 228 | // all your code, precompile all the underscore templates into 229 | // dist/debug/templates.js, compile all the application code into 230 | // dist/debug/require.js, and then concatenate the require/define shim 231 | // almond.js and dist/debug/templates.js into the require.js file. 232 | grunt.registerTask("debug", "clean lint jst requirejs concat styles"); 233 | 234 | // The release task will run the debug tasks and then minify the 235 | // dist/debug/require.js file and CSS files. 236 | grunt.registerTask("release", "debug min mincss"); 237 | 238 | }; 239 | -------------------------------------------------------------------------------- /vendor/h5bp/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | *display: inline; 34 | *zoom: 1; 35 | } 36 | 37 | /* 38 | * Prevents modern browsers from displaying `audio` without controls. 39 | * Remove excess height in iOS 5 devices. 40 | */ 41 | 42 | audio:not([controls]) { 43 | display: none; 44 | height: 0; 45 | } 46 | 47 | /* 48 | * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3, 49 | * and Safari 4. 50 | * Known issue: no IE 6 support. 51 | */ 52 | 53 | [hidden] { 54 | display: none; 55 | } 56 | 57 | /* ========================================================================== 58 | Base 59 | ========================================================================== */ 60 | 61 | /* 62 | * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using 63 | * `em` units. 64 | * 2. Prevents iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | font-size: 100%; /* 1 */ 70 | -webkit-text-size-adjust: 100%; /* 2 */ 71 | -ms-text-size-adjust: 100%; /* 2 */ 72 | } 73 | 74 | /* 75 | * Addresses `font-family` inconsistency between `textarea` and other form 76 | * elements. 77 | */ 78 | 79 | html, 80 | button, 81 | input, 82 | select, 83 | textarea { 84 | font-family: sans-serif; 85 | } 86 | 87 | /* 88 | * Addresses margins handled incorrectly in IE 6/7. 89 | */ 90 | 91 | body { 92 | margin: 0; 93 | } 94 | 95 | /* ========================================================================== 96 | Links 97 | ========================================================================== */ 98 | 99 | /* 100 | * Addresses `outline` inconsistency between Chrome and other browsers. 101 | */ 102 | 103 | a:focus { 104 | outline: thin dotted; 105 | } 106 | 107 | /* 108 | * Improves readability when focused and also mouse hovered in all browsers. 109 | */ 110 | 111 | a:active, 112 | a:hover { 113 | outline: 0; 114 | } 115 | 116 | /* ========================================================================== 117 | Typography 118 | ========================================================================== */ 119 | 120 | /* 121 | * Addresses font sizes and margins set differently in IE 6/7. 122 | * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5, 123 | * and Chrome. 124 | */ 125 | 126 | h1 { 127 | font-size: 2em; 128 | margin: 0.67em 0; 129 | } 130 | 131 | h2 { 132 | font-size: 1.5em; 133 | margin: 0.83em 0; 134 | } 135 | 136 | h3 { 137 | font-size: 1.17em; 138 | margin: 1em 0; 139 | } 140 | 141 | h4 { 142 | font-size: 1em; 143 | margin: 1.33em 0; 144 | } 145 | 146 | h5 { 147 | font-size: 0.83em; 148 | margin: 1.67em 0; 149 | } 150 | 151 | h6 { 152 | font-size: 0.75em; 153 | margin: 2.33em 0; 154 | } 155 | 156 | /* 157 | * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome. 158 | */ 159 | 160 | abbr[title] { 161 | border-bottom: 1px dotted; 162 | } 163 | 164 | /* 165 | * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. 166 | */ 167 | 168 | b, 169 | strong { 170 | font-weight: bold; 171 | } 172 | 173 | blockquote { 174 | margin: 1em 40px; 175 | } 176 | 177 | /* 178 | * Addresses styling not present in Safari 5 and Chrome. 179 | */ 180 | 181 | dfn { 182 | font-style: italic; 183 | } 184 | 185 | /* 186 | * Addresses styling not present in IE 6/7/8/9. 187 | */ 188 | 189 | mark { 190 | background: #ff0; 191 | color: #000; 192 | } 193 | 194 | /* 195 | * Addresses margins set differently in IE 6/7. 196 | */ 197 | 198 | p, 199 | pre { 200 | margin: 1em 0; 201 | } 202 | 203 | /* 204 | * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome. 205 | */ 206 | 207 | code, 208 | kbd, 209 | pre, 210 | samp { 211 | font-family: monospace, serif; 212 | _font-family: 'courier new', monospace; 213 | font-size: 1em; 214 | } 215 | 216 | /* 217 | * Improves readability of pre-formatted text in all browsers. 218 | */ 219 | 220 | pre { 221 | white-space: pre; 222 | white-space: pre-wrap; 223 | word-wrap: break-word; 224 | } 225 | 226 | /* 227 | * Addresses CSS quotes not supported in IE 6/7. 228 | */ 229 | 230 | q { 231 | quotes: none; 232 | } 233 | 234 | /* 235 | * Addresses `quotes` property not supported in Safari 4. 236 | */ 237 | 238 | q:before, 239 | q:after { 240 | content: ''; 241 | content: none; 242 | } 243 | 244 | /* 245 | * Addresses inconsistent and variable font size in all browsers. 246 | */ 247 | 248 | small { 249 | font-size: 80%; 250 | } 251 | 252 | /* 253 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 254 | */ 255 | 256 | sub, 257 | sup { 258 | font-size: 75%; 259 | line-height: 0; 260 | position: relative; 261 | vertical-align: baseline; 262 | } 263 | 264 | sup { 265 | top: -0.5em; 266 | } 267 | 268 | sub { 269 | bottom: -0.25em; 270 | } 271 | 272 | /* ========================================================================== 273 | Lists 274 | ========================================================================== */ 275 | 276 | /* 277 | * Addresses margins set differently in IE 6/7. 278 | */ 279 | 280 | dl, 281 | menu, 282 | ol, 283 | ul { 284 | margin: 1em 0; 285 | } 286 | 287 | dd { 288 | margin: 0 0 0 40px; 289 | } 290 | 291 | /* 292 | * Addresses paddings set differently in IE 6/7. 293 | */ 294 | 295 | menu, 296 | ol, 297 | ul { 298 | padding: 0 0 0 40px; 299 | } 300 | 301 | /* 302 | * Corrects list images handled incorrectly in IE 7. 303 | */ 304 | 305 | nav ul, 306 | nav ol { 307 | list-style: none; 308 | list-style-image: none; 309 | } 310 | 311 | /* ========================================================================== 312 | Embedded content 313 | ========================================================================== */ 314 | 315 | /* 316 | * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3. 317 | * 2. Improves image quality when scaled in IE 7. 318 | */ 319 | 320 | img { 321 | border: 0; /* 1 */ 322 | -ms-interpolation-mode: bicubic; /* 2 */ 323 | } 324 | 325 | /* 326 | * Corrects overflow displayed oddly in IE 9. 327 | */ 328 | 329 | svg:not(:root) { 330 | overflow: hidden; 331 | } 332 | 333 | /* ========================================================================== 334 | Figures 335 | ========================================================================== */ 336 | 337 | /* 338 | * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11. 339 | */ 340 | 341 | figure { 342 | margin: 0; 343 | } 344 | 345 | /* ========================================================================== 346 | Forms 347 | ========================================================================== */ 348 | 349 | /* 350 | * Corrects margin displayed oddly in IE 6/7. 351 | */ 352 | 353 | form { 354 | margin: 0; 355 | } 356 | 357 | /* 358 | * Define consistent border, margin, and padding. 359 | */ 360 | 361 | fieldset { 362 | border: 1px solid #c0c0c0; 363 | margin: 0 2px; 364 | padding: 0.35em 0.625em 0.75em; 365 | } 366 | 367 | /* 368 | * 1. Corrects color not being inherited in IE 6/7/8/9. 369 | * 2. Corrects text not wrapping in Firefox 3. 370 | * 3. Corrects alignment displayed oddly in IE 6/7. 371 | */ 372 | 373 | legend { 374 | border: 0; /* 1 */ 375 | padding: 0; 376 | white-space: normal; /* 2 */ 377 | *margin-left: -7px; /* 3 */ 378 | } 379 | 380 | /* 381 | * 1. Corrects font size not being inherited in all browsers. 382 | * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, 383 | * and Chrome. 384 | * 3. Improves appearance and consistency in all browsers. 385 | */ 386 | 387 | button, 388 | input, 389 | select, 390 | textarea { 391 | font-size: 100%; /* 1 */ 392 | margin: 0; /* 2 */ 393 | vertical-align: baseline; /* 3 */ 394 | *vertical-align: middle; /* 3 */ 395 | } 396 | 397 | /* 398 | * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in 399 | * the UA stylesheet. 400 | */ 401 | 402 | button, 403 | input { 404 | line-height: normal; 405 | } 406 | 407 | /* 408 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 409 | * and `video` controls. 410 | * 2. Corrects inability to style clickable `input` types in iOS. 411 | * 3. Improves usability and consistency of cursor style between image-type 412 | * `input` and others. 413 | * 4. Removes inner spacing in IE 7 without affecting normal text inputs. 414 | * Known issue: inner spacing remains in IE 6. 415 | */ 416 | 417 | button, 418 | html input[type="button"], /* 1 */ 419 | input[type="reset"], 420 | input[type="submit"] { 421 | -webkit-appearance: button; /* 2 */ 422 | cursor: pointer; /* 3 */ 423 | *overflow: visible; /* 4 */ 424 | } 425 | 426 | /* 427 | * Re-set default cursor for disabled elements. 428 | */ 429 | 430 | button[disabled], 431 | input[disabled] { 432 | cursor: default; 433 | } 434 | 435 | /* 436 | * 1. Addresses box sizing set to content-box in IE 8/9. 437 | * 2. Removes excess padding in IE 8/9. 438 | * 3. Removes excess padding in IE 7. 439 | * Known issue: excess padding remains in IE 6. 440 | */ 441 | 442 | input[type="checkbox"], 443 | input[type="radio"] { 444 | box-sizing: border-box; /* 1 */ 445 | padding: 0; /* 2 */ 446 | *height: 13px; /* 3 */ 447 | *width: 13px; /* 3 */ 448 | } 449 | 450 | /* 451 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 452 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 453 | * (include `-moz` to future-proof). 454 | */ 455 | 456 | input[type="search"] { 457 | -webkit-appearance: textfield; /* 1 */ 458 | -moz-box-sizing: content-box; 459 | -webkit-box-sizing: content-box; /* 2 */ 460 | box-sizing: content-box; 461 | } 462 | 463 | /* 464 | * Removes inner padding and search cancel button in Safari 5 and Chrome 465 | * on OS X. 466 | */ 467 | 468 | input[type="search"]::-webkit-search-cancel-button, 469 | input[type="search"]::-webkit-search-decoration { 470 | -webkit-appearance: none; 471 | } 472 | 473 | /* 474 | * Removes inner padding and border in Firefox 3+. 475 | */ 476 | 477 | button::-moz-focus-inner, 478 | input::-moz-focus-inner { 479 | border: 0; 480 | padding: 0; 481 | } 482 | 483 | /* 484 | * 1. Removes default vertical scrollbar in IE 6/7/8/9. 485 | * 2. Improves readability and alignment in all browsers. 486 | */ 487 | 488 | textarea { 489 | overflow: auto; /* 1 */ 490 | vertical-align: top; /* 2 */ 491 | } 492 | 493 | /* ========================================================================== 494 | Tables 495 | ========================================================================== */ 496 | 497 | /* 498 | * Remove most spacing between table cells. 499 | */ 500 | 501 | table { 502 | border-collapse: collapse; 503 | border-spacing: 0; 504 | } 505 | -------------------------------------------------------------------------------- /vendor/js/libs/almond.js: -------------------------------------------------------------------------------- 1 | /** 2 | * almond 0.2.3 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/almond for details 5 | */ 6 | //Going sloppy to avoid 'use strict' string cost, but strict practices should 7 | //be followed. 8 | /*jslint sloppy: true */ 9 | /*global setTimeout: false */ 10 | 11 | var requirejs, require, define; 12 | (function (undef) { 13 | var main, req, makeMap, handlers, 14 | defined = {}, 15 | waiting = {}, 16 | config = {}, 17 | defining = {}, 18 | hasOwn = Object.prototype.hasOwnProperty, 19 | aps = [].slice; 20 | 21 | function hasProp(obj, prop) { 22 | return hasOwn.call(obj, prop); 23 | } 24 | 25 | /** 26 | * Given a relative module name, like ./something, normalize it to 27 | * a real name that can be mapped to a path. 28 | * @param {String} name the relative name 29 | * @param {String} baseName a real name that the name arg is relative 30 | * to. 31 | * @returns {String} normalized name 32 | */ 33 | function normalize(name, baseName) { 34 | var nameParts, nameSegment, mapValue, foundMap, 35 | foundI, foundStarMap, starI, i, j, part, 36 | baseParts = baseName && baseName.split("/"), 37 | map = config.map, 38 | starMap = (map && map['*']) || {}; 39 | 40 | //Adjust any relative paths. 41 | if (name && name.charAt(0) === ".") { 42 | //If have a base name, try to normalize against it, 43 | //otherwise, assume it is a top-level require that will 44 | //be relative to baseUrl in the end. 45 | if (baseName) { 46 | //Convert baseName to array, and lop off the last part, 47 | //so that . matches that "directory" and not name of the baseName's 48 | //module. For instance, baseName of "one/two/three", maps to 49 | //"one/two/three.js", but we want the directory, "one/two" for 50 | //this normalization. 51 | baseParts = baseParts.slice(0, baseParts.length - 1); 52 | 53 | name = baseParts.concat(name.split("/")); 54 | 55 | //start trimDots 56 | for (i = 0; i < name.length; i += 1) { 57 | part = name[i]; 58 | if (part === ".") { 59 | name.splice(i, 1); 60 | i -= 1; 61 | } else if (part === "..") { 62 | if (i === 1 && (name[2] === '..' || name[0] === '..')) { 63 | //End of the line. Keep at least one non-dot 64 | //path segment at the front so it can be mapped 65 | //correctly to disk. Otherwise, there is likely 66 | //no path mapping for a path starting with '..'. 67 | //This can still fail, but catches the most reasonable 68 | //uses of .. 69 | break; 70 | } else if (i > 0) { 71 | name.splice(i - 1, 2); 72 | i -= 2; 73 | } 74 | } 75 | } 76 | //end trimDots 77 | 78 | name = name.join("/"); 79 | } else if (name.indexOf('./') === 0) { 80 | // No baseName, so this is ID is resolved relative 81 | // to baseUrl, pull off the leading dot. 82 | name = name.substring(2); 83 | } 84 | } 85 | 86 | //Apply map config if available. 87 | if ((baseParts || starMap) && map) { 88 | nameParts = name.split('/'); 89 | 90 | for (i = nameParts.length; i > 0; i -= 1) { 91 | nameSegment = nameParts.slice(0, i).join("/"); 92 | 93 | if (baseParts) { 94 | //Find the longest baseName segment match in the config. 95 | //So, do joins on the biggest to smallest lengths of baseParts. 96 | for (j = baseParts.length; j > 0; j -= 1) { 97 | mapValue = map[baseParts.slice(0, j).join('/')]; 98 | 99 | //baseName segment has config, find if it has one for 100 | //this name. 101 | if (mapValue) { 102 | mapValue = mapValue[nameSegment]; 103 | if (mapValue) { 104 | //Match, update name to the new value. 105 | foundMap = mapValue; 106 | foundI = i; 107 | break; 108 | } 109 | } 110 | } 111 | } 112 | 113 | if (foundMap) { 114 | break; 115 | } 116 | 117 | //Check for a star map match, but just hold on to it, 118 | //if there is a shorter segment match later in a matching 119 | //config, then favor over this star map. 120 | if (!foundStarMap && starMap && starMap[nameSegment]) { 121 | foundStarMap = starMap[nameSegment]; 122 | starI = i; 123 | } 124 | } 125 | 126 | if (!foundMap && foundStarMap) { 127 | foundMap = foundStarMap; 128 | foundI = starI; 129 | } 130 | 131 | if (foundMap) { 132 | nameParts.splice(0, foundI, foundMap); 133 | name = nameParts.join('/'); 134 | } 135 | } 136 | 137 | return name; 138 | } 139 | 140 | function makeRequire(relName, forceSync) { 141 | return function () { 142 | //A version of a require function that passes a moduleName 143 | //value for items that may need to 144 | //look up paths relative to the moduleName 145 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 146 | }; 147 | } 148 | 149 | function makeNormalize(relName) { 150 | return function (name) { 151 | return normalize(name, relName); 152 | }; 153 | } 154 | 155 | function makeLoad(depName) { 156 | return function (value) { 157 | defined[depName] = value; 158 | }; 159 | } 160 | 161 | function callDep(name) { 162 | if (hasProp(waiting, name)) { 163 | var args = waiting[name]; 164 | delete waiting[name]; 165 | defining[name] = true; 166 | main.apply(undef, args); 167 | } 168 | 169 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 170 | throw new Error('No ' + name); 171 | } 172 | return defined[name]; 173 | } 174 | 175 | //Turns a plugin!resource to [plugin, resource] 176 | //with the plugin being undefined if the name 177 | //did not have a plugin prefix. 178 | function splitPrefix(name) { 179 | var prefix, 180 | index = name ? name.indexOf('!') : -1; 181 | if (index > -1) { 182 | prefix = name.substring(0, index); 183 | name = name.substring(index + 1, name.length); 184 | } 185 | return [prefix, name]; 186 | } 187 | 188 | /** 189 | * Makes a name map, normalizing the name, and using a plugin 190 | * for normalization if necessary. Grabs a ref to plugin 191 | * too, as an optimization. 192 | */ 193 | makeMap = function (name, relName) { 194 | var plugin, 195 | parts = splitPrefix(name), 196 | prefix = parts[0]; 197 | 198 | name = parts[1]; 199 | 200 | if (prefix) { 201 | prefix = normalize(prefix, relName); 202 | plugin = callDep(prefix); 203 | } 204 | 205 | //Normalize according 206 | if (prefix) { 207 | if (plugin && plugin.normalize) { 208 | name = plugin.normalize(name, makeNormalize(relName)); 209 | } else { 210 | name = normalize(name, relName); 211 | } 212 | } else { 213 | name = normalize(name, relName); 214 | parts = splitPrefix(name); 215 | prefix = parts[0]; 216 | name = parts[1]; 217 | if (prefix) { 218 | plugin = callDep(prefix); 219 | } 220 | } 221 | 222 | //Using ridiculous property names for space reasons 223 | return { 224 | f: prefix ? prefix + '!' + name : name, //fullName 225 | n: name, 226 | pr: prefix, 227 | p: plugin 228 | }; 229 | }; 230 | 231 | function makeConfig(name) { 232 | return function () { 233 | return (config && config.config && config.config[name]) || {}; 234 | }; 235 | } 236 | 237 | handlers = { 238 | require: function (name) { 239 | return makeRequire(name); 240 | }, 241 | exports: function (name) { 242 | var e = defined[name]; 243 | if (typeof e !== 'undefined') { 244 | return e; 245 | } else { 246 | return (defined[name] = {}); 247 | } 248 | }, 249 | module: function (name) { 250 | return { 251 | id: name, 252 | uri: '', 253 | exports: defined[name], 254 | config: makeConfig(name) 255 | }; 256 | } 257 | }; 258 | 259 | main = function (name, deps, callback, relName) { 260 | var cjsModule, depName, ret, map, i, 261 | args = [], 262 | usingExports; 263 | 264 | //Use name if no relName 265 | relName = relName || name; 266 | 267 | //Call the callback to define the module, if necessary. 268 | if (typeof callback === 'function') { 269 | 270 | //Pull out the defined dependencies and pass the ordered 271 | //values to the callback. 272 | //Default to [require, exports, module] if no deps 273 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 274 | for (i = 0; i < deps.length; i += 1) { 275 | map = makeMap(deps[i], relName); 276 | depName = map.f; 277 | 278 | //Fast path CommonJS standard dependencies. 279 | if (depName === "require") { 280 | args[i] = handlers.require(name); 281 | } else if (depName === "exports") { 282 | //CommonJS module spec 1.1 283 | args[i] = handlers.exports(name); 284 | usingExports = true; 285 | } else if (depName === "module") { 286 | //CommonJS module spec 1.1 287 | cjsModule = args[i] = handlers.module(name); 288 | } else if (hasProp(defined, depName) || 289 | hasProp(waiting, depName) || 290 | hasProp(defining, depName)) { 291 | args[i] = callDep(depName); 292 | } else if (map.p) { 293 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 294 | args[i] = defined[depName]; 295 | } else { 296 | throw new Error(name + ' missing ' + depName); 297 | } 298 | } 299 | 300 | ret = callback.apply(defined[name], args); 301 | 302 | if (name) { 303 | //If setting exports via "module" is in play, 304 | //favor that over return value and exports. After that, 305 | //favor a non-undefined return value over exports use. 306 | if (cjsModule && cjsModule.exports !== undef && 307 | cjsModule.exports !== defined[name]) { 308 | defined[name] = cjsModule.exports; 309 | } else if (ret !== undef || !usingExports) { 310 | //Use the return value from the function. 311 | defined[name] = ret; 312 | } 313 | } 314 | } else if (name) { 315 | //May just be an object definition for the module. Only 316 | //worry about defining if have a module name. 317 | defined[name] = callback; 318 | } 319 | }; 320 | 321 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 322 | if (typeof deps === "string") { 323 | if (handlers[deps]) { 324 | //callback in this case is really relName 325 | return handlers[deps](callback); 326 | } 327 | //Just return the module wanted. In this scenario, the 328 | //deps arg is the module name, and second arg (if passed) 329 | //is just the relName. 330 | //Normalize module name, if it contains . or .. 331 | return callDep(makeMap(deps, callback).f); 332 | } else if (!deps.splice) { 333 | //deps is a config object, not an array. 334 | config = deps; 335 | if (callback.splice) { 336 | //callback is an array, which means it is a dependency list. 337 | //Adjust args if there are dependencies 338 | deps = callback; 339 | callback = relName; 340 | relName = null; 341 | } else { 342 | deps = undef; 343 | } 344 | } 345 | 346 | //Support require(['a']) 347 | callback = callback || function () {}; 348 | 349 | //If relName is a function, it is an errback handler, 350 | //so remove it. 351 | if (typeof relName === 'function') { 352 | relName = forceSync; 353 | forceSync = alt; 354 | } 355 | 356 | //Simulate async callback; 357 | if (forceSync) { 358 | main(undef, deps, callback, relName); 359 | } else { 360 | setTimeout(function () { 361 | main(undef, deps, callback, relName); 362 | }, 15); 363 | } 364 | 365 | return req; 366 | }; 367 | 368 | /** 369 | * Just drops the config on the floor, but returns req in case 370 | * the config return value is used. 371 | */ 372 | req.config = function (cfg) { 373 | config = cfg; 374 | return req; 375 | }; 376 | 377 | define = function (name, deps, callback) { 378 | 379 | //This module may not have dependencies 380 | if (!deps.splice) { 381 | //deps is not an array, so probably means 382 | //an object literal or factory function for 383 | //the value. Adjust args. 384 | callback = deps; 385 | deps = []; 386 | } 387 | 388 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 389 | waiting[name] = [name, deps, callback]; 390 | } 391 | }; 392 | 393 | define.amd = { 394 | jQuery: true 395 | }; 396 | }()); 397 | -------------------------------------------------------------------------------- /vendor/jam/backbone.layoutmanager/backbone.layoutmanager.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * backbone.layoutmanager.js v0.8.6 3 | * Copyright 2013, Tim Branyen (@tbranyen) 4 | * backbone.layoutmanager.js may be freely distributed under the MIT license. 5 | */ 6 | (function(window) { 7 | 8 | "use strict"; 9 | 10 | // Hoisted, referenced at the bottom of the source. This caches a list of all 11 | // LayoutManager options at definition time. 12 | var keys; 13 | 14 | // Localize global dependency references. 15 | var Backbone = window.Backbone; 16 | var _ = window._; 17 | var $ = Backbone.$; 18 | 19 | // Used for issuing warnings and debugging. 20 | var warn = window.console && window.console.warn; 21 | var trace = window.console && window.console.trace; 22 | 23 | // Maintain references to the two `Backbone.View` functions that are 24 | // overwritten so that they can be proxied. 25 | var _configure = Backbone.View.prototype._configure; 26 | var render = Backbone.View.prototype.render; 27 | 28 | // Cache these methods for performance. 29 | var aPush = Array.prototype.push; 30 | var aConcat = Array.prototype.concat; 31 | var aSplice = Array.prototype.splice; 32 | 33 | // LayoutManager is a wrapper around a `Backbone.View`. 34 | var LayoutManager = Backbone.View.extend({ 35 | // This named function allows for significantly easier debugging. 36 | constructor: function Layout(options) { 37 | // Options may not always be passed to the constructor, this ensures it is 38 | // always an object. 39 | options = options || {}; 40 | 41 | // Grant this View superpowers. 42 | LayoutManager.setupView(this, options); 43 | 44 | // Have Backbone set up the rest of this View. 45 | Backbone.View.call(this, options); 46 | }, 47 | 48 | // Shorthand to `setView` function with the `insert` flag set. 49 | insertView: function(selector, view) { 50 | // If the `view` argument exists, then a selector was passed in. This code 51 | // path will forward the selector on to `setView`. 52 | if (view) { 53 | return this.setView(selector, view, true); 54 | } 55 | 56 | // If no `view` argument is defined, then assume the first argument is the 57 | // View, somewhat now confusingly named `selector`. 58 | return this.setView(selector, true); 59 | }, 60 | 61 | // Iterate over an object and ensure every value is wrapped in an array to 62 | // ensure they will be inserted, then pass that object to `setViews`. 63 | insertViews: function(views) { 64 | // If an array of views was passed it should be inserted into the 65 | // root view. Much like calling insertView without a selector. 66 | if (_.isArray(views)) { 67 | return this.setViews({ "": views }); 68 | } 69 | 70 | _.each(views, function(view, selector) { 71 | views[selector] = _.isArray(view) ? view : [view]; 72 | }); 73 | 74 | return this.setViews(views); 75 | }, 76 | 77 | // Returns the View that matches the `getViews` filter function. 78 | getView: function(fn) { 79 | // If `getView` is invoked with undefined as the first argument, then the 80 | // second argument will be used instead. This is to allow 81 | // `getViews(undefined, fn)` to work as `getViews(fn)`. Useful for when 82 | // you are allowing an optional selector. 83 | if (fn == null) { 84 | fn = arguments[1]; 85 | } 86 | 87 | return this.getViews(fn).first().value(); 88 | }, 89 | 90 | // Provide a filter function to get a flattened array of all the subviews. 91 | // If the filter function is omitted it will return all subviews. If a 92 | // String is passed instead, it will return the Views for that selector. 93 | getViews: function(fn) { 94 | // Generate an array of all top level (no deeply nested) Views flattened. 95 | var views = _.chain(this.views).map(function(view) { 96 | return _.isArray(view) ? view : [view]; 97 | }, this).flatten().value(); 98 | 99 | // If the filter argument is a String, then return a chained Version of the 100 | // elements. 101 | if (typeof fn === "string") { 102 | return _.chain([this.views[fn]]).flatten(); 103 | } 104 | 105 | // If the argument passed is an Object, then pass it to `_.where`. 106 | if (typeof fn === "object") { 107 | return _.chain([_.where(views, fn)]).flatten(); 108 | } 109 | 110 | // If a filter function is provided, run it on all Views and return a 111 | // wrapped chain. Otherwise, simply return a wrapped chain of all Views. 112 | return _.chain(typeof fn === "function" ? _.filter(views, fn) : views); 113 | }, 114 | 115 | // Use this to remove Views, internally uses `getViews` so you can pass the 116 | // same argument here as you would to that method. 117 | removeView: function(fn) { 118 | // Allow an optional selector or function to find the right model and 119 | // remove nested Views based off the results of the selector or filter. 120 | return this.getViews(fn).each(function(nestedView) { 121 | nestedView.remove(); 122 | }); 123 | }, 124 | 125 | // This takes in a partial name and view instance and assigns them to 126 | // the internal collection of views. If a view is not a LayoutManager 127 | // instance, then mix in the LayoutManager prototype. This ensures 128 | // all Views can be used successfully. 129 | // 130 | // Must definitely wrap any render method passed in or defaults to a 131 | // typical render function `return layout(this).render()`. 132 | setView: function(name, view, insert) { 133 | var manager, existing, options; 134 | // Parent view, the one you are setting a View on. 135 | var root = this; 136 | 137 | // If no name was passed, use an empty string and shift all arguments. 138 | if (typeof name !== "string") { 139 | insert = view; 140 | view = name; 141 | name = ""; 142 | } 143 | 144 | // If the parent views object doesn't exist... create it. 145 | this.views = this.views || {}; 146 | 147 | // Shorthand the `__manager__` property. 148 | manager = view.__manager__; 149 | 150 | // Shorthand the View that potentially already exists. 151 | existing = this.views[name]; 152 | 153 | // If the View has not been properly set up, throw an Error message 154 | // indicating that the View needs `manage: true` set. 155 | if (!manager) { 156 | throw new Error("Please set `View#manage` property with selector '" + 157 | name + "' to `true`."); 158 | } 159 | 160 | // Assign options. 161 | options = view.getAllOptions(); 162 | 163 | // Add reference to the parentView. 164 | manager.parent = root; 165 | 166 | // Add reference to the placement selector used. 167 | manager.selector = name; 168 | 169 | // Set up event bubbling, inspired by Backbone.ViewMaster. Do not bubble 170 | // internal events that are triggered. 171 | view.on("all", function(name) { 172 | if (name !== "beforeRender" && name !== "afterRender") { 173 | root.trigger.apply(root, arguments); 174 | } 175 | }, view); 176 | 177 | // Code path is less complex for Views that are not being inserted. Simply 178 | // remove existing Views and bail out with the assignment. 179 | if (!insert) { 180 | // If the View we are adding has already been rendered, simply inject it 181 | // into the parent. 182 | if (manager.hasRendered) { 183 | // Apply the partial. 184 | options.partial(root.$el, view.$el, root.__manager__, manager); 185 | } 186 | 187 | // Ensure remove is called when swapping View's. 188 | if (existing) { 189 | // If the views are an array, iterate and remove each individually. 190 | _.each(aConcat.call([], existing), function(nestedView) { 191 | nestedView.remove(); 192 | }); 193 | } 194 | 195 | // Assign to main views object and return for chainability. 196 | return this.views[name] = view; 197 | } 198 | 199 | // Ensure this.views[name] is an array and push this View to the end. 200 | this.views[name] = aConcat.call([], existing || [], view); 201 | 202 | // Put the view into `insert` mode. 203 | manager.insert = true; 204 | 205 | return view; 206 | }, 207 | 208 | // Allows the setting of multiple views instead of a single view. 209 | setViews: function(views) { 210 | // Iterate over all the views and use the View's view method to assign. 211 | _.each(views, function(view, name) { 212 | // If the view is an array put all views into insert mode. 213 | if (_.isArray(view)) { 214 | return _.each(view, function(view) { 215 | this.insertView(name, view); 216 | }, this); 217 | } 218 | 219 | // Assign each view using the view function. 220 | this.setView(name, view); 221 | }, this); 222 | 223 | // Allow for chaining 224 | return this; 225 | }, 226 | 227 | // By default this should find all nested views and render them into 228 | // the this.el and call done once all of them have successfully been 229 | // resolved. 230 | // 231 | // This function returns a promise that can be chained to determine 232 | // once all subviews and main view have been rendered into the view.el. 233 | render: function() { 234 | var root = this; 235 | var options = root.getAllOptions(); 236 | var manager = root.__manager__; 237 | var parent = manager.parent; 238 | var rentManager = parent && parent.__manager__; 239 | var def = options.deferred(); 240 | 241 | // Triggered once the render has succeeded. 242 | function resolve() { 243 | var next, afterRender; 244 | 245 | // If there is a parent, attach. 246 | if (parent) { 247 | if (!options.contains(parent.el, root.el)) { 248 | // Apply the partial. 249 | options.partial(parent.$el, root.$el, rentManager, manager); 250 | } 251 | } 252 | 253 | // Ensure events are always correctly bound after rendering. 254 | root.delegateEvents(); 255 | 256 | // Set this View as successfully rendered. 257 | manager.hasRendered = true; 258 | 259 | // Only process the queue if it exists. 260 | if (next = manager.queue.shift()) { 261 | // Ensure that the next render is only called after all other 262 | // `done` handlers have completed. This will prevent `render` 263 | // callbacks from firing out of order. 264 | next(); 265 | } else { 266 | // Once the queue is depleted, remove it, the render process has 267 | // completed. 268 | delete manager.queue; 269 | } 270 | 271 | // Reusable function for triggering the afterRender callback and event 272 | // and setting the hasRendered flag. 273 | function completeRender() { 274 | var afterRender = options.afterRender; 275 | 276 | if (afterRender) { 277 | afterRender.call(root, root); 278 | } 279 | 280 | // Always emit an afterRender event. 281 | root.trigger("afterRender", root); 282 | 283 | // If there are multiple top level elements and `el: false` is used, 284 | // display a warning message and a stack trace. 285 | if (manager.noel && root.$el.length > 1) { 286 | // Do not display a warning while testing or if warning suppression 287 | // is enabled. 288 | if (warn && !options.suppressWarnings) { 289 | window.console.warn("Using `el: false` with multiple top level " + 290 | "elements is not supported."); 291 | 292 | // Provide a stack trace if available to aid with debugging. 293 | if (trace) { window.console.trace(); } 294 | } 295 | } 296 | } 297 | 298 | // If the parent is currently rendering, wait until it has completed 299 | // until calling the nested View's `afterRender`. 300 | if (rentManager && rentManager.queue) { 301 | // Wait until the parent View has finished rendering, which could be 302 | // asynchronous, and trigger afterRender on this View once it has 303 | // compeleted. 304 | parent.once("afterRender", completeRender); 305 | } else { 306 | // This View and its parent have both rendered. 307 | completeRender(); 308 | } 309 | 310 | return def.resolveWith(root, [root]); 311 | } 312 | 313 | // Actually facilitate a render. 314 | function actuallyRender() { 315 | var options = root.getAllOptions(); 316 | var manager = root.__manager__; 317 | var parent = manager.parent; 318 | var rentManager = parent && parent.__manager__; 319 | 320 | // The `_viewRender` method is broken out to abstract away from having 321 | // too much code in `actuallyRender`. 322 | root._render(LayoutManager._viewRender, options).done(function() { 323 | // If there are no children to worry about, complete the render 324 | // instantly. 325 | if (!_.keys(root.views).length) { 326 | return resolve(); 327 | } 328 | 329 | // Create a list of promises to wait on until rendering is done. 330 | // Since this method will run on all children as well, its sufficient 331 | // for a full hierarchical. 332 | var promises = _.map(root.views, function(view) { 333 | var insert = _.isArray(view); 334 | 335 | // If items are being inserted, they will be in a non-zero length 336 | // Array. 337 | if (insert && view.length) { 338 | // Schedule each view to be rendered in order and return a promise 339 | // representing the result of the final rendering. 340 | return _.reduce(view.slice(1), function(prevRender, view) { 341 | return prevRender.then(function() { 342 | return view.render(); 343 | }); 344 | // The first view should be rendered immediately, and the resulting 345 | // promise used to initialize the reduction. 346 | }, view[0].render()); 347 | } 348 | 349 | // Only return the fetch deferred, resolve the main deferred after 350 | // the element has been attached to it's parent. 351 | return !insert ? view.render() : view; 352 | }); 353 | 354 | // Once all nested Views have been rendered, resolve this View's 355 | // deferred. 356 | options.when(promises).done(resolve); 357 | }); 358 | } 359 | 360 | // Another render is currently happening if there is an existing queue, so 361 | // push a closure to render later into the queue. 362 | if (manager.queue) { 363 | aPush.call(manager.queue, actuallyRender); 364 | } else { 365 | manager.queue = []; 366 | 367 | // This the first `render`, preceeding the `queue` so render 368 | // immediately. 369 | actuallyRender(root, def); 370 | } 371 | 372 | // Add the View to the deferred so that `view.render().view.el` is 373 | // possible. 374 | def.view = root; 375 | 376 | // This is the promise that determines if the `render` function has 377 | // completed or not. 378 | return def; 379 | }, 380 | 381 | // Ensure the cleanup function is called whenever remove is called. 382 | remove: function() { 383 | // Force remove itself from its parent. 384 | LayoutManager._removeView(this, true); 385 | 386 | // Call the original remove function. 387 | return this._remove.apply(this, arguments); 388 | }, 389 | 390 | // Merge instance and global options. 391 | getAllOptions: function() { 392 | // Instance overrides take precedence, fallback to prototype options. 393 | return _.extend({}, this, LayoutManager.prototype.options, this.options); 394 | } 395 | }, 396 | { 397 | // Clearable cache. 398 | _cache: {}, 399 | 400 | // Creates a deferred and returns a function to call when finished. 401 | _makeAsync: function(options, done) { 402 | var handler = options.deferred(); 403 | 404 | // Used to handle asynchronous renders. 405 | handler.async = function() { 406 | handler._isAsync = true; 407 | 408 | return done; 409 | }; 410 | 411 | return handler; 412 | }, 413 | 414 | // This gets passed to all _render methods. The `root` value here is passed 415 | // from the `manage(this).render()` line in the `_render` function 416 | _viewRender: function(root, options) { 417 | var url, contents, fetchAsync, renderedEl; 418 | var manager = root.__manager__; 419 | 420 | // This function is responsible for pairing the rendered template into 421 | // the DOM element. 422 | function applyTemplate(rendered) { 423 | // Actually put the rendered contents into the element. 424 | if (rendered) { 425 | // If no container is specified, we must replace the content. 426 | if (manager.noel) { 427 | // Hold a reference to created element as replaceWith doesn't return new el. 428 | renderedEl = $(rendered); 429 | 430 | // Remove extra root elements 431 | root.$el.slice(1).remove(); 432 | 433 | root.$el.replaceWith(renderedEl); 434 | // Don't delegate events here - we'll do that in resolve() 435 | root.setElement(renderedEl, false); 436 | } else { 437 | options.html(root.$el, rendered); 438 | } 439 | } 440 | 441 | // Resolve only after fetch and render have succeeded. 442 | fetchAsync.resolveWith(root, [root]); 443 | } 444 | 445 | // Once the template is successfully fetched, use its contents to proceed. 446 | // Context argument is first, since it is bound for partial application 447 | // reasons. 448 | function done(context, contents) { 449 | // Store the rendered template someplace so it can be re-assignable. 450 | var rendered; 451 | // This allows the `render` method to be asynchronous as well as `fetch`. 452 | var renderAsync = LayoutManager._makeAsync(options, function(rendered) { 453 | applyTemplate(rendered); 454 | }); 455 | 456 | // Ensure the cache is up-to-date. 457 | LayoutManager.cache(url, contents); 458 | 459 | // Render the View into the el property. 460 | if (contents) { 461 | rendered = options.render.call(renderAsync, contents, context); 462 | } 463 | 464 | // If the function was synchronous, continue execution. 465 | if (!renderAsync._isAsync) { 466 | applyTemplate(rendered); 467 | } 468 | } 469 | 470 | return { 471 | // This `render` function is what gets called inside of the View render, 472 | // when `manage(this).render` is called. Returns a promise that can be 473 | // used to know when the element has been rendered into its parent. 474 | render: function() { 475 | var context = root.serialize || options.serialize; 476 | var template = root.template || options.template; 477 | 478 | // If data is a function, immediately call it. 479 | if (_.isFunction(context)) { 480 | context = context.call(root); 481 | } 482 | 483 | // This allows for `var done = this.async()` and then `done(contents)`. 484 | fetchAsync = LayoutManager._makeAsync(options, function(contents) { 485 | done(context, contents); 486 | }); 487 | 488 | // Set the url to the prefix + the view's template property. 489 | if (typeof template === "string") { 490 | url = options.prefix + template; 491 | } 492 | 493 | // Check if contents are already cached and if they are, simply process 494 | // the template with the correct data. 495 | if (contents = LayoutManager.cache(url)) { 496 | done(context, contents, url); 497 | 498 | return fetchAsync; 499 | } 500 | 501 | // Fetch layout and template contents. 502 | if (typeof template === "string") { 503 | contents = options.fetch.call(fetchAsync, options.prefix + template); 504 | // If the template is already a function, simply call it. 505 | } else if (typeof template === "function") { 506 | contents = template; 507 | // If its not a string and not undefined, pass the value to `fetch`. 508 | } else if (template != null) { 509 | contents = options.fetch.call(fetchAsync, template); 510 | } 511 | 512 | // If the function was synchronous, continue execution. 513 | if (!fetchAsync._isAsync) { 514 | done(context, contents); 515 | } 516 | 517 | return fetchAsync; 518 | } 519 | }; 520 | }, 521 | 522 | // Remove all nested Views. 523 | _removeViews: function(root, force) { 524 | var views; 525 | 526 | // Shift arguments around. 527 | if (typeof root === "boolean") { 528 | force = root; 529 | root = this; 530 | } 531 | 532 | // Allow removeView to be called on instances. 533 | root = root || this; 534 | 535 | // Iterate over all of the nested View's and remove. 536 | root.getViews().each(function(view) { 537 | // Force doesn't care about if a View has rendered or not. 538 | if (view.__manager__.hasRendered || force) { 539 | LayoutManager._removeView(view, force); 540 | } 541 | }); 542 | }, 543 | 544 | // Remove a single nested View. 545 | _removeView: function(view, force) { 546 | var parentViews; 547 | // Shorthand the manager for easier access. 548 | var manager = view.__manager__; 549 | // Test for keep. 550 | var keep = typeof view.keep === "boolean" ? view.keep : view.options.keep; 551 | 552 | // Only remove views that do not have `keep` attribute set, unless the 553 | // View is in `insert` mode and the force flag is set. 554 | if ((!keep && manager.insert === true) || force) { 555 | // Clean out the events. 556 | LayoutManager.cleanViews(view); 557 | 558 | // Since we are removing this view, force subviews to remove 559 | view._removeViews(true); 560 | 561 | // Remove the View completely. 562 | view.$el.remove(); 563 | 564 | // Bail out early if no parent exists. 565 | if (!manager.parent) { return; } 566 | 567 | // Assign (if they exist) the sibling Views to a property. 568 | parentViews = manager.parent.views[manager.selector]; 569 | 570 | // If this is an array of items remove items that are not marked to 571 | // keep. 572 | if (_.isArray(parentViews)) { 573 | // Remove duplicate Views. 574 | return _.each(_.clone(parentViews), function(view, i) { 575 | // If the managers match, splice off this View. 576 | if (view && view.__manager__ === manager) { 577 | aSplice.call(parentViews, i, 1); 578 | } 579 | }); 580 | } 581 | 582 | // Otherwise delete the parent selector. 583 | delete manager.parent.views[manager.selector]; 584 | } 585 | }, 586 | 587 | // Cache templates into LayoutManager._cache. 588 | cache: function(path, contents) { 589 | // If template path is found in the cache, return the contents. 590 | if (path in this._cache && contents == null) { 591 | return this._cache[path]; 592 | // Ensure path and contents aren't undefined. 593 | } else if (path != null && contents != null) { 594 | return this._cache[path] = contents; 595 | } 596 | 597 | // If the template is not in the cache, return undefined. 598 | }, 599 | 600 | // Accept either a single view or an array of views to clean of all DOM 601 | // events internal model and collection references and all Backbone.Events. 602 | cleanViews: function(views) { 603 | // Clear out all existing views. 604 | _.each(aConcat.call([], views), function(view) { 605 | // Remove all custom events attached to this View. 606 | view.unbind(); 607 | 608 | // Automatically unbind `model`. 609 | if (view.model instanceof Backbone.Model) { 610 | view.model.off(null, null, view); 611 | } 612 | 613 | // Automatically unbind `collection`. 614 | if (view.collection instanceof Backbone.Collection) { 615 | view.collection.off(null, null, view); 616 | } 617 | 618 | // Automatically unbind events bound to this View. 619 | view.stopListening(); 620 | 621 | // If a custom cleanup method was provided on the view, call it after 622 | // the initial cleanup is done 623 | _.result(view.getAllOptions(), "cleanup"); 624 | }); 625 | }, 626 | 627 | // This static method allows for global configuration of LayoutManager. 628 | configure: function(options) { 629 | _.extend(LayoutManager.prototype.options, options); 630 | 631 | // Allow LayoutManager to manage Backbone.View.prototype. 632 | if (options.manage) { 633 | Backbone.View.prototype.manage = true; 634 | } 635 | 636 | // Disable the element globally. 637 | if (options.el === false) { 638 | Backbone.View.prototype.el = false; 639 | } 640 | 641 | // Allow global configuration of `suppressWarnings`. 642 | if (options.suppressWarnings === true) { 643 | Backbone.View.prototype.suppressWarnings = true; 644 | } 645 | }, 646 | 647 | // Configure a View to work with the LayoutManager plugin. 648 | setupView: function(views, options) { 649 | // Set up all Views passed. 650 | _.each(aConcat.call([], views), function(view) { 651 | // If the View has already been setup, no need to do it again. 652 | if (view.__manager__) { 653 | return; 654 | } 655 | 656 | var views, declaredViews, viewOptions; 657 | var proto = LayoutManager.prototype; 658 | var viewOverrides = _.pick(view, keys); 659 | 660 | // Ensure necessary properties are set. 661 | _.defaults(view, { 662 | // Ensure a view always has a views object. 663 | views: {}, 664 | 665 | // Internal state object used to store whether or not a View has been 666 | // taken over by layout manager and if it has been rendered into the DOM. 667 | __manager__: {}, 668 | 669 | // Add the ability to remove all Views. 670 | _removeViews: LayoutManager._removeViews, 671 | 672 | // Add the ability to remove itself. 673 | _removeView: LayoutManager._removeView 674 | 675 | // Mix in all LayoutManager prototype properties as well. 676 | }, LayoutManager.prototype); 677 | 678 | // Extend the options with the prototype and passed options. 679 | options = view.options = _.defaults(options || {}, view.options, 680 | proto.options); 681 | 682 | // Ensure view events are properly copied over. 683 | viewOptions = _.pick(options, aConcat.call(["events"], 684 | _.values(options.events))); 685 | 686 | // Merge the View options into the View. 687 | _.extend(view, viewOptions); 688 | 689 | // If the View still has the Backbone.View#render method, remove it. Don't 690 | // want it accidentally overriding the LM render. 691 | if (viewOverrides.render === LayoutManager.prototype.render || 692 | viewOverrides.render === Backbone.View.prototype.render) { 693 | delete viewOverrides.render; 694 | } 695 | 696 | // Pick out the specific properties that can be dynamically added at 697 | // runtime and ensure they are available on the view object. 698 | _.extend(options, viewOverrides); 699 | 700 | // By default the original Remove function is the Backbone.View one. 701 | view._remove = Backbone.View.prototype.remove; 702 | 703 | // Always use this render function when using LayoutManager. 704 | view._render = function(manage, options) { 705 | // Keep the view consistent between callbacks and deferreds. 706 | var view = this; 707 | // Shorthand the manager. 708 | var manager = view.__manager__; 709 | // Cache these properties. 710 | var beforeRender = options.beforeRender; 711 | 712 | // Ensure all nested Views are properly scrubbed if re-rendering. 713 | if (manager.hasRendered) { 714 | this._removeViews(); 715 | } 716 | 717 | // If a beforeRender function is defined, call it. 718 | if (beforeRender) { 719 | beforeRender.call(this, this); 720 | } 721 | 722 | // Always emit a beforeRender event. 723 | this.trigger("beforeRender", this); 724 | 725 | // Render! 726 | return manage(this, options).render(); 727 | }; 728 | 729 | // Ensure the render is always set correctly. 730 | view.render = LayoutManager.prototype.render; 731 | 732 | // If the user provided their own remove override, use that instead of the 733 | // default. 734 | if (view.remove !== proto.remove) { 735 | view._remove = view.remove; 736 | view.remove = proto.remove; 737 | } 738 | 739 | // Normalize views to exist on either instance or options, default to 740 | // options. 741 | views = options.views || view.views; 742 | 743 | // Set the internal views, only if selectors have been provided. 744 | if (_.keys(views).length) { 745 | // Keep original object declared containing Views. 746 | declaredViews = views; 747 | 748 | // Reset the property to avoid duplication or overwritting. 749 | view.views = {}; 750 | 751 | // Set the declared Views. 752 | view.setViews(declaredViews); 753 | } 754 | 755 | // If a template is passed use that instead. 756 | if (view.options.template) { 757 | view.options.template = options.template; 758 | // Ensure the template is mapped over. 759 | } else if (view.template) { 760 | options.template = view.template; 761 | } 762 | }); 763 | } 764 | }); 765 | 766 | // Convenience assignment to make creating Layout's slightly shorter. 767 | Backbone.Layout = LayoutManager; 768 | // Tack on the version. 769 | LayoutManager.VERSION = "0.8.6"; 770 | 771 | // Override _configure to provide extra functionality that is necessary in 772 | // order for the render function reference to be bound during initialize. 773 | Backbone.View.prototype._configure = function(options) { 774 | var noel, retVal; 775 | 776 | // Remove the container element provided by Backbone. 777 | if ("el" in options ? options.el === false : this.el === false) { 778 | noel = true; 779 | } 780 | 781 | // Run the original _configure. 782 | retVal = _configure.apply(this, arguments); 783 | 784 | // If manage is set, do it! 785 | if (options.manage || this.manage) { 786 | // Set up this View. 787 | LayoutManager.setupView(this); 788 | } 789 | 790 | // Assign the `noel` property once we're sure the View we're working with is 791 | // managed by LayoutManager. 792 | if (this.__manager__) { 793 | this.__manager__.noel = noel; 794 | this.__manager__.suppressWarnings = options.suppressWarnings; 795 | } 796 | 797 | // Act like nothing happened. 798 | return retVal; 799 | }; 800 | 801 | // Default configuration options; designed to be overriden. 802 | LayoutManager.prototype.options = { 803 | // Prefix template/layout paths. 804 | prefix: "", 805 | 806 | // Can be used to supply a different deferred implementation. 807 | deferred: function() { 808 | return $.Deferred(); 809 | }, 810 | 811 | // Fetch is passed a path and is expected to return template contents as a 812 | // function or string. 813 | fetch: function(path) { 814 | return _.template($(path).html()); 815 | }, 816 | 817 | // This is the most common way you will want to partially apply a view into 818 | // a layout. 819 | partial: function($root, $el, rentManager, manager) { 820 | // If selector is specified, attempt to find it. 821 | if (manager.selector) { 822 | if (rentManager.noel) { 823 | var $filtered = $root.filter(manager.selector); 824 | $root = $filtered.length ? $filtered : $root.find(manager.selector); 825 | } else { 826 | $root = $root.find(manager.selector); 827 | } 828 | } 829 | 830 | // Use the insert method if insert argument is true. 831 | if (manager.insert) { 832 | this.insert($root, $el); 833 | } else { 834 | this.html($root, $el); 835 | } 836 | }, 837 | 838 | // Override this with a custom HTML method, passed a root element and content 839 | // (a jQuery collection or a string) to replace the innerHTML with. 840 | html: function($root, content) { 841 | $root.html(content); 842 | }, 843 | 844 | // Very similar to HTML except this one will appendChild by default. 845 | insert: function($root, $el) { 846 | $root.append($el); 847 | }, 848 | 849 | // Return a deferred for when all promises resolve/reject. 850 | when: function(promises) { 851 | return $.when.apply(null, promises); 852 | }, 853 | 854 | // By default, render using underscore's templating. 855 | render: function(template, context) { 856 | return template(context); 857 | }, 858 | 859 | // A method to determine if a View contains another. 860 | contains: function(parent, child) { 861 | return $.contains(parent, child); 862 | } 863 | }; 864 | 865 | // Maintain a list of the keys at define time. 866 | keys = _.keys(LayoutManager.prototype.options); 867 | 868 | })(typeof global === "object" ? global : this); 869 | -------------------------------------------------------------------------------- /vendor/jam/underscore/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.4.3 2 | // http://underscorejs.org 3 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // Baseline setup 9 | // -------------- 10 | 11 | // Establish the root object, `window` in the browser, or `global` on the server. 12 | var root = this; 13 | 14 | // Save the previous value of the `_` variable. 15 | var previousUnderscore = root._; 16 | 17 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | // Create quick reference variables for speed access to core prototypes. 24 | var push = ArrayProto.push, 25 | slice = ArrayProto.slice, 26 | concat = ArrayProto.concat, 27 | toString = ObjProto.toString, 28 | hasOwnProperty = ObjProto.hasOwnProperty; 29 | 30 | // All **ECMAScript 5** native function implementations that we hope to use 31 | // are declared here. 32 | var 33 | nativeForEach = ArrayProto.forEach, 34 | nativeMap = ArrayProto.map, 35 | nativeReduce = ArrayProto.reduce, 36 | nativeReduceRight = ArrayProto.reduceRight, 37 | nativeFilter = ArrayProto.filter, 38 | nativeEvery = ArrayProto.every, 39 | nativeSome = ArrayProto.some, 40 | nativeIndexOf = ArrayProto.indexOf, 41 | nativeLastIndexOf = ArrayProto.lastIndexOf, 42 | nativeIsArray = Array.isArray, 43 | nativeKeys = Object.keys, 44 | nativeBind = FuncProto.bind; 45 | 46 | // Create a safe reference to the Underscore object for use below. 47 | var _ = function(obj) { 48 | if (obj instanceof _) return obj; 49 | if (!(this instanceof _)) return new _(obj); 50 | this._wrapped = obj; 51 | }; 52 | 53 | // Export the Underscore object for **Node.js**, with 54 | // backwards-compatibility for the old `require()` API. If we're in 55 | // the browser, add `_` as a global object via a string identifier, 56 | // for Closure Compiler "advanced" mode. 57 | if (typeof exports !== 'undefined') { 58 | if (typeof module !== 'undefined' && module.exports) { 59 | exports = module.exports = _; 60 | } 61 | exports._ = _; 62 | } else { 63 | root._ = _; 64 | } 65 | 66 | // Current version. 67 | _.VERSION = '1.4.3'; 68 | 69 | // Collection Functions 70 | // -------------------- 71 | 72 | // The cornerstone, an `each` implementation, aka `forEach`. 73 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 74 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 75 | var each = _.each = _.forEach = function(obj, iterator, context) { 76 | if (obj == null) return; 77 | if (nativeForEach && obj.forEach === nativeForEach) { 78 | obj.forEach(iterator, context); 79 | } else if (obj.length === +obj.length) { 80 | for (var i = 0, l = obj.length; i < l; i++) { 81 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 82 | } 83 | } else { 84 | for (var key in obj) { 85 | if (_.has(obj, key)) { 86 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 87 | } 88 | } 89 | } 90 | }; 91 | 92 | // Return the results of applying the iterator to each element. 93 | // Delegates to **ECMAScript 5**'s native `map` if available. 94 | _.map = _.collect = function(obj, iterator, context) { 95 | var results = []; 96 | if (obj == null) return results; 97 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 98 | each(obj, function(value, index, list) { 99 | results[results.length] = iterator.call(context, value, index, list); 100 | }); 101 | return results; 102 | }; 103 | 104 | var reduceError = 'Reduce of empty array with no initial value'; 105 | 106 | // **Reduce** builds up a single result from a list of values, aka `inject`, 107 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 108 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 109 | var initial = arguments.length > 2; 110 | if (obj == null) obj = []; 111 | if (nativeReduce && obj.reduce === nativeReduce) { 112 | if (context) iterator = _.bind(iterator, context); 113 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 114 | } 115 | each(obj, function(value, index, list) { 116 | if (!initial) { 117 | memo = value; 118 | initial = true; 119 | } else { 120 | memo = iterator.call(context, memo, value, index, list); 121 | } 122 | }); 123 | if (!initial) throw new TypeError(reduceError); 124 | return memo; 125 | }; 126 | 127 | // The right-associative version of reduce, also known as `foldr`. 128 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 129 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 130 | var initial = arguments.length > 2; 131 | if (obj == null) obj = []; 132 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 133 | if (context) iterator = _.bind(iterator, context); 134 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 135 | } 136 | var length = obj.length; 137 | if (length !== +length) { 138 | var keys = _.keys(obj); 139 | length = keys.length; 140 | } 141 | each(obj, function(value, index, list) { 142 | index = keys ? keys[--length] : --length; 143 | if (!initial) { 144 | memo = obj[index]; 145 | initial = true; 146 | } else { 147 | memo = iterator.call(context, memo, obj[index], index, list); 148 | } 149 | }); 150 | if (!initial) throw new TypeError(reduceError); 151 | return memo; 152 | }; 153 | 154 | // Return the first value which passes a truth test. Aliased as `detect`. 155 | _.find = _.detect = function(obj, iterator, context) { 156 | var result; 157 | any(obj, function(value, index, list) { 158 | if (iterator.call(context, value, index, list)) { 159 | result = value; 160 | return true; 161 | } 162 | }); 163 | return result; 164 | }; 165 | 166 | // Return all the elements that pass a truth test. 167 | // Delegates to **ECMAScript 5**'s native `filter` if available. 168 | // Aliased as `select`. 169 | _.filter = _.select = function(obj, iterator, context) { 170 | var results = []; 171 | if (obj == null) return results; 172 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 173 | each(obj, function(value, index, list) { 174 | if (iterator.call(context, value, index, list)) results[results.length] = value; 175 | }); 176 | return results; 177 | }; 178 | 179 | // Return all the elements for which a truth test fails. 180 | _.reject = function(obj, iterator, context) { 181 | return _.filter(obj, function(value, index, list) { 182 | return !iterator.call(context, value, index, list); 183 | }, context); 184 | }; 185 | 186 | // Determine whether all of the elements match a truth test. 187 | // Delegates to **ECMAScript 5**'s native `every` if available. 188 | // Aliased as `all`. 189 | _.every = _.all = function(obj, iterator, context) { 190 | iterator || (iterator = _.identity); 191 | var result = true; 192 | if (obj == null) return result; 193 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 194 | each(obj, function(value, index, list) { 195 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 196 | }); 197 | return !!result; 198 | }; 199 | 200 | // Determine if at least one element in the object matches a truth test. 201 | // Delegates to **ECMAScript 5**'s native `some` if available. 202 | // Aliased as `any`. 203 | var any = _.some = _.any = function(obj, iterator, context) { 204 | iterator || (iterator = _.identity); 205 | var result = false; 206 | if (obj == null) return result; 207 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 208 | each(obj, function(value, index, list) { 209 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 210 | }); 211 | return !!result; 212 | }; 213 | 214 | // Determine if the array or object contains a given value (using `===`). 215 | // Aliased as `include`. 216 | _.contains = _.include = function(obj, target) { 217 | if (obj == null) return false; 218 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 219 | return any(obj, function(value) { 220 | return value === target; 221 | }); 222 | }; 223 | 224 | // Invoke a method (with arguments) on every item in a collection. 225 | _.invoke = function(obj, method) { 226 | var args = slice.call(arguments, 2); 227 | return _.map(obj, function(value) { 228 | return (_.isFunction(method) ? method : value[method]).apply(value, args); 229 | }); 230 | }; 231 | 232 | // Convenience version of a common use case of `map`: fetching a property. 233 | _.pluck = function(obj, key) { 234 | return _.map(obj, function(value){ return value[key]; }); 235 | }; 236 | 237 | // Convenience version of a common use case of `filter`: selecting only objects 238 | // with specific `key:value` pairs. 239 | _.where = function(obj, attrs) { 240 | if (_.isEmpty(attrs)) return []; 241 | return _.filter(obj, function(value) { 242 | for (var key in attrs) { 243 | if (attrs[key] !== value[key]) return false; 244 | } 245 | return true; 246 | }); 247 | }; 248 | 249 | // Return the maximum element or (element-based computation). 250 | // Can't optimize arrays of integers longer than 65,535 elements. 251 | // See: https://bugs.webkit.org/show_bug.cgi?id=80797 252 | _.max = function(obj, iterator, context) { 253 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 254 | return Math.max.apply(Math, obj); 255 | } 256 | if (!iterator && _.isEmpty(obj)) return -Infinity; 257 | var result = {computed : -Infinity, value: -Infinity}; 258 | each(obj, function(value, index, list) { 259 | var computed = iterator ? iterator.call(context, value, index, list) : value; 260 | computed >= result.computed && (result = {value : value, computed : computed}); 261 | }); 262 | return result.value; 263 | }; 264 | 265 | // Return the minimum element (or element-based computation). 266 | _.min = function(obj, iterator, context) { 267 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 268 | return Math.min.apply(Math, obj); 269 | } 270 | if (!iterator && _.isEmpty(obj)) return Infinity; 271 | var result = {computed : Infinity, value: Infinity}; 272 | each(obj, function(value, index, list) { 273 | var computed = iterator ? iterator.call(context, value, index, list) : value; 274 | computed < result.computed && (result = {value : value, computed : computed}); 275 | }); 276 | return result.value; 277 | }; 278 | 279 | // Shuffle an array. 280 | _.shuffle = function(obj) { 281 | var rand; 282 | var index = 0; 283 | var shuffled = []; 284 | each(obj, function(value) { 285 | rand = _.random(index++); 286 | shuffled[index - 1] = shuffled[rand]; 287 | shuffled[rand] = value; 288 | }); 289 | return shuffled; 290 | }; 291 | 292 | // An internal function to generate lookup iterators. 293 | var lookupIterator = function(value) { 294 | return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 295 | }; 296 | 297 | // Sort the object's values by a criterion produced by an iterator. 298 | _.sortBy = function(obj, value, context) { 299 | var iterator = lookupIterator(value); 300 | return _.pluck(_.map(obj, function(value, index, list) { 301 | return { 302 | value : value, 303 | index : index, 304 | criteria : iterator.call(context, value, index, list) 305 | }; 306 | }).sort(function(left, right) { 307 | var a = left.criteria; 308 | var b = right.criteria; 309 | if (a !== b) { 310 | if (a > b || a === void 0) return 1; 311 | if (a < b || b === void 0) return -1; 312 | } 313 | return left.index < right.index ? -1 : 1; 314 | }), 'value'); 315 | }; 316 | 317 | // An internal function used for aggregate "group by" operations. 318 | var group = function(obj, value, context, behavior) { 319 | var result = {}; 320 | var iterator = lookupIterator(value || _.identity); 321 | each(obj, function(value, index) { 322 | var key = iterator.call(context, value, index, obj); 323 | behavior(result, key, value); 324 | }); 325 | return result; 326 | }; 327 | 328 | // Groups the object's values by a criterion. Pass either a string attribute 329 | // to group by, or a function that returns the criterion. 330 | _.groupBy = function(obj, value, context) { 331 | return group(obj, value, context, function(result, key, value) { 332 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 333 | }); 334 | }; 335 | 336 | // Counts instances of an object that group by a certain criterion. Pass 337 | // either a string attribute to count by, or a function that returns the 338 | // criterion. 339 | _.countBy = function(obj, value, context) { 340 | return group(obj, value, context, function(result, key) { 341 | if (!_.has(result, key)) result[key] = 0; 342 | result[key]++; 343 | }); 344 | }; 345 | 346 | // Use a comparator function to figure out the smallest index at which 347 | // an object should be inserted so as to maintain order. Uses binary search. 348 | _.sortedIndex = function(array, obj, iterator, context) { 349 | iterator = iterator == null ? _.identity : lookupIterator(iterator); 350 | var value = iterator.call(context, obj); 351 | var low = 0, high = array.length; 352 | while (low < high) { 353 | var mid = (low + high) >>> 1; 354 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 355 | } 356 | return low; 357 | }; 358 | 359 | // Safely convert anything iterable into a real, live array. 360 | _.toArray = function(obj) { 361 | if (!obj) return []; 362 | if (_.isArray(obj)) return slice.call(obj); 363 | if (obj.length === +obj.length) return _.map(obj, _.identity); 364 | return _.values(obj); 365 | }; 366 | 367 | // Return the number of elements in an object. 368 | _.size = function(obj) { 369 | if (obj == null) return 0; 370 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 371 | }; 372 | 373 | // Array Functions 374 | // --------------- 375 | 376 | // Get the first element of an array. Passing **n** will return the first N 377 | // values in the array. Aliased as `head` and `take`. The **guard** check 378 | // allows it to work with `_.map`. 379 | _.first = _.head = _.take = function(array, n, guard) { 380 | if (array == null) return void 0; 381 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 382 | }; 383 | 384 | // Returns everything but the last entry of the array. Especially useful on 385 | // the arguments object. Passing **n** will return all the values in 386 | // the array, excluding the last N. The **guard** check allows it to work with 387 | // `_.map`. 388 | _.initial = function(array, n, guard) { 389 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 390 | }; 391 | 392 | // Get the last element of an array. Passing **n** will return the last N 393 | // values in the array. The **guard** check allows it to work with `_.map`. 394 | _.last = function(array, n, guard) { 395 | if (array == null) return void 0; 396 | if ((n != null) && !guard) { 397 | return slice.call(array, Math.max(array.length - n, 0)); 398 | } else { 399 | return array[array.length - 1]; 400 | } 401 | }; 402 | 403 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 404 | // Especially useful on the arguments object. Passing an **n** will return 405 | // the rest N values in the array. The **guard** 406 | // check allows it to work with `_.map`. 407 | _.rest = _.tail = _.drop = function(array, n, guard) { 408 | return slice.call(array, (n == null) || guard ? 1 : n); 409 | }; 410 | 411 | // Trim out all falsy values from an array. 412 | _.compact = function(array) { 413 | return _.filter(array, _.identity); 414 | }; 415 | 416 | // Internal implementation of a recursive `flatten` function. 417 | var flatten = function(input, shallow, output) { 418 | each(input, function(value) { 419 | if (_.isArray(value)) { 420 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 421 | } else { 422 | output.push(value); 423 | } 424 | }); 425 | return output; 426 | }; 427 | 428 | // Return a completely flattened version of an array. 429 | _.flatten = function(array, shallow) { 430 | return flatten(array, shallow, []); 431 | }; 432 | 433 | // Return a version of the array that does not contain the specified value(s). 434 | _.without = function(array) { 435 | return _.difference(array, slice.call(arguments, 1)); 436 | }; 437 | 438 | // Produce a duplicate-free version of the array. If the array has already 439 | // been sorted, you have the option of using a faster algorithm. 440 | // Aliased as `unique`. 441 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 442 | if (_.isFunction(isSorted)) { 443 | context = iterator; 444 | iterator = isSorted; 445 | isSorted = false; 446 | } 447 | var initial = iterator ? _.map(array, iterator, context) : array; 448 | var results = []; 449 | var seen = []; 450 | each(initial, function(value, index) { 451 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 452 | seen.push(value); 453 | results.push(array[index]); 454 | } 455 | }); 456 | return results; 457 | }; 458 | 459 | // Produce an array that contains the union: each distinct element from all of 460 | // the passed-in arrays. 461 | _.union = function() { 462 | return _.uniq(concat.apply(ArrayProto, arguments)); 463 | }; 464 | 465 | // Produce an array that contains every item shared between all the 466 | // passed-in arrays. 467 | _.intersection = function(array) { 468 | var rest = slice.call(arguments, 1); 469 | return _.filter(_.uniq(array), function(item) { 470 | return _.every(rest, function(other) { 471 | return _.indexOf(other, item) >= 0; 472 | }); 473 | }); 474 | }; 475 | 476 | // Take the difference between one array and a number of other arrays. 477 | // Only the elements present in just the first array will remain. 478 | _.difference = function(array) { 479 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 480 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 481 | }; 482 | 483 | // Zip together multiple lists into a single array -- elements that share 484 | // an index go together. 485 | _.zip = function() { 486 | var args = slice.call(arguments); 487 | var length = _.max(_.pluck(args, 'length')); 488 | var results = new Array(length); 489 | for (var i = 0; i < length; i++) { 490 | results[i] = _.pluck(args, "" + i); 491 | } 492 | return results; 493 | }; 494 | 495 | // Converts lists into objects. Pass either a single array of `[key, value]` 496 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 497 | // the corresponding values. 498 | _.object = function(list, values) { 499 | if (list == null) return {}; 500 | var result = {}; 501 | for (var i = 0, l = list.length; i < l; i++) { 502 | if (values) { 503 | result[list[i]] = values[i]; 504 | } else { 505 | result[list[i][0]] = list[i][1]; 506 | } 507 | } 508 | return result; 509 | }; 510 | 511 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 512 | // we need this function. Return the position of the first occurrence of an 513 | // item in an array, or -1 if the item is not included in the array. 514 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 515 | // If the array is large and already in sort order, pass `true` 516 | // for **isSorted** to use binary search. 517 | _.indexOf = function(array, item, isSorted) { 518 | if (array == null) return -1; 519 | var i = 0, l = array.length; 520 | if (isSorted) { 521 | if (typeof isSorted == 'number') { 522 | i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); 523 | } else { 524 | i = _.sortedIndex(array, item); 525 | return array[i] === item ? i : -1; 526 | } 527 | } 528 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 529 | for (; i < l; i++) if (array[i] === item) return i; 530 | return -1; 531 | }; 532 | 533 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 534 | _.lastIndexOf = function(array, item, from) { 535 | if (array == null) return -1; 536 | var hasIndex = from != null; 537 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 538 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 539 | } 540 | var i = (hasIndex ? from : array.length); 541 | while (i--) if (array[i] === item) return i; 542 | return -1; 543 | }; 544 | 545 | // Generate an integer Array containing an arithmetic progression. A port of 546 | // the native Python `range()` function. See 547 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 548 | _.range = function(start, stop, step) { 549 | if (arguments.length <= 1) { 550 | stop = start || 0; 551 | start = 0; 552 | } 553 | step = arguments[2] || 1; 554 | 555 | var len = Math.max(Math.ceil((stop - start) / step), 0); 556 | var idx = 0; 557 | var range = new Array(len); 558 | 559 | while(idx < len) { 560 | range[idx++] = start; 561 | start += step; 562 | } 563 | 564 | return range; 565 | }; 566 | 567 | // Function (ahem) Functions 568 | // ------------------ 569 | 570 | // Reusable constructor function for prototype setting. 571 | var ctor = function(){}; 572 | 573 | // Create a function bound to a given object (assigning `this`, and arguments, 574 | // optionally). Binding with arguments is also known as `curry`. 575 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 576 | // We check for `func.bind` first, to fail fast when `func` is undefined. 577 | _.bind = function(func, context) { 578 | var args, bound; 579 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 580 | if (!_.isFunction(func)) throw new TypeError; 581 | args = slice.call(arguments, 2); 582 | return bound = function() { 583 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 584 | ctor.prototype = func.prototype; 585 | var self = new ctor; 586 | ctor.prototype = null; 587 | var result = func.apply(self, args.concat(slice.call(arguments))); 588 | if (Object(result) === result) return result; 589 | return self; 590 | }; 591 | }; 592 | 593 | // Bind all of an object's methods to that object. Useful for ensuring that 594 | // all callbacks defined on an object belong to it. 595 | _.bindAll = function(obj) { 596 | var funcs = slice.call(arguments, 1); 597 | if (funcs.length == 0) funcs = _.functions(obj); 598 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 599 | return obj; 600 | }; 601 | 602 | // Memoize an expensive function by storing its results. 603 | _.memoize = function(func, hasher) { 604 | var memo = {}; 605 | hasher || (hasher = _.identity); 606 | return function() { 607 | var key = hasher.apply(this, arguments); 608 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 609 | }; 610 | }; 611 | 612 | // Delays a function for the given number of milliseconds, and then calls 613 | // it with the arguments supplied. 614 | _.delay = function(func, wait) { 615 | var args = slice.call(arguments, 2); 616 | return setTimeout(function(){ return func.apply(null, args); }, wait); 617 | }; 618 | 619 | // Defers a function, scheduling it to run after the current call stack has 620 | // cleared. 621 | _.defer = function(func) { 622 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 623 | }; 624 | 625 | // Returns a function, that, when invoked, will only be triggered at most once 626 | // during a given window of time. 627 | _.throttle = function(func, wait) { 628 | var context, args, timeout, result; 629 | var previous = 0; 630 | var later = function() { 631 | previous = new Date; 632 | timeout = null; 633 | result = func.apply(context, args); 634 | }; 635 | return function() { 636 | var now = new Date; 637 | var remaining = wait - (now - previous); 638 | context = this; 639 | args = arguments; 640 | if (remaining <= 0) { 641 | clearTimeout(timeout); 642 | timeout = null; 643 | previous = now; 644 | result = func.apply(context, args); 645 | } else if (!timeout) { 646 | timeout = setTimeout(later, remaining); 647 | } 648 | return result; 649 | }; 650 | }; 651 | 652 | // Returns a function, that, as long as it continues to be invoked, will not 653 | // be triggered. The function will be called after it stops being called for 654 | // N milliseconds. If `immediate` is passed, trigger the function on the 655 | // leading edge, instead of the trailing. 656 | _.debounce = function(func, wait, immediate) { 657 | var timeout, result; 658 | return function() { 659 | var context = this, args = arguments; 660 | var later = function() { 661 | timeout = null; 662 | if (!immediate) result = func.apply(context, args); 663 | }; 664 | var callNow = immediate && !timeout; 665 | clearTimeout(timeout); 666 | timeout = setTimeout(later, wait); 667 | if (callNow) result = func.apply(context, args); 668 | return result; 669 | }; 670 | }; 671 | 672 | // Returns a function that will be executed at most one time, no matter how 673 | // often you call it. Useful for lazy initialization. 674 | _.once = function(func) { 675 | var ran = false, memo; 676 | return function() { 677 | if (ran) return memo; 678 | ran = true; 679 | memo = func.apply(this, arguments); 680 | func = null; 681 | return memo; 682 | }; 683 | }; 684 | 685 | // Returns the first function passed as an argument to the second, 686 | // allowing you to adjust arguments, run code before and after, and 687 | // conditionally execute the original function. 688 | _.wrap = function(func, wrapper) { 689 | return function() { 690 | var args = [func]; 691 | push.apply(args, arguments); 692 | return wrapper.apply(this, args); 693 | }; 694 | }; 695 | 696 | // Returns a function that is the composition of a list of functions, each 697 | // consuming the return value of the function that follows. 698 | _.compose = function() { 699 | var funcs = arguments; 700 | return function() { 701 | var args = arguments; 702 | for (var i = funcs.length - 1; i >= 0; i--) { 703 | args = [funcs[i].apply(this, args)]; 704 | } 705 | return args[0]; 706 | }; 707 | }; 708 | 709 | // Returns a function that will only be executed after being called N times. 710 | _.after = function(times, func) { 711 | if (times <= 0) return func(); 712 | return function() { 713 | if (--times < 1) { 714 | return func.apply(this, arguments); 715 | } 716 | }; 717 | }; 718 | 719 | // Object Functions 720 | // ---------------- 721 | 722 | // Retrieve the names of an object's properties. 723 | // Delegates to **ECMAScript 5**'s native `Object.keys` 724 | _.keys = nativeKeys || function(obj) { 725 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 726 | var keys = []; 727 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 728 | return keys; 729 | }; 730 | 731 | // Retrieve the values of an object's properties. 732 | _.values = function(obj) { 733 | var values = []; 734 | for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); 735 | return values; 736 | }; 737 | 738 | // Convert an object into a list of `[key, value]` pairs. 739 | _.pairs = function(obj) { 740 | var pairs = []; 741 | for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); 742 | return pairs; 743 | }; 744 | 745 | // Invert the keys and values of an object. The values must be serializable. 746 | _.invert = function(obj) { 747 | var result = {}; 748 | for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; 749 | return result; 750 | }; 751 | 752 | // Return a sorted list of the function names available on the object. 753 | // Aliased as `methods` 754 | _.functions = _.methods = function(obj) { 755 | var names = []; 756 | for (var key in obj) { 757 | if (_.isFunction(obj[key])) names.push(key); 758 | } 759 | return names.sort(); 760 | }; 761 | 762 | // Extend a given object with all the properties in passed-in object(s). 763 | _.extend = function(obj) { 764 | each(slice.call(arguments, 1), function(source) { 765 | if (source) { 766 | for (var prop in source) { 767 | obj[prop] = source[prop]; 768 | } 769 | } 770 | }); 771 | return obj; 772 | }; 773 | 774 | // Return a copy of the object only containing the whitelisted properties. 775 | _.pick = function(obj) { 776 | var copy = {}; 777 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 778 | each(keys, function(key) { 779 | if (key in obj) copy[key] = obj[key]; 780 | }); 781 | return copy; 782 | }; 783 | 784 | // Return a copy of the object without the blacklisted properties. 785 | _.omit = function(obj) { 786 | var copy = {}; 787 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 788 | for (var key in obj) { 789 | if (!_.contains(keys, key)) copy[key] = obj[key]; 790 | } 791 | return copy; 792 | }; 793 | 794 | // Fill in a given object with default properties. 795 | _.defaults = function(obj) { 796 | each(slice.call(arguments, 1), function(source) { 797 | if (source) { 798 | for (var prop in source) { 799 | if (obj[prop] == null) obj[prop] = source[prop]; 800 | } 801 | } 802 | }); 803 | return obj; 804 | }; 805 | 806 | // Create a (shallow-cloned) duplicate of an object. 807 | _.clone = function(obj) { 808 | if (!_.isObject(obj)) return obj; 809 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 810 | }; 811 | 812 | // Invokes interceptor with the obj, and then returns obj. 813 | // The primary purpose of this method is to "tap into" a method chain, in 814 | // order to perform operations on intermediate results within the chain. 815 | _.tap = function(obj, interceptor) { 816 | interceptor(obj); 817 | return obj; 818 | }; 819 | 820 | // Internal recursive comparison function for `isEqual`. 821 | var eq = function(a, b, aStack, bStack) { 822 | // Identical objects are equal. `0 === -0`, but they aren't identical. 823 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 824 | if (a === b) return a !== 0 || 1 / a == 1 / b; 825 | // A strict comparison is necessary because `null == undefined`. 826 | if (a == null || b == null) return a === b; 827 | // Unwrap any wrapped objects. 828 | if (a instanceof _) a = a._wrapped; 829 | if (b instanceof _) b = b._wrapped; 830 | // Compare `[[Class]]` names. 831 | var className = toString.call(a); 832 | if (className != toString.call(b)) return false; 833 | switch (className) { 834 | // Strings, numbers, dates, and booleans are compared by value. 835 | case '[object String]': 836 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 837 | // equivalent to `new String("5")`. 838 | return a == String(b); 839 | case '[object Number]': 840 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 841 | // other numeric values. 842 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 843 | case '[object Date]': 844 | case '[object Boolean]': 845 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 846 | // millisecond representations. Note that invalid dates with millisecond representations 847 | // of `NaN` are not equivalent. 848 | return +a == +b; 849 | // RegExps are compared by their source patterns and flags. 850 | case '[object RegExp]': 851 | return a.source == b.source && 852 | a.global == b.global && 853 | a.multiline == b.multiline && 854 | a.ignoreCase == b.ignoreCase; 855 | } 856 | if (typeof a != 'object' || typeof b != 'object') return false; 857 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 858 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 859 | var length = aStack.length; 860 | while (length--) { 861 | // Linear search. Performance is inversely proportional to the number of 862 | // unique nested structures. 863 | if (aStack[length] == a) return bStack[length] == b; 864 | } 865 | // Add the first object to the stack of traversed objects. 866 | aStack.push(a); 867 | bStack.push(b); 868 | var size = 0, result = true; 869 | // Recursively compare objects and arrays. 870 | if (className == '[object Array]') { 871 | // Compare array lengths to determine if a deep comparison is necessary. 872 | size = a.length; 873 | result = size == b.length; 874 | if (result) { 875 | // Deep compare the contents, ignoring non-numeric properties. 876 | while (size--) { 877 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 878 | } 879 | } 880 | } else { 881 | // Objects with different constructors are not equivalent, but `Object`s 882 | // from different frames are. 883 | var aCtor = a.constructor, bCtor = b.constructor; 884 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 885 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 886 | return false; 887 | } 888 | // Deep compare objects. 889 | for (var key in a) { 890 | if (_.has(a, key)) { 891 | // Count the expected number of properties. 892 | size++; 893 | // Deep compare each member. 894 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 895 | } 896 | } 897 | // Ensure that both objects contain the same number of properties. 898 | if (result) { 899 | for (key in b) { 900 | if (_.has(b, key) && !(size--)) break; 901 | } 902 | result = !size; 903 | } 904 | } 905 | // Remove the first object from the stack of traversed objects. 906 | aStack.pop(); 907 | bStack.pop(); 908 | return result; 909 | }; 910 | 911 | // Perform a deep comparison to check if two objects are equal. 912 | _.isEqual = function(a, b) { 913 | return eq(a, b, [], []); 914 | }; 915 | 916 | // Is a given array, string, or object empty? 917 | // An "empty" object has no enumerable own-properties. 918 | _.isEmpty = function(obj) { 919 | if (obj == null) return true; 920 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 921 | for (var key in obj) if (_.has(obj, key)) return false; 922 | return true; 923 | }; 924 | 925 | // Is a given value a DOM element? 926 | _.isElement = function(obj) { 927 | return !!(obj && obj.nodeType === 1); 928 | }; 929 | 930 | // Is a given value an array? 931 | // Delegates to ECMA5's native Array.isArray 932 | _.isArray = nativeIsArray || function(obj) { 933 | return toString.call(obj) == '[object Array]'; 934 | }; 935 | 936 | // Is a given variable an object? 937 | _.isObject = function(obj) { 938 | return obj === Object(obj); 939 | }; 940 | 941 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 942 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 943 | _['is' + name] = function(obj) { 944 | return toString.call(obj) == '[object ' + name + ']'; 945 | }; 946 | }); 947 | 948 | // Define a fallback version of the method in browsers (ahem, IE), where 949 | // there isn't any inspectable "Arguments" type. 950 | if (!_.isArguments(arguments)) { 951 | _.isArguments = function(obj) { 952 | return !!(obj && _.has(obj, 'callee')); 953 | }; 954 | } 955 | 956 | // Optimize `isFunction` if appropriate. 957 | if (typeof (/./) !== 'function') { 958 | _.isFunction = function(obj) { 959 | return typeof obj === 'function'; 960 | }; 961 | } 962 | 963 | // Is a given object a finite number? 964 | _.isFinite = function(obj) { 965 | return isFinite(obj) && !isNaN(parseFloat(obj)); 966 | }; 967 | 968 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 969 | _.isNaN = function(obj) { 970 | return _.isNumber(obj) && obj != +obj; 971 | }; 972 | 973 | // Is a given value a boolean? 974 | _.isBoolean = function(obj) { 975 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 976 | }; 977 | 978 | // Is a given value equal to null? 979 | _.isNull = function(obj) { 980 | return obj === null; 981 | }; 982 | 983 | // Is a given variable undefined? 984 | _.isUndefined = function(obj) { 985 | return obj === void 0; 986 | }; 987 | 988 | // Shortcut function for checking if an object has a given property directly 989 | // on itself (in other words, not on a prototype). 990 | _.has = function(obj, key) { 991 | return hasOwnProperty.call(obj, key); 992 | }; 993 | 994 | // Utility Functions 995 | // ----------------- 996 | 997 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 998 | // previous owner. Returns a reference to the Underscore object. 999 | _.noConflict = function() { 1000 | root._ = previousUnderscore; 1001 | return this; 1002 | }; 1003 | 1004 | // Keep the identity function around for default iterators. 1005 | _.identity = function(value) { 1006 | return value; 1007 | }; 1008 | 1009 | // Run a function **n** times. 1010 | _.times = function(n, iterator, context) { 1011 | var accum = Array(n); 1012 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); 1013 | return accum; 1014 | }; 1015 | 1016 | // Return a random integer between min and max (inclusive). 1017 | _.random = function(min, max) { 1018 | if (max == null) { 1019 | max = min; 1020 | min = 0; 1021 | } 1022 | return min + (0 | Math.random() * (max - min + 1)); 1023 | }; 1024 | 1025 | // List of HTML entities for escaping. 1026 | var entityMap = { 1027 | escape: { 1028 | '&': '&', 1029 | '<': '<', 1030 | '>': '>', 1031 | '"': '"', 1032 | "'": ''', 1033 | '/': '/' 1034 | } 1035 | }; 1036 | entityMap.unescape = _.invert(entityMap.escape); 1037 | 1038 | // Regexes containing the keys and values listed immediately above. 1039 | var entityRegexes = { 1040 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1041 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1042 | }; 1043 | 1044 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1045 | _.each(['escape', 'unescape'], function(method) { 1046 | _[method] = function(string) { 1047 | if (string == null) return ''; 1048 | return ('' + string).replace(entityRegexes[method], function(match) { 1049 | return entityMap[method][match]; 1050 | }); 1051 | }; 1052 | }); 1053 | 1054 | // If the value of the named property is a function then invoke it; 1055 | // otherwise, return it. 1056 | _.result = function(object, property) { 1057 | if (object == null) return null; 1058 | var value = object[property]; 1059 | return _.isFunction(value) ? value.call(object) : value; 1060 | }; 1061 | 1062 | // Add your own custom functions to the Underscore object. 1063 | _.mixin = function(obj) { 1064 | each(_.functions(obj), function(name){ 1065 | var func = _[name] = obj[name]; 1066 | _.prototype[name] = function() { 1067 | var args = [this._wrapped]; 1068 | push.apply(args, arguments); 1069 | return result.call(this, func.apply(_, args)); 1070 | }; 1071 | }); 1072 | }; 1073 | 1074 | // Generate a unique integer id (unique within the entire client session). 1075 | // Useful for temporary DOM ids. 1076 | var idCounter = 0; 1077 | _.uniqueId = function(prefix) { 1078 | var id = '' + ++idCounter; 1079 | return prefix ? prefix + id : id; 1080 | }; 1081 | 1082 | // By default, Underscore uses ERB-style template delimiters, change the 1083 | // following template settings to use alternative delimiters. 1084 | _.templateSettings = { 1085 | evaluate : /<%([\s\S]+?)%>/g, 1086 | interpolate : /<%=([\s\S]+?)%>/g, 1087 | escape : /<%-([\s\S]+?)%>/g 1088 | }; 1089 | 1090 | // When customizing `templateSettings`, if you don't want to define an 1091 | // interpolation, evaluation or escaping regex, we need one that is 1092 | // guaranteed not to match. 1093 | var noMatch = /(.)^/; 1094 | 1095 | // Certain characters need to be escaped so that they can be put into a 1096 | // string literal. 1097 | var escapes = { 1098 | "'": "'", 1099 | '\\': '\\', 1100 | '\r': 'r', 1101 | '\n': 'n', 1102 | '\t': 't', 1103 | '\u2028': 'u2028', 1104 | '\u2029': 'u2029' 1105 | }; 1106 | 1107 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1108 | 1109 | // JavaScript micro-templating, similar to John Resig's implementation. 1110 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1111 | // and correctly escapes quotes within interpolated code. 1112 | _.template = function(text, data, settings) { 1113 | settings = _.defaults({}, settings, _.templateSettings); 1114 | 1115 | // Combine delimiters into one regular expression via alternation. 1116 | var matcher = new RegExp([ 1117 | (settings.escape || noMatch).source, 1118 | (settings.interpolate || noMatch).source, 1119 | (settings.evaluate || noMatch).source 1120 | ].join('|') + '|$', 'g'); 1121 | 1122 | // Compile the template source, escaping string literals appropriately. 1123 | var index = 0; 1124 | var source = "__p+='"; 1125 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1126 | source += text.slice(index, offset) 1127 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1128 | 1129 | if (escape) { 1130 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1131 | } 1132 | if (interpolate) { 1133 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1134 | } 1135 | if (evaluate) { 1136 | source += "';\n" + evaluate + "\n__p+='"; 1137 | } 1138 | index = offset + match.length; 1139 | return match; 1140 | }); 1141 | source += "';\n"; 1142 | 1143 | // If a variable is not specified, place data values in local scope. 1144 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1145 | 1146 | source = "var __t,__p='',__j=Array.prototype.join," + 1147 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1148 | source + "return __p;\n"; 1149 | 1150 | try { 1151 | var render = new Function(settings.variable || 'obj', '_', source); 1152 | } catch (e) { 1153 | e.source = source; 1154 | throw e; 1155 | } 1156 | 1157 | if (data) return render(data, _); 1158 | var template = function(data) { 1159 | return render.call(this, data, _); 1160 | }; 1161 | 1162 | // Provide the compiled function source as a convenience for precompilation. 1163 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1164 | 1165 | return template; 1166 | }; 1167 | 1168 | // Add a "chain" function, which will delegate to the wrapper. 1169 | _.chain = function(obj) { 1170 | return _(obj).chain(); 1171 | }; 1172 | 1173 | // OOP 1174 | // --------------- 1175 | // If Underscore is called as a function, it returns a wrapped object that 1176 | // can be used OO-style. This wrapper holds altered versions of all the 1177 | // underscore functions. Wrapped objects may be chained. 1178 | 1179 | // Helper function to continue chaining intermediate results. 1180 | var result = function(obj) { 1181 | return this._chain ? _(obj).chain() : obj; 1182 | }; 1183 | 1184 | // Add all of the Underscore functions to the wrapper object. 1185 | _.mixin(_); 1186 | 1187 | // Add all mutator Array functions to the wrapper. 1188 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1189 | var method = ArrayProto[name]; 1190 | _.prototype[name] = function() { 1191 | var obj = this._wrapped; 1192 | method.apply(obj, arguments); 1193 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1194 | return result.call(this, obj); 1195 | }; 1196 | }); 1197 | 1198 | // Add all accessor Array functions to the wrapper. 1199 | each(['concat', 'join', 'slice'], function(name) { 1200 | var method = ArrayProto[name]; 1201 | _.prototype[name] = function() { 1202 | return result.call(this, method.apply(this._wrapped, arguments)); 1203 | }; 1204 | }); 1205 | 1206 | _.extend(_.prototype, { 1207 | 1208 | // Start chaining a wrapped Underscore object. 1209 | chain: function() { 1210 | this._chain = true; 1211 | return this; 1212 | }, 1213 | 1214 | // Extracts the result from a wrapped and chained object. 1215 | value: function() { 1216 | return this._wrapped; 1217 | } 1218 | 1219 | }); 1220 | 1221 | }).call(this); 1222 | -------------------------------------------------------------------------------- /test/qunit/vendor/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit 1.2.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Mon Oct 31 14:00:02 UTC 2011 10 | * Last Commit: ee156923cdb01820e35e6bb579d5cf6bf55736d4 11 | */ 12 | 13 | (function(window) { 14 | 15 | var defined = { 16 | setTimeout: typeof window.setTimeout !== "undefined", 17 | sessionStorage: (function() { 18 | try { 19 | return !!sessionStorage.getItem; 20 | } catch(e) { 21 | return false; 22 | } 23 | })() 24 | }; 25 | 26 | var testId = 0, 27 | toString = Object.prototype.toString, 28 | hasOwn = Object.prototype.hasOwnProperty; 29 | 30 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 31 | this.name = name; 32 | this.testName = testName; 33 | this.expected = expected; 34 | this.testEnvironmentArg = testEnvironmentArg; 35 | this.async = async; 36 | this.callback = callback; 37 | this.assertions = []; 38 | }; 39 | Test.prototype = { 40 | init: function() { 41 | var tests = id("qunit-tests"); 42 | if (tests) { 43 | var b = document.createElement("strong"); 44 | b.innerHTML = "Running " + this.name; 45 | var li = document.createElement("li"); 46 | li.appendChild( b ); 47 | li.className = "running"; 48 | li.id = this.id = "test-output" + testId++; 49 | tests.appendChild( li ); 50 | } 51 | }, 52 | setup: function() { 53 | if (this.module != config.previousModule) { 54 | if ( config.previousModule ) { 55 | runLoggingCallbacks('moduleDone', QUnit, { 56 | name: config.previousModule, 57 | failed: config.moduleStats.bad, 58 | passed: config.moduleStats.all - config.moduleStats.bad, 59 | total: config.moduleStats.all 60 | } ); 61 | } 62 | config.previousModule = this.module; 63 | config.moduleStats = { all: 0, bad: 0 }; 64 | runLoggingCallbacks( 'moduleStart', QUnit, { 65 | name: this.module 66 | } ); 67 | } 68 | 69 | config.current = this; 70 | this.testEnvironment = extend({ 71 | setup: function() {}, 72 | teardown: function() {} 73 | }, this.moduleTestEnvironment); 74 | if (this.testEnvironmentArg) { 75 | extend(this.testEnvironment, this.testEnvironmentArg); 76 | } 77 | 78 | runLoggingCallbacks( 'testStart', QUnit, { 79 | name: this.testName, 80 | module: this.module 81 | }); 82 | 83 | // allow utility functions to access the current test environment 84 | // TODO why?? 85 | QUnit.current_testEnvironment = this.testEnvironment; 86 | 87 | try { 88 | if ( !config.pollution ) { 89 | saveGlobal(); 90 | } 91 | 92 | this.testEnvironment.setup.call(this.testEnvironment); 93 | } catch(e) { 94 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 95 | } 96 | }, 97 | run: function() { 98 | config.current = this; 99 | if ( this.async ) { 100 | QUnit.stop(); 101 | } 102 | 103 | if ( config.notrycatch ) { 104 | this.callback.call(this.testEnvironment); 105 | return; 106 | } 107 | try { 108 | this.callback.call(this.testEnvironment); 109 | } catch(e) { 110 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 111 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 112 | // else next test will carry the responsibility 113 | saveGlobal(); 114 | 115 | // Restart the tests if they're blocking 116 | if ( config.blocking ) { 117 | QUnit.start(); 118 | } 119 | } 120 | }, 121 | teardown: function() { 122 | config.current = this; 123 | try { 124 | this.testEnvironment.teardown.call(this.testEnvironment); 125 | checkPollution(); 126 | } catch(e) { 127 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 128 | } 129 | }, 130 | finish: function() { 131 | config.current = this; 132 | if ( this.expected != null && this.expected != this.assertions.length ) { 133 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 134 | } 135 | 136 | var good = 0, bad = 0, 137 | tests = id("qunit-tests"); 138 | 139 | config.stats.all += this.assertions.length; 140 | config.moduleStats.all += this.assertions.length; 141 | 142 | if ( tests ) { 143 | var ol = document.createElement("ol"); 144 | 145 | for ( var i = 0; i < this.assertions.length; i++ ) { 146 | var assertion = this.assertions[i]; 147 | 148 | var li = document.createElement("li"); 149 | li.className = assertion.result ? "pass" : "fail"; 150 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 151 | ol.appendChild( li ); 152 | 153 | if ( assertion.result ) { 154 | good++; 155 | } else { 156 | bad++; 157 | config.stats.bad++; 158 | config.moduleStats.bad++; 159 | } 160 | } 161 | 162 | // store result when possible 163 | if ( QUnit.config.reorder && defined.sessionStorage ) { 164 | if (bad) { 165 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); 166 | } else { 167 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); 168 | } 169 | } 170 | 171 | if (bad == 0) { 172 | ol.style.display = "none"; 173 | } 174 | 175 | var b = document.createElement("strong"); 176 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 177 | 178 | var a = document.createElement("a"); 179 | a.innerHTML = "Rerun"; 180 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 181 | 182 | addEvent(b, "click", function() { 183 | var next = b.nextSibling.nextSibling, 184 | display = next.style.display; 185 | next.style.display = display === "none" ? "block" : "none"; 186 | }); 187 | 188 | addEvent(b, "dblclick", function(e) { 189 | var target = e && e.target ? e.target : window.event.srcElement; 190 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 191 | target = target.parentNode; 192 | } 193 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 194 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 195 | } 196 | }); 197 | 198 | var li = id(this.id); 199 | li.className = bad ? "fail" : "pass"; 200 | li.removeChild( li.firstChild ); 201 | li.appendChild( b ); 202 | li.appendChild( a ); 203 | li.appendChild( ol ); 204 | 205 | } else { 206 | for ( var i = 0; i < this.assertions.length; i++ ) { 207 | if ( !this.assertions[i].result ) { 208 | bad++; 209 | config.stats.bad++; 210 | config.moduleStats.bad++; 211 | } 212 | } 213 | } 214 | 215 | try { 216 | QUnit.reset(); 217 | } catch(e) { 218 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 219 | } 220 | 221 | runLoggingCallbacks( 'testDone', QUnit, { 222 | name: this.testName, 223 | module: this.module, 224 | failed: bad, 225 | passed: this.assertions.length - bad, 226 | total: this.assertions.length 227 | } ); 228 | }, 229 | 230 | queue: function() { 231 | var test = this; 232 | synchronize(function() { 233 | test.init(); 234 | }); 235 | function run() { 236 | // each of these can by async 237 | synchronize(function() { 238 | test.setup(); 239 | }); 240 | synchronize(function() { 241 | test.run(); 242 | }); 243 | synchronize(function() { 244 | test.teardown(); 245 | }); 246 | synchronize(function() { 247 | test.finish(); 248 | }); 249 | } 250 | // defer when previous test run passed, if storage is available 251 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); 252 | if (bad) { 253 | run(); 254 | } else { 255 | synchronize(run, true); 256 | }; 257 | } 258 | 259 | }; 260 | 261 | var QUnit = { 262 | 263 | // call on start of module test to prepend name to all tests 264 | module: function(name, testEnvironment) { 265 | config.currentModule = name; 266 | config.currentModuleTestEnviroment = testEnvironment; 267 | }, 268 | 269 | asyncTest: function(testName, expected, callback) { 270 | if ( arguments.length === 2 ) { 271 | callback = expected; 272 | expected = null; 273 | } 274 | 275 | QUnit.test(testName, expected, callback, true); 276 | }, 277 | 278 | test: function(testName, expected, callback, async) { 279 | var name = '' + testName + '', testEnvironmentArg; 280 | 281 | if ( arguments.length === 2 ) { 282 | callback = expected; 283 | expected = null; 284 | } 285 | // is 2nd argument a testEnvironment? 286 | if ( expected && typeof expected === 'object') { 287 | testEnvironmentArg = expected; 288 | expected = null; 289 | } 290 | 291 | if ( config.currentModule ) { 292 | name = '' + config.currentModule + ": " + name; 293 | } 294 | 295 | if ( !validTest(config.currentModule + ": " + testName) ) { 296 | return; 297 | } 298 | 299 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 300 | test.module = config.currentModule; 301 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 302 | test.queue(); 303 | }, 304 | 305 | /** 306 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 307 | */ 308 | expect: function(asserts) { 309 | config.current.expected = asserts; 310 | }, 311 | 312 | /** 313 | * Asserts true. 314 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 315 | */ 316 | ok: function(a, msg) { 317 | a = !!a; 318 | var details = { 319 | result: a, 320 | message: msg 321 | }; 322 | msg = escapeInnerText(msg); 323 | runLoggingCallbacks( 'log', QUnit, details ); 324 | config.current.assertions.push({ 325 | result: a, 326 | message: msg 327 | }); 328 | }, 329 | 330 | /** 331 | * Checks that the first two arguments are equal, with an optional message. 332 | * Prints out both actual and expected values. 333 | * 334 | * Prefered to ok( actual == expected, message ) 335 | * 336 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 337 | * 338 | * @param Object actual 339 | * @param Object expected 340 | * @param String message (optional) 341 | */ 342 | equal: function(actual, expected, message) { 343 | QUnit.push(expected == actual, actual, expected, message); 344 | }, 345 | 346 | notEqual: function(actual, expected, message) { 347 | QUnit.push(expected != actual, actual, expected, message); 348 | }, 349 | 350 | deepEqual: function(actual, expected, message) { 351 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 352 | }, 353 | 354 | notDeepEqual: function(actual, expected, message) { 355 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 356 | }, 357 | 358 | strictEqual: function(actual, expected, message) { 359 | QUnit.push(expected === actual, actual, expected, message); 360 | }, 361 | 362 | notStrictEqual: function(actual, expected, message) { 363 | QUnit.push(expected !== actual, actual, expected, message); 364 | }, 365 | 366 | raises: function(block, expected, message) { 367 | var actual, ok = false; 368 | 369 | if (typeof expected === 'string') { 370 | message = expected; 371 | expected = null; 372 | } 373 | 374 | try { 375 | block(); 376 | } catch (e) { 377 | actual = e; 378 | } 379 | 380 | if (actual) { 381 | // we don't want to validate thrown error 382 | if (!expected) { 383 | ok = true; 384 | // expected is a regexp 385 | } else if (QUnit.objectType(expected) === "regexp") { 386 | ok = expected.test(actual); 387 | // expected is a constructor 388 | } else if (actual instanceof expected) { 389 | ok = true; 390 | // expected is a validation function which returns true is validation passed 391 | } else if (expected.call({}, actual) === true) { 392 | ok = true; 393 | } 394 | } 395 | 396 | QUnit.ok(ok, message); 397 | }, 398 | 399 | start: function(count) { 400 | config.semaphore -= count || 1; 401 | if (config.semaphore > 0) { 402 | // don't start until equal number of stop-calls 403 | return; 404 | } 405 | if (config.semaphore < 0) { 406 | // ignore if start is called more often then stop 407 | config.semaphore = 0; 408 | } 409 | // A slight delay, to avoid any current callbacks 410 | if ( defined.setTimeout ) { 411 | window.setTimeout(function() { 412 | if (config.semaphore > 0) { 413 | return; 414 | } 415 | if ( config.timeout ) { 416 | clearTimeout(config.timeout); 417 | } 418 | 419 | config.blocking = false; 420 | process(true); 421 | }, 13); 422 | } else { 423 | config.blocking = false; 424 | process(true); 425 | } 426 | }, 427 | 428 | stop: function(count) { 429 | config.semaphore += count || 1; 430 | config.blocking = true; 431 | 432 | if ( config.testTimeout && defined.setTimeout ) { 433 | clearTimeout(config.timeout); 434 | config.timeout = window.setTimeout(function() { 435 | QUnit.ok( false, "Test timed out" ); 436 | config.semaphore = 1; 437 | QUnit.start(); 438 | }, config.testTimeout); 439 | } 440 | } 441 | }; 442 | 443 | //We want access to the constructor's prototype 444 | (function() { 445 | function F(){}; 446 | F.prototype = QUnit; 447 | QUnit = new F(); 448 | //Make F QUnit's constructor so that we can add to the prototype later 449 | QUnit.constructor = F; 450 | })(); 451 | 452 | // Backwards compatibility, deprecated 453 | QUnit.equals = QUnit.equal; 454 | QUnit.same = QUnit.deepEqual; 455 | 456 | // Maintain internal state 457 | var config = { 458 | // The queue of tests to run 459 | queue: [], 460 | 461 | // block until document ready 462 | blocking: true, 463 | 464 | // when enabled, show only failing tests 465 | // gets persisted through sessionStorage and can be changed in UI via checkbox 466 | hidepassed: false, 467 | 468 | // by default, run previously failed tests first 469 | // very useful in combination with "Hide passed tests" checked 470 | reorder: true, 471 | 472 | // by default, modify document.title when suite is done 473 | altertitle: true, 474 | 475 | urlConfig: ['noglobals', 'notrycatch'], 476 | 477 | //logging callback queues 478 | begin: [], 479 | done: [], 480 | log: [], 481 | testStart: [], 482 | testDone: [], 483 | moduleStart: [], 484 | moduleDone: [] 485 | }; 486 | 487 | // Load paramaters 488 | (function() { 489 | var location = window.location || { search: "", protocol: "file:" }, 490 | params = location.search.slice( 1 ).split( "&" ), 491 | length = params.length, 492 | urlParams = {}, 493 | current; 494 | 495 | if ( params[ 0 ] ) { 496 | for ( var i = 0; i < length; i++ ) { 497 | current = params[ i ].split( "=" ); 498 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 499 | // allow just a key to turn on a flag, e.g., test.html?noglobals 500 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 501 | urlParams[ current[ 0 ] ] = current[ 1 ]; 502 | } 503 | } 504 | 505 | QUnit.urlParams = urlParams; 506 | config.filter = urlParams.filter; 507 | 508 | // Figure out if we're running the tests from a server or not 509 | QUnit.isLocal = !!(location.protocol === 'file:'); 510 | })(); 511 | 512 | // Expose the API as global variables, unless an 'exports' 513 | // object exists, in that case we assume we're in CommonJS 514 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 515 | extend(window, QUnit); 516 | window.QUnit = QUnit; 517 | } else { 518 | extend(exports, QUnit); 519 | exports.QUnit = QUnit; 520 | } 521 | 522 | // define these after exposing globals to keep them in these QUnit namespace only 523 | extend(QUnit, { 524 | config: config, 525 | 526 | // Initialize the configuration options 527 | init: function() { 528 | extend(config, { 529 | stats: { all: 0, bad: 0 }, 530 | moduleStats: { all: 0, bad: 0 }, 531 | started: +new Date, 532 | updateRate: 1000, 533 | blocking: false, 534 | autostart: true, 535 | autorun: false, 536 | filter: "", 537 | queue: [], 538 | semaphore: 0 539 | }); 540 | 541 | var tests = id( "qunit-tests" ), 542 | banner = id( "qunit-banner" ), 543 | result = id( "qunit-testresult" ); 544 | 545 | if ( tests ) { 546 | tests.innerHTML = ""; 547 | } 548 | 549 | if ( banner ) { 550 | banner.className = ""; 551 | } 552 | 553 | if ( result ) { 554 | result.parentNode.removeChild( result ); 555 | } 556 | 557 | if ( tests ) { 558 | result = document.createElement( "p" ); 559 | result.id = "qunit-testresult"; 560 | result.className = "result"; 561 | tests.parentNode.insertBefore( result, tests ); 562 | result.innerHTML = 'Running...
     '; 563 | } 564 | }, 565 | 566 | /** 567 | * Resets the test setup. Useful for tests that modify the DOM. 568 | * 569 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 570 | */ 571 | reset: function() { 572 | if ( window.jQuery ) { 573 | jQuery( "#qunit-fixture" ).html( config.fixture ); 574 | } else { 575 | var main = id( 'qunit-fixture' ); 576 | if ( main ) { 577 | main.innerHTML = config.fixture; 578 | } 579 | } 580 | }, 581 | 582 | /** 583 | * Trigger an event on an element. 584 | * 585 | * @example triggerEvent( document.body, "click" ); 586 | * 587 | * @param DOMElement elem 588 | * @param String type 589 | */ 590 | triggerEvent: function( elem, type, event ) { 591 | if ( document.createEvent ) { 592 | event = document.createEvent("MouseEvents"); 593 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 594 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 595 | elem.dispatchEvent( event ); 596 | 597 | } else if ( elem.fireEvent ) { 598 | elem.fireEvent("on"+type); 599 | } 600 | }, 601 | 602 | // Safe object type checking 603 | is: function( type, obj ) { 604 | return QUnit.objectType( obj ) == type; 605 | }, 606 | 607 | objectType: function( obj ) { 608 | if (typeof obj === "undefined") { 609 | return "undefined"; 610 | 611 | // consider: typeof null === object 612 | } 613 | if (obj === null) { 614 | return "null"; 615 | } 616 | 617 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; 618 | 619 | switch (type) { 620 | case 'Number': 621 | if (isNaN(obj)) { 622 | return "nan"; 623 | } else { 624 | return "number"; 625 | } 626 | case 'String': 627 | case 'Boolean': 628 | case 'Array': 629 | case 'Date': 630 | case 'RegExp': 631 | case 'Function': 632 | return type.toLowerCase(); 633 | } 634 | if (typeof obj === "object") { 635 | return "object"; 636 | } 637 | return undefined; 638 | }, 639 | 640 | push: function(result, actual, expected, message) { 641 | var details = { 642 | result: result, 643 | message: message, 644 | actual: actual, 645 | expected: expected 646 | }; 647 | 648 | message = escapeInnerText(message) || (result ? "okay" : "failed"); 649 | message = '' + message + ""; 650 | expected = escapeInnerText(QUnit.jsDump.parse(expected)); 651 | actual = escapeInnerText(QUnit.jsDump.parse(actual)); 652 | var output = message + ''; 653 | if (actual != expected) { 654 | output += ''; 655 | output += ''; 656 | } 657 | if (!result) { 658 | var source = sourceFromStacktrace(); 659 | if (source) { 660 | details.source = source; 661 | output += ''; 662 | } 663 | } 664 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + escapeInnerText(source) + '
    "; 665 | 666 | runLoggingCallbacks( 'log', QUnit, details ); 667 | 668 | config.current.assertions.push({ 669 | result: !!result, 670 | message: output 671 | }); 672 | }, 673 | 674 | url: function( params ) { 675 | params = extend( extend( {}, QUnit.urlParams ), params ); 676 | var querystring = "?", 677 | key; 678 | for ( key in params ) { 679 | if ( !hasOwn.call( params, key ) ) { 680 | continue; 681 | } 682 | querystring += encodeURIComponent( key ) + "=" + 683 | encodeURIComponent( params[ key ] ) + "&"; 684 | } 685 | return window.location.pathname + querystring.slice( 0, -1 ); 686 | }, 687 | 688 | extend: extend, 689 | id: id, 690 | addEvent: addEvent 691 | }); 692 | 693 | //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later 694 | //Doing this allows us to tell if the following methods have been overwritten on the actual 695 | //QUnit object, which is a deprecated way of using the callbacks. 696 | extend(QUnit.constructor.prototype, { 697 | // Logging callbacks; all receive a single argument with the listed properties 698 | // run test/logs.html for any related changes 699 | begin: registerLoggingCallback('begin'), 700 | // done: { failed, passed, total, runtime } 701 | done: registerLoggingCallback('done'), 702 | // log: { result, actual, expected, message } 703 | log: registerLoggingCallback('log'), 704 | // testStart: { name } 705 | testStart: registerLoggingCallback('testStart'), 706 | // testDone: { name, failed, passed, total } 707 | testDone: registerLoggingCallback('testDone'), 708 | // moduleStart: { name } 709 | moduleStart: registerLoggingCallback('moduleStart'), 710 | // moduleDone: { name, failed, passed, total } 711 | moduleDone: registerLoggingCallback('moduleDone') 712 | }); 713 | 714 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 715 | config.autorun = true; 716 | } 717 | 718 | QUnit.load = function() { 719 | runLoggingCallbacks( 'begin', QUnit, {} ); 720 | 721 | // Initialize the config, saving the execution queue 722 | var oldconfig = extend({}, config); 723 | QUnit.init(); 724 | extend(config, oldconfig); 725 | 726 | config.blocking = false; 727 | 728 | var urlConfigHtml = '', len = config.urlConfig.length; 729 | for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { 730 | config[val] = QUnit.urlParams[val]; 731 | urlConfigHtml += ''; 732 | } 733 | 734 | var userAgent = id("qunit-userAgent"); 735 | if ( userAgent ) { 736 | userAgent.innerHTML = navigator.userAgent; 737 | } 738 | var banner = id("qunit-header"); 739 | if ( banner ) { 740 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; 741 | addEvent( banner, "change", function( event ) { 742 | var params = {}; 743 | params[ event.target.name ] = event.target.checked ? true : undefined; 744 | window.location = QUnit.url( params ); 745 | }); 746 | } 747 | 748 | var toolbar = id("qunit-testrunner-toolbar"); 749 | if ( toolbar ) { 750 | var filter = document.createElement("input"); 751 | filter.type = "checkbox"; 752 | filter.id = "qunit-filter-pass"; 753 | addEvent( filter, "click", function() { 754 | var ol = document.getElementById("qunit-tests"); 755 | if ( filter.checked ) { 756 | ol.className = ol.className + " hidepass"; 757 | } else { 758 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 759 | ol.className = tmp.replace(/ hidepass /, " "); 760 | } 761 | if ( defined.sessionStorage ) { 762 | if (filter.checked) { 763 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 764 | } else { 765 | sessionStorage.removeItem("qunit-filter-passed-tests"); 766 | } 767 | } 768 | }); 769 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 770 | filter.checked = true; 771 | var ol = document.getElementById("qunit-tests"); 772 | ol.className = ol.className + " hidepass"; 773 | } 774 | toolbar.appendChild( filter ); 775 | 776 | var label = document.createElement("label"); 777 | label.setAttribute("for", "qunit-filter-pass"); 778 | label.innerHTML = "Hide passed tests"; 779 | toolbar.appendChild( label ); 780 | } 781 | 782 | var main = id('qunit-fixture'); 783 | if ( main ) { 784 | config.fixture = main.innerHTML; 785 | } 786 | 787 | if (config.autostart) { 788 | QUnit.start(); 789 | } 790 | }; 791 | 792 | addEvent(window, "load", QUnit.load); 793 | 794 | // addEvent(window, "error") gives us a useless event object 795 | window.onerror = function( message, file, line ) { 796 | if ( QUnit.config.current ) { 797 | ok( false, message + ", " + file + ":" + line ); 798 | } else { 799 | test( "global failure", function() { 800 | ok( false, message + ", " + file + ":" + line ); 801 | }); 802 | } 803 | }; 804 | 805 | function done() { 806 | config.autorun = true; 807 | 808 | // Log the last module results 809 | if ( config.currentModule ) { 810 | runLoggingCallbacks( 'moduleDone', QUnit, { 811 | name: config.currentModule, 812 | failed: config.moduleStats.bad, 813 | passed: config.moduleStats.all - config.moduleStats.bad, 814 | total: config.moduleStats.all 815 | } ); 816 | } 817 | 818 | var banner = id("qunit-banner"), 819 | tests = id("qunit-tests"), 820 | runtime = +new Date - config.started, 821 | passed = config.stats.all - config.stats.bad, 822 | html = [ 823 | 'Tests completed in ', 824 | runtime, 825 | ' milliseconds.
    ', 826 | '', 827 | passed, 828 | ' tests of ', 829 | config.stats.all, 830 | ' passed, ', 831 | config.stats.bad, 832 | ' failed.' 833 | ].join(''); 834 | 835 | if ( banner ) { 836 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 837 | } 838 | 839 | if ( tests ) { 840 | id( "qunit-testresult" ).innerHTML = html; 841 | } 842 | 843 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 844 | // show ✖ for good, ✔ for bad suite result in title 845 | // use escape sequences in case file gets loaded with non-utf-8-charset 846 | document.title = [ 847 | (config.stats.bad ? "\u2716" : "\u2714"), 848 | document.title.replace(/^[\u2714\u2716] /i, "") 849 | ].join(" "); 850 | } 851 | 852 | runLoggingCallbacks( 'done', QUnit, { 853 | failed: config.stats.bad, 854 | passed: passed, 855 | total: config.stats.all, 856 | runtime: runtime 857 | } ); 858 | } 859 | 860 | function validTest( name ) { 861 | var filter = config.filter, 862 | run = false; 863 | 864 | if ( !filter ) { 865 | return true; 866 | } 867 | 868 | var not = filter.charAt( 0 ) === "!"; 869 | if ( not ) { 870 | filter = filter.slice( 1 ); 871 | } 872 | 873 | if ( name.indexOf( filter ) !== -1 ) { 874 | return !not; 875 | } 876 | 877 | if ( not ) { 878 | run = true; 879 | } 880 | 881 | return run; 882 | } 883 | 884 | // so far supports only Firefox, Chrome and Opera (buggy) 885 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 886 | function sourceFromStacktrace() { 887 | try { 888 | throw new Error(); 889 | } catch ( e ) { 890 | if (e.stacktrace) { 891 | // Opera 892 | return e.stacktrace.split("\n")[6]; 893 | } else if (e.stack) { 894 | // Firefox, Chrome 895 | return e.stack.split("\n")[4]; 896 | } else if (e.sourceURL) { 897 | // Safari, PhantomJS 898 | // TODO sourceURL points at the 'throw new Error' line above, useless 899 | //return e.sourceURL + ":" + e.line; 900 | } 901 | } 902 | } 903 | 904 | function escapeInnerText(s) { 905 | if (!s) { 906 | return ""; 907 | } 908 | s = s + ""; 909 | return s.replace(/[\&<>]/g, function(s) { 910 | switch(s) { 911 | case "&": return "&"; 912 | case "<": return "<"; 913 | case ">": return ">"; 914 | default: return s; 915 | } 916 | }); 917 | } 918 | 919 | function synchronize( callback, last ) { 920 | config.queue.push( callback ); 921 | 922 | if ( config.autorun && !config.blocking ) { 923 | process(last); 924 | } 925 | } 926 | 927 | function process( last ) { 928 | var start = new Date().getTime(); 929 | config.depth = config.depth ? config.depth + 1 : 1; 930 | 931 | while ( config.queue.length && !config.blocking ) { 932 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 933 | config.queue.shift()(); 934 | } else { 935 | window.setTimeout( function(){ 936 | process( last ); 937 | }, 13 ); 938 | break; 939 | } 940 | } 941 | config.depth--; 942 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 943 | done(); 944 | } 945 | } 946 | 947 | function saveGlobal() { 948 | config.pollution = []; 949 | 950 | if ( config.noglobals ) { 951 | for ( var key in window ) { 952 | if ( !hasOwn.call( window, key ) ) { 953 | continue; 954 | } 955 | config.pollution.push( key ); 956 | } 957 | } 958 | } 959 | 960 | function checkPollution( name ) { 961 | var old = config.pollution; 962 | saveGlobal(); 963 | 964 | var newGlobals = diff( config.pollution, old ); 965 | if ( newGlobals.length > 0 ) { 966 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 967 | } 968 | 969 | var deletedGlobals = diff( old, config.pollution ); 970 | if ( deletedGlobals.length > 0 ) { 971 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 972 | } 973 | } 974 | 975 | // returns a new Array with the elements that are in a but not in b 976 | function diff( a, b ) { 977 | var result = a.slice(); 978 | for ( var i = 0; i < result.length; i++ ) { 979 | for ( var j = 0; j < b.length; j++ ) { 980 | if ( result[i] === b[j] ) { 981 | result.splice(i, 1); 982 | i--; 983 | break; 984 | } 985 | } 986 | } 987 | return result; 988 | } 989 | 990 | function fail(message, exception, callback) { 991 | if ( typeof console !== "undefined" && console.error && console.warn ) { 992 | console.error(message); 993 | console.error(exception); 994 | console.warn(callback.toString()); 995 | 996 | } else if ( window.opera && opera.postError ) { 997 | opera.postError(message, exception, callback.toString); 998 | } 999 | } 1000 | 1001 | function extend(a, b) { 1002 | for ( var prop in b ) { 1003 | if ( b[prop] === undefined ) { 1004 | delete a[prop]; 1005 | 1006 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1007 | } else if ( prop !== "constructor" || a !== window ) { 1008 | a[prop] = b[prop]; 1009 | } 1010 | } 1011 | 1012 | return a; 1013 | } 1014 | 1015 | function addEvent(elem, type, fn) { 1016 | if ( elem.addEventListener ) { 1017 | elem.addEventListener( type, fn, false ); 1018 | } else if ( elem.attachEvent ) { 1019 | elem.attachEvent( "on" + type, fn ); 1020 | } else { 1021 | fn(); 1022 | } 1023 | } 1024 | 1025 | function id(name) { 1026 | return !!(typeof document !== "undefined" && document && document.getElementById) && 1027 | document.getElementById( name ); 1028 | } 1029 | 1030 | function registerLoggingCallback(key){ 1031 | return function(callback){ 1032 | config[key].push( callback ); 1033 | }; 1034 | } 1035 | 1036 | // Supports deprecated method of completely overwriting logging callbacks 1037 | function runLoggingCallbacks(key, scope, args) { 1038 | //debugger; 1039 | var callbacks; 1040 | if ( QUnit.hasOwnProperty(key) ) { 1041 | QUnit[key].call(scope, args); 1042 | } else { 1043 | callbacks = config[key]; 1044 | for( var i = 0; i < callbacks.length; i++ ) { 1045 | callbacks[i].call( scope, args ); 1046 | } 1047 | } 1048 | } 1049 | 1050 | // Test for equality any JavaScript type. 1051 | // Author: Philippe Rathé 1052 | QUnit.equiv = function () { 1053 | 1054 | var innerEquiv; // the real equiv function 1055 | var callers = []; // stack to decide between skip/abort functions 1056 | var parents = []; // stack to avoiding loops from circular referencing 1057 | 1058 | // Call the o related callback with the given arguments. 1059 | function bindCallbacks(o, callbacks, args) { 1060 | var prop = QUnit.objectType(o); 1061 | if (prop) { 1062 | if (QUnit.objectType(callbacks[prop]) === "function") { 1063 | return callbacks[prop].apply(callbacks, args); 1064 | } else { 1065 | return callbacks[prop]; // or undefined 1066 | } 1067 | } 1068 | } 1069 | 1070 | var callbacks = function () { 1071 | 1072 | // for string, boolean, number and null 1073 | function useStrictEquality(b, a) { 1074 | if (b instanceof a.constructor || a instanceof b.constructor) { 1075 | // to catch short annotaion VS 'new' annotation of a 1076 | // declaration 1077 | // e.g. var i = 1; 1078 | // var j = new Number(1); 1079 | return a == b; 1080 | } else { 1081 | return a === b; 1082 | } 1083 | } 1084 | 1085 | return { 1086 | "string" : useStrictEquality, 1087 | "boolean" : useStrictEquality, 1088 | "number" : useStrictEquality, 1089 | "null" : useStrictEquality, 1090 | "undefined" : useStrictEquality, 1091 | 1092 | "nan" : function(b) { 1093 | return isNaN(b); 1094 | }, 1095 | 1096 | "date" : function(b, a) { 1097 | return QUnit.objectType(b) === "date" 1098 | && a.valueOf() === b.valueOf(); 1099 | }, 1100 | 1101 | "regexp" : function(b, a) { 1102 | return QUnit.objectType(b) === "regexp" 1103 | && a.source === b.source && // the regex itself 1104 | a.global === b.global && // and its modifers 1105 | // (gmi) ... 1106 | a.ignoreCase === b.ignoreCase 1107 | && a.multiline === b.multiline; 1108 | }, 1109 | 1110 | // - skip when the property is a method of an instance (OOP) 1111 | // - abort otherwise, 1112 | // initial === would have catch identical references anyway 1113 | "function" : function() { 1114 | var caller = callers[callers.length - 1]; 1115 | return caller !== Object && typeof caller !== "undefined"; 1116 | }, 1117 | 1118 | "array" : function(b, a) { 1119 | var i, j, loop; 1120 | var len; 1121 | 1122 | // b could be an object literal here 1123 | if (!(QUnit.objectType(b) === "array")) { 1124 | return false; 1125 | } 1126 | 1127 | len = a.length; 1128 | if (len !== b.length) { // safe and faster 1129 | return false; 1130 | } 1131 | 1132 | // track reference to avoid circular references 1133 | parents.push(a); 1134 | for (i = 0; i < len; i++) { 1135 | loop = false; 1136 | for (j = 0; j < parents.length; j++) { 1137 | if (parents[j] === a[i]) { 1138 | loop = true;// dont rewalk array 1139 | } 1140 | } 1141 | if (!loop && !innerEquiv(a[i], b[i])) { 1142 | parents.pop(); 1143 | return false; 1144 | } 1145 | } 1146 | parents.pop(); 1147 | return true; 1148 | }, 1149 | 1150 | "object" : function(b, a) { 1151 | var i, j, loop; 1152 | var eq = true; // unless we can proove it 1153 | var aProperties = [], bProperties = []; // collection of 1154 | // strings 1155 | 1156 | // comparing constructors is more strict than using 1157 | // instanceof 1158 | if (a.constructor !== b.constructor) { 1159 | return false; 1160 | } 1161 | 1162 | // stack constructor before traversing properties 1163 | callers.push(a.constructor); 1164 | // track reference to avoid circular references 1165 | parents.push(a); 1166 | 1167 | for (i in a) { // be strict: don't ensures hasOwnProperty 1168 | // and go deep 1169 | loop = false; 1170 | for (j = 0; j < parents.length; j++) { 1171 | if (parents[j] === a[i]) 1172 | loop = true; // don't go down the same path 1173 | // twice 1174 | } 1175 | aProperties.push(i); // collect a's properties 1176 | 1177 | if (!loop && !innerEquiv(a[i], b[i])) { 1178 | eq = false; 1179 | break; 1180 | } 1181 | } 1182 | 1183 | callers.pop(); // unstack, we are done 1184 | parents.pop(); 1185 | 1186 | for (i in b) { 1187 | bProperties.push(i); // collect b's properties 1188 | } 1189 | 1190 | // Ensures identical properties name 1191 | return eq 1192 | && innerEquiv(aProperties.sort(), bProperties 1193 | .sort()); 1194 | } 1195 | }; 1196 | }(); 1197 | 1198 | innerEquiv = function() { // can take multiple arguments 1199 | var args = Array.prototype.slice.apply(arguments); 1200 | if (args.length < 2) { 1201 | return true; // end transition 1202 | } 1203 | 1204 | return (function(a, b) { 1205 | if (a === b) { 1206 | return true; // catch the most you can 1207 | } else if (a === null || b === null || typeof a === "undefined" 1208 | || typeof b === "undefined" 1209 | || QUnit.objectType(a) !== QUnit.objectType(b)) { 1210 | return false; // don't lose time with error prone cases 1211 | } else { 1212 | return bindCallbacks(a, callbacks, [ b, a ]); 1213 | } 1214 | 1215 | // apply transition with (1..n) arguments 1216 | })(args[0], args[1]) 1217 | && arguments.callee.apply(this, args.splice(1, 1218 | args.length - 1)); 1219 | }; 1220 | 1221 | return innerEquiv; 1222 | 1223 | }(); 1224 | 1225 | /** 1226 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1227 | * http://flesler.blogspot.com Licensed under BSD 1228 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1229 | * 1230 | * @projectDescription Advanced and extensible data dumping for Javascript. 1231 | * @version 1.0.0 1232 | * @author Ariel Flesler 1233 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1234 | */ 1235 | QUnit.jsDump = (function() { 1236 | function quote( str ) { 1237 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1238 | }; 1239 | function literal( o ) { 1240 | return o + ''; 1241 | }; 1242 | function join( pre, arr, post ) { 1243 | var s = jsDump.separator(), 1244 | base = jsDump.indent(), 1245 | inner = jsDump.indent(1); 1246 | if ( arr.join ) 1247 | arr = arr.join( ',' + s + inner ); 1248 | if ( !arr ) 1249 | return pre + post; 1250 | return [ pre, inner + arr, base + post ].join(s); 1251 | }; 1252 | function array( arr, stack ) { 1253 | var i = arr.length, ret = Array(i); 1254 | this.up(); 1255 | while ( i-- ) 1256 | ret[i] = this.parse( arr[i] , undefined , stack); 1257 | this.down(); 1258 | return join( '[', ret, ']' ); 1259 | }; 1260 | 1261 | var reName = /^function (\w+)/; 1262 | 1263 | var jsDump = { 1264 | parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1265 | stack = stack || [ ]; 1266 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1267 | type = typeof parser; 1268 | var inStack = inArray(obj, stack); 1269 | if (inStack != -1) { 1270 | return 'recursion('+(inStack - stack.length)+')'; 1271 | } 1272 | //else 1273 | if (type == 'function') { 1274 | stack.push(obj); 1275 | var res = parser.call( this, obj, stack ); 1276 | stack.pop(); 1277 | return res; 1278 | } 1279 | // else 1280 | return (type == 'string') ? parser : this.parsers.error; 1281 | }, 1282 | typeOf:function( obj ) { 1283 | var type; 1284 | if ( obj === null ) { 1285 | type = "null"; 1286 | } else if (typeof obj === "undefined") { 1287 | type = "undefined"; 1288 | } else if (QUnit.is("RegExp", obj)) { 1289 | type = "regexp"; 1290 | } else if (QUnit.is("Date", obj)) { 1291 | type = "date"; 1292 | } else if (QUnit.is("Function", obj)) { 1293 | type = "function"; 1294 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1295 | type = "window"; 1296 | } else if (obj.nodeType === 9) { 1297 | type = "document"; 1298 | } else if (obj.nodeType) { 1299 | type = "node"; 1300 | } else if ( 1301 | // native arrays 1302 | toString.call( obj ) === "[object Array]" || 1303 | // NodeList objects 1304 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1305 | ) { 1306 | type = "array"; 1307 | } else { 1308 | type = typeof obj; 1309 | } 1310 | return type; 1311 | }, 1312 | separator:function() { 1313 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1314 | }, 1315 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1316 | if ( !this.multiline ) 1317 | return ''; 1318 | var chr = this.indentChar; 1319 | if ( this.HTML ) 1320 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1321 | return Array( this._depth_ + (extra||0) ).join(chr); 1322 | }, 1323 | up:function( a ) { 1324 | this._depth_ += a || 1; 1325 | }, 1326 | down:function( a ) { 1327 | this._depth_ -= a || 1; 1328 | }, 1329 | setParser:function( name, parser ) { 1330 | this.parsers[name] = parser; 1331 | }, 1332 | // The next 3 are exposed so you can use them 1333 | quote:quote, 1334 | literal:literal, 1335 | join:join, 1336 | // 1337 | _depth_: 1, 1338 | // This is the list of parsers, to modify them, use jsDump.setParser 1339 | parsers:{ 1340 | window: '[Window]', 1341 | document: '[Document]', 1342 | error:'[ERROR]', //when no parser is found, shouldn't happen 1343 | unknown: '[Unknown]', 1344 | 'null':'null', 1345 | 'undefined':'undefined', 1346 | 'function':function( fn ) { 1347 | var ret = 'function', 1348 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1349 | if ( name ) 1350 | ret += ' ' + name; 1351 | ret += '('; 1352 | 1353 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1354 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1355 | }, 1356 | array: array, 1357 | nodelist: array, 1358 | arguments: array, 1359 | object:function( map, stack ) { 1360 | var ret = [ ]; 1361 | QUnit.jsDump.up(); 1362 | for ( var key in map ) { 1363 | var val = map[key]; 1364 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); 1365 | } 1366 | QUnit.jsDump.down(); 1367 | return join( '{', ret, '}' ); 1368 | }, 1369 | node:function( node ) { 1370 | var open = QUnit.jsDump.HTML ? '<' : '<', 1371 | close = QUnit.jsDump.HTML ? '>' : '>'; 1372 | 1373 | var tag = node.nodeName.toLowerCase(), 1374 | ret = open + tag; 1375 | 1376 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1377 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1378 | if ( val ) 1379 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1380 | } 1381 | return ret + close + open + '/' + tag + close; 1382 | }, 1383 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1384 | var l = fn.length; 1385 | if ( !l ) return ''; 1386 | 1387 | var args = Array(l); 1388 | while ( l-- ) 1389 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1390 | return ' ' + args.join(', ') + ' '; 1391 | }, 1392 | key:quote, //object calls it internally, the key part of an item in a map 1393 | functionCode:'[code]', //function calls it internally, it's the content of the function 1394 | attribute:quote, //node calls it internally, it's an html attribute value 1395 | string:quote, 1396 | date:quote, 1397 | regexp:literal, //regex 1398 | number:literal, 1399 | 'boolean':literal 1400 | }, 1401 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1402 | id:'id', 1403 | name:'name', 1404 | 'class':'className' 1405 | }, 1406 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1407 | indentChar:' ',//indentation unit 1408 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1409 | }; 1410 | 1411 | return jsDump; 1412 | })(); 1413 | 1414 | // from Sizzle.js 1415 | function getText( elems ) { 1416 | var ret = "", elem; 1417 | 1418 | for ( var i = 0; elems[i]; i++ ) { 1419 | elem = elems[i]; 1420 | 1421 | // Get the text from text nodes and CDATA nodes 1422 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1423 | ret += elem.nodeValue; 1424 | 1425 | // Traverse everything else, except comment nodes 1426 | } else if ( elem.nodeType !== 8 ) { 1427 | ret += getText( elem.childNodes ); 1428 | } 1429 | } 1430 | 1431 | return ret; 1432 | }; 1433 | 1434 | //from jquery.js 1435 | function inArray( elem, array ) { 1436 | if ( array.indexOf ) { 1437 | return array.indexOf( elem ); 1438 | } 1439 | 1440 | for ( var i = 0, length = array.length; i < length; i++ ) { 1441 | if ( array[ i ] === elem ) { 1442 | return i; 1443 | } 1444 | } 1445 | 1446 | return -1; 1447 | } 1448 | 1449 | /* 1450 | * Javascript Diff Algorithm 1451 | * By John Resig (http://ejohn.org/) 1452 | * Modified by Chu Alan "sprite" 1453 | * 1454 | * Released under the MIT license. 1455 | * 1456 | * More Info: 1457 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1458 | * 1459 | * Usage: QUnit.diff(expected, actual) 1460 | * 1461 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1462 | */ 1463 | QUnit.diff = (function() { 1464 | function diff(o, n) { 1465 | var ns = {}; 1466 | var os = {}; 1467 | 1468 | for (var i = 0; i < n.length; i++) { 1469 | if (ns[n[i]] == null) 1470 | ns[n[i]] = { 1471 | rows: [], 1472 | o: null 1473 | }; 1474 | ns[n[i]].rows.push(i); 1475 | } 1476 | 1477 | for (var i = 0; i < o.length; i++) { 1478 | if (os[o[i]] == null) 1479 | os[o[i]] = { 1480 | rows: [], 1481 | n: null 1482 | }; 1483 | os[o[i]].rows.push(i); 1484 | } 1485 | 1486 | for (var i in ns) { 1487 | if ( !hasOwn.call( ns, i ) ) { 1488 | continue; 1489 | } 1490 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1491 | n[ns[i].rows[0]] = { 1492 | text: n[ns[i].rows[0]], 1493 | row: os[i].rows[0] 1494 | }; 1495 | o[os[i].rows[0]] = { 1496 | text: o[os[i].rows[0]], 1497 | row: ns[i].rows[0] 1498 | }; 1499 | } 1500 | } 1501 | 1502 | for (var i = 0; i < n.length - 1; i++) { 1503 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1504 | n[i + 1] == o[n[i].row + 1]) { 1505 | n[i + 1] = { 1506 | text: n[i + 1], 1507 | row: n[i].row + 1 1508 | }; 1509 | o[n[i].row + 1] = { 1510 | text: o[n[i].row + 1], 1511 | row: i + 1 1512 | }; 1513 | } 1514 | } 1515 | 1516 | for (var i = n.length - 1; i > 0; i--) { 1517 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1518 | n[i - 1] == o[n[i].row - 1]) { 1519 | n[i - 1] = { 1520 | text: n[i - 1], 1521 | row: n[i].row - 1 1522 | }; 1523 | o[n[i].row - 1] = { 1524 | text: o[n[i].row - 1], 1525 | row: i - 1 1526 | }; 1527 | } 1528 | } 1529 | 1530 | return { 1531 | o: o, 1532 | n: n 1533 | }; 1534 | } 1535 | 1536 | return function(o, n) { 1537 | o = o.replace(/\s+$/, ''); 1538 | n = n.replace(/\s+$/, ''); 1539 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1540 | 1541 | var str = ""; 1542 | 1543 | var oSpace = o.match(/\s+/g); 1544 | if (oSpace == null) { 1545 | oSpace = [" "]; 1546 | } 1547 | else { 1548 | oSpace.push(" "); 1549 | } 1550 | var nSpace = n.match(/\s+/g); 1551 | if (nSpace == null) { 1552 | nSpace = [" "]; 1553 | } 1554 | else { 1555 | nSpace.push(" "); 1556 | } 1557 | 1558 | if (out.n.length == 0) { 1559 | for (var i = 0; i < out.o.length; i++) { 1560 | str += '' + out.o[i] + oSpace[i] + ""; 1561 | } 1562 | } 1563 | else { 1564 | if (out.n[0].text == null) { 1565 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1566 | str += '' + out.o[n] + oSpace[n] + ""; 1567 | } 1568 | } 1569 | 1570 | for (var i = 0; i < out.n.length; i++) { 1571 | if (out.n[i].text == null) { 1572 | str += '' + out.n[i] + nSpace[i] + ""; 1573 | } 1574 | else { 1575 | var pre = ""; 1576 | 1577 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1578 | pre += '' + out.o[n] + oSpace[n] + ""; 1579 | } 1580 | str += " " + out.n[i].text + nSpace[i] + pre; 1581 | } 1582 | } 1583 | } 1584 | 1585 | return str; 1586 | }; 1587 | })(); 1588 | 1589 | })(this); 1590 | --------------------------------------------------------------------------------