diff --git a/front-api/db/migrate/20150130040553_add_description_suffix_to_unit.rb b/front-api/db/migrate/20150130040553_add_description_suffix_to_unit.rb new file mode 100644 index 0000000..1646292 --- /dev/null +++ b/front-api/db/migrate/20150130040553_add_description_suffix_to_unit.rb @@ -0,0 +1,5 @@ +class AddDescriptionSuffixToUnit < ActiveRecord::Migration + def change + add_column :units, :description_suffix, :string + end +end diff --git a/front-api/db/migrate/20150130041842_add_number_of_pieces_suffix_to_unit.rb b/front-api/db/migrate/20150130041842_add_number_of_pieces_suffix_to_unit.rb new file mode 100644 index 0000000..f35303c --- /dev/null +++ b/front-api/db/migrate/20150130041842_add_number_of_pieces_suffix_to_unit.rb @@ -0,0 +1,5 @@ +class AddNumberOfPiecesSuffixToUnit < ActiveRecord::Migration + def change + add_column :units, :number_of_pieces_suffix, :string + end +end diff --git a/front-api/db/schema.rb b/front-api/db/schema.rb index f8c845f..564e36b 100644 --- a/front-api/db/schema.rb +++ b/front-api/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150118082022) do +ActiveRecord::Schema.define(version: 20150130041842) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -62,7 +62,9 @@ ActiveRecord::Schema.define(version: 20150118082022) do create_table "units", force: :cascade do |t| t.string "name" - t.string "short_name", limit: 4 + t.string "short_name", limit: 4 + t.string "description_suffix" + t.string "number_of_pieces_suffix" end end diff --git a/front-api/node_modules/Backbone.Mutators/.editorconfig b/front-api/node_modules/Backbone.Mutators/.editorconfig new file mode 100644 index 0000000..b4fb3fb --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/.editorconfig @@ -0,0 +1,18 @@ +; EditorConfig is awesome: http://EditorConfig.org + +root = true ; top-most EditorConfig file + +; Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +; Space indention of 4 for all js files +[*.js] +indent_style = space +indent_size = 4 + +; Indentation override for the node based Gruntfile +[Gruntfile.js] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/front-api/node_modules/Backbone.Mutators/.jshintrc b/front-api/node_modules/Backbone.Mutators/.jshintrc new file mode 100644 index 0000000..6a704d8 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/.jshintrc @@ -0,0 +1,35 @@ +{ + "curly": true, + "eqeqeq": true, + "forin": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "sub": true, + "undef": true, + "unused": true, + "boss": true, + "eqnull": true, + "es5": true, + "node": false, + "browser": true, + "jquery": true, + "laxbreak": true, + "globals": { + "ok": true, + "expect": true, + "test": true, + "module": true, + "__dirname": true, + "asyncTest": true, + "start": true, + "equal": true, + "_": true, + "Backbone": true, + "exports": true, + "require": true, + "define": true, + "process": true + } +} diff --git a/front-api/node_modules/Backbone.Mutators/.npmignore b/front-api/node_modules/Backbone.Mutators/.npmignore new file mode 100644 index 0000000..67028ef --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/.npmignore @@ -0,0 +1,2 @@ +report +docs \ No newline at end of file diff --git a/front-api/node_modules/Backbone.Mutators/.travis.yml b/front-api/node_modules/Backbone.Mutators/.travis.yml new file mode 100644 index 0000000..9844dad --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/.travis.yml @@ -0,0 +1,15 @@ +--- +language: node_js +node_js: +- 0.8 +before_script: +- npm install grunt-cli -g +- npm install backbone@~1.x +- grunt travis --verbose +env: + matrix: + - secure: ! 'iDVcf5UvrZqr05dnnJx5guL+Vp+TW+gX/IA1H20sl4XxzgQhXWtcvpMGLckY + + YgI5lQmR3nIakU0/3hm8wd+RnXM04Ss+XLe6Bu2gNkNxpPuP9JMZRPdFNdsB + + NgzgpvREUGViRC4zITqjeJuKUaY4eM8un3k7f2Fqh0XD9QZ29KU=' diff --git a/front-api/node_modules/Backbone.Mutators/Gruntfile.js b/front-api/node_modules/Backbone.Mutators/Gruntfile.js new file mode 100644 index 0000000..d4cc4a6 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/Gruntfile.js @@ -0,0 +1,181 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + jshint: { + options: { + jshintrc:'.jshintrc' + }, + all: ['Gruntfile.js', 'src/*.js', 'test/*.js'] + }, + + qunit: { + options: { + '--web-security':'no', + timeout: 40000, + coverage: { + src: ['src/*.js'], + instrumentedFiles: 'temp', + htmlReport: 'report/coverage' + } + }, + all: ['test/index.html'] + }, + + complexity: { + generic: { + src: ['src/*.js'], + options: { + errorsOnly: false, + cyclomatic: 8, + halstead: 31, + maintainability: 100 + } + } + }, + + plato: { + bc: { + src: ['src/backbone.mutators.js'], + dest: 'docs/complexity', + options: { + jshint : grunt.file.readJSON('.jshintrc') + } + } + }, + + 'saucelabs-qunit': { + all: { + options: { + username: 'asciidisco', + urls: ['http://rawgithub.com/asciidisco/Backbone.Mutators/master/test/index_sauce.html'], + tunnelTimeout: 100000, + testTimeout: 100000, + testname: 'Backbone.Mutators', + tags: ['backbone', 'plugin', 'mutators', 'unittest'], + build: process.env.TRAVIS_JOB_ID, + browsers: [ { + browserName: 'internet explorer', + platform: 'XP', + version: '6' + }, { + browserName: 'internet explorer', + platform: 'XP', + version: '7' + }, { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '8' + }, { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '9' + }, { + browserName: 'firefox', + platform: 'Windows 7', + version: '21' + }, { + browserName: 'chrome', + platform: 'Windows 7' + }, { + browserName: 'opera', + platform: 'Windows 2008', + version: '12' + }] + } + } + }, + + nodeunit: { + all: ['test/*node_test.js'] + }, + + concat: { + options: { + stripBanners: true, + banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %>\n' + + '------------------------------\n' + + 'Build @ <%= grunt.template.today("yyyy-mm-dd") %>\n' + + 'Documentation and Full License Available at:\n' + + '<%= pkg.homepage %>\n' + + '<%= pkg.repository.url %>\n' + + 'Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n\n' + + 'Permission is hereby granted, free of charge, to any person obtaining a\n' + + 'copy of this software and associated documentation files (the "Software"),\n' + + 'to deal in the Software without restriction, including without limitation\n' + + 'the rights to use, copy, modify, merge, publish, distribute, sublicense,\n' + + 'and/or sell copies of the Software, and to permit persons to whom the\n\n' + + 'Software is furnished to do so, subject to the following conditions:\n' + + 'The above copyright notice and this permission notice shall be included in\n' + + 'all copies or substantial portions of the Software.\n\n' + + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\n' + + 'EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n' + + 'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n' + + 'IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n' + + 'DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n' + + 'ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n' + + 'IN THE SOFTWARE.*/\n' + }, + dist: { + src: ['src/<%= pkg.name.toLowerCase() %>.js'], + dest: '<%= pkg.name.toLowerCase() %>.js' + } + }, + + uglify: { + options: { + banner: '<%= concat.options.banner %>' + }, + dist: { + files: { + '<%= pkg.name.toLowerCase() %>.min.js': ['<%= pkg.name.toLowerCase() %>.js'] + } + } + }, + + docco: { + debug: { + src: ['src/*.js'], + options: { + output: 'docs/literal' + } + } + }, + + yuidoc: { + compile: { + name: '<%= pkg.name %>', + description: '<%= pkg.description %>', + version: '<%= pkg.version %>', + options: { + linkNatives: true, + selleck: true, + paths: 'src/', + outdir: 'docs/api' + } + } + } + + }); + + // Load 3rd party tasks + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-qunit-istanbul'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-nodeunit'); + grunt.loadNpmTasks('grunt-contrib-yuidoc'); + grunt.loadNpmTasks('grunt-saucelabs'); + grunt.loadNpmTasks('grunt-complexity'); + grunt.loadNpmTasks('grunt-docco'); + grunt.loadNpmTasks('grunt-plato'); + + // Default task. + grunt.registerTask('default', ['jshint', 'complexity', 'qunit', 'nodeunit', 'concat', 'uglify']); + grunt.registerTask('travis', ['jshint', 'complexity', 'qunit']); + + +}; diff --git a/front-api/node_modules/Backbone.Mutators/README.md b/front-api/node_modules/Backbone.Mutators/README.md new file mode 100644 index 0000000..5e22699 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/README.md @@ -0,0 +1,393 @@ +## Backbone.Mutators +Backbone plugin to override getters and setters with logic + +## Build Status, Project Page, Annotated Source & Tests +[![Build Status](https://secure.travis-ci.org/asciidisco/Backbone.Mutators.png?branch=master)](http://travis-ci.org/asciidisco/Backbone.Mutators) +[![Unit Test Status](https://saucelabs.com/buildstatus/asciidisco)](https://saucelabs.com/u/asciidisco)

+[Project Page](http://asciidisco.github.com/Backbone.Mutators/index.html)
+[Docs](http://asciidisco.github.com/Backbone.Mutators/docs/backbone.mutators.html)
+[Tests](http://asciidisco.github.com/Backbone.Mutators/test/index.html)
+[NPM registry](http://search.npmjs.org/#/Backbone.Mutators) + +## Introduction +Ever wanted Backbone to have getters and setters you can override with your own logic? +Yes?! Then Backbone.Mutators is the missing tool in your chain... + +## Installation + +The plugin itself implements the Universal Module Definition (UMD). +You can use it with a CommonJS like loader, or with an AMD loader or via +vanilla javascript. + +The plugin has two dependencies, underscore.js and backbone.js + +### Download +You can directly download the +[Development Version](https://raw.github.com/asciidisco/Backbone.Mutators/master/backbone.mutators.js) +or the +[Production Version](https://raw.github.com/asciidisco/Backbone.Mutators/master/backbone.mutators.min.js) +from the root folder + +### VOLO +```shell +$ volo add Backbone.Mutators +``` + +### NPM +```shell +$ npm install Backbone.Mutators +``` + +## Integration + +### AMD +```javascript +// AMD +require(['underscore', 'backbone', 'path/to/backbone.mutators'], function (_, Backbone, Mutators) { + /* Do stuff with Backbone here */ +}); +``` + +### CommonJS +```javascript +// CommonJS +var _ = require('underscore'); +var Backbone = require('backbone'); +var Mutators = require('backbone.mutators'); +``` + +### Vanilla JS +```html + + + + + +``` + +## Usage +Some lines of code explain more then thousand words... + +### Basic usage +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: function () { + return this.get('firstname') + ' ' + this.get('lastname'); + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); + + var user = new User(); + // use get to get the 'mutated' value + user.get('fullname') // 'Sugar Daddy' + // serialize the model and see the 'mutated' value in the resulting JSON + user.toJSON() // '{firstname: 'Sugar', lastname: 'Daddy', fullname: 'Sugar Daddy'}' +``` + +### Override getters +```javascript + var State = Backbone.Model.extend({ + // Define mutator properties + mutators: { + status: function () { + return this.get('status') === true ? 'Workish' : 'Bad bad error'; + } + }, + defaults: { + status: true + } + }); + + var state = new State(); + // use get to get the 'mutated' value + state.get('status') // 'Workish' + // serialize the model and see the 'mutated' value in the resulting JSON + state.toJSON() // '{status: 'Workish'}' +``` + +### Use setters +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: { + set: function (key, value, options, set) { + var names = value.split(' '); + this.set('firstname', names[0], options); + this.set('lastname', names[1], options); + }, + get: function () { + return this.get('firstname') + ' ' + this.get('lastname'); + } + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); + + var user = new User(); + // use get to get the 'mutated' value + user.set('fullname', 'Big Mama', {silent: true}); + // serialize the model and see the 'mutated' value in the resulting JSON + user.get('fullname') // 'Big Mama' + user.get('firstname'); // 'Big' + user.get('lastname'); // 'Mama' +``` + +### Catch model events +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: { + set: function (key, value, options, set) { + var names = value.split(' '); + this.set('firstname', names[0], options); + this.set('lastname', names[1], options); + }, + get: function () { + return this.get('firstname') + ' ' + this.get('lastname'); + } + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); + + var user = new User(); + + // bind mutator event + user.bind('mutators:set:fullname', function () { + console.log('Somebody sets a full name'); + }); + + // bind model events + user.bind('change:firstname', function () { + console.log('Somebody changed the first name'); + }); + + // bind model events + user.bind('change:lastname', function () { + console.log('Somebody changed the last name'); + }); + + // use get to get the 'mutated' value + user.set('fullname', 'Big Mama'); + + // serialize the model and see the 'mutated' value in the resulting JSON + user.get('fullname') // 'Big Mama' + user.get('firstname'); // 'Big' + user.get('lastname'); // 'Mama' +``` + +### Silence mutator events (while keeping the model events fired) +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: { + set: function (key, value, options, set) { + var names = value.split(' '); + this.set('firstname', names[0], options); + this.set('lastname', names[1], options); + }, + get: function () { + return this.get('firstname') + ' ' + this.get('lastname'); + } + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); + + var user = new User(); + + // bind mutator event + // will never be run + user.bind('mutators:set:fullname', function () { + console.log('Somebody sets a full name'); + }); + + // bind model events + // will still run + user.bind('change:firstname', function () { + console.log('Somebody changed the first name'); + }); + + // bind model events + // will still run + user.bind('change:lastname', function () { + console.log('Somebody changed the last name'); + }); + + // use get to get the 'mutated' value + user.set('fullname', 'Big Mama', {mutators: {silence: true}}); + + // serialize the model and see the 'mutated' value in the resulting JSON + user.get('fullname') // 'Big Mama' + user.get('firstname'); // 'Big' + user.get('lastname'); // 'Mama' +``` + +### Use mutated setters and call the original setter within +```javascript + var Spicy = Backbone.Model.extend({ + // Define mutator properties + mutators: { + iAcceptOnlyLowercaseStuff: { + set: function (key, value, options, set) { + // call the original setter with the lowercased value + set(key, value.toLowerCase(), options); + } + } + }, + defaults: { + iAcceptOnlyLowercaseStuff: 'sugar' + } + }); + + var spicy = new Spicy(); + // use get to get the 'mutated' value + spicy.set('iAcceptOnlyLowercaseStuff', 'SALT'); + spicy.get('iAcceptOnlyLowercaseStuff') // 'salt' +``` + +### Define one getter / setter method +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: function (key, value, options, set) { + if(key){ + var names = value.split(' '); + this.set('firstname', names[0], options); + this.set('lastname', names[1], options); + } + + return this.get('firstname') + ' ' + this.get('lastname'); + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); +``` + +### Define multiple mutators +```javascript + var User = Backbone.Model.extend({ + // Define mutator properties + mutators: { + fullname: { + set: function (key, value, options, set) { + var names = value.split(' '); + this.set('firstname', names[0], options); + this.set('lastname', names[1], options); + } + get: function () { + return this.get('firstname') + ' ' + this.get('lastname'); + } + }, + password: function () { + return md5(this.password); + } + }, + defaults: { + firstname: 'Sugar', + lastname: 'Daddy' + } + }); +``` + +### Define a getter as transient +Defining a getter as transient means that it will be omitted when Backbone saves the model. This is +useful if the backend system (whatever Backbone is syncing to) fails if you send it a property that does +not actually exist on the model. Note that this only works for mutators defined with a `get()` +function. + +In the example below, the `fullName` property will be available when toJSON is called under +non-syncing circumstances--for example, when providing this model to a template--but will be omitted +from the JSON when sync is called (because you called the `sync()` or `save()` method), and will not +be sent to the server. +```javascript +var Model = Backbone.Model.extend({ + defaults:{ + firstName:"Iain", + middleInit:"M", + lastName:"Banks" + }, + mutators:{ + fullName:{ + get: function() { + var fullName = this.get("firstName"); + fullName += " " + this.get("middleInit"); + fullName += ". " + this.get("lastName"); + return fullName; + }, + transient: true + } + } +}); +``` + +## Further reading +James Brown ([@ibjhb](https://github.com/ibjhb/Exploring-Backbone.Mutators)) +has written a blog article about Mutators ([Exploring Backbone.Mutators](http://ja.mesbrown.com/2012/03/exploring-backbone-mutators-plugin/)) + +## Changelog + +### 0.4.4 ++ Add mutator dependancies and change events [#33](https://github.com/asciidisco/Backbone.Mutators/commit/7c7626456991dd4de5419e7a5a0bd3e62090297f) + +### 0.4.3 ++ Made compatible with browserify [#32](https://github.com/asciidisco/Backbone.Mutators/commit/0d7f846727ae9b546bba203d4c4b7c2ff5704222) ++ Backbone dependency version bump [#32](https://github.com/asciidisco/Backbone.Mutators/commit/5c0db12e34aaa5f0fac39b76a9bf8c44e1bea641) + +### 0.4.2 ++ Fixes [#20](https://github.com/asciidisco/Backbone.Mutators/issues/20) + +### 0.4.1 ++ Fixes [#22](https://github.com/asciidisco/Backbone.Mutators/pull/22) ++ Fixes [#24](https://github.com/asciidisco/Backbone.Mutators/pull/24) + +### 0.3.1 ++ Change get context to modal instead of attributes ++ Added single getter / setter method + +### 0.3.1 ++ Change get context to modal instead of attributes ++ Added single getter / setter method + +### 0.3.0 ++ Removed the Cake based build process and moved to grunt ++ Mutators now integrates itself to backbone, no more manual extending needed ++ Added the {mutator: {silent: true}} option to prevent mutator set events from firering ++ Added unit tests for the new features ++ Moved from jslint to jshint ++ Tweaked docs ++ Removed not needed jquery and qunit-logging submodule / npm dependencies + +### 0.2.0 ++ Added the original Backbone.Model.set function as a fourth paramter for the mutated set ++ Added a 'mutators:set:{{YOUR_MUTATOR_PROPERTY}}' event when setting mutated properties ++ Added unit tests for the new features ++ Extended/fixed documentation ++ Added inline version tag [NOTE: Version 0.2.0 is fully backwards compatible] + +### 0.1.0 ++ Initial Release diff --git a/front-api/node_modules/Backbone.Mutators/backbone.mutators.js b/front-api/node_modules/Backbone.Mutators/backbone.mutators.js new file mode 100644 index 0000000..6447304 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/backbone.mutators.js @@ -0,0 +1,207 @@ +/*! Backbone.Mutators - v0.4.4 +------------------------------ +Build @ 2014-12-02 +Documentation and Full License Available at: +http://asciidisco.github.com/Backbone.Mutators/index.html +git://github.com/asciidisco/Backbone.Mutators.git +Copyright (c) 2014 Sebastian Golasch + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the + +Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE.*/ +(function (root, factory, undef) { + 'use strict'; + + if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(require('underscore'), require('backbone')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['underscore', 'backbone'], function (_, Backbone) { + // Check if we use the AMD branch of Back + _ = _ === undef ? root._ : _; + Backbone = Backbone === undef ? root.Backbone : Backbone; + return (root.returnExportsGlobal = factory(_, Backbone, root)); + }); + } else { + // Browser globals + root.returnExportsGlobal = factory(root._, root.Backbone); + } + +// Usage: +// +// Note: This plugin is UMD compatible, you can use it in node, amd and vanilla js envs +// +// Vanilla JS: +// +// +// +// +// Node: +// var _ = require('underscore'); +// var Backbone = require('backbone'); +// var Mutators = require('backbone.mutators'); +// +// +// AMD: +// define(['underscore', 'backbone', 'backbone.mutators'], function (_, Backbone, Mutators) { +// // insert sample from below +// return User; +// }); +// +// var User = Backbone.Model.extend({ +// mutators: { +// fullname: function () { +// return this.firstname + ' ' + this.lastname; +// } +// }, +// +// defaults: { +// firstname: 'Sebastian', +// lastname: 'Golasch' +// } +// }); +// +// var user = new User(); +// user.get('fullname') // returns 'Sebastian Golasch' +// user.toJSON() // return '{firstname: 'Sebastian', lastname: 'Golasch', fullname: 'Sebastian Golasch'}' + +}(this, function (_, Backbone, root, undef) { + 'use strict'; + + // check if we use the amd branch of backbone and underscore + Backbone = Backbone === undef ? root.Backbone : Backbone; + _ = _ === undef ? root._ : _; + + // extend backbones model prototype with the mutator functionality + var Mutator = function () {}, + oldGet = Backbone.Model.prototype.get, + oldSet = Backbone.Model.prototype.set, + oldToJson = Backbone.Model.prototype.toJSON; + + // This is necessary to ensure that Models declared without the mutators object do not throw and error + Mutator.prototype.mutators = {}; + + // override get functionality to fetch the mutator props + Mutator.prototype.get = function (attr) { + var isMutator = this.mutators !== undef; + + // check if we have a getter mutation + if (isMutator === true && _.isFunction(this.mutators[attr]) === true) { + return this.mutators[attr].call(this); + } + + // check if we have a deeper nested getter mutation + if (isMutator === true && _.isObject(this.mutators[attr]) === true && _.isFunction(this.mutators[attr].get) === true) { + return this.mutators[attr].get.call(this); + } + + return oldGet.call(this, attr); + }; + + // override set functionality to set the mutator props + Mutator.prototype.set = function (key, value, options) { + var isMutator = this.mutators !== undef, + ret = null, + attrs = null; + + ret = oldSet.call(this, key, value, options); + + // seamleassly stolen from backbone core + // check if the setter action is triggered + // using key <-> value or object + if (_.isObject(key) || key === null) { + attrs = key; + options = value; + } else { + attrs = {}; + attrs[key] = value; + } + + // check if we have a deeper nested setter mutation + if (isMutator === true && _.isObject(this.mutators[key]) === true) { + + // check if we need to set a single value + if (_.isFunction(this.mutators[key].set) === true) { + ret = this.mutators[key].set.call(this, key, attrs[key], options, _.bind(oldSet, this)); + } else if(_.isFunction(this.mutators[key])){ + ret = this.mutators[key].call(this, key, attrs[key], options, _.bind(oldSet, this)); + } + } + + if (isMutator === true && _.isObject(attrs)) { + _.each(attrs, _.bind(function (attr, attrKey) { + if (_.isObject(this.mutators[attrKey]) === true) { + // check if we need to set a single value + + var meth = this.mutators[attrKey]; + if(_.isFunction(meth.set)){ + meth = meth.set; + } + + if(_.isFunction(meth)){ + if (options === undef || (_.isObject(options) === true && options.silent !== true && (options.mutators !== undef && options.mutators.silent !== true))) { + this.trigger('mutators:set:' + attrKey); + } + meth.call(this, attrKey, attr, options, _.bind(oldSet, this)); + } + + } + }, this)); + } + + return ret; + }; + + // override toJSON functionality to serialize mutator properties + Mutator.prototype.toJSON = function (options) { + // fetch ye olde values + var attr = oldToJson.call(this), + isSaving, + isTransient; + // iterate over all mutators (if there are some) + _.each(this.mutators, _.bind(function (mutator, name) { + // check if we have some getter mutations + if (_.isObject(this.mutators[name]) === true && _.isFunction(this.mutators[name].get)) { + isSaving = _.has(options || {}, 'emulateHTTP'); + isTransient = this.mutators[name].transient; + if (!isSaving || !isTransient) { + attr[name] = _.bind(this.mutators[name].get, this)(); + } + } else if (_.isFunction(this.mutators[name])) { + attr[name] = _.bind(this.mutators[name], this)(); + } + }, this)); + + return attr; + }; + + // override get functionality to get HTML-escaped the mutator props + Mutator.prototype.escape = function (attr){ + var val = this.get(attr); + return _.escape(val == null ? '' : '' + val); + }; + + // extend the models prototype + _.extend(Backbone.Model.prototype, Mutator.prototype); + + // make mutators globally available under the Backbone namespace + Backbone.Mutators = Mutator; + return Mutator; +})); diff --git a/front-api/node_modules/Backbone.Mutators/backbone.mutators.min.js b/front-api/node_modules/Backbone.Mutators/backbone.mutators.min.js new file mode 100644 index 0000000..442e34b --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/backbone.mutators.min.js @@ -0,0 +1,26 @@ +/*! Backbone.Mutators - v0.4.4 +------------------------------ +Build @ 2014-12-02 +Documentation and Full License Available at: +http://asciidisco.github.com/Backbone.Mutators/index.html +git://github.com/asciidisco/Backbone.Mutators.git +Copyright (c) 2014 Sebastian Golasch + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the + +Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE.*/ +!function(a,b,c){"use strict";"object"==typeof exports?module.exports=b(require("underscore"),require("backbone")):"function"==typeof define&&define.amd?define(["underscore","backbone"],function(d,e){return d=d===c?a._:d,e=e===c?a.Backbone:e,a.returnExportsGlobal=b(d,e,a)}):a.returnExportsGlobal=b(a._,a.Backbone)}(this,function(a,b,c,d){"use strict";b=b===d?c.Backbone:b,a=a===d?c._:a;var e=function(){},f=b.Model.prototype.get,g=b.Model.prototype.set,h=b.Model.prototype.toJSON;return e.prototype.mutators={},e.prototype.get=function(b){var c=this.mutators!==d;return c===!0&&a.isFunction(this.mutators[b])===!0?this.mutators[b].call(this):c===!0&&a.isObject(this.mutators[b])===!0&&a.isFunction(this.mutators[b].get)===!0?this.mutators[b].get.call(this):f.call(this,b)},e.prototype.set=function(b,c,e){var f=this.mutators!==d,h=null,i=null;return h=g.call(this,b,c,e),a.isObject(b)||null===b?(i=b,e=c):(i={},i[b]=c),f===!0&&a.isObject(this.mutators[b])===!0&&(a.isFunction(this.mutators[b].set)===!0?h=this.mutators[b].set.call(this,b,i[b],e,a.bind(g,this)):a.isFunction(this.mutators[b])&&(h=this.mutators[b].call(this,b,i[b],e,a.bind(g,this)))),f===!0&&a.isObject(i)&&a.each(i,a.bind(function(b,c){if(a.isObject(this.mutators[c])===!0){var f=this.mutators[c];a.isFunction(f.set)&&(f=f.set),a.isFunction(f)&&((e===d||a.isObject(e)===!0&&e.silent!==!0&&e.mutators!==d&&e.mutators.silent!==!0)&&this.trigger("mutators:set:"+c),f.call(this,c,b,e,a.bind(g,this)))}},this)),h},e.prototype.toJSON=function(b){var c,d,e=h.call(this);return a.each(this.mutators,a.bind(function(f,g){a.isObject(this.mutators[g])===!0&&a.isFunction(this.mutators[g].get)?(c=a.has(b||{},"emulateHTTP"),d=this.mutators[g].transient,c&&d||(e[g]=a.bind(this.mutators[g].get,this)())):a.isFunction(this.mutators[g])&&(e[g]=a.bind(this.mutators[g],this)())},this)),e},e.prototype.escape=function(b){var c=this.get(b);return a.escape(null==c?"":""+c)},a.extend(b.Model.prototype,e.prototype),b.Mutators=e,e}); \ No newline at end of file diff --git a/front-api/node_modules/Backbone.Mutators/bower.json b/front-api/node_modules/Backbone.Mutators/bower.json new file mode 100644 index 0000000..aa9cde5 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower.json @@ -0,0 +1,9 @@ +{ + "name": "Backbone.Mutators", + "version": "0.4.4", + "main": "backbone.mutators.js", + "dependencies": { + "underscore": ">=1.4.4", + "backbone": ">=1.0.0" + } +} diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.bower.json b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.bower.json new file mode 100644 index 0000000..065bb1f --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.bower.json @@ -0,0 +1,28 @@ +{ + "name": "backbone", + "version": "1.1.2", + "main": "backbone.js", + "dependencies": { + "underscore": ">=1.5.0" + }, + "ignore": [ + "backbone-min.js", + "docs", + "examples", + "test", + "*.yml", + "*.map", + ".html", + "*.ico" + ], + "homepage": "https://github.com/jashkenas/backbone", + "_release": "1.1.2", + "_resolution": { + "type": "version", + "tag": "1.1.2", + "commit": "53f77901a4ea9c7cf75d3db93ddddf491998d90f" + }, + "_source": "git://github.com/jashkenas/backbone.git", + "_target": ">=1.0.0", + "_originalSource": "backbone" +} \ No newline at end of file diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.npmignore b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.npmignore new file mode 100644 index 0000000..6bcb69f --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/.npmignore @@ -0,0 +1,7 @@ +test/ +Rakefile +docs/ +raw/ +examples/ +index.html +.jshintrc diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CNAME b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CNAME new file mode 100644 index 0000000..dfadb79 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CNAME @@ -0,0 +1,2 @@ +backbonejs.org + diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CONTRIBUTING.md b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CONTRIBUTING.md new file mode 100644 index 0000000..5540361 --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/CONTRIBUTING.md @@ -0,0 +1,22 @@ +## How to Open a Backbone.js Ticket + +* Do not use tickets to ask for help with (debugging) your application. Ask on +the [mailing list](https://groups.google.com/forum/#!forum/backbonejs), +in the IRC channel (`#documentcloud` on Freenode), or if you understand your +specific problem, on [StackOverflow](http://stackoverflow.com/questions/tagged/backbone.js). + +* Before you open a ticket or send a pull request, +[search](https://github.com/jashkenas/backbone/issues) for previous +discussions about the same feature or issue. Add to the earlier ticket if you +find one. + +* Before sending a pull request for a feature or bug fix, be sure to have +[tests](http://backbonejs.org/test/). + +* Use the same coding style as the rest of the +[codebase](https://github.com/jashkenas/backbone/blob/master/backbone.js). + +* In your pull request, do not add documentation or rebuild the minified +`backbone-min.js` file. We'll do that before cutting a new release. + +* All pull requests should be made to the `master` branch. diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/LICENSE b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/LICENSE new file mode 100644 index 0000000..3ffd97d --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010-2014 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/README.md b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/README.md new file mode 100644 index 0000000..7b0482d --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/README.md @@ -0,0 +1,29 @@ + ____ __ __ + /\ _`\ /\ \ /\ \ __ + \ \ \ \ \ __ ___\ \ \/'\\ \ \____ ___ ___ __ /\_\ ____ + \ \ _ <' /'__`\ /'___\ \ , < \ \ '__`\ / __`\ /' _ `\ /'__`\ \/\ \ /',__\ + \ \ \ \ \/\ \ \.\_/\ \__/\ \ \\`\\ \ \ \ \/\ \ \ \/\ \/\ \/\ __/ __ \ \ \/\__, `\ + \ \____/\ \__/.\_\ \____\\ \_\ \_\ \_,__/\ \____/\ \_\ \_\ \____\/\_\_\ \ \/\____/ + \/___/ \/__/\/_/\/____/ \/_/\/_/\/___/ \/___/ \/_/\/_/\/____/\/_/\ \_\ \/___/ + \ \____/ + \/___/ + (_'_______________________________________________________________________________'_) + (_.———————————————————————————————————————————————————————————————————————————————._) + + +Backbone supplies structure to JavaScript-heavy applications by providing models key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface. + +For Docs, License, Tests, pre-packed downloads, and everything else, really, see: +http://backbonejs.org + +To suggest a feature, report a bug, or general discussion: +http://github.com/jashkenas/backbone/issues + +Backbone is an open-sourced component of DocumentCloud: +https://github.com/documentcloud + +Many thanks to our contributors: +http://github.com/jashkenas/backbone/contributors + +Special thanks to Robert Kieffer for the original philosophy behind Backbone. +http://github.com/broofa diff --git a/front-api/node_modules/Backbone.Mutators/bower_components/backbone/backbone.js b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/backbone.js new file mode 100644 index 0000000..24a550a --- /dev/null +++ b/front-api/node_modules/Backbone.Mutators/bower_components/backbone/backbone.js @@ -0,0 +1,1608 @@ +// Backbone.js 1.1.2 + +// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(root, factory) { + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'); + factory(root, exports, _); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); + } + +}(this, function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create local references to array methods we'll want to use later. + var array = []; + var push = array.push; + var slice = array.slice; + var splice = array.splice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.1.2'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // custom events. You may bind with `on` or remove with `off` callback + // functions to an event; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = { + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + on: function(name, callback, context) { + if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; + this._events || (this._events = {}); + var events = this._events[name] || (this._events[name] = []); + events.push({callback: callback, context: context, ctx: context || this}); + return this; + }, + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, it will be removed. + once: function(name, callback, context) { + if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; + var self = this; + var once = _.once(function() { + self.off(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + return this.on(name, once, context); + }, + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + off: function(name, callback, context) { + var retain, ev, events, names, i, l, j, k; + if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; + if (!name && !callback && !context) { + this._events = void 0; + return this; + } + names = name ? [name] : _.keys(this._events); + for (i = 0, l = names.length; i < l; i++) { + name = names[i]; + if (events = this._events[name]) { + this._events[name] = retain = []; + if (callback || context) { + for (j = 0, k = events.length; j < k; j++) { + ev = events[j]; + if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || + (context && context !== ev.context)) { + retain.push(ev); + } + } + } + if (!retain.length) delete this._events[name]; + } + } + + return this; + }, + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + trigger: function(name) { + if (!this._events) return this; + var args = slice.call(arguments, 1); + if (!eventsApi(this, 'trigger', name, args)) return this; + var events = this._events[name]; + var allEvents = this._events.all; + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, arguments); + return this; + }, + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + stopListening: function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + var remove = !name && !callback; + if (!callback && typeof name === 'object') callback = this; + if (obj) (listeningTo = {})[obj._listenId] = obj; + for (var id in listeningTo) { + obj = listeningTo[id]; + obj.off(name, callback, this); + if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; + } + return this; + } + + }; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Implement fancy features of the Events API such as multiple event + // names `"change blur"` and jQuery-style event maps `{change: action}` + // in terms of the existing API. + var eventsApi = function(obj, action, name, rest) { + if (!name) return true; + + // Handle event maps. + if (typeof name === 'object') { + for (var key in name) { + obj[action].apply(obj, [key, name[key]].concat(rest)); + } + return false; + } + + // Handle space separated event names. + if (eventSplitter.test(name)) { + var names = name.split(eventSplitter); + for (var i = 0, l = names.length; i < l; i++) { + obj[action].apply(obj, [names[i]].concat(rest)); + } + return false; + } + + return true; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; + + // Inversion-of-control versions of `on` and `once`. Tell *this* object to + // listen to an event in another object ... keeping track of what it's + // listening to. + _.each(listenMethods, function(implementation, method) { + Events[method] = function(obj, name, callback) { + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + listeningTo[id] = obj; + if (!callback && typeof name === 'object') callback = this; + obj[implementation](name, callback, this); + return this; + }; + }); + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.cid = _.uniqueId('c'); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + attrs = _.defaults({}, attrs, _.result(this, 'defaults')); + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + var attr, attrs, unset, changes, silent, changing, prev, current; + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + current = this.attributes, prev = this._previousAttributes; + + // Check for changes of `id`. + if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; + + // For each `set` attribute, update or delete the current value. + for (attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + this.changed[attr] = val; + } else { + delete this.changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0, l = changes.length; i < l; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var val, changed = false; + var old = this._changing ? this._previousAttributes : this.attributes; + for (var attr in diff) { + if (_.isEqual(old[attr], (val = diff[attr]))) continue; + (changed || (changed = {}))[attr] = val; + } + return changed; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Fetch the model from the server. If the server's representation of the + // model differs from its current attributes, they will be overridden, + // triggering a `"change"` event. + fetch: function(options) { + options = options ? _.clone(options) : {}; + if (options.parse === void 0) options.parse = true; + var model = this; + var success = options.success; + options.success = function(resp) { + if (!model.set(model.parse(resp, options), options)) return false; + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, val, options) { + var attrs, method, xhr, attributes = this.attributes; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true}, options); + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !options.wait) { + if (!this.set(attrs, options)) return false; + } else { + if (!this._validate(attrs, options)) return false; + } + + // Set temporary attributes if `{wait: true}`. + if (attrs && options.wait) { + this.attributes = _.extend({}, attributes, attrs); + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + if (options.parse === void 0) options.parse = true; + var model = this; + var success = options.success; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = model.parse(resp, options); + if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); + if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { + return false; + } + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch') options.attrs = attrs; + xhr = this.sync(method, this, options); + + // Restore attributes. + if (attrs && options.wait) this.attributes = attributes; + + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + + var destroy = function() { + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (options.wait || model.isNew()) destroy(); + if (success) success(model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + if (this.isNew()) { + options.success(); + return false; + } + wrapError(this, options); + + var xhr = this.sync('delete', this, options); + if (!options.wait) destroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, options) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return !this.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend(options || {}, { validate: true })); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Underscore methods that we want to implement on the Model. + var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + _.each(modelMethods, function(method) { + Model.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.attributes); + return _[method].apply(_, args); + }; + }); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analagous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + if (options.model) this.model = options.model; + if (options.comparator !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model){ return model.toJSON(options); }); + }, + + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Add a model, or list of models to the set. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + var singular = !_.isArray(models); + models = singular ? [models] : _.clone(models); + options || (options = {}); + var i, l, index, model; + for (i = 0, l = models.length; i < l; i++) { + model = models[i] = this.get(models[i]); + if (!model) continue; + delete this._byId[model.id]; + delete this._byId[model.cid]; + index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + this._removeReference(model, options); + } + return singular ? models[0] : models; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + options = _.defaults({}, options, setOptions); + if (options.parse) models = this.parse(models, options); + var singular = !_.isArray(models); + models = singular ? (models ? [models] : []) : _.clone(models); + var i, l, id, model, attrs, existing, sort; + var at = options.at; + var targetModel = this.model; + var sortable = this.comparator && (at == null) && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + var toAdd = [], toRemove = [], modelMap = {}; + var add = options.add, merge = options.merge, remove = options.remove; + var order = !sortable && add && remove ? [] : false; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + for (i = 0, l = models.length; i < l; i++) { + attrs = models[i] || {}; + if (attrs instanceof Model) { + id = model = attrs; + } else { + id = attrs[targetModel.prototype.idAttribute || 'id']; + } + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + if (existing = this.get(id)) { + if (remove) modelMap[existing.cid] = true; + if (merge) { + attrs = attrs === model ? model.attributes : attrs; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(attrs, options); + if (!model) continue; + toAdd.push(model); + this._addReference(model, options); + } + + // Do not add multiple models with the same `id`. + model = existing || model; + if (order && (model.isNew() || !modelMap[model.id])) order.push(model); + modelMap[model.id] = true; + } + + // Remove nonexistent models if appropriate. + if (remove) { + for (i = 0, l = this.length; i < l; ++i) { + if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); + } + if (toRemove.length) this.remove(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + if (toAdd.length || (order && order.length)) { + if (sortable) sort = true; + this.length += toAdd.length; + if (at != null) { + for (i = 0, l = toAdd.length; i < l; i++) { + this.models.splice(at + i, 0, toAdd[i]); + } + } else { + if (order) this.models.length = 0; + var orderedModels = order || toAdd; + for (i = 0, l = orderedModels.length; i < l; i++) { + this.models.push(orderedModels[i]); + } + } + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort events. + if (!options.silent) { + for (i = 0, l = toAdd.length; i < l; i++) { + (model = toAdd[i]).trigger('add', model, this, options); + } + if (sort || (order && order.length)) this.trigger('sort', this, options); + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options || (options = {}); + for (var i = 0, l = this.models.length; i < l; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + this.remove(model, options); + return model; + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + this.remove(model, options); + return model; + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid]; + }, + + // Get the model at the given index. + at: function(index) { + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return this[first ? 'find' : 'filter'](function(model) { + for (var key in attrs) { + if (attrs[key] !== model.get(key)) return false; + } + return true; + }); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + // Run sort based on type of `comparator`. + if (_.isString(this.comparator) || this.comparator.length === 1) { + this.models = this.sortBy(this.comparator, this); + } else { + this.models.sort(_.bind(this.comparator, this)); + } + + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return _.invoke(this.models, 'get', attr); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = options ? _.clone(options) : {}; + if (options.parse === void 0) options.parse = true; + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success(collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + options = options ? _.clone(options) : {}; + if (!(model = this._prepareModel(model, options))) return false; + if (!options.wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(model, resp) { + if (options.wait) collection.add(model, options); + if (success) success(model, resp, options); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models); + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (attrs instanceof Model) return attrs; + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + if (model.id != null) this._byId[model.id] = model; + if (!model.collection) model.collection = this; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + if (this === model.collection) delete model.collection; + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (model && event === 'change:' + model.idAttribute) { + delete this._byId[model.previous(model.idAttribute)]; + if (model.id != null) this._byId[model.id] = model; + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', + 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', + 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', + 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', + 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', + 'lastIndexOf', 'isEmpty', 'chain', 'sample']; + + // Mix in each Underscore method as a proxy to `Collection#models`. + _.each(methods, function(method) { + Collection.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.models); + return _[method].apply(_, args); + }; + }); + + // Underscore methods that take a property name as an argument. + var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; + + // Use attributes instead of properties. + _.each(attributeMethods, function(method) { + Collection.prototype[method] = function(value, context) { + var iterator = _.isFunction(value) ? value : function(model) { + return model.get(value); + }; + return _[method](this.models, iterator, context); + }; + }); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be preferred to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this.$el.remove(); + this.stopListening(); + return this; + }, + + // Change the view's element (`this.el` property), including event + // re-delegation. + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); + this.setElement($el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // If we're sending a `PATCH` request, and we're in an old Internet Explorer + // that still has ActiveX enabled by default, override jQuery to use that + // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. + if (params.type === 'PATCH' && noXhrPatch) { + params.xhr = function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }; + } + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + var noXhrPatch = + typeof window !== 'undefined' && !!window.ActiveXObject && + !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + // Backbone.Router + // --------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + router.execute(callback, args); + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + _.bindAll(this, 'checkUrl'); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for detecting MSIE. + var isExplorer = /msie [\w.]+/; + + // Cached regex for removing a trailing slash. + var trailingSlash = /\/$/; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Are we at the app root? + atRoot: function() { + return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root; + }, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the cross-browser normalized URL fragment, either from the URL, + // the hash, or the override. + getFragment: function(fragment, forcePushState) { + if (fragment == null) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = decodeURI(this.location.pathname + this.location.search); + var root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); + } else { + fragment = this.getHash(); + } + } + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error("Backbone.history has already been started"); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var fragment = this.getFragment(); + var docMode = document.documentMode; + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + if (oldIE && this._wantsHashChange) { + var frame = Backbone.$('