├── app
├── templates
│ ├── list-topbar.html
│ ├── detail-topbar.html
│ ├── detail-main.html
│ └── list-main.html
├── styles
│ ├── modules
│ │ └── _animals.scss
│ ├── _app.scss
│ ├── vendor
│ │ ├── h5bp
│ │ │ ├── _chromeframe.scss
│ │ │ ├── _media.scss
│ │ │ ├── _main.scss
│ │ │ ├── _helpers.scss
│ │ │ └── _normalize.scss
│ │ └── _h5bp.scss
│ ├── index.scss
│ ├── _base.scss
│ └── _core.scss
└── scripts
│ ├── run.js
│ ├── router.js
│ ├── core
│ ├── device.js
│ └── scaffold.js
│ ├── modules
│ ├── list.js
│ └── detail.js
│ ├── config.js
│ ├── activities
│ └── animals.js
│ ├── app.js
│ └── main.js
├── .gitignore
├── favicon.ico
├── assets
├── img
│ ├── startup
│ │ ├── startup.png
│ │ ├── startup-retina.png
│ │ ├── startup-tablet-landscape.png
│ │ ├── startup-tablet-portrait.png
│ │ ├── startup-tablet-landscape-retina.png
│ │ └── startup-tablet-portrait-retina.png
│ └── touch
│ │ ├── apple-touch-icon.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── apple-touch-icon-114x114-precomposed.png
│ │ ├── apple-touch-icon-144x144-precomposed.png
│ │ ├── apple-touch-icon-57x57-precomposed.png
│ │ └── apple-touch-icon-72x72-precomposed.png
├── js
│ └── shims.js
└── css
│ └── index.css
├── test
├── vendor
│ ├── jasmine_favicon.png
│ ├── MIT.LICENSE
│ ├── jasmine.async.js
│ ├── jasmine.css
│ └── jasmine-jquery.js
├── custom-matchers.js
└── spec
│ ├── list.spec.js
│ ├── detail.spec.js
│ └── skeleton.spec.js
├── package.json
├── vendor
├── jam
│ ├── handlebars
│ │ └── package.json
│ ├── underscore
│ │ └── package.json
│ ├── backbone
│ │ └── package.json
│ ├── jquery
│ │ ├── src
│ │ │ └── sizzle
│ │ │ │ ├── package.json
│ │ │ │ └── speed
│ │ │ │ └── benchmark.js
│ │ │ │ └── package.json
│ │ ├── package.json
│ │ └── test
│ │ │ └── qunit
│ │ │ └── package.json
│ ├── lodash
│ │ ├── package.json
│ │ └── lodash.underscore.min.js
│ ├── backbone.layoutmanager
│ │ └── package.json
│ └── require.config.js
└── js
│ ├── plugins
│ ├── backbone.super.js
│ ├── tappivate.js
│ └── backbone.activities.js
│ └── libs
│ ├── recognizr.js
│ ├── fastclick.js
│ └── almond.js
├── config.rb
├── spec.html
├── readme.md
├── index.html
├── contributing.md
├── api
└── fakeserver.js
└── grunt.js
/app/templates/list-topbar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist/
2 | .sass-cache/
3 | .DS_Store
4 | npm-debug.log
5 |
6 |
--------------------------------------------------------------------------------
/app/templates/detail-topbar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/styles/modules/_animals.scss:
--------------------------------------------------------------------------------
1 | @import "base";
2 |
3 | .detail {
4 | margin: 10px;
5 | }
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/favicon.ico
--------------------------------------------------------------------------------
/app/templates/detail-main.html:
--------------------------------------------------------------------------------
1 |
2 | {{ description }}
--------------------------------------------------------------------------------
/app/scripts/run.js:
--------------------------------------------------------------------------------
1 | define(
2 | ['config'],
3 |
4 | function() {
5 | require(['main']);
6 | });
--------------------------------------------------------------------------------
/app/templates/list-main.html:
--------------------------------------------------------------------------------
1 | {{#animals}}{{ name }}{{/animals}}
--------------------------------------------------------------------------------
/assets/img/startup/startup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup.png
--------------------------------------------------------------------------------
/test/vendor/jasmine_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/test/vendor/jasmine_favicon.png
--------------------------------------------------------------------------------
/assets/img/startup/startup-retina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup-retina.png
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon.png
--------------------------------------------------------------------------------
/app/styles/_app.scss:
--------------------------------------------------------------------------------
1 | // Place all app-specific common styles here.
2 | // Individual styles should live in the 'modules' folder.
3 |
4 | @import "base";
--------------------------------------------------------------------------------
/assets/img/startup/startup-tablet-landscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup-tablet-landscape.png
--------------------------------------------------------------------------------
/assets/img/startup/startup-tablet-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup-tablet-portrait.png
--------------------------------------------------------------------------------
/app/styles/vendor/h5bp/_chromeframe.scss:
--------------------------------------------------------------------------------
1 | // Deprecation warning
2 | @warn "The chromeframe mixin has moved to _main.scss file starting with Boilerplate version 4.0";
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/assets/img/startup/startup-tablet-landscape-retina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup-tablet-landscape-retina.png
--------------------------------------------------------------------------------
/assets/img/startup/startup-tablet-portrait-retina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/startup/startup-tablet-portrait-retina.png
--------------------------------------------------------------------------------
/app/styles/index.scss:
--------------------------------------------------------------------------------
1 | /* Base styles defined in common 'core' partial */
2 | @import "core";
3 |
4 | /* Module Styles go here */
5 | @import "app";
6 | @import "modules/animals";
7 |
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon-114x114-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon-114x114-precomposed.png
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon-144x144-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon-144x144-precomposed.png
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon-57x57-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon-57x57-precomposed.png
--------------------------------------------------------------------------------
/assets/img/touch/apple-touch-icon-72x72-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ace/mobile-backbone-boilerplate/master/assets/img/touch/apple-touch-icon-72x72-precomposed.png
--------------------------------------------------------------------------------
/assets/js/shims.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | // Fix Android JSON.parse bug
4 | // http://code.google.com/p/android/issues/detail?id=11973
5 | JSON.originalParse = JSON.parse;
6 |
7 | JSON.parse = function(text) {
8 | if(text) {
9 | return JSON.originalParse(text);
10 | } else {
11 | return text;
12 | }
13 | };
14 |
15 | })();
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mobile-backbone-boilerplate",
3 | "version": "0.1.0",
4 |
5 | "jam": {
6 | "packageDir": "vendor/jam",
7 | "baseUrl": "app/scripts",
8 |
9 | "dependencies": {
10 | "backbone": "*",
11 | "jquery": "*",
12 | "lodash": "*",
13 | "backbone.layoutmanager": "*",
14 | "handlebars": "*"
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/jam/handlebars/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "handlebars",
3 | "version": "1.0.0-beta.6.jam.1",
4 | "description": "Extension of the Mustache logicless template language",
5 | "homepage": "http://www.handlebarsjs.com/",
6 | "keywords": [
7 | "handlebars",
8 | "mustache",
9 | "template",
10 | "html"
11 | ],
12 | "main": "handlebars.js"
13 | }
14 |
--------------------------------------------------------------------------------
/app/scripts/router.js:
--------------------------------------------------------------------------------
1 | define(
2 | [
3 | "backbone.activities",
4 | "activities/animals"
5 | ],
6 | function(Backbone, Animals) {
7 |
8 | var activities = {
9 | 'animals': new Animals()
10 | };
11 |
12 | var Router = Backbone.ActivityRouter.extend({
13 | activities: activities,
14 | responsive: false,
15 | defaultRoute: '!/animals'
16 | });
17 |
18 | return Router;
19 |
20 | });
--------------------------------------------------------------------------------
/test/custom-matchers.js:
--------------------------------------------------------------------------------
1 | // Custom matchers
2 | beforeEach(function() {
3 | this.addMatchers({
4 | toBeHtml: function(html) {
5 | var actual = this.actual.replace(/\n/g, '');
6 | var expected = jasmine.JQuery.browserTagCaseIndependentHtml(html.replace(/\n/g, ''));
7 |
8 | this.message = function() {
9 | return "Expected " + actual + " to be " + expected;
10 | };
11 |
12 | return actual === expected;
13 | }
14 | });
15 | });
--------------------------------------------------------------------------------
/app/styles/vendor/_h5bp.scss:
--------------------------------------------------------------------------------
1 | // https://github.com/heygrady/compass-h5bp
2 | // A fork of https://github.com/sporkd/compass-h5bp/
3 | //
4 | // HTML5 ✰ Boilerplate
5 | //
6 | // What follows is the result of much research on cross-browser styling.
7 | // Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
8 | // Kroc Camen, and the H5BP dev community and team.
9 |
10 | @import "h5bp/normalize";
11 | @import "h5bp/main";
12 | @import "h5bp/helpers";
13 | @import "h5bp/media";
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/config.rb:
--------------------------------------------------------------------------------
1 | # Compass configuration file, used by the `compass` CLI.
2 | # To install compass, `gem install compass`
3 |
4 | # Require any additional compass plugins here.
5 |
6 | http_path = "/"
7 | css_dir = "assets/css"
8 | sass_dir = "app/styles"
9 | images_dir = "assets/img"
10 | javascripts_dir = "assets/js/plugins"
11 | fonts_dir = "assets/font"
12 |
13 | # You can select your preferred output style here (can be overridden via the command line):
14 | # output_style = :expanded or :nested or :compact or :compressed
15 | output_style = ( environment == :production ) ? :compressed : :expanded
16 |
17 | # To enable relative paths to assets via compass helper functions.
18 | relative_assets = true
19 |
--------------------------------------------------------------------------------
/app/scripts/core/device.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Feature control according to device.
3 | */
4 | define(
5 | [
6 | "recognizr"
7 | ],
8 | function(device) {
9 | // Set CSS classes from recognizr
10 | var classStr = '';
11 |
12 | if (device.browser && device.browser.family) {
13 | classStr += ' ' + device.browser.family;
14 | }
15 |
16 | if (device.scroll) {
17 | if (device.scroll.toolbar) {
18 | classStr += ' ' + device.scroll.toolbar + 'bar';
19 | }
20 |
21 | if (device.scroll.form) {
22 | classStr += ' ' + device.scroll.form + 'form';
23 | }
24 |
25 | }
26 |
27 | if (device.animations) {
28 | classStr += ' ' + 'animations';
29 | }
30 |
31 | // Set the correct class on the root HTML element.
32 | document.documentElement.className += classStr;
33 |
34 | return device;
35 |
36 | });
--------------------------------------------------------------------------------
/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 | "lodash" : "~0.9"
9 | },
10 | "devDependencies": {
11 | "phantomjs": "0.2.2"
12 | },
13 | "scripts": {
14 | "test": "phantomjs test/vendor/runner.js test/index.html"
15 | },
16 | "main" : "backbone.js",
17 | "version" : "0.9.9",
18 | "jam": {
19 | "main": "backbone.js",
20 | "include": ["backbone.js"],
21 | "dependencies": {
22 | "lodash": "~0.9"
23 | },
24 | "shim": {
25 | "deps": ["lodash"],
26 | "exports": "Backbone"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/vendor/jam/jquery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "title": "jQuery",
4 | "description": "JavaScript library for DOM operations",
5 | "version": "1.8.3",
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 | "dependencies": {},
25 | "devDependencies": {
26 | "grunt-compare-size": ">=0.1.0",
27 | "grunt-git-authors": ">=1.0.0",
28 | "grunt": "~0.3.9",
29 | "testswarm": "0.2.2"
30 | },
31 | "keywords": [],
32 | "jam": {
33 | "main": "dist/jquery.js",
34 | "include": ["dist/jquery.js"]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/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.10.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": "0.2.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/scripts/modules/list.js:
--------------------------------------------------------------------------------
1 | define([
2 | "scaffold",
3 | "app"
4 | ],
5 | function(Scaffold, app) {
6 |
7 | var List = app.module();
8 |
9 | List.Collection = Scaffold.Collection.extend({
10 | url: '/api/animals'
11 | });
12 |
13 | List.Views.Main = Scaffold.View.extend({
14 | tagName: 'ul',
15 | template: 'list-main',
16 | className: 'list',
17 | attributes: {
18 | 'data-tap': 'list'
19 | },
20 |
21 | serialize: function() {
22 | // Namespace the collection for Handlebars
23 | return {
24 | animals: this.collection.toJSON()
25 | };
26 | },
27 |
28 | initialize: function() {
29 | this.collection = this.collection || new List.Collection();
30 | this.startListening();
31 | },
32 |
33 | startListening: function() {
34 | this.listenTo(this.collection, 'reset', this.render);
35 | }
36 |
37 |
38 | });
39 |
40 | List.Views.Topbar = Scaffold.View.extend({
41 | template: 'list-topbar',
42 | className: 'headerbar-inner'
43 | });
44 |
45 | return List;
46 |
47 | });
--------------------------------------------------------------------------------
/test/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 |
--------------------------------------------------------------------------------
/app/styles/_base.scss:
--------------------------------------------------------------------------------
1 | // Base partial.
2 | // This file should be included by every module.
3 | // For this reason, it should contain definitions only - no explicit styles.
4 | // Otherwise, these styles will be repeated within the stylesheet.
5 |
6 | $legacy-support-for-ie: false;
7 | $legacy-support-for-mozilla: false;
8 |
9 | $experimental-support-for-opera: false;
10 | $experimental-support-for-microsoft: false;
11 |
12 | // Variables go first - use to override any in imports below
13 | $background-colour: #efefef;
14 | $headerbar-background-colour: #028868;
15 | $headerbar-text-colour: #f0f0f0;
16 | $list-active-background-color: lighten($headerbar-background-colour, 10%);
17 | $list-active-text-colour: #f0f0f0;
18 |
19 | $footerbar-height: 0;
20 |
21 | // Library imports
22 | // Make sure these libraries are mixins only, no explicit styles
23 | @import "compass/css3";
24 | @import "compass/css3/user-interface"; // https://github.com/chriseppstein/compass/issues/759
25 |
26 | // Mixins
27 | @mixin text-ellipsis {
28 | white-space: nowrap;
29 | overflow: hidden;
30 | text-overflow: ellipsis;
31 | }
32 |
33 | @mixin list-style-reset {
34 | list-style: none;
35 | margin: 0;
36 | padding: 0;
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/vendor/jam/lodash/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lodash",
3 | "version": "1.0.0-rc.3",
4 | "description": "An alternative to Underscore.js, delivering consistency, customization, performance, and extra features.",
5 | "homepage": "http://lodash.com",
6 | "main": "./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 && node ./test/test-build.js"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/scripts/modules/detail.js:
--------------------------------------------------------------------------------
1 | define([
2 | "scaffold",
3 | "app"
4 | ],
5 | function(Scaffold, app) {
6 |
7 | var Detail = app.module();
8 |
9 | Detail.Model = Scaffold.Model.extend({
10 |
11 | defaults: {
12 | id: null,
13 | name: '',
14 | description: '',
15 | img: ''
16 | },
17 |
18 | url: function() {
19 | return '/api/animals/' + this.get('id');
20 | }
21 | });
22 |
23 | Detail.Views.Main = Scaffold.View.extend({
24 | template: 'detail-main',
25 | className: 'detail',
26 |
27 | initialize: function() {
28 | this.model = this.model || new Detail.Model();
29 | this.startListening();
30 | },
31 |
32 | startListening: function() {
33 | this.listenTo(this.model, 'change', this.render);
34 | }
35 | });
36 |
37 | Detail.Views.Topbar = Scaffold.View.extend({
38 | template: 'detail-topbar',
39 | className: 'headerbar-inner',
40 |
41 | initialize: function() {
42 | this.model = this.model || new Detail.Model();
43 | this.startListening();
44 | },
45 |
46 | startListening: function() {
47 | this.listenTo(this.model, 'change', this.render);
48 | }
49 | });
50 |
51 | return Detail;
52 |
53 | });
--------------------------------------------------------------------------------
/test/vendor/jasmine.async.js:
--------------------------------------------------------------------------------
1 | // Jasmine.Async, v0.1.0
2 | // Copyright (c)2012 Muted Solutions, LLC. All Rights Reserved.
3 | // Distributed under MIT license
4 | // http://github.com/derickbailey/jasmine.async
5 | this.AsyncSpec = (function(global){
6 |
7 | // Private Methods
8 | // ---------------
9 |
10 | function runAsync(block){
11 | return function(){
12 | var done = false;
13 | var complete = function(){ done = true; };
14 |
15 | runs(function(){
16 | block(complete);
17 | });
18 |
19 | waitsFor(function(){
20 | return done;
21 | });
22 | };
23 | }
24 |
25 | // Constructor Function
26 | // --------------------
27 |
28 | function AsyncSpec(spec){
29 | this.spec = spec;
30 | }
31 |
32 | // Public API
33 | // ----------
34 |
35 | AsyncSpec.prototype.beforeEach = function(block){
36 | this.spec.beforeEach(runAsync(block));
37 | };
38 |
39 | AsyncSpec.prototype.afterEach = function(block){
40 | this.spec.afterEach(runAsync(block));
41 | };
42 |
43 | AsyncSpec.prototype.it = function(description, block){
44 | // For some reason, `it` is not attached to the current
45 | // test suite, so it has to be called from the global
46 | // context.
47 | global.it(description, runAsync(block));
48 | };
49 |
50 | return AsyncSpec;
51 | })(this);
52 |
--------------------------------------------------------------------------------
/vendor/jam/backbone.layoutmanager/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Tim Branyen (@tbranyen)",
3 | "name": "backbone.layoutmanager",
4 | "description": "A manager for templates & layouts in Backbone.js",
5 | "version": "0.8.1",
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": "~0.9",
17 | "underscore.deferred": "~0.4",
18 | "cheerio": "~0.10",
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": "~0.9",
35 | "jquery": "~1.8"
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/styles/vendor/h5bp/_media.scss:
--------------------------------------------------------------------------------
1 | // Print styles
2 | // Inlined to avoid required HTTP connection: h5bp.com/r
3 |
4 | @mixin h5bp-media {
5 | @media print {
6 | @include h5bp-media-print;
7 | }
8 | }
9 |
10 | @mixin h5bp-media-print {
11 | * {
12 | background: transparent !important;
13 | color: #000 !important; // Black prints faster: h5bp.com/s
14 | box-shadow:none !important;
15 | text-shadow: none !important;
16 | }
17 |
18 | a,
19 | a:visited {
20 | text-decoration: underline;
21 | }
22 |
23 | a[href]:after {
24 | content: " (" attr(href) ")";
25 | }
26 |
27 | abbr[title]:after {
28 | content: " (" attr(title) ")";
29 | }
30 |
31 | //
32 | // Don't show links for images, or javascript/internal links
33 | //
34 |
35 | .ir a:after,
36 | a[href^="javascript:"]:after,
37 | a[href^="#"]:after {
38 | content: "";
39 | }
40 |
41 | pre,
42 | blockquote {
43 | border: 1px solid #999;
44 | page-break-inside: avoid;
45 | }
46 |
47 | thead {
48 | display: table-header-group; // h5bp.com/t
49 | }
50 |
51 | tr,
52 | img {
53 | page-break-inside: avoid;
54 | }
55 |
56 | img {
57 | max-width: 100% !important;
58 | }
59 |
60 | @page {
61 | margin: 0.5cm;
62 | }
63 |
64 | p,
65 | h2,
66 | h3 {
67 | orphans: 3;
68 | widows: 3;
69 | }
70 |
71 | h2,
72 | h3 {
73 | page-break-after: avoid;
74 | }
75 | }
--------------------------------------------------------------------------------
/app/scripts/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"],
7 |
8 | paths: {
9 |
10 | // Use the underscore build of Lo-Dash to minimize incompatibilities.
11 | "lodash": "../../vendor/jam/lodash/lodash.underscore",
12 |
13 | // plugins and libraries not available as jam.js packages
14 | "recognizr": "../../vendor/js/libs/recognizr",
15 | "fastclick": "../../vendor/js/libs/fastclick",
16 | "tappivate": "../../vendor/js/plugins/tappivate",
17 | "backbone.activities": "../../vendor/js/plugins/backbone.activities",
18 | "backbone.super": "../../vendor/js/plugins/backbone.super",
19 |
20 | 'scaffold': 'core/scaffold'
21 | },
22 |
23 | map: {
24 | // Ensure Lo-Dash is used instead of underscore.
25 | "*": { "underscore": "lodash" }
26 |
27 | // Put additional maps here.
28 | },
29 |
30 | shim: {
31 | // Handlebars has no dependencies.
32 | "lodash": {
33 | exports: "_"
34 | },
35 |
36 | // Backbone.Super depends on Backbone.
37 | "backbone.super": {
38 | deps: ["backbone"],
39 | exports: "Backbone"
40 | },
41 |
42 | // Backbone.Activities depends on Backbone and exports Backbone
43 | "backbone.activities": {
44 | deps: ["backbone"],
45 | exports: "Backbone"
46 | },
47 |
48 | "tappivate": {
49 | deps: ["jquery"]
50 | },
51 |
52 | "fastclick": {
53 | exports: "FastClick"
54 | }
55 | }
56 |
57 | });
58 |
--------------------------------------------------------------------------------
/spec.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 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/scripts/activities/animals.js:
--------------------------------------------------------------------------------
1 | define([
2 | "backbone.activities",
3 | "modules/list",
4 | "modules/detail"
5 | ],
6 | function(Backbone, List, Detail) {
7 |
8 | var ListHandler = Backbone.ActivityRouteHandler.extend({
9 |
10 | onStart: function() {
11 | // Render the data when the activity starts
12 | this.updateRegions({
13 | 'headerbar': new List.Views.Topbar(),
14 | 'main': new List.Views.Main({
15 | collection: this.activity.collection
16 | })
17 | });
18 |
19 | }
20 |
21 | });
22 |
23 | var DetailHandler = Backbone.ActivityRouteHandler.extend({
24 |
25 | onStart: function(id) {
26 | this.model = new Detail.Model({id: id});
27 | this.updateRegions({
28 | 'headerbar': new Detail.Views.Topbar({ model: this.model }),
29 | 'main': new Detail.Views.Main({ model: this.model })
30 | });
31 | this.model.fetch();
32 | },
33 |
34 | onStop: function() {
35 | // Forget the model state
36 | this.model = null; // Garbage collect model in Topbar and Main views
37 | delete this.model; // Remove model from this handler
38 | }
39 |
40 | });
41 |
42 | var Activity = Backbone.Activity.extend({
43 |
44 | initialize: function() {
45 | // Singleton collection
46 | this.collection = new List.Collection();
47 | },
48 |
49 | onCreate: function() {
50 | // fetch the data when we first land on animals
51 | this.collection.fetch();
52 | },
53 |
54 | routes: {
55 | '!/animals': new ListHandler(),
56 | '!/animals/:id': new DetailHandler()
57 | }
58 |
59 | });
60 |
61 | return Activity;
62 |
63 | });
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | define(
2 | [
3 | "lodash",
4 | "backbone"
5 | ],
6 | function(_, Backbone) {
7 |
8 | // Provide a global location to place configuration settings and module
9 | // creation.
10 | var app = {
11 | // The root path to run the application.
12 | root: ""
13 | };
14 |
15 | // Convenience function for registering a method as an event
16 | var _registerWith = function(namespace, context) {
17 | return function(item, key) {
18 | if(_.isFunction(item)) {
19 | // Add an event listener on this function which
20 | // can be accessed via app.trigger() at any time
21 | app.on(namespace + ':' + key, function() {
22 | item.apply(context, arguments);
23 | });
24 | }
25 | };
26 |
27 | };
28 |
29 | // Mix Backbone.Events into the app object.
30 | return _.extend(app, {
31 | // Create a custom object with a nested Views object.
32 | module: function(additionalProps) {
33 | return _.extend({
34 | Views: {}
35 | }, additionalProps);
36 | },
37 |
38 | // A way to register modules for application-wide events.
39 | register: function(namespace, module, context) {
40 | if(!context) {
41 | context = module;
42 | }
43 |
44 | // Register each function in the module
45 | // as an event that can be called
46 | // via app.trigger() at a later date
47 | _.each(module, _registerWith(namespace, context));
48 |
49 | },
50 |
51 | // Deregister everything previously bound to this module.
52 | deregister: function(module) {
53 | app.off(null, null, module);
54 | }
55 |
56 | }, Backbone.Events);
57 |
58 | });
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Mobile Backbone Boilerplate
2 | ===========================
3 |
4 | A mobile-optimised version of the original Backbone Boilerplate project. Comes complete with a typical list-detail example application.
5 |
6 | 
7 |
8 | Backbone Boilerplate
9 | ====================
10 |
11 | This boilerplate is the product of much research and frustration. Existing
12 | boilerplates freely modify Backbone core, lack a build process, and are very
13 | prescriptive; Backbone Boilerplate changes that.
14 |
15 | Organize your application with a logical file structure, develop your
16 | Models/Collections/Views/Routers inside modules, and build knowing you have
17 | efficient code that will not bottleneck your users.
18 |
19 | Thanks to our
20 | [Contributors](https://github.com/tbranyen/backbone-boilerplate/contributors)!
21 |
22 | Special Thanks to: [cowboy](http://github.com/cowboy),
23 | [iros](http://github.com/iros), [nimbupani](http://github.com/nimbupani),
24 | [wookiehangover](http://github.com/wookiehangover), and
25 | [jugglinmike](http://github.com/jugglinmike) for helping me create this project.
26 |
27 | Extra Special Thanks to: [Paul Guinan](http://bigredhair.com/work/paul.html)
28 | for giving me usage rights to his fantastic Boilerplate character.
29 |
30 | ## Documentation ##
31 |
32 | View the Backbone Boilerplate documentation here:
33 |
34 | [GitHub Wiki](https://github.com/tbranyen/backbone-boilerplate/wiki)
35 |
36 | ## Build process ##
37 |
38 | To use the new and improved build process, please visit the
39 | [grunt-bbb](https://github.com/backbone-boilerplate/grunt-bbb)
40 | plugin repo and follow the instructions to install. Basing your project off
41 | this repo will allow the `bbb` commands to work out-of-the-box.
42 |
--------------------------------------------------------------------------------
/app/styles/vendor/h5bp/_main.scss:
--------------------------------------------------------------------------------
1 | $line-height: 1.4 !default;
2 | $font-color: #222 !default;
3 | $font-family: sans-serif !default;
4 | $font-size: 1em !default;
5 | $selected-font-color: #fff !default;
6 | $selected-background-color: #b3d4fc !default;
7 |
8 | @mixin h5bp-main {
9 | @include h5bp-base-styles;
10 | @include h5bp-chromeframe;
11 | }
12 |
13 | //
14 | // Base styles: opinionated defaults
15 | //
16 | @mixin h5bp-base-styles {
17 |
18 | html,
19 | button,
20 | input,
21 | select,
22 | textarea {
23 | color: $font-color;
24 | }
25 |
26 | body {
27 | font-size: $font-size;
28 | line-height: $line-height;
29 | }
30 |
31 | // Remove text-shadow in selection highlight: h5bp.com/i
32 | // These selection declarations have to be separate.
33 | // Customize the background color to match your design.
34 | ::-moz-selection {
35 | background: $selected-background-color;
36 | text-shadow: none;
37 | }
38 |
39 | ::selection {
40 | background: $selected-background-color;
41 | text-shadow: none;
42 | }
43 |
44 | // A better looking default horizontal rule
45 | hr {
46 | display: block;
47 | height: 1px;
48 | border: 0;
49 | border-top: 1px solid #ccc;
50 | margin: 1em 0;
51 | padding: 0;
52 | }
53 |
54 | // Remove the gap between images and the bottom of their containers: h5bp.com/i/440
55 | img {
56 | vertical-align: middle;
57 | }
58 |
59 | // Remove default fieldset styles.
60 | fieldset {
61 | border: 0;
62 | margin: 0;
63 | padding: 0;
64 | }
65 |
66 | // Allow only vertical resizing of textareas.
67 | textarea {
68 | resize: vertical;
69 | }
70 | }
71 |
72 | //
73 | // Chrome Frame Prompt
74 | //
75 |
76 | @mixin h5bp-chromeframe {
77 | .chromeframe {
78 | margin: 0.2em 0;
79 | background: #ccc;
80 | color: #000;
81 | padding: 0.2em 0;
82 | }
83 | }
--------------------------------------------------------------------------------
/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": "handlebars",
15 | "location": "../../vendor/jam/handlebars",
16 | "main": "handlebars.js"
17 | },
18 | {
19 | "name": "jquery",
20 | "location": "../../vendor/jam/jquery",
21 | "main": "dist/jquery.js"
22 | },
23 | {
24 | "name": "lodash",
25 | "location": "../../vendor/jam/lodash",
26 | "main": "./lodash.js"
27 | },
28 | {
29 | "name": "underscore",
30 | "location": "../../vendor/jam/underscore",
31 | "main": "underscore.js"
32 | }
33 | ],
34 | "version": "0.2.11",
35 | "shim": {
36 | "backbone": {
37 | "deps": [
38 | "lodash"
39 | ],
40 | "exports": "Backbone"
41 | },
42 | "backbone.layoutmanager": {
43 | "deps": [
44 | "jquery",
45 | "backbone",
46 | "underscore"
47 | ],
48 | "exports": "Backbone.Layout"
49 | },
50 | "underscore": {
51 | "exports": "_"
52 | }
53 | }
54 | };
55 |
56 | if (typeof require !== "undefined" && require.config) {
57 | require.config({packages: jam.packages, shim: jam.shim});
58 | }
59 | else {
60 | var require = {packages: jam.packages, shim: jam.shim};
61 | }
62 |
63 | if (typeof exports !== "undefined" && typeof module !== "undefined") {
64 | module.exports = jam;
65 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backbone Mobile Boilerplate
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/vendor/js/plugins/backbone.super.js:
--------------------------------------------------------------------------------
1 | // https://gist.github.com/1542120
2 | //
3 | // This method gives you an easier way of calling super
4 | // when you're using Backbone in plain javascript.
5 | // It lets you avoid writing the constructor's name multiple
6 | // times. You still have to specify the name of the method.
7 | //
8 | // So instead of having to write:
9 | //
10 | // User = Backbone.Model.extend({
11 | // save: function(attrs) {
12 | // this.beforeSave(attrs);
13 | // return User.__super__.save.apply(this, arguments);
14 | // }
15 | // });
16 | //
17 | // You get to write:
18 | //
19 | // User = Backbone.Model.extend({
20 | // save: function(attrs) {
21 | // this.beforeSave(attrs);
22 | // return this._super("save", arguments);
23 | // }
24 | // });
25 | //
26 |
27 | ;(function(Backbone) {
28 |
29 | // The super method takes two parameters: a method name
30 | // and an array of arguments to pass to the overridden method.
31 | // This is to optimize for the common case of passing 'arguments'.
32 | function _super(methodName, args) {
33 |
34 | // Keep track of how far up the prototype chain we have traversed,
35 | // in order to handle nested calls to _super.
36 | this._superCallObjects || (this._superCallObjects = {});
37 | var currentObject = this._superCallObjects[methodName] || this,
38 | parentObject = findSuper(methodName, currentObject);
39 | this._superCallObjects[methodName] = parentObject;
40 |
41 | var result = parentObject[methodName].apply(this, args || []);
42 | delete this._superCallObjects[methodName];
43 | return result;
44 | }
45 |
46 | // Find the next object up the prototype chain that has a
47 | // different implementation of the method.
48 | function findSuper(methodName, childObject) {
49 | var object = childObject;
50 | while (object[methodName] === childObject[methodName]) {
51 | object = object.constructor.__super__;
52 | }
53 | return object;
54 | }
55 |
56 | _.each(["Model", "Collection", "View", "Router"], function(klass) {
57 | Backbone[klass].prototype._super = _super;
58 | });
59 |
60 | })(Backbone);
--------------------------------------------------------------------------------
/app/scripts/main.js:
--------------------------------------------------------------------------------
1 | require(
2 | [
3 | "jquery",
4 | "fastclick",
5 | "app",
6 | "router",
7 | "scaffold",
8 | "core/device",
9 | "tappivate"
10 | ],
11 | function($, FastClick, app, Router, Scaffold, device, tappivate) {
12 |
13 | $(function() {
14 | // Define the regions in the page; create a Region for each by passing
15 | // in the parent element.
16 | var regions = {
17 | 'headerbar': new Scaffold.Region({ el: '#headerbar' }),
18 | 'main': new Scaffold.Region({ el: '#main' })
19 | };
20 |
21 | // Set up the router for the application and pass in the regions.
22 | app.router = new Router({
23 | regions: regions,
24 | el: '#app'
25 | });
26 |
27 | // Setup FastClick to prevent 300ms button delay
28 | new FastClick(document.getElementById('app'));
29 |
30 | // Setup tappivate to mimic native button taps
31 | $('#app').tappivate();
32 |
33 | // Trigger the initial route, set the
34 | // root folder to '' by default. Change in app.js.
35 | Backbone.history.start({ pushState: false, root: app.root });
36 |
37 | });
38 |
39 |
40 | // All navigation that is relative should be passed through the navigate
41 | // method, to be processed by the router. If the link has a `data-bypass`
42 | // attribute, bypass the delegation completely.
43 | $(document).on("click", "a[href]:not([data-bypass])", function(evt) {
44 | // Get the absolute anchor href.
45 | var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
46 | // Get the absolute root.
47 | var root = location.protocol + "//" + location.host + app.root;
48 |
49 | // Ensure the root is part of the anchor href, meaning it's relative.
50 | if (href.prop.slice(0, root.length) === root) {
51 | // Stop the default event to ensure the link will not cause a page
52 | // refresh.
53 | evt.preventDefault();
54 |
55 | // `Backbone.history.navigate` is sufficient for all Routers and will
56 | // trigger the correct events. The Router's internal `navigate` method
57 | // calls this anyways. The fragment is sliced from the root.
58 | Backbone.history.navigate(href.attr, true);
59 | }
60 | });
61 |
62 | });
--------------------------------------------------------------------------------
/app/scripts/core/scaffold.js:
--------------------------------------------------------------------------------
1 | define(
2 | [
3 | // Core
4 | "jquery",
5 | "lodash",
6 | "backbone",
7 | "backbone.layoutmanager",
8 | "handlebars",
9 | "app",
10 |
11 | // Plugins
12 | "backbone.super"
13 | ],
14 | function($, _, Backbone, LayoutManager, Handlebars, app) {
15 |
16 | // Localize or create a new JavaScript Template object.
17 | var JST = window.JST = window.JST || {};
18 |
19 | // Configure LayoutManager with Backbone Boilerplate defaults.
20 | LayoutManager.configure({
21 | // Allow LayoutManager to augment Backbone.View.prototype.
22 | manage: true,
23 |
24 | prefix: "app/templates/",
25 |
26 | fetch: function(path) {
27 | // Concatenate the file extension.
28 | path = path + ".html";
29 |
30 | // If cached, use the compiled template.
31 | if(JST[path]) {
32 | if(!JST[path].__compiled__) {
33 | JST[path] = Handlebars.template(JST[path]);
34 | JST[path].__compiled__ = true;
35 | }
36 | return JST[path];
37 | }
38 |
39 | // Put fetch into `async-mode`.
40 | var done = this.async();
41 |
42 | // Seek out the template asynchronously.
43 | // Explicitly specify the template as HTML
44 | // as some mobile WebViews will return a template
45 | // containing a single wrapper div as an
46 | // XML Document object
47 | $.get(app.root + path, function(contents) {
48 | JST[path] = Handlebars.compile(contents);
49 | JST[path].__compiled__ = true;
50 | done(JST[path]);
51 | }, "text");
52 | }
53 | });
54 |
55 | var Scaffold = {
56 | Model: Backbone.Model.extend({}),
57 |
58 | Collection: Backbone.Collection.extend({}),
59 |
60 | View: Backbone.View.extend({
61 |
62 | bindTo: function(object, evt, callback) {
63 | // Alias for old code
64 | this.listenTo(object, evt, callback);
65 | },
66 |
67 | cleanup: function() {
68 | this.stopListening();
69 | if (_.isFunction(this.dispose)) {
70 | this.dispose();
71 | }
72 | },
73 |
74 | // Default serialize function, if a model exsits.
75 | serialize: function() {
76 | if (this.model) {
77 | return this.model.toJSON();
78 | }
79 | }
80 | }),
81 |
82 | Region: Backbone.Layout
83 | };
84 |
85 | return Scaffold;
86 | });
87 |
--------------------------------------------------------------------------------
/app/styles/vendor/h5bp/_helpers.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Helper classes
3 | //
4 |
5 | @mixin h5bp-helpers {
6 |
7 | .ir { @include image-replacement; }
8 |
9 | .hidden { @include hidden; }
10 |
11 | .visuallyhidden { @include visually-hidden; }
12 |
13 | .invisible { @include invisible; }
14 |
15 | .clearfix { @include micro-clearfix; }
16 |
17 | }
18 |
19 | // Image replacement
20 | @mixin image-replacement($img: none, $x: 50%, $y: 50%) {
21 | background-color: transparent;
22 | border: 0;
23 | overflow: hidden;
24 | // IE 6/7 fallback
25 | *text-indent: -9999px;
26 |
27 | &:before {
28 | content: "";
29 | display: block;
30 | width: 0;
31 | height: 100%;
32 | }
33 |
34 | @if $img != none {
35 | background-image: image-url($img);
36 | background-position: $x $y;
37 | }
38 | }
39 |
40 | // Uses image dimensions
41 | @mixin sized-image-replacement($img, $x: 50%, $y: 50%) {
42 | @include image-replacement($img, $x, $y);
43 | width: image-width($img);
44 | height: image-height($img);
45 | }
46 |
47 | // Hide from both screenreaders and browsers: h5bp.com/u
48 | @mixin hidden {
49 | display: none !important;
50 | visibility: hidden;
51 | }
52 |
53 | // Hide only visually, but have it available for screenreaders: h5bp.com/v
54 | @mixin visually-hidden {
55 | border: 0;
56 | clip: rect(0 0 0 0);
57 | height: 1px;
58 | margin: -1px;
59 | overflow: hidden;
60 | padding: 0;
61 | position: absolute;
62 | width: 1px;
63 |
64 | // Extends the .visuallyhidden class to allow the element to be focusable
65 | // when navigated to via the keyboard: h5bp.com/p
66 | &.focusable:active,
67 | &.focusable:focus {
68 | clip: auto;
69 | height: auto;
70 | margin: 0;
71 | overflow: visible;
72 | position: static;
73 | width: auto;
74 | }
75 | }
76 |
77 | // Hide visually and from screenreaders, but maintain layout
78 | @mixin invisible {
79 | visibility: hidden;
80 | }
81 |
82 | // Clearfix: contain floats
83 | //
84 | // For modern browsers
85 | // 1. The space content is one way to avoid an Opera bug when the
86 | // `contenteditable` attribute is included anywhere else in the document.
87 | // Otherwise it causes space to appear at the top and bottom of elements
88 | // that receive the `clearfix` class.
89 | // 2. The use of `table` rather than `block` is only necessary if using
90 | // `:before` to contain the top-margins of child elements.
91 | @mixin micro-clearfix {
92 | &:before,
93 | &:after {
94 | content: " "; // 1
95 | display: table; // 2
96 | }
97 |
98 | &:after {
99 | clear: both;
100 | }
101 |
102 | // For IE 6/7 only
103 | // Include this rule to trigger hasLayout and contain floats.
104 | & {
105 | *zoom: 1;
106 | }
107 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/vendor/js/plugins/tappivate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Tappivate Zepto plugin
3 | * Makes your mobile tappable buttons and lists feel a little more native-y
4 | * The perfect companion to Zepto's touch module, also compatible with jQuery.
5 | * Author: @fiznool
6 | * Licensed under the MIT license
7 | *
8 | */
9 |
10 | ;(function ( $, window, document, undefined ) {
11 |
12 | var defaults = {
13 | listItemActivationDelay: 150,
14 | buttonDeactivationDelay: 100,
15 | onActivate: function($el) {
16 | $el.addClass('active');
17 | },
18 | onDeactivate: function($el) {
19 | $el.removeClass('active');
20 | }
21 | };
22 |
23 | // A base handler which shares common functionaity for buttons and lists
24 | var Handler = function() {
25 | this.timerId = null;
26 | };
27 |
28 | Handler.prototype.activate = function($el) {
29 | this.onActivate($el);
30 | };
31 |
32 | Handler.prototype.deactivate = function($el) {
33 | this.onDeactivate($el);
34 | };
35 |
36 | Handler.prototype.cancelTimeout = function() {
37 | if (this.timerId) {
38 | clearTimeout(this.timerId);
39 | }
40 | };
41 |
42 | Handler.prototype.afterDelay = function(callback) {
43 | this.cancelTimeout();
44 | this.timerId = setTimeout(callback, this.delay);
45 | };
46 |
47 | Handler.prototype.cancelDelay = function(callback) {
48 | this.cancelTimeout();
49 | callback.apply(this);
50 | };
51 |
52 | // Override these three in the subclasses
53 | Handler.prototype.touchstart = function($el) {};
54 | Handler.prototype.touchmove = function($el) {};
55 | Handler.prototype.touchend = function($el) {};
56 |
57 |
58 | var ButtonHandler = function(options) {
59 | this.delay = options.delay || defaults.buttonDeactivationDelay;
60 | this.onActivate = options.activate || defaults.onActivate;
61 | this.onDeactivate = options.deactivate || defaults.onDeactivate;
62 | };
63 |
64 | ButtonHandler.prototype = new Handler();
65 |
66 | ButtonHandler.prototype.touchstart = function($el) {
67 | this.cancelDelay(function() { this.activate($el); });
68 | };
69 |
70 | ButtonHandler.prototype.touchend = function($el) {
71 | var that = this;
72 | this.afterDelay(function() { that.deactivate($el); });
73 | };
74 |
75 |
76 | var ListHandler = function(options) {
77 | this.delay = options.delay || defaults.listItemActivationDelay;
78 | this.onActivate = options.activate || defaults.onActivate;
79 | this.onDeactivate = options.deactivate || defaults.onDeactivate;
80 | };
81 |
82 | ListHandler.prototype = new Handler();
83 |
84 | ListHandler.prototype.touchstart = function($el) {
85 | var that = this;
86 | this.afterDelay(function() { that.activate($el); });
87 | };
88 |
89 | ListHandler.prototype.touchmove = function($el) {
90 | this.cancelDelay(function() { this.deactivate($el); });
91 | };
92 |
93 | ListHandler.prototype.touchend = function($el) {
94 | this.cancelDelay(function() { this.deactivate($el); });
95 | };
96 |
97 |
98 | $.fn.tappivate = function(options) {
99 |
100 | options = options || {};
101 |
102 | var buttonHandler = new ButtonHandler(options);
103 | var listHandler = new ListHandler(options);
104 |
105 | // Use jQuery.on with the passed in parent element to match
106 | // all children with the selector passed as the second parameter.
107 | // The selectors match any element with the data-tap attribute set.
108 | // The ~= part of the selector is a whitespace-aware selector:
109 | // it allows the selector to match whole words but also can match
110 | // multiple words separated by whitespace.
111 | // This is useful for lists which you want to exhibit different behaviour.
112 | // e.g.
113 | // would be matched by both [data-tap~="list"] and [data-tap~="nav"]
114 |
115 |
116 | this.on('touchstart', '[data-tap~="btn"]', function() {
117 | buttonHandler.touchstart($(this));
118 | });
119 |
120 | this.on('touchend touchleave touchcancel', '[data-tap~="btn"]', function() {
121 | buttonHandler.touchend($(this));
122 | });
123 |
124 | this.on('touchstart', '[data-tap~="list"] > li', function() {
125 | listHandler.touchstart($(this));
126 | });
127 |
128 | this.on('touchmove', '[data-tap~="list"] > li', function() {
129 | listHandler.touchmove($(this));
130 | });
131 |
132 | this.on('touchend touchleave touchcancel', '[data-tap~="nav"] > li', function() {
133 | listHandler.touchend($(this));
134 | });
135 |
136 | return this;
137 |
138 | };
139 |
140 | })( window.Zepto || window.jQuery, window, document );
141 |
--------------------------------------------------------------------------------
/test/spec/list.spec.js:
--------------------------------------------------------------------------------
1 | // Tests are derived from the excellent book by Addy Osmani
2 | // http://addyosmani.github.com/backbone-fundamentals/#unit-testing-backbone-applications-with-jasmine
3 | define([
4 | 'modules/list'
5 | ], function(List) {
6 |
7 | describe("animals list module", function() {
8 |
9 | it("is structured correctly", function() {
10 | expect(List).toBeDefined();
11 | });
12 |
13 | describe("collection", function() {
14 | it("exists in list module", function() {
15 | expect(List.Collection).toBeDefined();
16 | });
17 |
18 | it("has a defined URL", function() {
19 | var c = new List.Collection();
20 | expect(c.url).toBeDefined();
21 | expect(c.url).toBe('/api/animals');
22 | });
23 | });
24 |
25 | describe("main view", function() {
26 | var v;
27 |
28 | describe("scaffolding", function() {
29 | beforeEach(function() {
30 | v = new List.Views.Main();
31 | });
32 |
33 | afterEach(function() {
34 | v = null;
35 | });
36 |
37 | it("exists in list module", function() {
38 | expect(List.Views).toBeDefined();
39 | expect(List.Views.Main).toBeDefined();
40 | });
41 |
42 | it("has ul tagname", function() {
43 | expect(v.tagName).toBe('ul');
44 | expect(v.el.tagName.toLowerCase()).toBe('ul');
45 | expect(v.$el).toBe('ul');
46 | });
47 |
48 | it("has list-main template", function() {
49 | expect(v.template).toBe('list-main');
50 | });
51 |
52 | it("has list className", function() {
53 | expect(v.className).toBe('list');
54 | expect(v.$el).toHaveClass('list');
55 | });
56 |
57 | it("has data-tap=list attribute", function() {
58 | expect(v.attributes).toBeDefined();
59 | expect(v.attributes['data-tap']).toBeDefined();
60 | expect(v.attributes['data-tap']).toBe('list');
61 | expect(v.$el).toHaveAttr('data-tap', 'list');
62 | });
63 |
64 | it("must create an empty collection if none is defined", function() {
65 | expect(v.collection).toBeDefined();
66 | });
67 |
68 | it("must use an existing collection if one is defined", function() {
69 | var c = new List.Collection();
70 | expect(v.collection).not.toBe(c);
71 |
72 | v = new List.Views.Main({ collection: c });
73 | expect(v.collection).toBe(c);
74 | });
75 |
76 | });
77 |
78 | // LayoutManager uses promises for rendering.
79 | // We need to wait for the promise to be resolved before
80 | // testing that rendering has worked correctly.
81 | // For this, we use the jasmine Async plugin:
82 | // http://lostechies.com/derickbailey/2012/08/18/jasmine-async-making-asynchronous-testing-with-jasmine-suck-less/
83 | describe("rendering", function() {
84 |
85 | var async = new AsyncSpec(this);
86 |
87 | beforeEach(function() {
88 | v = new List.Views.Main();
89 | });
90 |
91 | afterEach(function() {
92 | v = null;
93 | });
94 |
95 | async.it("produces an empty list with no collection data", function(done) {
96 | v.render().then(function() {
97 | expect(v.el.outerHTML).toBe('');
98 | done();
99 | });
100 |
101 | });
102 |
103 | async.it("produces a list with some collection data", function(done) {
104 | v.collection = new List.Collection([
105 | { name: 'Lion', id: 'lion' },
106 | { name: 'Badger', id: 'badger'}
107 | ]);
108 |
109 | v.render().then(function() {
110 | expect(v.el.innerHTML).toBe('LionBadger');
111 | done();
112 | });
113 |
114 | });
115 |
116 | it("renders when collection changes", function() {
117 | var done = false;
118 |
119 | spyOn(v, 'render');
120 |
121 | // To test Backbone events, you should put all your
122 | // 'listenTo' calls in a separate function.
123 | // This is so that the spy setup above can be re-bound
124 | // to the Backbone event by calling stopListening
125 | // and then startListening again.
126 | // Without this call, render cannot be spied on.
127 | v.stopListening();
128 | v.startListening();
129 |
130 | v.collection.reset([
131 | { name: 'Lion', id: 'lion' },
132 | { name: 'Badger', id: 'badger'}
133 | ]);
134 |
135 | expect(v.render).toHaveBeenCalled();
136 |
137 | });
138 |
139 | });
140 |
141 | });
142 |
143 | });
144 |
145 | });
--------------------------------------------------------------------------------
/test/spec/detail.spec.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'modules/detail'
3 | ], function(Module) {
4 |
5 | describe("animals detail module", function() {
6 |
7 | it("is structured correctly", function() {
8 | expect(Module).toBeDefined();
9 | });
10 |
11 | describe("model", function() {
12 |
13 | it("exists in module", function() {
14 | expect(Module.Model).toBeDefined();
15 | });
16 |
17 | it("can be created with default attribute values", function() {
18 | var m = new Module.Model();
19 | expect(m.get('id')).toBeNull();
20 | expect(m.get('name')).toBe('');
21 | expect(m.get('description')).toBe('');
22 | expect(m.get('img')).toBe('');
23 | });
24 |
25 | it("has a defined URL when initiated with an ID", function() {
26 | var m = new Module.Model({
27 | id: 1
28 | });
29 |
30 | expect(m.url).toBeDefined();
31 | expect(m.url()).toBe('/api/animals/1');
32 | });
33 |
34 | });
35 |
36 | describe("main view", function() {
37 | var v;
38 |
39 | it("exists in module", function() {
40 | expect(Module.Views).toBeDefined();
41 | expect(Module.Views.Main).toBeDefined();
42 | });
43 |
44 | describe("scaffolding", function() {
45 | beforeEach(function() {
46 | v = new Module.Views.Main();
47 | });
48 |
49 | afterEach(function() {
50 | v = null;
51 | });
52 |
53 | it("has div root element", function() {
54 | expect(v.$el).toBe('div');
55 | });
56 |
57 | it("has detail-main template", function() {
58 | expect(v.template).toBe('detail-main');
59 | });
60 |
61 | it("has detail className", function() {
62 | expect(v.$el).toHaveClass('detail');
63 | });
64 |
65 | it("must create an empty model if none is defined", function() {
66 | expect(v.model).toBeDefined();
67 | });
68 |
69 | it("must use an existing model if one is defined", function() {
70 | var m = new Module.Model();
71 | expect(v.model).not.toBe(m);
72 |
73 | v = new Module.Views.Main({ model: m });
74 | expect(v.model).toBe(m);
75 | });
76 |
77 | });
78 |
79 | // LayoutManager uses promises for rendering.
80 | // We need to wait for the promise to be resolved before
81 | // testing that rendering has worked correctly.
82 | // For this, we use the jasmine Async plugin:
83 | // http://lostechies.com/derickbailey/2012/08/18/jasmine-async-making-asynchronous-testing-with-jasmine-suck-less/
84 | describe("rendering", function() {
85 |
86 | var async = new AsyncSpec(this);
87 |
88 | beforeEach(function() {
89 | v = new Module.Views.Main();
90 | });
91 |
92 | afterEach(function() {
93 | v = null;
94 | });
95 |
96 | async.it("produces expected HTML with no data", function(done) {
97 | var expected =
98 | '' +
99 | '
![]()
' +
100 | '
' +
101 | '
';
102 |
103 | v.render().then(function() {
104 | expect(v.el.outerHTML).toBeHtml(expected);
105 | done();
106 | });
107 |
108 | });
109 |
110 | async.it("produces expected HTML with model data", function(done) {
111 | var expected =
112 | '' +
113 | '

' +
114 | '
King of the Jungle.
' +
115 | '
';
116 |
117 | v.model = new Module.Model({
118 | id: 1,
119 | name: 'Lion',
120 | img: 'lion.jpg',
121 | description: 'King of the Jungle.'
122 | });
123 |
124 | v.render().then(function() {
125 | expect(v.el.outerHTML).toBeHtml(expected);
126 | done();
127 | });
128 |
129 | });
130 |
131 | it("renders when model changes", function() {
132 | spyOn(v, 'render');
133 |
134 | // To test Backbone events, you should put all your
135 | // 'listenTo' calls in a separate function.
136 | // This is so that the spy setup above can be re-bound
137 | // to the Backbone event by calling stopModuleening
138 | // and then startModuleening again.
139 | // Without this call, render cannot be spied on.
140 | v.stopListening();
141 | v.startListening();
142 |
143 | v.model.set({
144 | id: 1,
145 | name: 'Lion',
146 | img: 'lion.jpg',
147 | description: 'King of the Jungle.'
148 | });
149 |
150 | expect(v.render).toHaveBeenCalled();
151 |
152 | });
153 |
154 | });
155 |
156 | });
157 |
158 | });
159 |
160 | });
--------------------------------------------------------------------------------
/app/styles/_core.scss:
--------------------------------------------------------------------------------
1 | // Core styles for the app.
2 |
3 | @import "base";
4 | @import "vendor/h5bp";
5 |
6 | // H5BP stuff
7 | @include h5bp-normalize;
8 | @include h5bp-base-styles;
9 | @include h5bp-helpers;
10 |
11 | /*************** Core layout ***************/
12 |
13 | * {
14 | // apply a natural box layout model to all elements
15 | // http://paulirish.com/2012/box-sizing-border-box-ftw/
16 | @include box-sizing(border-box);
17 |
18 | // Disable long tap to copy/paste, except on input fields
19 | &:not(input), &:not(textarea), &:not(select) {
20 | -webkit-touch-callout: none;
21 | -webkit-user-select: none;
22 | }
23 |
24 | // Disable grey highlight on any anchor or element with click handler
25 | -webkit-tap-highlight-color: rgba(0,0,0,0);
26 | }
27 |
28 | // Ensure the app fills the screen
29 | html, body {
30 | width: 100%;
31 | height: 100%;
32 | }
33 |
34 | #app {
35 | min-height: 100%;
36 | position: relative;
37 | }
38 |
39 | body {
40 | background-color: $background-colour;
41 | }
42 |
43 |
44 | /*************** Forms ***************/
45 |
46 | // Thanks to 37Signals
47 | // http://37signals.com/svn/posts/2609-customizing-web-forms-with-css3-and-webkit/
48 | input, textarea, select {
49 | border: 1px solid #555;
50 | padding: 0.5em;
51 | font-size: 14px;
52 | line-height: 1.2em;
53 | width: 80%;
54 | background: #fff;
55 | @include border-radius(0.5em);
56 |
57 | // Allow form field selection!
58 | -webkit-user-select: text;
59 | }
60 |
61 | // Remove padding on buttons so they can be styled easily
62 | button {
63 | padding: 0;
64 | }
65 |
66 | select {
67 | height: 2.5em;
68 | }
69 |
70 | input, textarea {
71 | @include appearance(none);
72 | }
73 |
74 | input[type=checkbox],
75 | input[type=radio] {
76 | display: inline-block;
77 | font-size: 15px;
78 | line-height: 1em;
79 | margin: 0 0.25em 0 0;
80 | padding: 0;
81 | width: 2em;
82 | height: 2em;
83 | @include border-radius(0.25em);
84 | vertical-align: text-top;
85 | }
86 |
87 | input[type=radio] {
88 | @include border-radius(2em); /* Make radios round */
89 | }
90 |
91 | input[type=checkbox]:checked {
92 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABOJJREFUeNrsm01IVFEUx6+jIBl9TTWkSZrmqGFh9EGRERWki9wEgS1y074JF30sosJNtgiVVoEEuUgIIlCiKNqolFa4UMwKKyOttIyKrECx83/c+zpzm0nH8X3pHDi8Gd+bmfebc87/nnPVpMnJSTGfzCfmmSWAE8BzzFLifYPKijJHAa413UlE2NIIO22UYWfpsIu8laJ9fi5HeCd5B/k58n04EnzHVC9KirfxqK+9aDvpk0cPaukQwuO0hYtE5ppc8e5tvxj78R0/ClGk6+dEShMoonqJfBuer8rIMmBh6Rnjov9lj5Dp7X1ggj0r09eIanZOgXGEffk8LN687lOXtnpatAg0jQ4N5BV6VGEDr/rEyPCQetr0v3R2PTDBFtHhJnleckqKyM0rEouX+I1zv3//FP0velTdTpBXTQXramCCPUCHW+TJgMzKyRepqQuMc4B8/qxLTIyP42kn+VGC7fHsOszrdWUgg2ALzHOoVylORgpL2DHPdlocFrXKYT8MDXDYui3b9x6OBdZ1ESbY60qcgoXFZr3CsM4CWFqIYOtn8hkpLgE1lVgXpwhKXE6wLZ7tpSUslLgUsPmFm8z1VYOFEu8m2HbPDg8cFgocXF9sKnEE2GKC7Yn3M50WrQY7YR0FVgKFNNZhIVBWwDoGLKedClWzHBZKzNR492zCOgJMsMfUaKcLFJoKRJepcftsf77PZli0i3V4jKWHw6JdZE1FKJ6lxxXAcpZtVh3UsuUB85waBFgHVW/Vffhsgl1Nh6tqvINzAyygye4S7HEr78WuCGP5yUP3xGdZpchyxMPUc9DqG/HZEN1atdaibrlBpJgiV1F0x6y+nyk7rcqKMtTeSblXtJS8jfz+dLZEpUgZipwbLBJYhnjdsm2ZkBWKHDMwwZq7g8xK4HRuB1IwWlR43SKNuSKrupUDfJOVIjVtYLnHa+4OppMjQt++jlLb9xwRKpV9cNl/6nYF1FgXKa1uj9q5NPqiwCIVtyEq6zdsNSKk0hHCw1rBUlmjUes2e21B2DmA2l230xEt1KvwU3T0VIQZAhQ0BSgk11i+3kasW5XKbL1tFzZbNOBWlXrdXQ9V+oUZvgiWqifZqQuqDPQvC+8n19s2q9fbmIBJgdHWlePGcIPYIZQ3GmaqrnEtRfaI3I8qQQbo662WyqecmtKirsOAJkdqN0FNWSqaBtjMNevU00PkZ/AAW6q6saHAkVSOpfGAinYiQmxfyTS/P2BGGd8BtlX5fhQMr4O6k710MrrTApbboJeNWXVwQK2dYVFeGVgdKeKG4Xq8Tlo1RfeXq4EldCMae9Tx+791+E8tA1ZXZVzPBoNGpzcNY+mlq1X/G6mWAY101qM7MjwY9nrPAFOU25VqR6plvZtS0ZUl0OykUMUzLV0xROjj4JQXatGtES6xmIBlLRuKLVU3qo2ODrsuujOdh29Hq2VuTJlvCBfZTIABMIE61pcoZYg+ayEbPQ1Mad2rohxpiVKNhrT7wmU20y2emkhpjdru7X6sfv5JiZzngeUS9RRpqyYpDAaAZYP9Hkrn924Djue3h9jb2jwqf1vAVLvOqdHPamCIV4iNfBgMqshbaEy0EeGELTWs0hobeqfJ95NvBKxwucW1Ly2hscNxj/yX8IDNu7+XTkr8G08COAGcAPaS/RFgAE+yYfmhJZIUAAAAAElFTkSuQmCC);
93 | @include background-size(30px 30px);
94 | }
95 |
96 | // Override searchbar css from the normalise script
97 | // Stops iOS applying really rounded corners to the textfield
98 | input[type="search"] {
99 | @include appearance(none);
100 | @include box-sizing(border-box);
101 | }
102 |
103 |
104 | /*************** Lists ***************/
105 |
106 | // Disable default list styling so that nav lists look native-y
107 | .list {
108 | @include list-style-reset;
109 |
110 | li + li {
111 | border-top: 1px solid #bbb;
112 | }
113 |
114 | li.active {
115 | background-color: $list-active-background-color;
116 | color: $list-active-text-colour;
117 | }
118 |
119 | a {
120 | display: block;
121 | height: 44px;
122 | line-height: 44px;
123 | padding: 0 10px;
124 | text-decoration: none;
125 | &:link, &:visited, &:hover, &:active {
126 | color: inherit;
127 | }
128 | }
129 |
130 | }
131 |
132 |
133 | /*************** Toolbars ***************/
134 |
135 | $toolbar-default-height: 44px;
136 | $toolbar-default-font-size: 20px;
137 |
138 | $headerbar-height: $toolbar-default-height !default;
139 | $headerbar-font-size: $toolbar-default-font-size !default;
140 | $footerbar-height: $toolbar-default-height !default;
141 | $footerbar-font-size: $toolbar-default-font-size !default;
142 |
143 |
144 | // By default, the toolbars are inline.
145 | .toolbar {
146 | position: absolute;
147 | width: 100%;
148 | z-index: 2;
149 | margin: 0;
150 | }
151 |
152 | #headerbar {
153 | top: 0;
154 | height: $headerbar-height;
155 | background-color: $headerbar-background-colour;
156 | color: $headerbar-text-colour;
157 | .headerbar-title {
158 | margin: 0;
159 | padding: 0 10px;
160 | line-height: $headerbar-height;
161 | font-size: $headerbar-font-size;
162 | }
163 | }
164 |
165 | #footerbar {
166 | bottom: 0;
167 | height: $footerbar-height;
168 | .footerbar-title {
169 | margin: 0;
170 | padding: 0 10px;
171 | line-height: $footerbar-height;
172 | font-size: $footerbar-font-size;
173 | }
174 | }
175 |
176 | #main {
177 | z-index: 1;
178 | padding-top: $headerbar-height;
179 | padding-bottom: $footerbar-height;
180 | }
181 |
182 | // Adding the .fixedbar class to the html element will fix the toolbars using position:fixed.
183 | // This is supported on most recent mobile OS, except when we have input fields on the page.
184 | // In this case, it is recommended to disable the fixed bars by temporarily removing the
185 | // .fixedbar class.
186 |
187 | .fixedbar {
188 | #headerbar, #footerbar {
189 | position: fixed;
190 | }
191 |
192 | #main {
193 | overflow: auto;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/vendor/js/libs/recognizr.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Recognizr
3 | * v0.4.0
4 | *
5 | * A small library to detect the capabilities of mobile web browsers.
6 | *
7 | * Mobile browsers are a bit of a pain. Some support native scrolling, others proclaim to support animations.
8 | * Real-world usage, however is patchy. This library uses UA sniffing (gosh!) to check what type of mobile browser
9 | * is present, and returns a simple object to indicate its capabilities.
10 | *
11 | * If AMD is present, it will not expose a global variable. Otherwise, Recognizr can be accessed using the Recognizr global.
12 | *
13 | * Thanks to @jtward for much hours of headscratching and frustration in debugging various browsers!
14 | */
15 |
16 | (function (root, factory) {
17 | if (typeof define === 'function' && define.amd) {
18 | // AMD. Register as an anonymous module.
19 | define([], factory);
20 | } else {
21 | // Browser globals
22 | root.Recognizr = factory();
23 | }
24 | }(window, function () {
25 |
26 | // Recognizr
27 |
28 | var Device = function(browserFamily, browserVersion, toolbar, form, animations, audio) {
29 | this.browser = {
30 | 'family': browserFamily,
31 | 'version': browserVersion
32 | };
33 | this.scroll = {
34 | 'toolbar': toolbar,
35 | 'form': form
36 | };
37 | this.animations = animations;
38 | this.audio = audio;
39 | };
40 |
41 | // Calculates what the deive is capable of, depending on the UA string.
42 | // It would be nice to use Modernizr, but somethings can't be feature detected
43 | // reliably (e.g. overflow:scroll support). Other things (e.g. animations) will
44 | // work in theory on some devices, but really shouldn't be, for performance reasons.
45 | var Recognizr = (function(ua) {
46 |
47 | // UA regexes from https://raw.github.com/tobie/ua-parser/master/regexes.yaml
48 | var webkit = ua.match(/AppleWebKit\/([0-9]+)/);
49 | var wkversion = webkit && webkit[1];
50 | var ios = ua.match(/OS (\d+)_(\d+)(?:_(\d+))?.*AppleWebKit/);
51 | var iosversion = ios && ios.slice(1);
52 | // android versions are required because 2.2-3.2 have the same webkit version
53 | var android = ua.match(/Android (\d+)\.(\d+)(?:[.\-]([a-z0-9]+))?/);
54 | var androidversion = android && android.slice(1);
55 | var msie = ua.match(/MSIE (\d+)\.(\d+).*IEMobile/);
56 | var msieversion = msie && msie.slice(1);
57 | var blackberry = ua.match(/Blackberry.*Version\/(\d+)\.(\d+)(?:\.(\d+))?/);
58 | var bbversion = blackberry && blackberry.slice(1);
59 | var firefox = ua.match(/rv:(\d+)\.(\d+).*Firefox/);
60 | var ffversion = firefox && firefox.slice(1);
61 | var chrome = ua.match(/Chrome\/(\d+)\.(\d+)/) || ua.match(/Chromium\/(\d+)\.(\d+)/);
62 | var chromeversion = chrome && chrome.slice(1);
63 |
64 | // iOS: Mobile Safari or Chrome for iOS
65 | if (ua.indexOf("iPhone") > -1 || ua.indexOf("iPad") > -1 || ua.indexOf("iPod") > -1) {
66 | if (iosversion[0] >= 5) {
67 | // iOS 5/6
68 | // Supports pos:fixed but needs to be disabled when forms are present
69 | // as toolbars will start scrolling inline when form elements are active
70 | return new Device('ios', iosversion, 'fixed', 'inline', true, true);
71 |
72 | } else if(iosversion[0] >= 4) {
73 | // iOS 4.x
74 | // We no longer like iScroll, so scroll inline. Animations are out, too.
75 | return new Device('ios', iosversion, 'inline', 'inline', false, true);
76 |
77 | } else {
78 | // iOS < 4
79 | // Now audio is out.
80 | return new Device('ios', iosversion, 'inline', 'inline', false, false);
81 | }
82 | }
83 |
84 | // FIREFOX
85 | // Platforms: Windows/Macintosh/Linux/Android (not present for B2G)
86 | // Form factors: Tablet/Mobile (not present for desktop)
87 | // see https://developer.mozilla.org/en-US/docs/Gecko_user_agent_string_reference for info
88 | if (firefox) {
89 | // as of FF16, nearly good enough for fixed forms: the footer does not return to
90 | // the bottom of the page after the keyboard is hidden until the page is scrolled
91 | return new Device('firefox', ffversion, 'fixed', 'inline', true, true);
92 |
93 | }
94 |
95 | // ANDROID: Stock Browser
96 | if (ua.indexOf('Android') > -1) {
97 | if(androidversion[0] >= 4) {
98 | if(androidversion[0] >= 5 || androidversion[1] >= 1) {
99 | // 4.1 (jellybean) or higher; TODO: check form scrolling
100 | return new Device('android', androidversion, 'fixed', 'inline', true, true);
101 |
102 | } else {
103 | // 4.0 (ice-cream sandwich)
104 | // Supports pos:fixed but needs to be disabled when forms are present
105 | // as input element scrolls on top of the toolbar.
106 | // Animations are OK.
107 | return new Device('android', androidversion, 'fixed', 'inline', true, true);
108 |
109 | }
110 |
111 | } else if (wkversion && wkversion >= 533) { // Android 2.2+
112 | if(androidversion[0] >= 3 || androidversion[1] >= 3) {
113 | // Android 2.3 - 3.2
114 | // Supports pos:fixed but needs to be disabled when forms are present
115 | // as input element scrolls on top of the toolbar.
116 | // Animations are disabled.
117 | // Audio is enabled.
118 | return new Device('android', androidversion, 'fixed', 'inline', false, true);
119 |
120 | } else {
121 | // Android 2.2
122 | // as 2.3 - 3.2, except that audio is disabled, and use inline scrolling.
123 | return new Device('android', androidversion, 'inline', 'inline', false, false);
124 |
125 | }
126 | } else {
127 | // Sucky Android version. Minimal anything.
128 | return new Device('android', androidversion, 'inline', 'inline', false, false);
129 |
130 | }
131 | }
132 |
133 | // CHROME / CHROMIUM
134 | // Well supported, even form fields can be scrolled.
135 | if (ua.indexOf('Chrome') > -1 || ua.indexOf('Chromium') > -1) {
136 | return new Device('chrome', chromeversion, 'fixed', 'fixed', true, true);
137 |
138 | }
139 |
140 | // EVERYTHING ELSE
141 | // Basics. Inline scrolling, no animations, no audio.
142 | return new Device('unknown', '', 'inline', 'inline', false, false);
143 |
144 | })(navigator.userAgent);
145 |
146 |
147 | // Just return a value to define the module export.
148 | // This example returns an object, but the module
149 | // can return a function as the exported value.
150 | return Recognizr;
151 |
152 | }));
--------------------------------------------------------------------------------
/test/vendor/jasmine.css:
--------------------------------------------------------------------------------
1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 |
3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | #HTMLReporter a { text-decoration: none; }
5 | #HTMLReporter a:hover { text-decoration: underline; }
6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 | #HTMLReporter .version { color: #aaaaaa; }
10 | #HTMLReporter .banner { margin-top: 14px; }
11 | #HTMLReporter .duration { color: #aaaaaa; float: right; }
12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
24 | #HTMLReporter .runningAlert { background-color: #666666; }
25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
28 | #HTMLReporter .passingAlert { background-color: #a6b779; }
29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
30 | #HTMLReporter .failingAlert { background-color: #cf867e; }
31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
32 | #HTMLReporter .results { margin-top: 14px; }
33 | #HTMLReporter #details { display: none; }
34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 | #HTMLReporter.showDetails .summary { display: none; }
39 | #HTMLReporter.showDetails #details { display: block; }
40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 | #HTMLReporter .summary { margin-top: 14px; }
42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
45 | #HTMLReporter .description + .suite { margin-top: 0; }
46 | #HTMLReporter .suite { margin-top: 14px; }
47 | #HTMLReporter .suite a { color: #333333; }
48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; }
49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
51 | #HTMLReporter .resultMessage span.result { display: block; }
52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
53 |
54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
61 | #TrivialReporter .runner.running { background-color: yellow; }
62 | #TrivialReporter .options { text-align: right; font-size: .8em; }
63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
64 | #TrivialReporter .suite .suite { margin: 5px; }
65 | #TrivialReporter .suite.passed { background-color: #dfd; }
66 | #TrivialReporter .suite.failed { background-color: #fdd; }
67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
71 | #TrivialReporter .spec.skipped { background-color: #bbb; }
72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
73 | #TrivialReporter .passed { background-color: #cfc; display: none; }
74 | #TrivialReporter .failed { background-color: #fbb; }
75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
77 | #TrivialReporter .resultMessage .mismatch { color: black; }
78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
83 |
--------------------------------------------------------------------------------
/test/spec/skeleton.spec.js:
--------------------------------------------------------------------------------
1 | // This is a skeleton spec, designed to be reused to ensure that the
2 | // most common characteristics of a module are tested.
3 |
4 | // Tests are derived from the excellent book by Addy Osmani
5 | // http://addyosmani.github.com/backbone-fundamentals/#unit-testing-backbone-applications-with-jasmine
6 |
7 | define([
8 | 'modules/example'
9 | ], function(Module) {
10 |
11 | describe("example module", function() {
12 |
13 | it("is structured correctly", function() {
14 | expect(Module).toBeDefined();
15 | });
16 |
17 | describe("model", function() {
18 |
19 | it("exists in module", function() {
20 | expect(Module.Model).toBeDefined();
21 | });
22 |
23 | it("can be created with default attribute values", function() {
24 | var m = new Module.Model();
25 | expect(m.get('example').toBe(''));
26 | });
27 |
28 | it("has a defined URL", function() {
29 | var m = new Module.Model();
30 | expect(m.url).toBeDefined();
31 | expect(m.url).toBe('/example/url');
32 | });
33 |
34 | it("will follow validation rules", function() {
35 | var errorCallback = jasmine.createSpy('-error event callback-');
36 |
37 | var m = new Module.Model();
38 |
39 | m.on('error', errorCallback);
40 |
41 | m.set('a_boolean_value', 'a_non_boolean_string');
42 |
43 | var errorArgs = errorCallback.mostRecentCall.args;
44 | expect(errorArgs).toBeDefined();
45 | expect(errorArgs[0].toBe(m));
46 | expect(errorArgs[1]).toBe('Model.a_boolean_value must be a boolean value');
47 |
48 | });
49 |
50 | });
51 |
52 | describe("collection", function() {
53 | it("exists in module", function() {
54 | expect(Module.Collection).toBeDefined();
55 | });
56 |
57 | it("has a defined URL", function() {
58 | var c = new Module.Collection();
59 | expect(c.url).toBeDefined();
60 | expect(c.url).toBe('/example/url');
61 | });
62 | });
63 |
64 | describe("main view", function() {
65 | var v;
66 |
67 | it("exists in module", function() {
68 | expect(Module.Views).toBeDefined();
69 | expect(Module.Views.Main).toBeDefined();
70 | });
71 |
72 | describe("scaffolding", function() {
73 | beforeEach(function() {
74 | v = new Module.Views.Main();
75 | });
76 |
77 | afterEach(function() {
78 | v = null;
79 | });
80 |
81 | it("has div root element", function() {
82 | expect(v.$el).toBe('div');
83 | });
84 |
85 | it("has main template", function() {
86 | expect(v.template).toBe('main');
87 | });
88 |
89 | it("has example className", function() {
90 | expect(v.$el).toHaveClass('example');
91 | });
92 |
93 | it("has example attribute", function() {
94 | expect(v.$el).toHaveAttr('data-example', 'example');
95 | });
96 |
97 | it("must create an empty model if none is defined", function() {
98 | expect(v.model).toBeDefined();
99 | });
100 |
101 | it("must use an existing model if one is defined", function() {
102 | var m = new Module.Model();
103 | expect(v.model).not.toBe(m);
104 |
105 | v = new Module.Views.Main({ model: m });
106 | expect(v.model).toBe(m);
107 | });
108 |
109 | it("must create an empty collection if none is defined", function() {
110 | expect(v.collection).toBeDefined();
111 | });
112 |
113 | it("must use an existing collection if one is defined", function() {
114 | var c = new Module.Collection();
115 | expect(v.collection).not.toBe(c);
116 |
117 | v = new Module.Views.Main({ collection: c });
118 | expect(v.collection).toBe(c);
119 | });
120 |
121 | });
122 |
123 | // LayoutManager uses promises for rendering.
124 | // We need to wait for the promise to be resolved before
125 | // testing that rendering has worked correctly.
126 | // For this, we use the jasmine Async plugin:
127 | // http://lostechies.com/derickbailey/2012/08/18/jasmine-async-making-asynchronous-testing-with-jasmine-suck-less/
128 | describe("rendering", function() {
129 |
130 | var async = new AsyncSpec(this);
131 |
132 | beforeEach(function() {
133 | v = new Module.Views.Main();
134 | });
135 |
136 | afterEach(function() {
137 | v = null;
138 | });
139 |
140 | async.it("produces expected HTML with no data", function(done) {
141 | v.render().then(function() {
142 | expect(v.el.outerHTML).toBe('');
143 | done();
144 | });
145 |
146 | });
147 |
148 | async.it("produces expected HTML with model data", function(done) {
149 | v.model = new Module.Model({
150 | // Some data goes here
151 | });
152 |
153 | v.render().then(function() {
154 | expect(v.el.outerHTML).toBeHtml('Put expected HTML here');
155 | done();
156 | });
157 |
158 | });
159 |
160 | async.it("produces expected HTML with collection data", function(done) {
161 | v.collection = new Module.Collection([
162 | // Some data goes here
163 | ]);
164 |
165 | v.render().then(function() {
166 | expect(v.el.outerHTML).toBeHtml('Put expected HTML here');
167 | done();
168 | });
169 |
170 | });
171 |
172 | it("renders when model changes", function() {
173 | spyOn(v, 'render');
174 |
175 | // To test Backbone events, you should put all your
176 | // 'listenTo' calls in a separate function.
177 | // This is so that the spy setup above can be re-bound
178 | // to the Backbone event by calling stopModuleening
179 | // and then startModuleening again.
180 | // Without this call, render cannot be spied on.
181 | v.stopListening();
182 | v.startListening();
183 |
184 | v.collection.model.set({
185 | // Some data goes here
186 | });
187 |
188 | expect(v.render).toHaveBeenCalled();
189 |
190 | });
191 |
192 | it("renders when collection changes", function() {
193 | spyOn(v, 'render');
194 |
195 | // To test Backbone events, you should put all your
196 | // 'listenTo' calls in a separate function.
197 | // This is so that the spy setup above can be re-bound
198 | // to the Backbone event by calling stopModuleening
199 | // and then startModuleening again.
200 | // Without this call, render cannot be spied on.
201 | v.stopListening();
202 | v.startListening();
203 |
204 | v.collection.reset([
205 | // Some data goes here
206 | ]);
207 |
208 | expect(v.render).toHaveBeenCalled();
209 |
210 | });
211 |
212 | });
213 |
214 | });
215 |
216 | });
217 |
218 | });
--------------------------------------------------------------------------------
/api/fakeserver.js:
--------------------------------------------------------------------------------
1 | // Fake server to simulate API responses.
2 |
3 | (function() {
4 |
5 | sinon.FakeXMLHttpRequest.useFilters = true;
6 | sinon.log = console.log.bind(console);
7 |
8 | var overrideOffline = true;
9 |
10 | var server = sinon.fakeServer.create();
11 |
12 | server.autoRespond = true;
13 | server.autoRespondAfter = 5; // Increase this to model server latency
14 |
15 | sinon.FakeXMLHttpRequest.addFilter(function(method, url, async, username, password) {
16 | // return truthy iff url does not start '/api/'
17 | var matchesApiUrl = /^\/api\//.test(url);
18 | if (matchesApiUrl) {
19 | console.log('Sinon request caught: ' + method + ' ' + url);
20 | }
21 |
22 | return !matchesApiUrl;
23 | });
24 |
25 | // parseQueryString takes a callback and returns a function that calls the callback after having parsed the query params into an object
26 | var parseQueryString = function(queryString) {
27 | // Convert a query string to an object hash of key/value pairs
28 | // Any duplicated keys will be overwritten
29 | var queries = {};
30 | var queryArr = queryString.split('&');
31 | var i, len, q;
32 |
33 | for (i = 0, len = queryArr.length; i < len; i ++) {
34 | q = queryArr[i].split('=');
35 | queries[q[0]] = q[1];
36 | }
37 |
38 | return queries;
39 | };
40 |
41 | // respondWith takes arguments similar to $.ajax:
42 | // type: an HTTP verb (e.g. GET, POST)
43 | // url: the url to match (e.g. /api/animals.json)
44 | // contentType: the content type of the returned data (e.g. application/json)
45 | // headers: an object containing the headers returned by the fake server
46 | // status: the status code to return (e.g. 200). Optional; defaults to 200.
47 | // responseText: either the string returned by the fake server, or a function
48 | // if responseText is a function, it takes the request and must return an object. The returned object must contain responseText and may contain status, contentType and headers.
49 | var respondWith = function(options) {
50 |
51 | server.respondWith(options.type, options.url, function() {
52 | var xhr = arguments[0];
53 | var status, headers, body;
54 |
55 | if(!overrideOffline && !navigator.onLine) {
56 | status = 404;
57 | body = "";
58 |
59 | } else {
60 | body = options.body;
61 |
62 | if(typeof body === 'function') {
63 | // Call the function, to return the responseText.
64 | body = body.apply(this, Array.prototype.slice.apply(arguments));
65 | }
66 |
67 | if (typeof body !== 'string') {
68 | body = JSON.stringify(body);
69 | }
70 | }
71 |
72 | // Send the response
73 | status = options.status || 200;
74 |
75 | headers = options.headers || {};
76 | headers.contentType = 'application/json';
77 |
78 | console.log("Sinon: sending response: ", status, headers, body);
79 |
80 | xhr.respond(status, headers, body);
81 |
82 | });
83 | };
84 |
85 |
86 | /************** RESPONSES GO HERE **************/
87 |
88 | var animals = {
89 | 'lion': {
90 | "name": "Lion",
91 | "description": "The lion (Panthera leo) is one of the four big cats in the genus Panthera, and a member of the family Felidae. With some males exceeding 250 kg (550 lb) in weight,[4] it is the second-largest living cat after the tiger. Wild lions currently exist in Sub-Saharan Africa and in Asia with an endangered remnant population in Gir Forest National Park in India, having disappeared from North Africa and Southwest Asia in historic times. Until the late Pleistocene, about 10,000 years ago, the lion was the most widespread large land mammal after humans. They were found in most of Africa, across Eurasia from western Europe to India, and in the Americas from the Yukon to Peru.[5] The lion is a vulnerable species, having seen a possibly irreversible population decline of thirty to fifty percent over the past two decades in its African range.[2] Lion populations are untenable outside designated reserves and national parks. Although the cause of the decline is not fully understood, habitat loss and conflicts with humans are currently the greatest causes of concern. Within Africa, the West African lion population is particularly endangered.",
92 | "img":"http://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Lion_waiting_in_Namibia.jpg/250px-Lion_waiting_in_Namibia.jpg"
93 | },
94 | 'honey-badger': {
95 | "name": "Honey Badger",
96 | "description": "The honey badger (Mellivora capensis), also known as the ratel, is a species of mustelid native to Africa, Southwest Asia, and the Indian Subcontinent. Despite its name, the honey badger does not closely resemble other badger species, instead it bears more anatomical similarities to weasels. It is classed as Least Concern by the IUCN owing to its extensive range and general environmental adaptations. It is primarily a carnivorous species, and has few natural predators because of its thick skin and ferocious defensive abilities.",
97 | "img": "http://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Honey_badger.jpg/220px-Honey_badger.jpg"
98 | },
99 | 'elephant': {
100 | "name": "Elephant",
101 | "description": "Elephants are large land mammals in two extant genera of the family Elephantidae: Elephas and Loxodonta, with the third genus Mammuthus extinct.[1] Three species of elephant are recognized: the African bush elephant, the African forest elephant and the Indian or Asian elephant;[2] although some group the two African species into one[3] and some researchers also postulate the existence of a fourth species in West Africa.[4] All other species and genera of Elephantidae are extinct. Most have been extinct since the last ice age, although dwarf forms of mammoths might have survived as late as 2,000 BCE.[5] Elephants and other Elephantidae were once classified with other thick-skinned animals in a now invalid order, Pachydermata.",
102 | "img": "http://upload.wikimedia.org/wikipedia/commons/thumb/3/37/African_Bush_Elephant.jpg/180px-African_Bush_Elephant.jpg"
103 | },
104 | 'black-rhino': {
105 | "name": "Black Rhinoceros",
106 | "description": "The black rhinoceros (Diceros bicornis) is a large, thick-skinned herbivore having one or two upright horns on the nasal bridge. Rhinoceros may refer to either black or white rhinoceros. Among Big Five game hunters, the black rhinoceroses are preferred, although it is now critically endangered.",
107 | "img":"http://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Black_rhinos_in_crater.jpg/320px-Black_rhinos_in_crater.jpg"
108 | },
109 | 'cape-buffalo': {
110 | "name": "Cape Buffalo",
111 | "description": "The African or Cape buffalo (Syncerus caffer) is a large horned bovid. Buffalo are sometimes reported to kill more people in Africa than any other animal, although the same claim is also made of hippos and crocodiles.[6] It is considered the most dangerous of the Big Five, reportedly causing the most hunter deaths, with wounded animals reported to ambush and attack pursuers.",
112 | "img":"http://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/African_Buffalo.JPG/320px-African_Buffalo.JPG"
113 | },
114 | 'leopard': {
115 | "name": "Leopard",
116 | "description": "The leopard (Panthera pardus) is a large, carnivorous feline having either tawny fur with dark rosette-like markings or black fur. Of the Big Five, it is most difficult to acquire hunting licenses for leopards. The leopard is sometimes considered the most difficult of the Big Five to hunt because of their nocturnal and secretive nature. They are wary of humans and will take flight in the face of danger. The leopard is solitary by nature, and is most active between sunset and sunrise, although it may hunt during the day in some areas. Leopards can be found in the savanna grasslands, brush land and forested areas in Africa.",
117 | "img": "http://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Leopard_africa.jpg/360px-Leopard_africa.jpg"
118 | }
119 | };
120 |
121 | respondWith({
122 | type: 'GET',
123 | url: '/api/animals',
124 | status: 200,
125 | body: function(xhr, id) {
126 | var n, a = [];
127 | for (n in animals) {
128 | if (animals.hasOwnProperty(n)) {
129 | a.push({
130 | 'id': n,
131 | 'name': animals[n].name
132 | });
133 | }
134 | }
135 | return a;
136 | }
137 | });
138 |
139 | respondWith({
140 | type: 'GET',
141 | url: /\/api\/animals\/(.+)/,
142 | status: 200,
143 | body: function(xhr, id) {
144 | return animals[id];
145 | }
146 | });
147 |
148 | // Example of query string parsing
149 | respondWith({
150 | type: 'GET',
151 | url: /\/api\/search\?(.*)/,
152 | status: 200,
153 | body: function(xhr, query) {
154 | // Expecting '/api/search?name=lion'
155 | var resp;
156 | query = parseQueryString(query);
157 | if (query.name) {
158 | resp = animals[query.name];
159 | }
160 |
161 | if (!resp) {
162 | resp = { error: "Could not find animal." };
163 | }
164 |
165 | return resp;
166 | }
167 | });
168 |
169 | })();
170 |
--------------------------------------------------------------------------------
/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.loadNpmTasks('grunt-compass');
7 |
8 | grunt.initConfig({
9 |
10 | // The lint task will run the build configuration and the application
11 | // JavaScript through JSHint and report any errors. You can change the
12 | // options for this task, by reading this:
13 | // https://github.com/cowboy/grunt/blob/master/docs/task_lint.md
14 | lint: {
15 | files: [
16 | "build/config.js", "app/**/*.js"
17 | ]
18 | },
19 |
20 | // The jshint option for scripturl is set to lax, because the anchor
21 | // override inside main.js needs to test for them so as to not accidentally
22 | // route.
23 | jshint: {
24 | options: {
25 | scripturl: true
26 | }
27 | },
28 |
29 | // The handlebars task compiles all application templates into JavaScript
30 | // functions using Handlebars templating engine.
31 | //
32 | // Since this task defaults to writing to the same file as the jst task,
33 | // edit the debug task replacing jst with handlebars.
34 | //
35 | // The concat task depends on this file to exist, so if you decide to
36 | // remove this, ensure concat is updated accordingly.
37 | handlebars: {
38 | "dist/debug/templates.js": ["app/templates/**/*.html"]
39 | },
40 |
41 | // Uses the compass grunt plugin as found here:
42 | // https://github.com/kahlil/grunt-compass
43 | compass: {
44 | dev: {
45 | config: 'config.rb'
46 | },
47 | debug: {
48 | src: 'app/styles',
49 | dest: 'dist/debug',
50 | config: 'config.rb'
51 | },
52 | release: {
53 | environment: 'production',
54 | src: 'app/styles',
55 | dest: 'dist/release',
56 | config: 'config.rb'
57 | }
58 | },
59 |
60 | // This task simplifies working with CSS inside Backbone Boilerplate
61 | // projects. Instead of manually specifying your stylesheets inside the
62 | // configuration, you can use `@imports` and this task will concatenate
63 | // only those paths.
64 | styles: {
65 | // Out the concatenated contents of the following styles into the below
66 | // development file path.
67 | "dist/debug/index.css": {
68 | // Point this to where your `index.css` file is location.
69 | src: "app/styles/index.css",
70 |
71 | // The relative path to use for the @imports.
72 | paths: ["app/styles"],
73 |
74 | // Point to where styles live.
75 | prefix: "app/styles/",
76 |
77 | // Additional production-only stylesheets here.
78 | additional: []
79 | }
80 | },
81 |
82 | // This task uses James Burke's excellent r.js AMD build tool. In the
83 | // future other builders may be contributed as drop-in alternatives.
84 | requirejs: {
85 | // Include the main configuration file.
86 | mainConfigFile: "app/scripts/config.js",
87 |
88 | // Also include the JamJS configuration file.
89 | jamConfig: "vendor/jam/require.config.js",
90 |
91 | // Output file.
92 | out: "dist/debug/require.js",
93 |
94 | // Root application module.
95 | name: "main",
96 | deps: ["config"],
97 |
98 | // Do not wrap everything in an IIFE.
99 | wrap: false
100 |
101 | },
102 |
103 | // The concatenate task is used here to merge the almond require/define
104 | // shim and the templates into the application code. It's named
105 | // dist/debug/require.js, because we want to only load one script file in
106 | // index.html.
107 | concat: {
108 | dist: {
109 | src: [
110 | "vendor/js/libs/almond.js",
111 | "dist/debug/templates.js",
112 | "dist/debug/require.js"
113 | ],
114 |
115 | dest: "dist/debug/require.js",
116 |
117 | separator: ";"
118 | }
119 | },
120 |
121 | // This task uses the MinCSS Node.js project to take all your CSS files in
122 | // order and concatenate them into a single CSS file named index.css. It
123 | // also minifies all the CSS as well. This is named index.css, because we
124 | // only want to load one stylesheet in index.html.
125 | mincss: {
126 | "dist/release/index.css": [
127 | "dist/debug/index.css"
128 | ]
129 | },
130 |
131 | // Takes the built require.js file and minifies it for filesize benefits.
132 | min: {
133 | "dist/release/require.js": [
134 | "dist/debug/require.js"
135 | ]
136 | },
137 |
138 | // Running the server without specifying an action will run the defaults,
139 | // port: 8000 and host: 127.0.0.1. If you would like to change these
140 | // defaults, simply add in the properties `port` and `host` respectively.
141 | // Alternatively you can omit the port and host properties and the server
142 | // task will instead default to process.env.PORT or process.env.HOST.
143 | //
144 | // Changing the defaults might look something like this:
145 | //
146 | // server: {
147 | // host: "127.0.0.1", port: 9001
148 | // debug: { ... can set host and port here too ...
149 | // }
150 | //
151 | // To learn more about using the server task, please refer to the code
152 | // until documentation has been written.
153 | server: {
154 | // This makes it easier for deploying, by defaulting to any IP.
155 | host: "0.0.0.0",
156 |
157 | // Ensure the favicon is mapped correctly.
158 | files: { "favicon.ico": "favicon.ico" },
159 |
160 | // API
161 | folders: { "api": "api" },
162 |
163 | // For styles.
164 | prefix: "app/styles/",
165 |
166 | debug: {
167 | // This makes it easier for deploying, by defaulting to any IP.
168 | host: "0.0.0.0",
169 |
170 | // Ensure the favicon is mapped correctly.
171 | files: "",
172 |
173 | // Map `server:debug` to `debug` folders.
174 | folders: {
175 | "api": "api",
176 | "app": "dist/debug",
177 | "vendor/js/libs": "dist/debug",
178 | "app/styles": "dist/debug"
179 | }
180 | },
181 |
182 | release: {
183 | // This makes it easier for deploying, by defaulting to any IP.
184 | host: "0.0.0.0",
185 |
186 | // Ensure the favicon is mapped correctly.
187 | files: "",
188 |
189 | // Map `server:release` to `release` folders.
190 | folders: {
191 | "api": "api",
192 | "app": "dist/release",
193 | "vendor/js/libs": "dist/release",
194 | "app/styles": "dist/release"
195 | }
196 | }
197 | },
198 |
199 | // The headless Jasmine testing is provided by grunt-jasmine-task. Simply
200 | // point the configuration to your test directory.
201 | jasmine: {
202 | all: ["spec.html"]
203 | },
204 |
205 | // The watch task can be used to monitor the filesystem and execute
206 | // specific tasks when files are modified. By default, the watch task is
207 | // available to compile CSS if you are unable to use the runtime compiler
208 | // (use if you have a custom server, PhoneGap, Adobe Air, etc.)
209 | watch: {
210 | files: ["grunt.js", "vendor/**/*", "app/**/*"],
211 | tasks: "styles"
212 | },
213 |
214 | // The clean task ensures all files are removed from the dist/ directory so
215 | // that no files linger from previous builds.
216 | clean: ["dist/"]
217 |
218 | // If you want to generate targeted `index.html` builds into the `dist/`
219 | // folders, uncomment the following configuration block and use the
220 | // conditionals inside `index.html`.
221 | //targethtml: {
222 | // debug: {
223 | // src: "index.html",
224 | // dest: "dist/debug/index.html"
225 | // },
226 | //
227 | // release: {
228 | // src: "index.html",
229 | // dest: "dist/release/index.html"
230 | // }
231 | //},
232 |
233 | // This task will copy assets into your build directory,
234 | // automatically. This makes an entirely encapsulated build into
235 | // each directory.
236 | //copy: {
237 | // debug: {
238 | // files: {
239 | // "dist/debug/app/": "app/**",
240 | // "dist/debug/vendor/": "vendor/**"
241 | // }
242 | // },
243 |
244 | // release: {
245 | // files: {
246 | // "dist/release/app/": "app/**",
247 | // "dist/release/vendor/": "vendor/**"
248 | // }
249 | // }
250 | //}
251 |
252 | });
253 |
254 | // The preflight task will lint your code and run the jasmine tests.
255 | grunt.registerTask("preflight", "lint jasmine");
256 |
257 | // The prepare task will run the preflight, remove all contents inside
258 | // the dist/ folder, precompile all the Handlebars templates into
259 | // dist/debug/templates.js, compile all the application code into
260 | // dist/debug/require.js, and then concatenate the require/define shim
261 | // almond.js and dist/debug/templates.js into the require.js file.
262 | grunt.registerTask("prepare", "preflight clean handlebars requirejs concat");
263 |
264 | // The debug task will run the prepare tasks and then compile the CSS files.
265 | grunt.registerTask("debug", "prepare compass:debug");
266 |
267 | // The release task will run the prepare tasks and then minify the
268 | // dist/debug/require.js file and CSS files.
269 | grunt.registerTask("release", "prepare min compass:release");
270 |
271 | };
272 |
--------------------------------------------------------------------------------
/app/styles/vendor/h5bp/_normalize.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Normalize v1.0.1 | MIT License | git.io/normalize
3 | //
4 |
5 | @mixin h5bp-normalize {
6 | @include h5bp-display;
7 | @include h5bp-base;
8 | @include h5bp-links;
9 | @include h5bp-typography;
10 | @include h5bp-lists;
11 | @include h5bp-embeds;
12 | @include h5bp-figures;
13 | @include h5bp-forms;
14 | @include h5bp-tables;
15 | }
16 |
17 |
18 | // Html5 display definitions
19 | @mixin h5bp-display {
20 |
21 | // Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3.
22 | article,
23 | aside,
24 | details,
25 | figcaption,
26 | figure,
27 | footer,
28 | header,
29 | hgroup,
30 | nav,
31 | section,
32 | summary {
33 | display: block;
34 | }
35 |
36 | // Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
37 | audio,
38 | canvas,
39 | video {
40 | display: inline-block;
41 | *display: inline;
42 | *zoom: 1;
43 | }
44 |
45 | // Prevents modern browsers from displaying `audio` without controls.
46 | // Remove excess height in iOS 5 devices.
47 | audio:not([controls]) {
48 | display: none;
49 | height: 0;
50 | }
51 |
52 | // Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3,
53 | // and Safari 4.
54 | // Known issue: no IE 6 support.
55 | [hidden] {
56 | display: none;
57 | }
58 |
59 | }
60 |
61 |
62 | // Base
63 | @mixin h5bp-base {
64 |
65 | // 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using
66 | // `em` units.
67 | // 2. Prevents iOS text size adjust after orientation change, without disabling
68 | // user zoom.
69 | html {
70 | font-size: 100%; // 1
71 | -webkit-text-size-adjust: 100%; // 2
72 | -ms-text-size-adjust: 100%; // 2
73 | }
74 |
75 | // Addresses `font-family` inconsistency between `textarea` and other form
76 | // elements.
77 |
78 | html,
79 | button,
80 | input,
81 | select,
82 | textarea {
83 | font-family: sans-serif;
84 | }
85 |
86 | // Addresses margins handled incorrectly in IE 6/7.
87 |
88 | body {
89 | margin: 0;
90 | }
91 |
92 | }
93 |
94 | // Deprecation
95 | @mixin h5bp-selection {
96 | @warn "The selection mixin has been removed. The selection code is in the h5bp-base-styles mixin starting with Boilerplate version 4.0.";
97 | }
98 |
99 | // Links
100 | @mixin h5bp-links {
101 |
102 | // Addresses `outline` inconsistency between Chrome and other browsers.
103 | a:focus {
104 | outline: thin dotted;
105 | }
106 |
107 | // Improves readability when focused and also mouse hovered in all browsers.
108 | a:active,
109 | a:hover {
110 | outline: 0;
111 | }
112 |
113 | }
114 |
115 |
116 | // Typography
117 | @mixin h5bp-typography {
118 |
119 | // Addresses font sizes and margins set differently in IE 6/7.
120 | // Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5,
121 | // and Chrome.
122 | h1 {
123 | font-size: 2em;
124 | margin: 0.67em 0;
125 | }
126 |
127 | h2 {
128 | font-size: 1.5em;
129 | margin: 0.83em 0;
130 | }
131 |
132 | h3 {
133 | font-size: 1.17em;
134 | margin: 1em 0;
135 | }
136 |
137 | h4 {
138 | font-size: 1em;
139 | margin: 1.33em 0;
140 | }
141 |
142 | h5 {
143 | font-size: 0.83em;
144 | margin: 1.67em 0;
145 | }
146 |
147 | h6 {
148 | font-size: 0.75em;
149 | margin: 2.33em 0;
150 | }
151 |
152 | // Addresses styling not present in IE 7/8/9, Safari 5, and Chrome.
153 | abbr[title] {
154 | border-bottom: 1px dotted;
155 | }
156 |
157 | // Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
158 | b,
159 | strong {
160 | font-weight: bold;
161 | }
162 |
163 | blockquote {
164 | margin: 1em 40px;
165 | }
166 |
167 | // Addresses styling not present in Safari 5 and Chrome.
168 | dfn {
169 | font-style: italic;
170 | }
171 |
172 | // Addresses styling not present in IE 6/7/8/9.
173 | mark {
174 | background: #ff0;
175 | color: #000;
176 | }
177 |
178 | // Addresses margins set differently in IE 6/7.
179 | p,
180 | pre {
181 | margin: 1em 0;
182 | }
183 |
184 | // Corrects font family set oddly in IE 6, Safari 4/5, and Chrome.
185 | code,
186 | kbd,
187 | pre,
188 | samp {
189 | font-family: monospace, serif;
190 | _font-family: 'courier new', monospace;
191 | font-size: 1em;
192 | }
193 |
194 | // Improves readability of pre-formatted text in all browsers.
195 | pre {
196 | white-space: pre;
197 | white-space: pre-wrap;
198 | word-wrap: break-word;
199 | }
200 |
201 | // Addresses CSS quotes not supported in IE 6/7.
202 | q {
203 | quotes: none;
204 | }
205 |
206 | // Addresses `quotes` property not supported in Safari 4.
207 | q:before,
208 | q:after {
209 | content: '';
210 | content: none;
211 | }
212 |
213 | // Addresses inconsistent and variable font size in all browsers.
214 | small {
215 | font-size: 80%;
216 | }
217 |
218 | // Prevents `sub` and `sup` affecting `line-height` in all browsers.
219 | sub,
220 | sup {
221 | font-size: 75%;
222 | line-height: 0;
223 | position: relative;
224 | vertical-align: baseline;
225 | }
226 |
227 | sup {
228 | top: -0.5em;
229 | }
230 |
231 | sub {
232 | bottom: -0.25em;
233 | }
234 |
235 | }
236 |
237 |
238 | // Lists
239 | @mixin h5bp-lists {
240 |
241 | // Addresses margins set differently in IE 6/7.
242 | dl,
243 | menu,
244 | ol,
245 | ul {
246 | margin: 1em 0;
247 | }
248 |
249 | dd {
250 | margin: 0 0 0 40px;
251 | }
252 |
253 | // Addresses paddings set differently in IE 6/7.
254 | menu,
255 | ol,
256 | ul {
257 | padding: 0 0 0 40px;
258 | }
259 |
260 | }
261 |
262 |
263 | // Embedded content
264 | @mixin h5bp-embeds {
265 |
266 | // 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3.
267 | // 2. Improves image quality when scaled in IE 7.
268 | img {
269 | border: 0; /* 1 */
270 | -ms-interpolation-mode: bicubic; /* 2 */
271 | }
272 |
273 | // Corrects overflow displayed oddly in IE 9.
274 | svg:not(:root) {
275 | overflow: hidden;
276 | }
277 |
278 | }
279 |
280 |
281 | // Figures
282 | @mixin h5bp-figures {
283 |
284 | // Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
285 | figure {
286 | margin: 0;
287 | }
288 |
289 | }
290 |
291 |
292 | // Forms
293 | @mixin h5bp-forms {
294 |
295 | // Corrects margin displayed oddly in IE 6/7.
296 | form {
297 | margin: 0;
298 | }
299 |
300 | // Define consistent border, margin, and padding.
301 | fieldset {
302 | border: 1px solid #c0c0c0;
303 | margin: 0 2px;
304 | padding: 0.35em 0.625em 0.75em;
305 | }
306 |
307 | // 1. Corrects color not being inherited in IE 6/7/8/9.
308 | // 2. Corrects text not wrapping in Firefox 3.
309 | // 3. Corrects alignment displayed oddly in IE 6/7.
310 | legend {
311 | border: 0; // 1
312 | padding: 0;
313 | white-space: normal; // 2
314 | *margin-left: -7px; // 3
315 | }
316 |
317 | // 1. Corrects font size not being inherited in all browsers.
318 | // 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5,
319 | // and Chrome.
320 | // 3. Improves appearance and consistency in all browsers.button,
321 | input,
322 | select,
323 | textarea {
324 | font-size: 100%; // 1
325 | margin: 0; // 2
326 | vertical-align: baseline; // 3
327 | *vertical-align: middle; // 3
328 | }
329 |
330 | // Addresses Firefox 3+ setting `line-height` on `input` using `!important` in
331 | // the UA stylesheet.
332 | button,
333 | input {
334 | line-height: normal;
335 | }
336 |
337 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
338 | // and `video` controls.
339 | // 2. Corrects inability to style clickable `input` types in iOS.
340 | // 3. Improves usability and consistency of cursor style between image-type
341 | // `input` and others.
342 | // 4. Removes inner spacing in IE 7 without affecting normal text inputs.
343 | // Known issue: inner spacing remains in IE 6.
344 | button,
345 | html input[type="button"], // 1
346 | input[type="reset"],
347 | input[type="submit"] {
348 | -webkit-appearance: button; // 2
349 | cursor: pointer; // 3
350 | *overflow: visible; // 4
351 | }
352 |
353 | // Re-set default cursor for disabled elements.
354 | button[disabled],
355 | input[disabled] {
356 | cursor: default;
357 | }
358 |
359 | // 1. Addresses box sizing set to content-box in IE 8/9.
360 | // 2. Removes excess padding in IE 8/9.
361 | // 3. Removes excess padding in IE 7.
362 | // Known issue: excess padding remains in IE 6.
363 | input[type="checkbox"],
364 | input[type="radio"] {
365 | box-sizing: border-box; // 1
366 | padding: 0; // 2
367 | *height: 13px; // 3
368 | *width: 13px; // 3
369 | }
370 |
371 | // 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
372 | // 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
373 | // (include `-moz` to future-proof).
374 | input[type="search"] {
375 | -webkit-appearance: textfield; // 1
376 | -moz-box-sizing: content-box;
377 | -webkit-box-sizing: content-box; // 2
378 | box-sizing: content-box;
379 | }
380 |
381 | // Removes inner padding and search cancel button in Safari 5 and Chrome
382 | // on OS X.
383 | input[type="search"]::-webkit-search-cancel-button,
384 | input[type="search"]::-webkit-search-decoration {
385 | -webkit-appearance: none;
386 | }
387 |
388 | // Removes inner padding and border in Firefox 3+.
389 | button::-moz-focus-inner,
390 | input::-moz-focus-inner {
391 | border: 0;
392 | padding: 0;
393 | }
394 |
395 | // 1. Removes default vertical scrollbar in IE 6/7/8/9.
396 | // 2. Improves readability and alignment in all browsers.
397 | textarea {
398 | overflow: auto; // 1
399 | vertical-align: top; // 2
400 | }
401 |
402 | }
403 |
404 |
405 | // Tables
406 | @mixin h5bp-tables {
407 |
408 | // Remove most spacing between table cells.
409 | table {
410 | border-collapse: collapse;
411 | border-spacing: 0;
412 | }
413 |
414 | }
--------------------------------------------------------------------------------
/vendor/js/libs/fastclick.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
3 | *
4 | * @version 0.3.6
5 | * @codingstandard ftlabs-jslint
6 | * @copyright The Financial Times Limited [All Rights Reserved]
7 | * @license MIT License (see LICENSE.txt)
8 | */
9 |
10 | /*jslint browser:true*/
11 | /*global define*/
12 |
13 |
14 | /**
15 | * Instantiate fast-clicking listeners on the specificed layer.
16 | *
17 | * @constructor
18 | * @param {Element} layer The layer to listen on
19 | */
20 | function FastClick(layer) {
21 | 'use strict';
22 | var oldOnClick, that = this;
23 |
24 |
25 | /**
26 | * Whether a click is currently being tracked.
27 | *
28 | * @type boolean
29 | */
30 | this.trackingClick = false;
31 |
32 |
33 | /**
34 | * The element being tracked for a click.
35 | *
36 | * @type Element
37 | */
38 | this.targetElement = null;
39 |
40 |
41 | /**
42 | * The FastClick layer.
43 | *
44 | * @type Element
45 | */
46 | this.layer = layer;
47 |
48 | if (!layer || !layer.nodeType) {
49 | throw new TypeError('Layer must be a document node');
50 | }
51 |
52 | // Bind handlers to this instance
53 | this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); };
54 | this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); };
55 | this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); };
56 | this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); };
57 | this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); };
58 |
59 | // Devices that don't support touch don't need FastClick
60 | if (typeof window.ontouchstart === 'undefined') {
61 | return;
62 | }
63 |
64 | // Set up event handlers as required
65 | layer.addEventListener('click', this.onClick, true);
66 | layer.addEventListener('touchstart', this.onTouchStart, true);
67 | layer.addEventListener('touchmove', this.onTouchMove, true);
68 | layer.addEventListener('touchend', this.onTouchEnd, true);
69 | layer.addEventListener('touchcancel', this.onTouchCancel, true);
70 |
71 | // If a handler is already declared in the element's onclick attribute, it will be fired before
72 | // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
73 | // adding it as listener.
74 | if (typeof layer.onclick === 'function') {
75 |
76 | // Android browser on at least 3.2 requires a new reference to the function in layer.onclick
77 | // - the old one won't work if passed to addEventListener directly.
78 | oldOnClick = layer.onclick;
79 | layer.addEventListener('click', function(event) {
80 | oldOnClick(event);
81 | }, false);
82 | layer.onclick = null;
83 | }
84 | }
85 |
86 |
87 | /**
88 | * Android requires an exception for labels.
89 | *
90 | * @type boolean
91 | */
92 | FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
93 |
94 |
95 | /**
96 | * Determine whether a given element requires a native click.
97 | *
98 | * @param {Element} target DOM element
99 | * @returns {boolean} Returns true if the element needs a native click
100 | */
101 | FastClick.prototype.needsClick = function(target) {
102 | 'use strict';
103 | switch (target.nodeName.toLowerCase()) {
104 | case 'label':
105 | case 'video':
106 | return true;
107 | default:
108 | return (/\bneedsclick\b/).test(target.className);
109 | }
110 | };
111 |
112 |
113 | /**
114 | * Determine whether a given element requires a call to focus to simulate click into element.
115 | *
116 | * @param {Element} target target DOM element.
117 | * @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
118 | */
119 | FastClick.prototype.needsFocus = function(target) {
120 | 'use strict';
121 | switch (target.nodeName.toLowerCase()) {
122 | case 'textarea':
123 | case 'select':
124 | return true;
125 | case 'input':
126 | switch (target.type) {
127 | case 'button':
128 | case 'checkbox':
129 | case 'file':
130 | case 'image':
131 | case 'radio':
132 | case 'submit':
133 | return false;
134 | }
135 | return true;
136 | default:
137 | return (/\bneedsfocus\b/).test(target.className);
138 | }
139 | };
140 |
141 |
142 | /**
143 | * Send a click event to the element if it needs it.
144 | *
145 | * @returns {boolean} Whether the click was sent or not
146 | */
147 | FastClick.prototype.maybeSendClick = function(targetElement, event) {
148 | 'use strict';
149 | var clickEvent, touch;
150 |
151 | // Prevent the actual click from going though - unless the target node is marked as requiring
152 | // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted
153 | // to open the options list and so the original event is required.
154 | if (this.needsClick(targetElement)) {
155 | return false;
156 | }
157 |
158 | // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
159 | if (document.activeElement && document.activeElement !== targetElement) {
160 | document.activeElement.blur();
161 | }
162 |
163 | touch = event.changedTouches[0];
164 |
165 | // Synthesise a click event, with an extra attribute so it can be tracked
166 | clickEvent = document.createEvent('MouseEvents');
167 | clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
168 | clickEvent.forwardedTouchEvent = true;
169 | targetElement.dispatchEvent(clickEvent);
170 |
171 | return true;
172 | };
173 |
174 |
175 | /**
176 | * On touch start, record the position and scroll offset.
177 | *
178 | * @param {Event} event
179 | * @returns {boolean}
180 | */
181 | FastClick.prototype.onTouchStart = function(event) {
182 | 'use strict';
183 | var touch = event.targetTouches[0];
184 |
185 | this.trackingClick = true;
186 | this.targetElement = event.target;
187 |
188 | this.touchStartX = touch.pageX;
189 | this.touchStartY = touch.pageY;
190 |
191 | return true;
192 | };
193 |
194 |
195 | /**
196 | * Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
197 | *
198 | * @param {Event} event
199 | * @returns {boolean}
200 | */
201 | FastClick.prototype.touchHasMoved = function(event) {
202 | 'use strict';
203 | var touch = event.targetTouches[0];
204 |
205 | if (Math.abs(touch.pageX - this.touchStartX) > 10 || Math.abs(touch.pageY - this.touchStartY) > 10) {
206 | return true;
207 | }
208 |
209 | return false;
210 | };
211 |
212 |
213 | /**
214 | * Update the last position.
215 | *
216 | * @param {Event} event
217 | * @returns {boolean}
218 | */
219 | FastClick.prototype.onTouchMove = function(event) {
220 | 'use strict';
221 | if (!this.trackingClick) {
222 | return true;
223 | }
224 |
225 | // If the touch has moved, cancel the click tracking
226 | if (this.targetElement !== event.target || this.touchHasMoved(event)) {
227 | this.trackingClick = false;
228 | this.targetElement = null;
229 | }
230 |
231 | return true;
232 | };
233 |
234 |
235 | /**
236 | * On touch end, determine whether to send a click event at once.
237 | *
238 | * @param {Event} event
239 | * @returns {boolean}
240 | */
241 | FastClick.prototype.onTouchEnd = function(event) {
242 | 'use strict';
243 | var forElement, targetElement = this.targetElement;
244 |
245 | if (!this.trackingClick) {
246 | return true;
247 | }
248 |
249 | this.trackingClick = false;
250 |
251 | if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) {
252 | forElement = document.getElementById(targetElement.htmlFor);
253 | if (forElement) {
254 | targetElement.focus();
255 | if (this.deviceIsAndroid) {
256 | return false;
257 | }
258 |
259 | if (this.maybeSendClick(forElement, event)) {
260 | event.preventDefault();
261 | }
262 |
263 | return false;
264 | }
265 | } else if (this.needsFocus(targetElement)) {
266 | targetElement.focus();
267 | if (targetElement.tagName.toLowerCase() !== 'select') {
268 | event.preventDefault();
269 | }
270 | return false;
271 | }
272 |
273 | if (!this.maybeSendClick(targetElement, event)) {
274 | return false;
275 | }
276 |
277 | event.preventDefault();
278 | return false;
279 | };
280 |
281 |
282 | /**
283 | * On touch cancel, stop tracking the click.
284 | *
285 | * @returns {void}
286 | */
287 | FastClick.prototype.onTouchCancel = function() {
288 | 'use strict';
289 | this.trackingClick = false;
290 | this.targetElement = null;
291 | };
292 |
293 |
294 | /**
295 | * On actual clicks, determine whether this is a touch-generated click, a click action occurring
296 | * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
297 | * an actual click which should be permitted.
298 | *
299 | * @param {Event} event
300 | * @returns {boolean}
301 | */
302 | FastClick.prototype.onClick = function(event) {
303 | 'use strict';
304 | var oldTargetElement;
305 |
306 | if (event.forwardedTouchEvent) {
307 | return true;
308 | }
309 |
310 | // If a target element was never set (because a touch event was never fired) allow the click
311 | if (!this.targetElement) {
312 | return true;
313 | }
314 |
315 | oldTargetElement = this.targetElement;
316 | this.targetElement = null;
317 |
318 | // Programmatically generated events targeting a specific element should be permitted
319 | if (!event.cancelable) {
320 | return true;
321 | }
322 |
323 | // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
324 | if (event.target.type === 'submit' && event.detail === 0) {
325 | return true;
326 | }
327 |
328 | // Derive and check the target element to see whether the click needs to be permitted;
329 | // unless explicitly enabled, prevent non-touch click events from triggering actions,
330 | // to prevent ghost/doubleclicks.
331 | if (!this.needsClick(oldTargetElement)) {
332 |
333 | // Prevent any user-added listeners declared on FastClick element from being fired.
334 | if (event.stopImmediatePropagation) {
335 | event.stopImmediatePropagation();
336 | }
337 |
338 | // Cancel the event
339 | event.stopPropagation();
340 | event.preventDefault();
341 |
342 | return false;
343 | }
344 |
345 | // If clicks are permitted, return true for the action to go through.
346 | return true;
347 | };
348 |
349 |
350 | /**
351 | * Remove all FastClick's event listeners.
352 | *
353 | * @returns {void}
354 | */
355 | FastClick.prototype.destroy = function() {
356 | 'use strict';
357 | var layer = this.layer;
358 |
359 | layer.removeEventListener('click', this.onClick, true);
360 | layer.removeEventListener('touchstart', this.onTouchStart, true);
361 | layer.removeEventListener('touchmove', this.onTouchMove, true);
362 | layer.removeEventListener('touchend', this.onTouchEnd, true);
363 | layer.removeEventListener('touchcancel', this.onTouchCancel, true);
364 | };
365 |
366 |
367 | if (typeof define === 'function' && define.amd) {
368 |
369 | // AMD. Register as an anonymous module.
370 | define(function() {
371 | 'use strict';
372 | return FastClick;
373 | });
374 | }
375 |
--------------------------------------------------------------------------------
/vendor/jam/lodash/lodash.underscore.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Lo-Dash 1.0.0-rc.3 (Custom Build) lodash.com/license
3 | Build: `lodash underscore -m -o ./lodash.underscore.min.js`
4 | Underscore.js 1.4.3 underscorejs.org/LICENSE
5 | */
6 | ;(function(n,t){function r(n,t){var r;if(n)for(r in t||(t=V),n)if(ft.call(n,r)&&t(n[r],r,n)===Y)break}function e(n,t){var r;if(n)for(r in t||(t=V),n)if(t(n[r],r,n)===Y)break}function u(n,t,r){if(n){var t=t&&typeof r=="undefined"?t:f(t,r),e=n.length,r=-1;if(typeof e=="number")for(;++rt||typeof n=="undefined")return 1;if(nr?0:r);++ee&&(e=r,a=n)
13 | });else for(;++ia&&(a=n[i]);return a}function T(n,t){return F(n,t+"")}function q(n,t,r,e){var i=3>arguments.length,t=f(t,e,Y);if(qt(n)){var o=-1,a=n.length;for(i&&(r=n[++o]);++oarguments.length;if(typeof u!="number")var o=Bt(n),u=o.length;return t=f(t,e,Y),N(n,function(e,a,f){a=o?o[--u]:--u,r=i?(i=K,n[a]):t(r,n[a],a,f)}),r}function D(n,t,r){var e,t=f(t,r);if(qt(n))for(var r=-1,i=n.length;++rr?_t(0,u+r):r||0)-1;else if(r)return e=C(n,t),n[e]===t?e:-1;for(;++e>>1,r(n[e])I(a,c))&&(r&&a.push(c),o.push(e))}return o}function U(n,t){return Ot||st&&2"']/g,ut=/['\n\r\t\u2028\u2029\\]/g,it=Math.ceil,ot=W.concat,at=Math.floor,ft=Q.hasOwnProperty,ct=W.push,lt=Q.toString,st=tt.test(st=p.bind)&&st,pt=tt.test(pt=Array.isArray)&&pt,ht=n.isFinite,vt=n.isNaN,gt=tt.test(gt=Object.keys)&>,_t=Math.max,yt=Math.min,mt=Math.random,dt="[object Array]",bt="[object Boolean]",jt="[object Date]",wt="[object Number]",xt="[object Object]",At="[object RegExp]",Et="[object String]",Q=!!n.attachEvent,Q=st&&!/\n|true/.test(st+Q),Ot=st&&!Q,St=(St={0:1,length:1},W.splice.call(St,0,1),St[0]),kt=arguments.constructor==Object,Nt={"boolean":K,"function":H,object:H,number:K,string:K,undefined:K},Ft={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};
17 | i.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""},i.isArguments=function(n){return"[object Arguments]"==lt.call(n)},i.isArguments(arguments)||(i.isArguments=function(n){return n?ft.call(n,"callee"):K});var Rt={"&":"&","<":"<",">":">",'"':""","'":"'"},Tt=m(Rt),qt=pt||function(n){return kt&&n instanceof Array||lt.call(n)==dt};b(/x/)&&(b=function(n){return n instanceof Function||"[object Function]"==lt.call(n)});var Bt=gt?function(n){return j(n)?gt(n):[]
18 | }:g;i.after=function(n,t){return 1>n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},i.bind=U,i.bindAll=function(n){for(var t=arguments,r=1I(e,i,r)&&u.push(i)
20 | }return u},i.filter=S,i.flatten=$,i.forEach=N,i.functions=y,i.groupBy=function(n,t,r){var e={},t=f(t,r);return N(n,function(n,r,u){r=t(n,r,u),(ft.call(e,r)?e[r]:e[r]=[]).push(n)}),e},i.initial=function(n,t,r){if(!n)return[];var e=n.length;return p(n,0,yt(_t(0,e-(t==J||r?1:t||0)),e))},i.intersection=function(n){var t=arguments,r=t.length,e=-1,u=n?n.length:0,i=[];n:for(;++eI(i,o)){for(var a=r;--a;)if(0>I(t[a],o))continue n;i.push(o)}}return i},i.invert=m,i.invoke=function(n,t){var r=p(arguments,2),e=typeof t=="function",u=[];
21 | return N(n,function(n){u.push((e?t:n[t]).apply(n,r))}),u},i.keys=Bt,i.map=F,i.max=R,i.memoize=function(n,t){var r={};return function(){var e=t?t.apply(this,arguments):arguments[0];return ft.call(r,e)?r[e]:r[e]=n.apply(this,arguments)}},i.min=function(n,t,r){var e=1/0,i=-1,o=n?n.length:0,a=e;if(t||!qt(n))t=f(t,r),u(n,function(n,r,u){r=t(n,r,u),rI(t,e,1)&&(r[e]=n)}),r},i.once=function(n){var t,r=K;return function(){return r?t:(r=H,t=n.apply(this,arguments),n=J,t)}},i.pairs=function(n){var t=[];return r(n,function(n,r){t.push([r,n])}),t},i.pick=function(n){for(var t=0,r=ot.apply(W,arguments),e=r.length,u={};++tI(arguments,u,1)&&e.push(u)}return e},i.wrap=function(n,t){return function(){var r=[n];return ct.apply(r,arguments),t.apply(this,r)}},i.zip=function(n){for(var t=-1,r=n?R(T(arguments,"length")):0,e=Array(r);++tr?_t(0,e+r):yt(r,e-1))+1);e--;)if(n[e]===t)return e;return-1},i.mixin=G,i.noConflict=function(){return n._=Z,this},i.random=function(n,t){return n==J&&t==J&&(t=1),n=+n||0,t==J&&(t=n,n=0),n+at(mt()*((+t||0)-n+1))},i.reduce=q,i.reduceRight=B,i.result=function(n,t){var r=n?n[t]:J;return b(r)?n[t]():r},i.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Bt(n).length
28 | },i.some=D,i.sortedIndex=C,i.template=function(n,t,r){n||(n="");var r=_({},r,i.templateSettings),e=0,u="__p+='",o=r.variable;n.replace(RegExp((r.escape||rt).source+"|"+(r.interpolate||rt).source+"|"+(r.evaluate||rt).source+"|$","g"),function(t,r,i,o,a){u+=n.slice(e,a).replace(ut,c),u+=r?"'+_['escape']("+r+")+'":o?"';"+o+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",e=a+t.length}),u+="';\n",o||(o="obj",u="with("+o+"||{}){"+u+"}"),u="function("+o+"){var __t,__p='',__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}"+u+"return __p}";
29 | try{var a=Function("_","return "+u)(i)}catch(f){throw f.source=u,f}return t?a(t):(a.source=u,a)},i.unescape=function(n){return n==J?"":(n+"").replace(nt,h)},i.uniqueId=function(n){var t=++X+"";return n?n+t:t},i.all=O,i.any=D,i.detect=k,i.foldl=q,i.foldr=B,i.include=E,i.inject=q,i.first=M,i.last=function(n,t,r){if(n){var e=n.length;return t==J||r?n[e-1]:p(n,_t(0,e-t))}},i.take=M,i.head=M,i.chain=function(n){return n=new i(n),n.__chain__=H,n},i.VERSION="1.0.0-rc.3",G(i),i.prototype.chain=function(){return this.__chain__=H,this
30 | },i.prototype.value=function(){return this.__wrapped__},u("pop push reverse shift sort splice unshift".split(" "),function(n){var t=W[n];i.prototype[n]=function(){var n=this.__wrapped__;return t.apply(n,arguments),St&&0===n.length&&delete n[0],this}}),u(["concat","join","slice"],function(n){var t=W[n];i.prototype[n]=function(){var n=t.apply(this.__wrapped__,arguments);return this.__chain__&&(n=new i(n),n.__chain__=H),n}}),L?typeof module=="object"&&module&&module.exports==L?(module.exports=i)._=i:L._=i:n._=i
31 | })(this);
--------------------------------------------------------------------------------
/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/js/plugins/backbone.activities.js:
--------------------------------------------------------------------------------
1 | (function(root) {
2 | "use strict";
3 |
4 | var Backbone = root.Backbone;
5 | var _ = root._ || root.underscore || root.lodash;
6 | var $ = Backbone.$ || root.$ || root.jQuery || root.Zepto || root.ender;
7 |
8 | var VERSION = '0.5.1';
9 |
10 | Backbone.ActivityRouter = Backbone.Router.extend({
11 |
12 | constructor: function(options) {
13 | options = options || {};
14 |
15 | // an ActivityRouter's el is the point at which the layout class is added.
16 | // this lets you hook CSS onto specific layouts
17 | this._$el = $(options.el);
18 |
19 | // regions is an object of region names to Layouts.
20 | // e.g. { 'main': new Backbone.Layout({ el: '#main' }), ... }
21 | this.regions = options.regions;
22 |
23 | // defaultRoute is a url fragment. It may be specified in the class or overridden
24 | // when instantiated
25 | this._defaultRoute = options.defaultRoute || this.defaultRoute;
26 |
27 | // initialLayout is a string. If defined, the layout is set later in the constructor
28 | // It may be specified in the class or overridden when instantiated
29 | this._initialLayout = options.initialLayout || this.initialLayout;
30 |
31 | // routes is an array of objects which contain the route RegEx as well as the
32 | // corresponding activity and handler
33 | this._routes = [];
34 |
35 | // create a route for each entry in each activity's routes object
36 | _.each(this.activities, function(activity, activityName) {
37 |
38 | // give the activity a reference to the router
39 | activity.router = this;
40 | activity.handlers = activity.handlers || {};
41 | _.each(activity.routes, function(handlerName, route) {
42 | var handler;
43 |
44 | // the handler may be attached directly to the routes object
45 | // if so, put it in handlers and use route as its name
46 | if (handlerName instanceof Backbone.ActivityRouteHandler) {
47 | activity.handlers[route] = handlerName;
48 | handlerName = route;
49 | }
50 |
51 | handler = activity.handlers[handlerName];
52 | handler.router = this;
53 | handler.regions = this.regions;
54 | handler.activity = activity;
55 |
56 | // add this route to the internal array
57 | this._routes.push({
58 | route: this._routeToRegExp(route),
59 | activityName: activityName,
60 | handlerName: handlerName
61 | });
62 |
63 | // use the activity name plus the route handler name for uniqueness
64 | this.route(route, activityName + '-' + handlerName, _.bind(function() {
65 |
66 | this._handleRoute(activityName,
67 | handlerName,
68 | Array.prototype.slice.apply(arguments));
69 |
70 | }, this));
71 | }, this);
72 | }, this);
73 |
74 | // set up the default route
75 | if (_.isString(this._defaultRoute)) {
76 |
77 | // the default route may contain arguments
78 | this._defaultRoute = this._getFragmentRoute(this._defaultRoute);
79 |
80 | this._routes.push({
81 | route: this._routeToRegExp(''),
82 | activityName: this._defaultRoute.activityName,
83 | handlerName: this._defaultRoute.handlerName,
84 | args: this._defaultRoute.args
85 | });
86 |
87 | this.route('',
88 | this._defaultRoute.activityName + '-' + this._defaultRoute.handlerName,
89 | _.bind(function() {
90 |
91 | this._handleRoute(this._defaultRoute.activityName,
92 | this._defaultRoute.handlerName,
93 | this._defaultRoute.args);
94 |
95 | }, this));
96 | }
97 |
98 | // initialize initial layout.
99 | // if the router is responsive, setLayout should be called whenever the desired
100 | // layout changes.
101 | if (this._initialLayout) {
102 | this.setLayout(this._initialLayout);
103 | }
104 |
105 | // manually call the superclass constructor
106 | Backbone.Router.prototype.constructor.call(this, options);
107 | },
108 |
109 | // setLayout sets the app layout. This triggers the corresponding layout in the current
110 | // activity's current route handler
111 | setLayout: function(name) {
112 | var activity = this.activities[this._currentActivityName];
113 | var handler;
114 |
115 | // update the layout class on the parent element
116 | if (this._$el) {
117 | this._$el.removeClass('layout-' + this.currentLayout)
118 | .addClass('layout-' + name);
119 | }
120 |
121 | this.currentLayout = name;
122 |
123 | if (activity) {
124 | handler = activity.handlers[this._currentHandlerName];
125 |
126 | if (handler && handler.layouts && handler.layouts[this.currentLayout]) {
127 | handler.layouts[this.currentLayout].apply(handler, this._currentArgs);
128 |
129 | }
130 | }
131 | },
132 |
133 | // Handle the activity lifecycle
134 | _didRoute: function(activityName, handlerName, args) {
135 |
136 | var didChangeActivity = this._currentActivityName !== activityName;
137 | var activity = this.activities[this._currentActivityName];
138 | var handler = activity && activity.handlers[this._currentHandlerName];
139 |
140 | // first, stop the old route
141 | if (handler) {
142 | handler.onStop();
143 | }
144 |
145 | if (activity && didChangeActivity) {
146 | activity.onStop();
147 | }
148 |
149 | // old route is stopped, change the current route
150 |
151 | this._$el.removeClass('activity-' + this._currentActivityName)
152 | .removeClass('activityhandler-' + this._currentActivityName + '-' + this._currentHandlerName);
153 |
154 | this._currentActivityName = activityName;
155 | this._currentHandlerName = handlerName;
156 | this._currentArgs = args;
157 | activity = this.activities[activityName];
158 | handler = activity.handlers[handlerName];
159 |
160 | this._$el.addClass('activity-' + this._currentActivityName)
161 | .addClass('activityhandler-' + this._currentActivityName + '-' + this._currentHandlerName);
162 |
163 | // start the new route
164 | if (!activity._initialized) {
165 | activity.onCreate();
166 | activity._initialized = true;
167 | }
168 |
169 | if (didChangeActivity) {
170 | activity.onStart();
171 | }
172 |
173 | if (!handler._initialized) {
174 | handler.onCreate();
175 | handler._initialized = true;
176 | }
177 |
178 | handler.onStart.apply(handler, this._currentArgs);
179 |
180 | if (this.currentLayout &&
181 | handler.layouts &&
182 | typeof handler.layouts[this.currentLayout] === 'function') {
183 | handler.layouts[this.currentLayout].apply(handler, this._currentArgs);
184 | }
185 | },
186 |
187 | // When called once authenticated, calls didRoute using the current URL fragment
188 | resolveAuthentication: function() {
189 | var fragment = Backbone.history.fragment;
190 | var routeObj = this._getFragmentRoute(fragment);
191 | var redirect = this._authenticateRoute(routeObj.activityName, routeObj.handlerName, routeObj.args);
192 |
193 | // if authentication passed then a redirect will not be returned
194 | if (!redirect) {
195 | // call didRoute to show the protected page
196 | this._didRoute.call(this, routeObj.activityName, routeObj.handlerName, routeObj.args);
197 | }
198 | },
199 |
200 | _handleRoute: function(activityName, handlerName, args) {
201 | var redirect = this._authenticateRoute(activityName, handlerName, args);
202 |
203 | // allow the redirect to provided via a function call
204 | if (_.isFunction(redirect)) {
205 | redirect = redirect();
206 | }
207 |
208 | // if the redirect is a URL fragment, extract the activity info
209 | if (_.isString(redirect)) {
210 | redirect = this._getFragmentRoute(redirect);
211 | }
212 |
213 | // delegate to didRoute to implement the activity lifecycle
214 | if (redirect) {
215 | this._didRoute(redirect.activityName, redirect.handlerName, redirect.args);
216 | }
217 | else {
218 | this._didRoute(activityName, handlerName, args);
219 | }
220 | },
221 |
222 | _authenticateRoute: function(activityName, handlerName, args) {
223 | var activity = this.activities[activityName];
224 | var handler = activity.handlers[handlerName];
225 | var authenticatorContext;
226 | var redirect;
227 | var redirectContext;
228 |
229 | // if the activity is protected and there is an authenticator, check the authentication.
230 | // If the authentication fails then return the redirect
231 | var handlerAuth = handler.authenticate;
232 | var activityAuth = activity.authenticate;
233 | var routerAuth = this.authenticate;
234 |
235 | // authenticator precedence: handler > activity > router
236 | var authenticator = handlerAuth || activityAuth || routerAuth;
237 |
238 | // use authentication if protected and there is an authenticator
239 | if (authenticator && (handler.isProtected || activity.isProtected)) {
240 |
241 | authenticatorContext = handlerAuth ? handler : (activityAuth ? activity : this);
242 |
243 | // authentication fails if a falsy value is returned
244 | if (!authenticator.call(authenticatorContext, activityName, handlerName, args)) {
245 |
246 | var handlerRedirect = handler.authenticateRedirect;
247 | var activityRedirect = activity.authenticateRedirect;
248 | var routerRedirect = this.authenticateRedirect;
249 |
250 | redirect = handlerRedirect || activityRedirect || routerRedirect;
251 |
252 | if (_.isFunction(redirect)) {
253 | // redirect context for a handler or activity is the activity
254 | redirectContext = handlerRedirect ? handler : (activityRedirect ? activity : this);
255 | redirect = redirect.call(redirectContext, activityName, handlerName, args);
256 | }
257 |
258 | return redirect;
259 | }
260 | }
261 | return false;
262 | },
263 |
264 | // return the data for a fragment
265 | _getFragmentRoute: function(fragment) {
266 | var result = _.clone(_.find(this._routes, function(routeObj) {
267 | return routeObj.route.test(fragment);
268 | }, this));
269 | var args = this._extractParameters(result.route, fragment);
270 | if (args) {
271 | result.args = args;
272 | }
273 | return result;
274 | },
275 |
276 | VERSION: VERSION
277 |
278 | });
279 |
280 | // Activity constructor
281 | Backbone.Activity = function(options) {
282 | this._configure(options || {});
283 | this.initialize.apply(this, arguments);
284 | };
285 |
286 | // mix events into the prototype
287 | _.extend(Backbone.Activity.prototype, Backbone.Events, {
288 |
289 | // Performs the initial configuration of an Activity with a set of options.
290 | // Keys with special meaning *(routes)* are attached directly to the activity.
291 | _configure: function(options) {
292 | if (options.routes) {
293 | this.routes = options.routes;
294 | }
295 | if (options.handlers) {
296 | this.handlers = options.handlers;
297 | }
298 | },
299 |
300 | // Initialize is an empty function by default. Override it with your own
301 | // initialization logic.
302 | initialize: function() {},
303 |
304 | // The router uses this value to determine whether to call an activity's onCreate
305 | // callback
306 | _initialized: false,
307 |
308 | // callback stubs
309 | onCreate: function() {},
310 | onStart: function() {},
311 | onStop: function() {},
312 |
313 | VERSION: VERSION
314 |
315 | });
316 |
317 | // use backbone's extend (referencing via View here, but they're all the same)
318 | Backbone.Activity.extend = Backbone.View.extend;
319 |
320 | // Activity constructor
321 | Backbone.ActivityRouteHandler = function(options) {
322 | this._configure(options || {});
323 | this.initialize.apply(this, arguments);
324 | };
325 |
326 | // mix events into the prototype
327 | _.extend(Backbone.ActivityRouteHandler.prototype, Backbone.Events, {
328 |
329 | // regions is a map from region names to region objects.
330 | // Setup is handled by the ActivityRouter constructor.
331 | // This object will be the same for all handlers associated with the same router.
332 | regions: {},
333 |
334 | // layouts is an object of layout names to layout functions
335 | layouts: {},
336 |
337 | // Performs the initial configuration of an ActivityRouteHandler with a set of options.
338 | // Keys with special meaning *(layouts)* are attached directly to the activity.
339 | _configure: function(options) {
340 | if (options.layouts) {
341 | this.layouts = options.layouts;
342 | }
343 | },
344 |
345 | // Initialize is an empty function by default. Override it with your own
346 | // initialization logic.
347 | initialize: function() {},
348 |
349 | // The router uses this value to determine whether to call an activity's onCreate
350 | // callback
351 | _initialized: false,
352 |
353 | // updateRegions takes an object of regions by name
354 | // For each region given, the corresponding views are inserted. See updateRegion
355 | // below for details.
356 | updateRegions: function(regions) {
357 |
358 | _.each(regions, function(views, regionName) {
359 | this.updateRegion(regionName, views);
360 | }, this);
361 |
362 | },
363 |
364 | // updateRegion takes a region and either a view or an object with a template
365 | // and a views object.
366 | // The views are inserted into the region, replacing any existing views.
367 | //
368 | // Example: passing a single view:
369 | // updateRegion('main', view);
370 | //
371 | // Example: passing an array of views (note that if the views' templates are not
372 | // cached and differ then LayoutManager does not guarantee that the views will be
373 | // inserted into the document in order):
374 | // updateRegion('main', [ myViewUsingTamplateFoo, myOtherViewUsingTemplateFoo ])
375 | //
376 | // Example: passing an object of views:
377 | // updateRegion('main', {
378 | // template: 'mytemplate',
379 | // views: {
380 | // '.myclass': myView
381 | // }
382 | // })
383 | //
384 | updateRegion: function(region, views) {
385 |
386 | // retrieve the actual region by its name
387 | region = this.regions[region];
388 |
389 | // beware: hacks; we need to remove the views that were present previously
390 | // also set hasRendered to false so that LM doesn't ditch the new views when render is called
391 | region._removeViews(true);
392 | region.__manager__.hasRendered = false;
393 |
394 | // reset the template for the region for the first two cases
395 | // (given a view; given an array of views)
396 | region.template = undefined;
397 |
398 | // Clear any remaining HTML in the region
399 | // This might have been left over from a previous template
400 | region.$el.empty();
401 |
402 | // if we have a single view, insert it directly into the region
403 | if (views instanceof Backbone.View) {
404 | region.insertView('', views);
405 | }
406 |
407 | // if we have an array of views, insert them
408 | // beware: if the templates for the views are different then LM may not render them in order!
409 | else if (_.isArray(views)) {
410 | region.setViews({
411 | '': views
412 | });
413 | }
414 |
415 | // set the template of the region and then insert the views into their places
416 | else if (_.isObject(views)) {
417 | region.template = views.template;
418 | region.setViews(views.views);
419 | }
420 |
421 | // render the region and all of its views
422 | region.render();
423 | },
424 |
425 | // callback stubs
426 | onCreate: function() {},
427 | onStart: function() {},
428 | onStop: function() {},
429 |
430 | VERSION: VERSION
431 |
432 | });
433 |
434 | // use backbone's extend (referencing via View here, but they're all the same)
435 | Backbone.ActivityRouteHandler.extend = Backbone.View.extend;
436 |
437 | // The module returns Backbone.
438 | return Backbone;
439 | }(this));
--------------------------------------------------------------------------------
/assets/css/index.css:
--------------------------------------------------------------------------------
1 | /* Base styles defined in common 'core' partial */
2 | /* line 32, ../../app/styles/vendor/h5bp/_normalize.scss */
3 | article,
4 | aside,
5 | details,
6 | figcaption,
7 | figure,
8 | footer,
9 | header,
10 | hgroup,
11 | nav,
12 | section,
13 | summary {
14 | display: block;
15 | }
16 |
17 | /* line 39, ../../app/styles/vendor/h5bp/_normalize.scss */
18 | audio,
19 | canvas,
20 | video {
21 | display: inline-block;
22 | *display: inline;
23 | *zoom: 1;
24 | }
25 |
26 | /* line 47, ../../app/styles/vendor/h5bp/_normalize.scss */
27 | audio:not([controls]) {
28 | display: none;
29 | height: 0;
30 | }
31 |
32 | /* line 55, ../../app/styles/vendor/h5bp/_normalize.scss */
33 | [hidden] {
34 | display: none;
35 | }
36 |
37 | /* line 69, ../../app/styles/vendor/h5bp/_normalize.scss */
38 | html {
39 | font-size: 100%;
40 | -webkit-text-size-adjust: 100%;
41 | -ms-text-size-adjust: 100%;
42 | }
43 |
44 | /* line 82, ../../app/styles/vendor/h5bp/_normalize.scss */
45 | html,
46 | button,
47 | input,
48 | select,
49 | textarea {
50 | font-family: sans-serif;
51 | }
52 |
53 | /* line 88, ../../app/styles/vendor/h5bp/_normalize.scss */
54 | body {
55 | margin: 0;
56 | }
57 |
58 | /* line 103, ../../app/styles/vendor/h5bp/_normalize.scss */
59 | a:focus {
60 | outline: thin dotted;
61 | }
62 |
63 | /* line 109, ../../app/styles/vendor/h5bp/_normalize.scss */
64 | a:active,
65 | a:hover {
66 | outline: 0;
67 | }
68 |
69 | /* line 122, ../../app/styles/vendor/h5bp/_normalize.scss */
70 | h1 {
71 | font-size: 2em;
72 | margin: 0.67em 0;
73 | }
74 |
75 | /* line 127, ../../app/styles/vendor/h5bp/_normalize.scss */
76 | h2 {
77 | font-size: 1.5em;
78 | margin: 0.83em 0;
79 | }
80 |
81 | /* line 132, ../../app/styles/vendor/h5bp/_normalize.scss */
82 | h3 {
83 | font-size: 1.17em;
84 | margin: 1em 0;
85 | }
86 |
87 | /* line 137, ../../app/styles/vendor/h5bp/_normalize.scss */
88 | h4 {
89 | font-size: 1em;
90 | margin: 1.33em 0;
91 | }
92 |
93 | /* line 142, ../../app/styles/vendor/h5bp/_normalize.scss */
94 | h5 {
95 | font-size: 0.83em;
96 | margin: 1.67em 0;
97 | }
98 |
99 | /* line 147, ../../app/styles/vendor/h5bp/_normalize.scss */
100 | h6 {
101 | font-size: 0.75em;
102 | margin: 2.33em 0;
103 | }
104 |
105 | /* line 153, ../../app/styles/vendor/h5bp/_normalize.scss */
106 | abbr[title] {
107 | border-bottom: 1px dotted;
108 | }
109 |
110 | /* line 159, ../../app/styles/vendor/h5bp/_normalize.scss */
111 | b,
112 | strong {
113 | font-weight: bold;
114 | }
115 |
116 | /* line 163, ../../app/styles/vendor/h5bp/_normalize.scss */
117 | blockquote {
118 | margin: 1em 40px;
119 | }
120 |
121 | /* line 168, ../../app/styles/vendor/h5bp/_normalize.scss */
122 | dfn {
123 | font-style: italic;
124 | }
125 |
126 | /* line 173, ../../app/styles/vendor/h5bp/_normalize.scss */
127 | mark {
128 | background: #ff0;
129 | color: #000;
130 | }
131 |
132 | /* line 180, ../../app/styles/vendor/h5bp/_normalize.scss */
133 | p,
134 | pre {
135 | margin: 1em 0;
136 | }
137 |
138 | /* line 188, ../../app/styles/vendor/h5bp/_normalize.scss */
139 | code,
140 | kbd,
141 | pre,
142 | samp {
143 | font-family: monospace, serif;
144 | _font-family: 'courier new', monospace;
145 | font-size: 1em;
146 | }
147 |
148 | /* line 195, ../../app/styles/vendor/h5bp/_normalize.scss */
149 | pre {
150 | white-space: pre;
151 | white-space: pre-wrap;
152 | word-wrap: break-word;
153 | }
154 |
155 | /* line 202, ../../app/styles/vendor/h5bp/_normalize.scss */
156 | q {
157 | quotes: none;
158 | }
159 |
160 | /* line 208, ../../app/styles/vendor/h5bp/_normalize.scss */
161 | q:before,
162 | q:after {
163 | content: '';
164 | content: none;
165 | }
166 |
167 | /* line 214, ../../app/styles/vendor/h5bp/_normalize.scss */
168 | small {
169 | font-size: 80%;
170 | }
171 |
172 | /* line 220, ../../app/styles/vendor/h5bp/_normalize.scss */
173 | sub,
174 | sup {
175 | font-size: 75%;
176 | line-height: 0;
177 | position: relative;
178 | vertical-align: baseline;
179 | }
180 |
181 | /* line 227, ../../app/styles/vendor/h5bp/_normalize.scss */
182 | sup {
183 | top: -0.5em;
184 | }
185 |
186 | /* line 231, ../../app/styles/vendor/h5bp/_normalize.scss */
187 | sub {
188 | bottom: -0.25em;
189 | }
190 |
191 | /* line 245, ../../app/styles/vendor/h5bp/_normalize.scss */
192 | dl,
193 | menu,
194 | ol,
195 | ul {
196 | margin: 1em 0;
197 | }
198 |
199 | /* line 249, ../../app/styles/vendor/h5bp/_normalize.scss */
200 | dd {
201 | margin: 0 0 0 40px;
202 | }
203 |
204 | /* line 256, ../../app/styles/vendor/h5bp/_normalize.scss */
205 | menu,
206 | ol,
207 | ul {
208 | padding: 0 0 0 40px;
209 | }
210 |
211 | /* line 268, ../../app/styles/vendor/h5bp/_normalize.scss */
212 | img {
213 | border: 0;
214 | /* 1 */
215 | -ms-interpolation-mode: bicubic;
216 | /* 2 */
217 | }
218 |
219 | /* line 274, ../../app/styles/vendor/h5bp/_normalize.scss */
220 | svg:not(:root) {
221 | overflow: hidden;
222 | }
223 |
224 | /* line 285, ../../app/styles/vendor/h5bp/_normalize.scss */
225 | figure {
226 | margin: 0;
227 | }
228 |
229 | /* line 296, ../../app/styles/vendor/h5bp/_normalize.scss */
230 | form {
231 | margin: 0;
232 | }
233 |
234 | /* line 301, ../../app/styles/vendor/h5bp/_normalize.scss */
235 | fieldset {
236 | border: 1px solid #c0c0c0;
237 | margin: 0 2px;
238 | padding: 0.35em 0.625em 0.75em;
239 | }
240 |
241 | /* line 310, ../../app/styles/vendor/h5bp/_normalize.scss */
242 | legend {
243 | border: 0;
244 | padding: 0;
245 | white-space: normal;
246 | *margin-left: -7px;
247 | }
248 |
249 | /* line 323, ../../app/styles/vendor/h5bp/_normalize.scss */
250 | input,
251 | select,
252 | textarea {
253 | font-size: 100%;
254 | margin: 0;
255 | vertical-align: baseline;
256 | *vertical-align: middle;
257 | }
258 |
259 | /* line 333, ../../app/styles/vendor/h5bp/_normalize.scss */
260 | button,
261 | input {
262 | line-height: normal;
263 | }
264 |
265 | /* line 347, ../../app/styles/vendor/h5bp/_normalize.scss */
266 | button,
267 | html input[type="button"],
268 | input[type="reset"],
269 | input[type="submit"] {
270 | -webkit-appearance: button;
271 | cursor: pointer;
272 | *overflow: visible;
273 | }
274 |
275 | /* line 355, ../../app/styles/vendor/h5bp/_normalize.scss */
276 | button[disabled],
277 | input[disabled] {
278 | cursor: default;
279 | }
280 |
281 | /* line 364, ../../app/styles/vendor/h5bp/_normalize.scss */
282 | input[type="checkbox"],
283 | input[type="radio"] {
284 | box-sizing: border-box;
285 | padding: 0;
286 | *height: 13px;
287 | *width: 13px;
288 | }
289 |
290 | /* line 374, ../../app/styles/vendor/h5bp/_normalize.scss */
291 | input[type="search"] {
292 | -webkit-appearance: textfield;
293 | -moz-box-sizing: content-box;
294 | -webkit-box-sizing: content-box;
295 | box-sizing: content-box;
296 | }
297 |
298 | /* line 384, ../../app/styles/vendor/h5bp/_normalize.scss */
299 | input[type="search"]::-webkit-search-cancel-button,
300 | input[type="search"]::-webkit-search-decoration {
301 | -webkit-appearance: none;
302 | }
303 |
304 | /* line 390, ../../app/styles/vendor/h5bp/_normalize.scss */
305 | button::-moz-focus-inner,
306 | input::-moz-focus-inner {
307 | border: 0;
308 | padding: 0;
309 | }
310 |
311 | /* line 397, ../../app/styles/vendor/h5bp/_normalize.scss */
312 | textarea {
313 | overflow: auto;
314 | vertical-align: top;
315 | }
316 |
317 | /* line 409, ../../app/styles/vendor/h5bp/_normalize.scss */
318 | table {
319 | border-collapse: collapse;
320 | border-spacing: 0;
321 | }
322 |
323 | /* line 22, ../../app/styles/vendor/h5bp/_main.scss */
324 | html,
325 | button,
326 | input,
327 | select,
328 | textarea {
329 | color: #222222;
330 | }
331 |
332 | /* line 26, ../../app/styles/vendor/h5bp/_main.scss */
333 | body {
334 | font-size: 1em;
335 | line-height: 1.4;
336 | }
337 |
338 | /* line 34, ../../app/styles/vendor/h5bp/_main.scss */
339 | ::-moz-selection {
340 | background: #b3d4fc;
341 | text-shadow: none;
342 | }
343 |
344 | /* line 39, ../../app/styles/vendor/h5bp/_main.scss */
345 | ::selection {
346 | background: #b3d4fc;
347 | text-shadow: none;
348 | }
349 |
350 | /* line 45, ../../app/styles/vendor/h5bp/_main.scss */
351 | hr {
352 | display: block;
353 | height: 1px;
354 | border: 0;
355 | border-top: 1px solid #ccc;
356 | margin: 1em 0;
357 | padding: 0;
358 | }
359 |
360 | /* line 55, ../../app/styles/vendor/h5bp/_main.scss */
361 | img {
362 | vertical-align: middle;
363 | }
364 |
365 | /* line 60, ../../app/styles/vendor/h5bp/_main.scss */
366 | fieldset {
367 | border: 0;
368 | margin: 0;
369 | padding: 0;
370 | }
371 |
372 | /* line 67, ../../app/styles/vendor/h5bp/_main.scss */
373 | textarea {
374 | resize: vertical;
375 | }
376 |
377 | /* line 7, ../../app/styles/vendor/h5bp/_helpers.scss */
378 | .ir {
379 | background-color: transparent;
380 | border: 0;
381 | overflow: hidden;
382 | *text-indent: -9999px;
383 | }
384 | /* line 27, ../../app/styles/vendor/h5bp/_helpers.scss */
385 | .ir:before {
386 | content: "";
387 | display: block;
388 | width: 0;
389 | height: 100%;
390 | }
391 |
392 | /* line 9, ../../app/styles/vendor/h5bp/_helpers.scss */
393 | .hidden {
394 | display: none !important;
395 | visibility: hidden;
396 | }
397 |
398 | /* line 11, ../../app/styles/vendor/h5bp/_helpers.scss */
399 | .visuallyhidden {
400 | border: 0;
401 | clip: rect(0 0 0 0);
402 | height: 1px;
403 | margin: -1px;
404 | overflow: hidden;
405 | padding: 0;
406 | position: absolute;
407 | width: 1px;
408 | }
409 | /* line 67, ../../app/styles/vendor/h5bp/_helpers.scss */
410 | .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus {
411 | clip: auto;
412 | height: auto;
413 | margin: 0;
414 | overflow: visible;
415 | position: static;
416 | width: auto;
417 | }
418 |
419 | /* line 13, ../../app/styles/vendor/h5bp/_helpers.scss */
420 | .invisible {
421 | visibility: hidden;
422 | }
423 |
424 | /* line 93, ../../app/styles/vendor/h5bp/_helpers.scss */
425 | .clearfix:before, .clearfix:after {
426 | content: " ";
427 | display: table;
428 | }
429 | /* line 98, ../../app/styles/vendor/h5bp/_helpers.scss */
430 | .clearfix:after {
431 | clear: both;
432 | }
433 | /* line 104, ../../app/styles/vendor/h5bp/_helpers.scss */
434 | .clearfix {
435 | *zoom: 1;
436 | }
437 |
438 | /*************** Core layout ***************/
439 | /* line 13, ../../app/styles/_core.scss */
440 | * {
441 | -webkit-box-sizing: border-box;
442 | -moz-box-sizing: border-box;
443 | box-sizing: border-box;
444 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
445 | }
446 | /* line 19, ../../app/styles/_core.scss */
447 | *:not(input), *:not(textarea), *:not(select) {
448 | -webkit-touch-callout: none;
449 | -webkit-user-select: none;
450 | }
451 |
452 | /* line 29, ../../app/styles/_core.scss */
453 | html, body {
454 | width: 100%;
455 | height: 100%;
456 | }
457 |
458 | /* line 34, ../../app/styles/_core.scss */
459 | #app {
460 | min-height: 100%;
461 | position: relative;
462 | }
463 |
464 | /* line 39, ../../app/styles/_core.scss */
465 | body {
466 | background-color: #efefef;
467 | }
468 |
469 | /*************** Forms ***************/
470 | /* line 48, ../../app/styles/_core.scss */
471 | input, textarea, select {
472 | border: 1px solid #555;
473 | padding: 0.5em;
474 | font-size: 14px;
475 | line-height: 1.2em;
476 | width: 80%;
477 | background: #fff;
478 | -webkit-border-radius: 0.5em;
479 | -moz-border-radius: 0.5em;
480 | border-radius: 0.5em;
481 | -webkit-user-select: text;
482 | }
483 |
484 | /* line 62, ../../app/styles/_core.scss */
485 | button {
486 | padding: 0;
487 | }
488 |
489 | /* line 66, ../../app/styles/_core.scss */
490 | select {
491 | height: 2.5em;
492 | }
493 |
494 | /* line 70, ../../app/styles/_core.scss */
495 | input, textarea {
496 | -webkit-appearance: none;
497 | -moz-appearance: none;
498 | appearance: none;
499 | }
500 |
501 | /* line 75, ../../app/styles/_core.scss */
502 | input[type=checkbox],
503 | input[type=radio] {
504 | display: inline-block;
505 | font-size: 15px;
506 | line-height: 1em;
507 | margin: 0 0.25em 0 0;
508 | padding: 0;
509 | width: 2em;
510 | height: 2em;
511 | -webkit-border-radius: 0.25em;
512 | -moz-border-radius: 0.25em;
513 | border-radius: 0.25em;
514 | vertical-align: text-top;
515 | }
516 |
517 | /* line 87, ../../app/styles/_core.scss */
518 | input[type=radio] {
519 | -webkit-border-radius: 2em;
520 | -moz-border-radius: 2em;
521 | border-radius: 2em;
522 | /* Make radios round */
523 | }
524 |
525 | /* line 91, ../../app/styles/_core.scss */
526 | input[type=checkbox]:checked {
527 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABOJJREFUeNrsm01IVFEUx6+jIBl9TTWkSZrmqGFh9EGRERWki9wEgS1y074JF30sosJNtgiVVoEEuUgIIlCiKNqolFa4UMwKKyOttIyKrECx83/c+zpzm0nH8X3pHDi8Gd+bmfebc87/nnPVpMnJSTGfzCfmmSWAE8BzzFLifYPKijJHAa413UlE2NIIO22UYWfpsIu8laJ9fi5HeCd5B/k58n04EnzHVC9KirfxqK+9aDvpk0cPaukQwuO0hYtE5ppc8e5tvxj78R0/ClGk6+dEShMoonqJfBuer8rIMmBh6Rnjov9lj5Dp7X1ggj0r09eIanZOgXGEffk8LN687lOXtnpatAg0jQ4N5BV6VGEDr/rEyPCQetr0v3R2PTDBFtHhJnleckqKyM0rEouX+I1zv3//FP0velTdTpBXTQXramCCPUCHW+TJgMzKyRepqQuMc4B8/qxLTIyP42kn+VGC7fHsOszrdWUgg2ALzHOoVylORgpL2DHPdlocFrXKYT8MDXDYui3b9x6OBdZ1ESbY60qcgoXFZr3CsM4CWFqIYOtn8hkpLgE1lVgXpwhKXE6wLZ7tpSUslLgUsPmFm8z1VYOFEu8m2HbPDg8cFgocXF9sKnEE2GKC7Yn3M50WrQY7YR0FVgKFNNZhIVBWwDoGLKedClWzHBZKzNR492zCOgJMsMfUaKcLFJoKRJepcftsf77PZli0i3V4jKWHw6JdZE1FKJ6lxxXAcpZtVh3UsuUB85waBFgHVW/Vffhsgl1Nh6tqvINzAyygye4S7HEr78WuCGP5yUP3xGdZpchyxMPUc9DqG/HZEN1atdaibrlBpJgiV1F0x6y+nyk7rcqKMtTeSblXtJS8jfz+dLZEpUgZipwbLBJYhnjdsm2ZkBWKHDMwwZq7g8xK4HRuB1IwWlR43SKNuSKrupUDfJOVIjVtYLnHa+4OppMjQt++jlLb9xwRKpV9cNl/6nYF1FgXKa1uj9q5NPqiwCIVtyEq6zdsNSKk0hHCw1rBUlmjUes2e21B2DmA2l230xEt1KvwU3T0VIQZAhQ0BSgk11i+3kasW5XKbL1tFzZbNOBWlXrdXQ9V+oUZvgiWqifZqQuqDPQvC+8n19s2q9fbmIBJgdHWlePGcIPYIZQ3GmaqrnEtRfaI3I8qQQbo662WyqecmtKirsOAJkdqN0FNWSqaBtjMNevU00PkZ/AAW6q6saHAkVSOpfGAinYiQmxfyTS/P2BGGd8BtlX5fhQMr4O6k710MrrTApbboJeNWXVwQK2dYVFeGVgdKeKG4Xq8Tlo1RfeXq4EldCMae9Tx+791+E8tA1ZXZVzPBoNGpzcNY+mlq1X/G6mWAY101qM7MjwY9nrPAFOU25VqR6plvZtS0ZUl0OykUMUzLV0xROjj4JQXatGtES6xmIBlLRuKLVU3qo2ODrsuujOdh29Hq2VuTJlvCBfZTIABMIE61pcoZYg+ayEbPQ1Mad2rohxpiVKNhrT7wmU20y2emkhpjdru7X6sfv5JiZzngeUS9RRpqyYpDAaAZYP9Hkrn924Djue3h9jb2jwqf1vAVLvOqdHPamCIV4iNfBgMqshbaEy0EeGELTWs0hobeqfJ95NvBKxwucW1Ly2hscNxj/yX8IDNu7+XTkr8G08COAGcAPaS/RFgAE+yYfmhJZIUAAAAAElFTkSuQmCC);
528 | -webkit-background-size: 30px 30px;
529 | -moz-background-size: 30px 30px;
530 | background-size: 30px 30px;
531 | }
532 |
533 | /* line 98, ../../app/styles/_core.scss */
534 | input[type="search"] {
535 | -webkit-appearance: none;
536 | -moz-appearance: none;
537 | appearance: none;
538 | -webkit-box-sizing: border-box;
539 | -moz-box-sizing: border-box;
540 | box-sizing: border-box;
541 | }
542 |
543 | /*************** Lists ***************/
544 | /* line 107, ../../app/styles/_core.scss */
545 | .list {
546 | list-style: none;
547 | margin: 0;
548 | padding: 0;
549 | }
550 | /* line 110, ../../app/styles/_core.scss */
551 | .list li + li {
552 | border-top: 1px solid #bbb;
553 | }
554 | /* line 114, ../../app/styles/_core.scss */
555 | .list li.active {
556 | background-color: #03ba8e;
557 | color: #f0f0f0;
558 | }
559 | /* line 119, ../../app/styles/_core.scss */
560 | .list a {
561 | display: block;
562 | height: 44px;
563 | line-height: 44px;
564 | padding: 0 10px;
565 | text-decoration: none;
566 | }
567 | /* line 125, ../../app/styles/_core.scss */
568 | .list a:link, .list a:visited, .list a:hover, .list a:active {
569 | color: inherit;
570 | }
571 |
572 | /*************** Toolbars ***************/
573 | /* line 145, ../../app/styles/_core.scss */
574 | .toolbar {
575 | position: absolute;
576 | width: 100%;
577 | z-index: 2;
578 | margin: 0;
579 | }
580 |
581 | /* line 152, ../../app/styles/_core.scss */
582 | #headerbar {
583 | top: 0;
584 | height: 44px;
585 | background-color: #028868;
586 | color: #f0f0f0;
587 | }
588 | /* line 157, ../../app/styles/_core.scss */
589 | #headerbar .headerbar-title {
590 | margin: 0;
591 | padding: 0 10px;
592 | line-height: 44px;
593 | font-size: 20px;
594 | }
595 |
596 | /* line 165, ../../app/styles/_core.scss */
597 | #footerbar {
598 | bottom: 0;
599 | height: 0;
600 | }
601 | /* line 168, ../../app/styles/_core.scss */
602 | #footerbar .footerbar-title {
603 | margin: 0;
604 | padding: 0 10px;
605 | line-height: 0;
606 | font-size: 20px;
607 | }
608 |
609 | /* line 176, ../../app/styles/_core.scss */
610 | #main {
611 | z-index: 1;
612 | padding-top: 44px;
613 | padding-bottom: 0;
614 | }
615 |
616 | /* line 188, ../../app/styles/_core.scss */
617 | .fixedbar #headerbar, .fixedbar #footerbar {
618 | position: fixed;
619 | }
620 | /* line 192, ../../app/styles/_core.scss */
621 | .fixedbar #main {
622 | overflow: auto;
623 | }
624 |
625 | /* Module Styles go here */
626 | /* line 3, ../../app/styles/modules/_animals.scss */
627 | .detail {
628 | margin: 10px;
629 | }
630 |
--------------------------------------------------------------------------------
/test/vendor/jasmine-jquery.js:
--------------------------------------------------------------------------------
1 | var readFixtures = function() {
2 | return jasmine.getFixtures().proxyCallTo_('read', arguments)
3 | }
4 |
5 | var preloadFixtures = function() {
6 | jasmine.getFixtures().proxyCallTo_('preload', arguments)
7 | }
8 |
9 | var loadFixtures = function() {
10 | jasmine.getFixtures().proxyCallTo_('load', arguments)
11 | }
12 |
13 | var appendLoadFixtures = function() {
14 | jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
15 | }
16 |
17 | var setFixtures = function(html) {
18 | jasmine.getFixtures().proxyCallTo_('set', arguments)
19 | }
20 |
21 | var appendSetFixtures = function() {
22 | jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
23 | }
24 |
25 | var sandbox = function(attributes) {
26 | return jasmine.getFixtures().sandbox(attributes)
27 | }
28 |
29 | var spyOnEvent = function(selector, eventName) {
30 | return jasmine.JQuery.events.spyOn(selector, eventName)
31 | }
32 |
33 | var preloadStyleFixtures = function() {
34 | jasmine.getStyleFixtures().proxyCallTo_('preload', arguments)
35 | }
36 |
37 | var loadStyleFixtures = function() {
38 | jasmine.getStyleFixtures().proxyCallTo_('load', arguments)
39 | }
40 |
41 | var appendLoadStyleFixtures = function() {
42 | jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments)
43 | }
44 |
45 | var setStyleFixtures = function(html) {
46 | jasmine.getStyleFixtures().proxyCallTo_('set', arguments)
47 | }
48 |
49 | var appendSetStyleFixtures = function(html) {
50 | jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments)
51 | }
52 |
53 | var loadJSONFixtures = function() {
54 | return jasmine.getJSONFixtures().proxyCallTo_('load', arguments)
55 | }
56 |
57 | var getJSONFixture = function(url) {
58 | return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url]
59 | }
60 |
61 | jasmine.spiedEventsKey = function (selector, eventName) {
62 | return [$(selector).selector, eventName].toString()
63 | }
64 |
65 | jasmine.getFixtures = function() {
66 | return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
67 | }
68 |
69 | jasmine.getStyleFixtures = function() {
70 | return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures()
71 | }
72 |
73 | jasmine.Fixtures = function() {
74 | this.containerId = 'jasmine-fixtures'
75 | this.fixturesCache_ = {}
76 | this.fixturesPath = 'spec/javascripts/fixtures'
77 | }
78 |
79 | jasmine.Fixtures.prototype.set = function(html) {
80 | this.cleanUp()
81 | this.createContainer_(html)
82 | }
83 |
84 | jasmine.Fixtures.prototype.appendSet= function(html) {
85 | this.addToContainer_(html)
86 | }
87 |
88 | jasmine.Fixtures.prototype.preload = function() {
89 | this.read.apply(this, arguments)
90 | }
91 |
92 | jasmine.Fixtures.prototype.load = function() {
93 | this.cleanUp()
94 | this.createContainer_(this.read.apply(this, arguments))
95 | }
96 |
97 | jasmine.Fixtures.prototype.appendLoad = function() {
98 | this.addToContainer_(this.read.apply(this, arguments))
99 | }
100 |
101 | jasmine.Fixtures.prototype.read = function() {
102 | var htmlChunks = []
103 |
104 | var fixtureUrls = arguments
105 | for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
106 | htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
107 | }
108 |
109 | return htmlChunks.join('')
110 | }
111 |
112 | jasmine.Fixtures.prototype.clearCache = function() {
113 | this.fixturesCache_ = {}
114 | }
115 |
116 | jasmine.Fixtures.prototype.cleanUp = function() {
117 | $('#' + this.containerId).remove()
118 | }
119 |
120 | jasmine.Fixtures.prototype.sandbox = function(attributes) {
121 | var attributesToSet = attributes || {}
122 | return $('').attr(attributesToSet)
123 | }
124 |
125 | jasmine.Fixtures.prototype.createContainer_ = function(html) {
126 | var container
127 | if(html instanceof $) {
128 | container = $('')
129 | container.html(html)
130 | } else {
131 | container = '' + html + '
'
132 | }
133 | $(document.body).append(container)
134 | }
135 |
136 | jasmine.Fixtures.prototype.addToContainer_ = function(html){
137 | var container = $(document.body).find('#'+this.containerId).append(html)
138 | if(!container.length){
139 | this.createContainer_(html)
140 | }
141 | }
142 |
143 | jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
144 | if (typeof this.fixturesCache_[url] === 'undefined') {
145 | this.loadFixtureIntoCache_(url)
146 | }
147 | return this.fixturesCache_[url]
148 | }
149 |
150 | jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
151 | var url = this.makeFixtureUrl_(relativeUrl)
152 | var request = $.ajax({
153 | type: "GET",
154 | url: url + "?" + new Date().getTime(),
155 | async: false
156 | })
157 | this.fixturesCache_[relativeUrl] = request.responseText
158 | }
159 |
160 | jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){
161 | return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
162 | }
163 |
164 | jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
165 | return this[methodName].apply(this, passedArguments)
166 | }
167 |
168 |
169 | jasmine.StyleFixtures = function() {
170 | this.fixturesCache_ = {}
171 | this.fixturesNodes_ = []
172 | this.fixturesPath = 'spec/javascripts/fixtures'
173 | }
174 |
175 | jasmine.StyleFixtures.prototype.set = function(css) {
176 | this.cleanUp()
177 | this.createStyle_(css)
178 | }
179 |
180 | jasmine.StyleFixtures.prototype.appendSet = function(css) {
181 | this.createStyle_(css)
182 | }
183 |
184 | jasmine.StyleFixtures.prototype.preload = function() {
185 | this.read_.apply(this, arguments)
186 | }
187 |
188 | jasmine.StyleFixtures.prototype.load = function() {
189 | this.cleanUp()
190 | this.createStyle_(this.read_.apply(this, arguments))
191 | }
192 |
193 | jasmine.StyleFixtures.prototype.appendLoad = function() {
194 | this.createStyle_(this.read_.apply(this, arguments))
195 | }
196 |
197 | jasmine.StyleFixtures.prototype.cleanUp = function() {
198 | while(this.fixturesNodes_.length) {
199 | this.fixturesNodes_.pop().remove()
200 | }
201 | }
202 |
203 | jasmine.StyleFixtures.prototype.createStyle_ = function(html) {
204 | var styleText = $('').html(html).text(),
205 | style = $('')
206 |
207 | this.fixturesNodes_.push(style)
208 |
209 | $('head').append(style)
210 | }
211 |
212 | jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache
213 |
214 | jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read
215 |
216 | jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_
217 |
218 | jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_
219 |
220 | jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_
221 |
222 | jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_
223 |
224 | jasmine.getJSONFixtures = function() {
225 | return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures()
226 | }
227 |
228 | jasmine.JSONFixtures = function() {
229 | this.fixturesCache_ = {}
230 | this.fixturesPath = 'spec/javascripts/fixtures/json'
231 | }
232 |
233 | jasmine.JSONFixtures.prototype.load = function() {
234 | this.read.apply(this, arguments)
235 | return this.fixturesCache_
236 | }
237 |
238 | jasmine.JSONFixtures.prototype.read = function() {
239 | var fixtureUrls = arguments
240 | for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
241 | this.getFixtureData_(fixtureUrls[urlIndex])
242 | }
243 | return this.fixturesCache_
244 | }
245 |
246 | jasmine.JSONFixtures.prototype.clearCache = function() {
247 | this.fixturesCache_ = {}
248 | }
249 |
250 | jasmine.JSONFixtures.prototype.getFixtureData_ = function(url) {
251 | this.loadFixtureIntoCache_(url)
252 | return this.fixturesCache_[url]
253 | }
254 |
255 | jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
256 | var self = this
257 | var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
258 | $.ajax({
259 | async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
260 | cache: false,
261 | dataType: 'json',
262 | url: url,
263 | done: function(data) {
264 | self.fixturesCache_[relativeUrl] = data
265 | },
266 | fail: function(jqXHR, status, errorThrown) {
267 | throw Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')')
268 | }
269 | })
270 | }
271 |
272 | jasmine.JSONFixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
273 | return this[methodName].apply(this, passedArguments)
274 | }
275 |
276 | jasmine.JQuery = function() {}
277 |
278 | jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
279 | return $('').append(html).html()
280 | }
281 |
282 | jasmine.JQuery.elementToString = function(element) {
283 | var domEl = $(element).get(0)
284 | if (domEl == undefined || domEl.cloneNode)
285 | return $('').append($(element).clone()).html()
286 | else
287 | return element.toString()
288 | }
289 |
290 | jasmine.JQuery.matchersClass = {}
291 |
292 | !function(namespace) {
293 | var data = {
294 | spiedEvents: {},
295 | handlers: []
296 | }
297 |
298 | namespace.events = {
299 | spyOn: function(selector, eventName) {
300 | var handler = function(e) {
301 | data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = e
302 | }
303 | $(selector).bind(eventName, handler)
304 | data.handlers.push(handler)
305 | return {
306 | selector: selector,
307 | eventName: eventName,
308 | handler: handler,
309 | reset: function(){
310 | delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
311 | }
312 | }
313 | },
314 |
315 | wasTriggered: function(selector, eventName) {
316 | return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)])
317 | },
318 |
319 | wasPrevented: function(selector, eventName) {
320 | var e;
321 | return (e = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) && e.isDefaultPrevented()
322 | },
323 |
324 | cleanUp: function() {
325 | data.spiedEvents = {}
326 | data.handlers = []
327 | }
328 | }
329 | }(jasmine.JQuery)
330 |
331 | !function(){
332 | var jQueryMatchers = {
333 | toHaveClass: function(className) {
334 | return this.actual.hasClass(className)
335 | },
336 |
337 | toHaveCss: function(css){
338 | for (var prop in css){
339 | if (this.actual.css(prop) !== css[prop]) return false
340 | }
341 | return true
342 | },
343 |
344 | toBeVisible: function() {
345 | return this.actual.is(':visible')
346 | },
347 |
348 | toBeHidden: function() {
349 | return this.actual.is(':hidden')
350 | },
351 |
352 | toBeSelected: function() {
353 | return this.actual.is(':selected')
354 | },
355 |
356 | toBeChecked: function() {
357 | return this.actual.is(':checked')
358 | },
359 |
360 | toBeEmpty: function() {
361 | return this.actual.is(':empty')
362 | },
363 |
364 | toExist: function() {
365 | return $(document).find(this.actual).length
366 | },
367 |
368 | toHaveLength: function(length) {
369 | return this.actual.length === length
370 | },
371 |
372 | toHaveAttr: function(attributeName, expectedAttributeValue) {
373 | return hasProperty(this.actual.attr(attributeName), expectedAttributeValue)
374 | },
375 |
376 | toHaveProp: function(propertyName, expectedPropertyValue) {
377 | return hasProperty(this.actual.prop(propertyName), expectedPropertyValue)
378 | },
379 |
380 | toHaveId: function(id) {
381 | return this.actual.attr('id') == id
382 | },
383 |
384 | toHaveHtml: function(html) {
385 | return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html)
386 | },
387 |
388 | toContainHtml: function(html){
389 | var actualHtml = this.actual.html()
390 | var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html)
391 | return (actualHtml.indexOf(expectedHtml) >= 0)
392 | },
393 |
394 | toHaveText: function(text) {
395 | var trimmedText = $.trim(this.actual.text())
396 | if (text && $.isFunction(text.test)) {
397 | return text.test(trimmedText)
398 | } else {
399 | return trimmedText == text
400 | }
401 | },
402 |
403 | toHaveValue: function(value) {
404 | return this.actual.val() == value
405 | },
406 |
407 | toHaveData: function(key, expectedValue) {
408 | return hasProperty(this.actual.data(key), expectedValue)
409 | },
410 |
411 | toBe: function(selector) {
412 | return this.actual.is(selector)
413 | },
414 |
415 | toContain: function(selector) {
416 | return this.actual.find(selector).length
417 | },
418 |
419 | toBeDisabled: function(selector){
420 | return this.actual.is(':disabled')
421 | },
422 |
423 | toBeFocused: function(selector) {
424 | return this.actual.is(':focus')
425 | },
426 |
427 | toHandle: function(event) {
428 |
429 | var events = $._data(this.actual.get(0), "events")
430 |
431 | if(!events || !event || typeof event !== "string") {
432 | return false
433 | }
434 |
435 | var namespaces = event.split(".")
436 | var eventType = namespaces.shift()
437 | var sortedNamespaces = namespaces.slice(0).sort()
438 | var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
439 |
440 | if(events[eventType] && namespaces.length) {
441 | for(var i = 0; i < events[eventType].length; i++) {
442 | var namespace = events[eventType][i].namespace
443 | if(namespaceRegExp.test(namespace)) {
444 | return true
445 | }
446 | }
447 | } else {
448 | return events[eventType] && events[eventType].length > 0
449 | }
450 | },
451 |
452 | // tests the existence of a specific event binding + handler
453 | toHandleWith: function(eventName, eventHandler) {
454 | var stack = $._data(this.actual.get(0), "events")[eventName]
455 | for (var i = 0; i < stack.length; i++) {
456 | if (stack[i].handler == eventHandler) return true
457 | }
458 | return false
459 | }
460 | }
461 |
462 | var hasProperty = function(actualValue, expectedValue) {
463 | if (expectedValue === undefined) return actualValue !== undefined
464 | return actualValue == expectedValue
465 | }
466 |
467 | var bindMatcher = function(methodName) {
468 | var builtInMatcher = jasmine.Matchers.prototype[methodName]
469 |
470 | jasmine.JQuery.matchersClass[methodName] = function() {
471 | if (this.actual
472 | && (this.actual instanceof $
473 | || jasmine.isDomNode(this.actual))) {
474 | this.actual = $(this.actual)
475 | var result = jQueryMatchers[methodName].apply(this, arguments)
476 | var element
477 | if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML")
478 | this.actual = jasmine.JQuery.elementToString(this.actual)
479 | return result
480 | }
481 |
482 | if (builtInMatcher) {
483 | return builtInMatcher.apply(this, arguments)
484 | }
485 |
486 | return false
487 | }
488 | }
489 |
490 | for(var methodName in jQueryMatchers) {
491 | bindMatcher(methodName)
492 | }
493 | }()
494 |
495 | beforeEach(function() {
496 | this.addMatchers(jasmine.JQuery.matchersClass)
497 | this.addMatchers({
498 | toHaveBeenTriggeredOn: function(selector) {
499 | this.message = function() {
500 | return [
501 | "Expected event " + this.actual + " to have been triggered on " + selector,
502 | "Expected event " + this.actual + " not to have been triggered on " + selector
503 | ]
504 | }
505 | return jasmine.JQuery.events.wasTriggered(selector, this.actual)
506 | }
507 | })
508 | this.addMatchers({
509 | toHaveBeenTriggered: function(){
510 | var eventName = this.actual.eventName,
511 | selector = this.actual.selector
512 | this.message = function() {
513 | return [
514 | "Expected event " + eventName + " to have been triggered on " + selector,
515 | "Expected event " + eventName + " not to have been triggered on " + selector
516 | ]
517 | }
518 | return jasmine.JQuery.events.wasTriggered(selector, eventName)
519 | }
520 | })
521 | this.addMatchers({
522 | toHaveBeenPreventedOn: function(selector) {
523 | this.message = function() {
524 | return [
525 | "Expected event " + this.actual + " to have been prevented on " + selector,
526 | "Expected event " + this.actual + " not to have been prevented on " + selector
527 | ]
528 | }
529 | return jasmine.JQuery.events.wasPrevented(selector, this.actual)
530 | }
531 | })
532 | this.addMatchers({
533 | toHaveBeenPrevented: function() {
534 | var eventName = this.actual.eventName,
535 | selector = this.actual.selector
536 | this.message = function() {
537 | return [
538 | "Expected event " + eventName + " to have been prevented on " + selector,
539 | "Expected event " + eventName + " not to have been prevented on " + selector
540 | ]
541 | }
542 | return jasmine.JQuery.events.wasPrevented(selector, eventName)
543 | }
544 | })
545 | })
546 |
547 | afterEach(function() {
548 | jasmine.getFixtures().cleanUp()
549 | jasmine.getStyleFixtures().cleanUp()
550 | jasmine.JQuery.events.cleanUp()
551 | })
--------------------------------------------------------------------------------