diff --git a/labs/dependency-examples/chaplin-brunch/.editorconfig b/labs/dependency-examples/chaplin-brunch/.editorconfig
new file mode 100644
index 0000000000..f4eb7985f7
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/.editorconfig
@@ -0,0 +1,6 @@
+# editorconfig.org
+root = true
+
+[*.coffee]
+indent_style = space
+indent_size = 2
diff --git a/labs/dependency-examples/chaplin-brunch/.gitignore b/labs/dependency-examples/chaplin-brunch/.gitignore
new file mode 100644
index 0000000000..ff16d27ce5
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/.gitignore
@@ -0,0 +1,5 @@
+# NPM packages folder.
+node_modules/
+
+# Brunch folder for temporary files.
+tmp/
diff --git a/labs/dependency-examples/chaplin-brunch/README.md b/labs/dependency-examples/chaplin-brunch/README.md
new file mode 100644
index 0000000000..9696fc5e65
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/README.md
@@ -0,0 +1,15 @@
+# Brunch with Chaplin TODOMVC
+Brunch with Chaplin is a skeleton (boilerplate) for [Brunch](http://brunch.io)
+based on [Chaplin](https://github.com/chaplinjs/chaplin) framework.
+
+The application is based on the skeleton.
+
+## Getting started
+* Install [Brunch](http://brunch.io) if you hadn’t already (`npm install -g brunch`).
+* Execute `npm install` in the root directory once.
+* Execute `brunch build` in the root directory to build app every time. That’s all.
+* Execute `brunch watch` if you want to continiously rebuild the app
+on every change. To run the app then, you will need to open `public/index.html` in your browser (assuming the root is `/todomvc/` root or so).
+
+## Author
+The stuff was made by [@paulmillr](http://paulmillr.com).
diff --git a/labs/dependency-examples/chaplin-brunch/app/application.coffee b/labs/dependency-examples/chaplin-brunch/app/application.coffee
new file mode 100644
index 0000000000..4743eee66a
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/application.coffee
@@ -0,0 +1,67 @@
+Chaplin = require 'chaplin'
+mediator = require 'mediator'
+routes = require 'routes'
+HeaderController = require 'controllers/header-controller'
+FooterController = require 'controllers/footer-controller'
+TodosController = require 'controllers/todos-controller'
+Todos = require 'models/todos'
+Layout = require 'views/layout'
+
+# The application object
+module.exports = class Application extends Chaplin.Application
+ # Set your application name here so the document title is set to
+ # “Controller title – Site title” (see Layout#adjustTitle)
+ title: 'Chaplin • TodoMVC'
+
+ initialize: ->
+ super
+
+ # Initialize core components
+ @initDispatcher controllerSuffix: '-controller'
+ @initLayout()
+ @initMediator()
+
+ # Application-specific scaffold
+ @initControllers()
+
+ # Register all routes and start routing
+ @initRouter routes, pushState: no
+ # You might pass Router/History options as the second parameter.
+ # Chaplin enables pushState per default and Backbone uses / as
+ # the root per default. You might change that in the options
+ # if necessary:
+ # @initRouter routes, pushState: false, root: '/subdir/'
+
+ # Freeze the application instance to prevent further changes
+ Object.freeze? this
+
+ # Override standard layout initializer
+ # ------------------------------------
+ initLayout: ->
+ # Use an application-specific Layout class. Currently this adds
+ # no features to the standard Chaplin Layout, it’s an empty placeholder.
+ @layout = new Layout {@title}
+
+ # Instantiate common controllers
+ # ------------------------------
+ initControllers: ->
+ # These controllers are active during the whole application runtime.
+ # You don’t need to instantiate all controllers here, only special
+ # controllers which do not to respond to routes. They may govern models
+ # and views which are needed the whole time, for example header, footer
+ # or navigation views.
+ # e.g. new NavigationController()
+ new HeaderController()
+ new FooterController()
+ new TodosController()
+
+ # Create additional mediator properties
+ # -------------------------------------
+ initMediator: ->
+ # Create a user property
+ mediator.user = null
+ # Add additional application-specific properties and methods
+ mediator.todos = new Todos()
+ mediator.todos.fetch()
+ # Seal the mediator
+ mediator.seal()
diff --git a/labs/dependency-examples/chaplin-brunch/app/assets/index.html b/labs/dependency-examples/chaplin-brunch/app/assets/index.html
new file mode 100644
index 0000000000..697f80096e
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/assets/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Chaplin • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/labs/dependency-examples/chaplin-brunch/app/controllers/base/controller.coffee b/labs/dependency-examples/chaplin-brunch/app/controllers/base/controller.coffee
new file mode 100644
index 0000000000..bb7b74bdbc
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/controllers/base/controller.coffee
@@ -0,0 +1,3 @@
+Chaplin = require 'chaplin'
+
+module.exports = class Controller extends Chaplin.Controller
diff --git a/labs/dependency-examples/chaplin-brunch/app/controllers/footer-controller.coffee b/labs/dependency-examples/chaplin-brunch/app/controllers/footer-controller.coffee
new file mode 100644
index 0000000000..d8f67d2619
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/controllers/footer-controller.coffee
@@ -0,0 +1,8 @@
+Controller = require 'controllers/base/controller'
+FooterView = require 'views/footer-view'
+mediator = require 'mediator'
+
+module.exports = class FooterController extends Controller
+ initialize: ->
+ super
+ @view = new FooterView collection: mediator.todos
diff --git a/labs/dependency-examples/chaplin-brunch/app/controllers/header-controller.coffee b/labs/dependency-examples/chaplin-brunch/app/controllers/header-controller.coffee
new file mode 100644
index 0000000000..c685410d07
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/controllers/header-controller.coffee
@@ -0,0 +1,8 @@
+Controller = require 'controllers/base/controller'
+HeaderView = require 'views/header-view'
+mediator = require 'mediator'
+
+module.exports = class HeaderController extends Controller
+ initialize: ->
+ super
+ @view = new HeaderView collection: mediator.todos
diff --git a/labs/dependency-examples/chaplin-brunch/app/controllers/index-controller.coffee b/labs/dependency-examples/chaplin-brunch/app/controllers/index-controller.coffee
new file mode 100644
index 0000000000..849d7d1947
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/controllers/index-controller.coffee
@@ -0,0 +1,7 @@
+Controller = require 'controllers/base/controller'
+
+module.exports = class IndexController extends Controller
+ title: 'Todo list'
+
+ list: (options) ->
+ @publishEvent 'todos:filter', options.filterer?.trim() ? 'all'
diff --git a/labs/dependency-examples/chaplin-brunch/app/controllers/todos-controller.coffee b/labs/dependency-examples/chaplin-brunch/app/controllers/todos-controller.coffee
new file mode 100644
index 0000000000..76478750e9
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/controllers/todos-controller.coffee
@@ -0,0 +1,8 @@
+Controller = require 'controllers/base/controller'
+TodosView = require 'views/todos-view'
+mediator = require 'mediator'
+
+module.exports = class TodosController extends Controller
+ initialize: ->
+ super
+ @view = new TodosView collection: mediator.todos
diff --git a/labs/dependency-examples/chaplin-brunch/app/initialize.coffee b/labs/dependency-examples/chaplin-brunch/app/initialize.coffee
new file mode 100644
index 0000000000..2968445812
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/initialize.coffee
@@ -0,0 +1,6 @@
+Application = require 'application'
+
+# Initialize the application on DOM ready event.
+$ ->
+ app = new Application()
+ app.initialize()
diff --git a/labs/dependency-examples/chaplin-brunch/app/lib/support.coffee b/labs/dependency-examples/chaplin-brunch/app/lib/support.coffee
new file mode 100644
index 0000000000..d3098599f3
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/lib/support.coffee
@@ -0,0 +1,13 @@
+Chaplin = require 'chaplin'
+utils = require 'lib/utils'
+
+# Application-specific feature detection
+# --------------------------------------
+
+# Delegate to Chaplin’s support module
+support = utils.beget Chaplin.support
+
+# _(support).extend
+ # someMethod: ->
+
+module.exports = support
diff --git a/labs/dependency-examples/chaplin-brunch/app/lib/utils.coffee b/labs/dependency-examples/chaplin-brunch/app/lib/utils.coffee
new file mode 100644
index 0000000000..d797c9feec
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/lib/utils.coffee
@@ -0,0 +1,12 @@
+Chaplin = require 'chaplin'
+
+# Application-specific utilities
+# ------------------------------
+
+# Delegate to Chaplin’s utils module
+utils = Chaplin.utils.beget Chaplin.utils
+
+# _(utils).extend
+# someMethod: ->
+
+module.exports = utils
diff --git a/labs/dependency-examples/chaplin-brunch/app/lib/view-helper.coffee b/labs/dependency-examples/chaplin-brunch/app/lib/view-helper.coffee
new file mode 100644
index 0000000000..7e5fcaad6d
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/lib/view-helper.coffee
@@ -0,0 +1,39 @@
+mediator = require 'mediator'
+utils = require 'chaplin/lib/utils'
+
+# Application-specific view helpers
+# ---------------------------------
+
+# http://handlebarsjs.com/#helpers
+
+# Conditional evaluation
+# ----------------------
+
+# Choose block by user login status
+Handlebars.registerHelper 'if_logged_in', (options) ->
+ if mediator.user
+ options.fn(this)
+ else
+ options.inverse(this)
+
+# Map helpers
+# -----------
+
+# Make 'with' behave a little more mustachey
+Handlebars.registerHelper 'with', (context, options) ->
+ if not context or Handlebars.Utils.isEmpty context
+ options.inverse(this)
+ else
+ options.fn(context)
+
+# Inverse for 'with'
+Handlebars.registerHelper 'without', (context, options) ->
+ inverse = options.inverse
+ options.inverse = options.fn
+ options.fn = inverse
+ Handlebars.helpers.with.call(this, context, options)
+
+# Evaluate block with context being current user
+Handlebars.registerHelper 'with_user', (options) ->
+ context = mediator.user?.serialize() or {}
+ Handlebars.helpers.with.call(this, context, options)
diff --git a/labs/dependency-examples/chaplin-brunch/app/mediator.coffee b/labs/dependency-examples/chaplin-brunch/app/mediator.coffee
new file mode 100644
index 0000000000..cae2e1b4d3
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/mediator.coffee
@@ -0,0 +1 @@
+module.exports = require('chaplin').mediator
diff --git a/labs/dependency-examples/chaplin-brunch/app/models/base/collection.coffee b/labs/dependency-examples/chaplin-brunch/app/models/base/collection.coffee
new file mode 100644
index 0000000000..e52c3cdffa
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/models/base/collection.coffee
@@ -0,0 +1,9 @@
+Chaplin = require 'chaplin'
+Model = require 'models/base/model'
+
+module.exports = class Collection extends Chaplin.Collection
+ # Use the project base model per default, not Chaplin.Model
+ model: Model
+
+ # Mixin a synchronization state machine
+ # _(@prototype).extend Chaplin.SyncMachine
diff --git a/labs/dependency-examples/chaplin-brunch/app/models/base/model.coffee b/labs/dependency-examples/chaplin-brunch/app/models/base/model.coffee
new file mode 100644
index 0000000000..0b4aa31242
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/models/base/model.coffee
@@ -0,0 +1,5 @@
+Chaplin = require 'chaplin'
+
+module.exports = class Model extends Chaplin.Model
+ # Mixin a synchronization state machine
+ # _(@prototype).extend Chaplin.SyncMachine
diff --git a/labs/dependency-examples/chaplin-brunch/app/models/todo.coffee b/labs/dependency-examples/chaplin-brunch/app/models/todo.coffee
new file mode 100644
index 0000000000..026c1aa8f7
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/models/todo.coffee
@@ -0,0 +1,16 @@
+Model = require 'models/base/model'
+
+module.exports = class Todo extends Model
+ defaults:
+ title: ''
+ completed: no
+
+ initialize: ->
+ super
+ @set 'created', Date.now() if @isNew()
+
+ toggle: ->
+ @set completed: not @get('completed')
+
+ isVisible: ->
+ isCompleted = @get('completed')
diff --git a/labs/dependency-examples/chaplin-brunch/app/models/todos.coffee b/labs/dependency-examples/chaplin-brunch/app/models/todos.coffee
new file mode 100644
index 0000000000..449c27c10e
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/models/todos.coffee
@@ -0,0 +1,18 @@
+Collection = require 'models/base/collection'
+Todo = require 'models/todo'
+
+module.exports = class Todos extends Collection
+ model: Todo
+ localStorage: new Store 'todos-chaplin'
+
+ allAreCompleted: ->
+ @getCompleted().length is @length
+
+ getCompleted: ->
+ @where completed: yes
+
+ getActive: ->
+ @where completed: no
+
+ comparator: (todo) ->
+ todo.get('created')
diff --git a/labs/dependency-examples/chaplin-brunch/app/routes.coffee b/labs/dependency-examples/chaplin-brunch/app/routes.coffee
new file mode 100644
index 0000000000..34fbc6b64c
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/routes.coffee
@@ -0,0 +1,3 @@
+module.exports = (match) ->
+ match ':filterer', 'index#list'
+ match '', 'index#list'
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/base/collection-view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/base/collection-view.coffee
new file mode 100644
index 0000000000..c84f94c2e6
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/base/collection-view.coffee
@@ -0,0 +1,7 @@
+Chaplin = require 'chaplin'
+View = require 'views/base/view'
+
+module.exports = class CollectionView extends Chaplin.CollectionView
+ # This class doesn’t inherit from the application-specific View class,
+ # so we need to borrow the method from the View prototype:
+ getTemplateFunction: View::getTemplateFunction
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/base/view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/base/view.coffee
new file mode 100644
index 0000000000..430287ae90
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/base/view.coffee
@@ -0,0 +1,7 @@
+Chaplin = require 'chaplin'
+require 'lib/view-helper' # Just load the view helpers, no return value
+
+module.exports = class View extends Chaplin.View
+ # Precompiled templates function initializer.
+ getTemplateFunction: ->
+ @template
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/footer-view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/footer-view.coffee
new file mode 100644
index 0000000000..efc1ade4bf
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/footer-view.coffee
@@ -0,0 +1,40 @@
+View = require 'views/base/view'
+template = require 'views/templates/footer'
+
+module.exports = class FooterView extends View
+ autoRender: yes
+ el: '#footer'
+ template: template
+
+ initialize: ->
+ super
+ @subscribeEvent 'todos:filter', @updateFilterer
+ @modelBind 'all', @renderCounter
+ @delegate 'click', '#clear-completed', @clearCompleted
+
+ render: =>
+ super
+ @renderCounter()
+
+ updateFilterer: (filterer) =>
+ filterer = '' if filterer is 'all'
+ @$('#filters a')
+ .removeClass('selected')
+ .filter("[href='#/#{filterer}']")
+ .addClass('selected')
+
+ renderCounter: =>
+ total = @collection.length
+ active = @collection.getActive().length
+ completed = @collection.getCompleted().length
+
+ @$('#todo-count > strong').html active
+ countDescription = (if active is 1 then 'item' else 'items')
+ @$('.todo-count-title').text countDescription
+
+ @$('#completed-count').html "(#{completed})"
+ @$('#clear-completed').toggle(completed > 0)
+ @$el.toggle(total > 0)
+
+ clearCompleted: ->
+ @publishEvent 'todos:clear'
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/header-view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/header-view.coffee
new file mode 100644
index 0000000000..40988909d8
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/header-view.coffee
@@ -0,0 +1,18 @@
+View = require 'views/base/view'
+template = require 'views/templates/header'
+
+module.exports = class HeaderView extends View
+ autoRender: yes
+ el: '#header'
+ template: template
+
+ initialize: ->
+ super
+ @delegate 'keypress', '#new-todo', @createOnEnter
+
+ createOnEnter: (event) =>
+ ENTER_KEY = 13
+ title = $(event.currentTarget).val().trim()
+ return if event.keyCode isnt ENTER_KEY or not title
+ @collection.create {title}
+ @$('#new-todo').val ''
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/layout.coffee b/labs/dependency-examples/chaplin-brunch/app/views/layout.coffee
new file mode 100644
index 0000000000..95d9066c46
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/layout.coffee
@@ -0,0 +1,10 @@
+Chaplin = require 'chaplin'
+
+# Layout is the top-level application ‘view’.
+module.exports = class Layout extends Chaplin.Layout
+ initialize: ->
+ super
+ @subscribeEvent 'todos:filter', @changeFilterer
+
+ changeFilterer: (filterer = 'all') ->
+ $('#todoapp').attr 'class', "filter-#{filterer}"
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/templates/footer.hbs b/labs/dependency-examples/chaplin-brunch/app/views/templates/footer.hbs
new file mode 100644
index 0000000000..2d6da43acb
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/templates/footer.hbs
@@ -0,0 +1,20 @@
+
+
+ items
+ left
+
+
+
+ Clear completed
+
+
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/templates/header.hbs b/labs/dependency-examples/chaplin-brunch/app/views/templates/header.hbs
new file mode 100644
index 0000000000..8b235ae18d
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/templates/header.hbs
@@ -0,0 +1,2 @@
+todos
+
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/templates/todo.hbs b/labs/dependency-examples/chaplin-brunch/app/views/templates/todo.hbs
new file mode 100644
index 0000000000..ffe17da100
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/templates/todo.hbs
@@ -0,0 +1,6 @@
+
+
+ {{title}}
+
+
+
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/templates/todos.hbs b/labs/dependency-examples/chaplin-brunch/app/views/templates/todos.hbs
new file mode 100644
index 0000000000..51920bdf69
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/templates/todos.hbs
@@ -0,0 +1,3 @@
+
+Mark all as complete
+
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/todo-view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/todo-view.coffee
new file mode 100644
index 0000000000..cadc749e6c
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/todo-view.coffee
@@ -0,0 +1,40 @@
+View = require 'views/base/view'
+template = require 'views/templates/todo'
+
+module.exports = class TodoView extends View
+ template: template
+ tagName: 'li'
+
+ initialize: ->
+ super
+ @modelBind 'change', @render
+ @delegate 'click', '.destroy', @destroy
+ @delegate 'dblclick', 'label', @edit
+ @delegate 'keypress', '.edit', @save
+ @delegate 'click', '.toggle', @toggle
+ @delegate 'blur', '.edit', @save
+
+ render: =>
+ super
+ # Reset classes, re-add the appropriate ones.
+ @$el.removeClass 'active completed'
+ className = if @model.get('completed') then 'completed' else 'active'
+ @$el.addClass className
+
+ destroy: =>
+ @model.destroy()
+
+ toggle: =>
+ @model.toggle().save()
+
+ edit: =>
+ @$el.addClass 'editing'
+ @$('.edit').focus()
+
+ save: (event) =>
+ ENTER_KEY = 13
+ title = $(event.currentTarget).val().trim()
+ return @model.destroy() unless title
+ return if event.type is 'keypress' and event.keyCode isnt ENTER_KEY
+ @model.save {title}
+ @$el.removeClass 'editing'
diff --git a/labs/dependency-examples/chaplin-brunch/app/views/todos-view.coffee b/labs/dependency-examples/chaplin-brunch/app/views/todos-view.coffee
new file mode 100644
index 0000000000..0782b67167
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/app/views/todos-view.coffee
@@ -0,0 +1,31 @@
+CollectionView = require 'views/base/collection-view'
+template = require 'views/templates/todos'
+TodoView = require 'views/todo-view'
+
+module.exports = class TodosView extends CollectionView
+ el: '#main'
+ itemView: TodoView
+ listSelector: '#todo-list'
+ template: template
+
+ initialize: ->
+ super
+ @subscribeEvent 'todos:clear', @clear
+ @modelBind 'all', @renderCheckbox
+ @delegate 'click', '#toggle-all', @toggleCompleted
+
+ render: =>
+ super
+ @renderCheckbox()
+
+ renderCheckbox: =>
+ @$('#toggle-all').prop 'checked', @collection.allAreCompleted()
+ @$el.toggle(@collection.length isnt 0)
+
+ toggleCompleted: (event) =>
+ isChecked = event.currentTarget.checked
+ @collection.each (todo) -> todo.save completed: isChecked
+
+ clear: ->
+ @collection.getCompleted().forEach (model) ->
+ model.destroy()
diff --git a/labs/dependency-examples/chaplin-brunch/config.coffee b/labs/dependency-examples/chaplin-brunch/config.coffee
new file mode 100644
index 0000000000..cd0be4a29d
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/config.coffee
@@ -0,0 +1,29 @@
+exports.config =
+ # See http://brunch.readthedocs.org/en/latest/config.html for documentation.
+ files:
+ javascripts:
+ joinTo:
+ 'javascripts/app.js': /^app/
+ 'javascripts/vendor.js': /^vendor/
+ 'test/javascripts/test.js': /^test[\\/](?!vendor)/
+ 'test/javascripts/test-vendor.js': /^test[\\/](?=vendor)/
+ order:
+ # Files in `vendor` directories are compiled before other files
+ # even if they aren't specified in order.before.
+ before: [
+ 'vendor/scripts/console-helper.js',
+ 'vendor/scripts/jquery-1.8.2.js',
+ 'vendor/scripts/underscore-1.4.2.js',
+ 'vendor/scripts/backbone-0.9.2.js'
+ ]
+
+ stylesheets:
+ joinTo:
+ 'stylesheets/app.css': /^(app|vendor)/
+ 'test/stylesheets/test.css': /^test/
+ order:
+ before: ['vendor/styles/normalize-2.0.1.css']
+ after: ['vendor/styles/helpers.css']
+
+ templates:
+ joinTo: 'javascripts/app.js'
diff --git a/labs/dependency-examples/chaplin-brunch/package.json b/labs/dependency-examples/chaplin-brunch/package.json
new file mode 100644
index 0000000000..8141f63134
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/package.json
@@ -0,0 +1,30 @@
+{
+ "author": "Paul Miller (http://paulmillr.com/)",
+ "name": "brunch-with-chaplin-todomvc",
+ "description": "Brunch with Chaplin TODOMVC app",
+ "version": "0.0.1",
+ "engines": {
+ "node": "0.8 || 0.9"
+ },
+ "scripts": {
+ "start": "brunch watch --server",
+ "test": "brunch test"
+ },
+ "dependencies": {
+ "javascript-brunch": ">= 1.0 < 1.5",
+ "coffee-script-brunch": ">= 1.0 < 1.5",
+
+ "css-brunch": ">= 1.0 < 1.5",
+ "stylus-brunch": ">= 1.0 < 1.5",
+
+ "handlebars-brunch": ">= 1.0 < 1.5",
+
+ "uglify-js-brunch": ">= 1.0 < 1.5",
+ "clean-css-brunch": ">= 1.0 < 1.5"
+ },
+ "devDependencies": {
+ "chai": "~1.2.0",
+ "sinon": "~1.4.2",
+ "sinon-chai": "~2.1.2"
+ }
+}
diff --git a/labs/dependency-examples/chaplin-brunch/public/index.html b/labs/dependency-examples/chaplin-brunch/public/index.html
new file mode 100644
index 0000000000..697f80096e
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/public/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Chaplin • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/labs/dependency-examples/chaplin-brunch/public/javascripts/app.js b/labs/dependency-examples/chaplin-brunch/public/javascripts/app.js
new file mode 100644
index 0000000000..60f5f76b72
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/public/javascripts/app.js
@@ -0,0 +1,937 @@
+(function(/*! Brunch !*/) {
+ 'use strict';
+
+ var globals = typeof window !== 'undefined' ? window : global;
+ if (typeof globals.require === 'function') return;
+
+ var modules = {};
+ var cache = {};
+
+ var has = function(object, name) {
+ return ({}).hasOwnProperty.call(object, name);
+ };
+
+ var expand = function(root, name) {
+ var results = [], parts, part;
+ if (/^\.\.?(\/|$)/.test(name)) {
+ parts = [root, name].join('/').split('/');
+ } else {
+ parts = name.split('/');
+ }
+ for (var i = 0, length = parts.length; i < length; i++) {
+ part = parts[i];
+ if (part === '..') {
+ results.pop();
+ } else if (part !== '.' && part !== '') {
+ results.push(part);
+ }
+ }
+ return results.join('/');
+ };
+
+ var dirname = function(path) {
+ return path.split('/').slice(0, -1).join('/');
+ };
+
+ var localRequire = function(path) {
+ return function(name) {
+ var dir = dirname(path);
+ var absolute = expand(dir, name);
+ return globals.require(absolute);
+ };
+ };
+
+ var initModule = function(name, definition) {
+ var module = {id: name, exports: {}};
+ definition(module.exports, localRequire(name), module);
+ var exports = cache[name] = module.exports;
+ return exports;
+ };
+
+ var require = function(name) {
+ var path = expand(name, '.');
+
+ if (has(cache, path)) return cache[path];
+ if (has(modules, path)) return initModule(path, modules[path]);
+
+ var dirIndex = expand(path, './index');
+ if (has(cache, dirIndex)) return cache[dirIndex];
+ if (has(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
+
+ throw new Error('Cannot find module "' + name + '"');
+ };
+
+ var define = function(bundle, fn) {
+ if (typeof bundle === 'object') {
+ for (var key in bundle) {
+ if (has(bundle, key)) {
+ modules[key] = bundle[key];
+ }
+ }
+ } else {
+ modules[bundle] = fn;
+ }
+ }
+
+ globals.require = require;
+ globals.require.define = define;
+ globals.require.brunch = true;
+})();
+
+window.require.define({"application": function(exports, require, module) {
+ var Application, Chaplin, FooterController, HeaderController, Layout, Todos, TodosController, mediator, routes,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ mediator = require('mediator');
+
+ routes = require('routes');
+
+ HeaderController = require('controllers/header-controller');
+
+ FooterController = require('controllers/footer-controller');
+
+ TodosController = require('controllers/todos-controller');
+
+ Todos = require('models/todos');
+
+ Layout = require('views/layout');
+
+ module.exports = Application = (function(_super) {
+
+ __extends(Application, _super);
+
+ function Application() {
+ return Application.__super__.constructor.apply(this, arguments);
+ }
+
+ Application.prototype.title = 'Chaplin • TodoMVC';
+
+ Application.prototype.initialize = function() {
+ Application.__super__.initialize.apply(this, arguments);
+ this.initDispatcher({
+ controllerSuffix: '-controller'
+ });
+ this.initLayout();
+ this.initMediator();
+ this.initControllers();
+ this.initRouter(routes, {
+ pushState: false
+ });
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ Application.prototype.initLayout = function() {
+ return this.layout = new Layout({
+ title: this.title
+ });
+ };
+
+ Application.prototype.initControllers = function() {
+ new HeaderController();
+ new FooterController();
+ return new TodosController();
+ };
+
+ Application.prototype.initMediator = function() {
+ mediator.user = null;
+ mediator.todos = new Todos();
+ mediator.todos.fetch();
+ return mediator.seal();
+ };
+
+ return Application;
+
+ })(Chaplin.Application);
+
+}});
+
+window.require.define({"controllers/base/controller": function(exports, require, module) {
+ var Chaplin, Controller,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ module.exports = Controller = (function(_super) {
+
+ __extends(Controller, _super);
+
+ function Controller() {
+ return Controller.__super__.constructor.apply(this, arguments);
+ }
+
+ return Controller;
+
+ })(Chaplin.Controller);
+
+}});
+
+window.require.define({"controllers/footer-controller": function(exports, require, module) {
+ var Controller, FooterController, FooterView, mediator,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Controller = require('controllers/base/controller');
+
+ FooterView = require('views/footer-view');
+
+ mediator = require('mediator');
+
+ module.exports = FooterController = (function(_super) {
+
+ __extends(FooterController, _super);
+
+ function FooterController() {
+ return FooterController.__super__.constructor.apply(this, arguments);
+ }
+
+ FooterController.prototype.initialize = function() {
+ FooterController.__super__.initialize.apply(this, arguments);
+ return this.view = new FooterView({
+ collection: mediator.todos
+ });
+ };
+
+ return FooterController;
+
+ })(Controller);
+
+}});
+
+window.require.define({"controllers/header-controller": function(exports, require, module) {
+ var Controller, HeaderController, HeaderView, mediator,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Controller = require('controllers/base/controller');
+
+ HeaderView = require('views/header-view');
+
+ mediator = require('mediator');
+
+ module.exports = HeaderController = (function(_super) {
+
+ __extends(HeaderController, _super);
+
+ function HeaderController() {
+ return HeaderController.__super__.constructor.apply(this, arguments);
+ }
+
+ HeaderController.prototype.initialize = function() {
+ HeaderController.__super__.initialize.apply(this, arguments);
+ return this.view = new HeaderView({
+ collection: mediator.todos
+ });
+ };
+
+ return HeaderController;
+
+ })(Controller);
+
+}});
+
+window.require.define({"controllers/index-controller": function(exports, require, module) {
+ var Controller, IndexController,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Controller = require('controllers/base/controller');
+
+ module.exports = IndexController = (function(_super) {
+
+ __extends(IndexController, _super);
+
+ function IndexController() {
+ return IndexController.__super__.constructor.apply(this, arguments);
+ }
+
+ IndexController.prototype.title = 'Todo list';
+
+ IndexController.prototype.list = function(options) {
+ var _ref, _ref1;
+ return this.publishEvent('todos:filter', (_ref = (_ref1 = options.filterer) != null ? _ref1.trim() : void 0) != null ? _ref : 'all');
+ };
+
+ return IndexController;
+
+ })(Controller);
+
+}});
+
+window.require.define({"controllers/todos-controller": function(exports, require, module) {
+ var Controller, TodosController, TodosView, mediator,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Controller = require('controllers/base/controller');
+
+ TodosView = require('views/todos-view');
+
+ mediator = require('mediator');
+
+ module.exports = TodosController = (function(_super) {
+
+ __extends(TodosController, _super);
+
+ function TodosController() {
+ return TodosController.__super__.constructor.apply(this, arguments);
+ }
+
+ TodosController.prototype.initialize = function() {
+ TodosController.__super__.initialize.apply(this, arguments);
+ return this.view = new TodosView({
+ collection: mediator.todos
+ });
+ };
+
+ return TodosController;
+
+ })(Controller);
+
+}});
+
+window.require.define({"initialize": function(exports, require, module) {
+ var Application;
+
+ Application = require('application');
+
+ $(function() {
+ var app;
+ app = new Application();
+ return app.initialize();
+ });
+
+}});
+
+window.require.define({"lib/support": function(exports, require, module) {
+ var Chaplin, support, utils;
+
+ Chaplin = require('chaplin');
+
+ utils = require('lib/utils');
+
+ support = utils.beget(Chaplin.support);
+
+ module.exports = support;
+
+}});
+
+window.require.define({"lib/utils": function(exports, require, module) {
+ var Chaplin, utils;
+
+ Chaplin = require('chaplin');
+
+ utils = Chaplin.utils.beget(Chaplin.utils);
+
+ module.exports = utils;
+
+}});
+
+window.require.define({"lib/view-helper": function(exports, require, module) {
+ var mediator, utils;
+
+ mediator = require('mediator');
+
+ utils = require('chaplin/lib/utils');
+
+ Handlebars.registerHelper('if_logged_in', function(options) {
+ if (mediator.user) {
+ return options.fn(this);
+ } else {
+ return options.inverse(this);
+ }
+ });
+
+ Handlebars.registerHelper('with', function(context, options) {
+ if (!context || Handlebars.Utils.isEmpty(context)) {
+ return options.inverse(this);
+ } else {
+ return options.fn(context);
+ }
+ });
+
+ Handlebars.registerHelper('without', function(context, options) {
+ var inverse;
+ inverse = options.inverse;
+ options.inverse = options.fn;
+ options.fn = inverse;
+ return Handlebars.helpers["with"].call(this, context, options);
+ });
+
+ Handlebars.registerHelper('with_user', function(options) {
+ var context, _ref;
+ context = ((_ref = mediator.user) != null ? _ref.serialize() : void 0) || {};
+ return Handlebars.helpers["with"].call(this, context, options);
+ });
+
+}});
+
+window.require.define({"mediator": function(exports, require, module) {
+
+ module.exports = require('chaplin').mediator;
+
+}});
+
+window.require.define({"models/base/collection": function(exports, require, module) {
+ var Chaplin, Collection, Model,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ Model = require('models/base/model');
+
+ module.exports = Collection = (function(_super) {
+
+ __extends(Collection, _super);
+
+ function Collection() {
+ return Collection.__super__.constructor.apply(this, arguments);
+ }
+
+ Collection.prototype.model = Model;
+
+ return Collection;
+
+ })(Chaplin.Collection);
+
+}});
+
+window.require.define({"models/base/model": function(exports, require, module) {
+ var Chaplin, Model,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ module.exports = Model = (function(_super) {
+
+ __extends(Model, _super);
+
+ function Model() {
+ return Model.__super__.constructor.apply(this, arguments);
+ }
+
+ return Model;
+
+ })(Chaplin.Model);
+
+}});
+
+window.require.define({"models/todo": function(exports, require, module) {
+ var Model, Todo,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Model = require('models/base/model');
+
+ module.exports = Todo = (function(_super) {
+
+ __extends(Todo, _super);
+
+ function Todo() {
+ return Todo.__super__.constructor.apply(this, arguments);
+ }
+
+ Todo.prototype.defaults = {
+ title: '',
+ completed: false
+ };
+
+ Todo.prototype.initialize = function() {
+ Todo.__super__.initialize.apply(this, arguments);
+ if (this.isNew()) {
+ return this.set('created', Date.now());
+ }
+ };
+
+ Todo.prototype.toggle = function() {
+ return this.set({
+ completed: !this.get('completed')
+ });
+ };
+
+ Todo.prototype.isVisible = function() {
+ var isCompleted;
+ return isCompleted = this.get('completed');
+ };
+
+ return Todo;
+
+ })(Model);
+
+}});
+
+window.require.define({"models/todos": function(exports, require, module) {
+ var Collection, Todo, Todos,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Collection = require('models/base/collection');
+
+ Todo = require('models/todo');
+
+ module.exports = Todos = (function(_super) {
+
+ __extends(Todos, _super);
+
+ function Todos() {
+ return Todos.__super__.constructor.apply(this, arguments);
+ }
+
+ Todos.prototype.model = Todo;
+
+ Todos.prototype.localStorage = new Store('todos-chaplin');
+
+ Todos.prototype.allAreCompleted = function() {
+ return this.getCompleted().length === this.length;
+ };
+
+ Todos.prototype.getCompleted = function() {
+ return this.where({
+ completed: true
+ });
+ };
+
+ Todos.prototype.getActive = function() {
+ return this.where({
+ completed: false
+ });
+ };
+
+ Todos.prototype.comparator = function(todo) {
+ return todo.get('created');
+ };
+
+ return Todos;
+
+ })(Collection);
+
+}});
+
+window.require.define({"routes": function(exports, require, module) {
+
+ module.exports = function(match) {
+ match(':filterer', 'index#list');
+ return match('', 'index#list');
+ };
+
+}});
+
+window.require.define({"views/base/collection-view": function(exports, require, module) {
+ var Chaplin, CollectionView, View,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ View = require('views/base/view');
+
+ module.exports = CollectionView = (function(_super) {
+
+ __extends(CollectionView, _super);
+
+ function CollectionView() {
+ return CollectionView.__super__.constructor.apply(this, arguments);
+ }
+
+ CollectionView.prototype.getTemplateFunction = View.prototype.getTemplateFunction;
+
+ return CollectionView;
+
+ })(Chaplin.CollectionView);
+
+}});
+
+window.require.define({"views/base/view": function(exports, require, module) {
+ var Chaplin, View,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ require('lib/view-helper');
+
+ module.exports = View = (function(_super) {
+
+ __extends(View, _super);
+
+ function View() {
+ return View.__super__.constructor.apply(this, arguments);
+ }
+
+ View.prototype.getTemplateFunction = function() {
+ return this.template;
+ };
+
+ return View;
+
+ })(Chaplin.View);
+
+}});
+
+window.require.define({"views/footer-view": function(exports, require, module) {
+ var FooterView, View, template,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ View = require('views/base/view');
+
+ template = require('views/templates/footer');
+
+ module.exports = FooterView = (function(_super) {
+
+ __extends(FooterView, _super);
+
+ function FooterView() {
+ this.renderCounter = __bind(this.renderCounter, this);
+
+ this.updateFilterer = __bind(this.updateFilterer, this);
+
+ this.render = __bind(this.render, this);
+ return FooterView.__super__.constructor.apply(this, arguments);
+ }
+
+ FooterView.prototype.autoRender = true;
+
+ FooterView.prototype.el = '#footer';
+
+ FooterView.prototype.template = template;
+
+ FooterView.prototype.initialize = function() {
+ FooterView.__super__.initialize.apply(this, arguments);
+ this.subscribeEvent('todos:filter', this.updateFilterer);
+ this.modelBind('all', this.renderCounter);
+ return this.delegate('click', '#clear-completed', this.clearCompleted);
+ };
+
+ FooterView.prototype.render = function() {
+ FooterView.__super__.render.apply(this, arguments);
+ return this.renderCounter();
+ };
+
+ FooterView.prototype.updateFilterer = function(filterer) {
+ if (filterer === 'all') {
+ filterer = '';
+ }
+ return this.$('#filters a').removeClass('selected').filter("[href='#/" + filterer + "']").addClass('selected');
+ };
+
+ FooterView.prototype.renderCounter = function() {
+ var active, completed, countDescription, total;
+ total = this.collection.length;
+ active = this.collection.getActive().length;
+ completed = this.collection.getCompleted().length;
+ this.$('#todo-count > strong').html(active);
+ countDescription = (active === 1 ? 'item' : 'items');
+ this.$('.todo-count-title').text(countDescription);
+ this.$('#completed-count').html("(" + completed + ")");
+ this.$('#clear-completed').toggle(completed > 0);
+ return this.$el.toggle(total > 0);
+ };
+
+ FooterView.prototype.clearCompleted = function() {
+ return this.publishEvent('todos:clear');
+ };
+
+ return FooterView;
+
+ })(View);
+
+}});
+
+window.require.define({"views/header-view": function(exports, require, module) {
+ var HeaderView, View, template,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ View = require('views/base/view');
+
+ template = require('views/templates/header');
+
+ module.exports = HeaderView = (function(_super) {
+
+ __extends(HeaderView, _super);
+
+ function HeaderView() {
+ this.createOnEnter = __bind(this.createOnEnter, this);
+ return HeaderView.__super__.constructor.apply(this, arguments);
+ }
+
+ HeaderView.prototype.autoRender = true;
+
+ HeaderView.prototype.el = '#header';
+
+ HeaderView.prototype.template = template;
+
+ HeaderView.prototype.initialize = function() {
+ HeaderView.__super__.initialize.apply(this, arguments);
+ return this.delegate('keypress', '#new-todo', this.createOnEnter);
+ };
+
+ HeaderView.prototype.createOnEnter = function(event) {
+ var ENTER_KEY, title;
+ ENTER_KEY = 13;
+ title = $(event.currentTarget).val().trim();
+ if (event.keyCode !== ENTER_KEY || !title) {
+ return;
+ }
+ this.collection.create({
+ title: title
+ });
+ return this.$('#new-todo').val('');
+ };
+
+ return HeaderView;
+
+ })(View);
+
+}});
+
+window.require.define({"views/layout": function(exports, require, module) {
+ var Chaplin, Layout,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ Chaplin = require('chaplin');
+
+ module.exports = Layout = (function(_super) {
+
+ __extends(Layout, _super);
+
+ function Layout() {
+ return Layout.__super__.constructor.apply(this, arguments);
+ }
+
+ Layout.prototype.initialize = function() {
+ Layout.__super__.initialize.apply(this, arguments);
+ return this.subscribeEvent('todos:filter', this.changeFilterer);
+ };
+
+ Layout.prototype.changeFilterer = function(filterer) {
+ if (filterer == null) {
+ filterer = 'all';
+ }
+ return $('#todoapp').attr('class', "filter-" + filterer);
+ };
+
+ return Layout;
+
+ })(Chaplin.Layout);
+
+}});
+
+window.require.define({"views/templates/footer": function(exports, require, module) {
+ module.exports = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+
+
+
+ return "\n \n items \n left\n \n\n\n Clear completed\n \n \n";});
+}});
+
+window.require.define({"views/templates/header": function(exports, require, module) {
+ module.exports = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+
+
+
+ return "todos \n \n";});
+}});
+
+window.require.define({"views/templates/todo": function(exports, require, module) {
+ module.exports = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+ var buffer = "", stack1, foundHelper, self=this, functionType="function", escapeExpression=this.escapeExpression;
+
+ function program1(depth0,data) {
+
+
+ return " checked";}
+
+ buffer += "\n \n ";
+ foundHelper = helpers.title;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.title; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + " \n \n
\n \n";
+ return buffer;});
+}});
+
+window.require.define({"views/templates/todos": function(exports, require, module) {
+ module.exports = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+
+
+
+ return " \nMark all as complete \n\n";});
+}});
+
+window.require.define({"views/todo-view": function(exports, require, module) {
+ var TodoView, View, template,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ View = require('views/base/view');
+
+ template = require('views/templates/todo');
+
+ module.exports = TodoView = (function(_super) {
+
+ __extends(TodoView, _super);
+
+ function TodoView() {
+ this.save = __bind(this.save, this);
+
+ this.edit = __bind(this.edit, this);
+
+ this.toggle = __bind(this.toggle, this);
+
+ this.destroy = __bind(this.destroy, this);
+
+ this.render = __bind(this.render, this);
+ return TodoView.__super__.constructor.apply(this, arguments);
+ }
+
+ TodoView.prototype.template = template;
+
+ TodoView.prototype.tagName = 'li';
+
+ TodoView.prototype.initialize = function() {
+ TodoView.__super__.initialize.apply(this, arguments);
+ this.modelBind('change', this.render);
+ this.delegate('click', '.destroy', this.destroy);
+ this.delegate('dblclick', 'label', this.edit);
+ this.delegate('keypress', '.edit', this.save);
+ this.delegate('click', '.toggle', this.toggle);
+ return this.delegate('blur', '.edit', this.save);
+ };
+
+ TodoView.prototype.render = function() {
+ var className;
+ TodoView.__super__.render.apply(this, arguments);
+ this.$el.removeClass('active completed');
+ className = this.model.get('completed') ? 'completed' : 'active';
+ return this.$el.addClass(className);
+ };
+
+ TodoView.prototype.destroy = function() {
+ return this.model.destroy();
+ };
+
+ TodoView.prototype.toggle = function() {
+ return this.model.toggle().save();
+ };
+
+ TodoView.prototype.edit = function() {
+ this.$el.addClass('editing');
+ return this.$('.edit').focus();
+ };
+
+ TodoView.prototype.save = function(event) {
+ var ENTER_KEY, title;
+ ENTER_KEY = 13;
+ title = $(event.currentTarget).val().trim();
+ if (!title) {
+ return this.model.destroy();
+ }
+ if (event.type === 'keypress' && event.keyCode !== ENTER_KEY) {
+ return;
+ }
+ this.model.save({
+ title: title
+ });
+ return this.$el.removeClass('editing');
+ };
+
+ return TodoView;
+
+ })(View);
+
+}});
+
+window.require.define({"views/todos-view": function(exports, require, module) {
+ var CollectionView, TodoView, TodosView, template,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ CollectionView = require('views/base/collection-view');
+
+ template = require('views/templates/todos');
+
+ TodoView = require('views/todo-view');
+
+ module.exports = TodosView = (function(_super) {
+
+ __extends(TodosView, _super);
+
+ function TodosView() {
+ this.toggleCompleted = __bind(this.toggleCompleted, this);
+
+ this.renderCheckbox = __bind(this.renderCheckbox, this);
+
+ this.render = __bind(this.render, this);
+ return TodosView.__super__.constructor.apply(this, arguments);
+ }
+
+ TodosView.prototype.el = '#main';
+
+ TodosView.prototype.itemView = TodoView;
+
+ TodosView.prototype.listSelector = '#todo-list';
+
+ TodosView.prototype.template = template;
+
+ TodosView.prototype.initialize = function() {
+ TodosView.__super__.initialize.apply(this, arguments);
+ this.subscribeEvent('todos:clear', this.clear);
+ this.modelBind('all', this.renderCheckbox);
+ return this.delegate('click', '#toggle-all', this.toggleCompleted);
+ };
+
+ TodosView.prototype.render = function() {
+ TodosView.__super__.render.apply(this, arguments);
+ return this.renderCheckbox();
+ };
+
+ TodosView.prototype.renderCheckbox = function() {
+ this.$('#toggle-all').prop('checked', this.collection.allAreCompleted());
+ return this.$el.toggle(this.collection.length !== 0);
+ };
+
+ TodosView.prototype.toggleCompleted = function(event) {
+ var isChecked;
+ isChecked = event.currentTarget.checked;
+ return this.collection.each(function(todo) {
+ return todo.save({
+ completed: isChecked
+ });
+ });
+ };
+
+ TodosView.prototype.clear = function() {
+ return this.collection.getCompleted().forEach(function(model) {
+ return model.destroy();
+ });
+ };
+
+ return TodosView;
+
+ })(CollectionView);
+
+}});
+
diff --git a/labs/dependency-examples/chaplin-brunch/public/javascripts/vendor.js b/labs/dependency-examples/chaplin-brunch/public/javascripts/vendor.js
new file mode 100644
index 0000000000..c5ea3d620f
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/public/javascripts/vendor.js
@@ -0,0 +1,3855 @@
+(function(/*! Brunch !*/) {
+ 'use strict';
+
+ var globals = typeof window !== 'undefined' ? window : global;
+ if (typeof globals.require === 'function') return;
+
+ var modules = {};
+ var cache = {};
+
+ var has = function(object, name) {
+ return ({}).hasOwnProperty.call(object, name);
+ };
+
+ var expand = function(root, name) {
+ var results = [], parts, part;
+ if (/^\.\.?(\/|$)/.test(name)) {
+ parts = [root, name].join('/').split('/');
+ } else {
+ parts = name.split('/');
+ }
+ for (var i = 0, length = parts.length; i < length; i++) {
+ part = parts[i];
+ if (part === '..') {
+ results.pop();
+ } else if (part !== '.' && part !== '') {
+ results.push(part);
+ }
+ }
+ return results.join('/');
+ };
+
+ var dirname = function(path) {
+ return path.split('/').slice(0, -1).join('/');
+ };
+
+ var localRequire = function(path) {
+ return function(name) {
+ var dir = dirname(path);
+ var absolute = expand(dir, name);
+ return globals.require(absolute);
+ };
+ };
+
+ var initModule = function(name, definition) {
+ var module = {id: name, exports: {}};
+ definition(module.exports, localRequire(name), module);
+ var exports = cache[name] = module.exports;
+ return exports;
+ };
+
+ var require = function(name) {
+ var path = expand(name, '.');
+
+ if (has(cache, path)) return cache[path];
+ if (has(modules, path)) return initModule(path, modules[path]);
+
+ var dirIndex = expand(path, './index');
+ if (has(cache, dirIndex)) return cache[dirIndex];
+ if (has(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
+
+ throw new Error('Cannot find module "' + name + '"');
+ };
+
+ var define = function(bundle, fn) {
+ if (typeof bundle === 'object') {
+ for (var key in bundle) {
+ if (has(bundle, key)) {
+ modules[key] = bundle[key];
+ }
+ }
+ } else {
+ modules[bundle] = fn;
+ }
+ }
+
+ globals.require = require;
+ globals.require.define = define;
+ globals.require.brunch = true;
+})();
+
+// Make it safe to do console.log() always.
+(function (con) {
+ var method;
+ var dummy = function() {};
+ var methods = ('assert,count,debug,dir,dirxml,error,exception,group,' +
+ 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
+ 'time,timeEnd,trace,warn').split(',');
+ while (method = methods.pop()) {
+ con[method] = con[method] || dummy;
+ }
+})(window.console = window.console || {});
+;
+
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `global`
+ // on the server).
+ var root = this;
+
+ // 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 a local reference to slice/splice.
+ var slice = Array.prototype.slice;
+ var splice = Array.prototype.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.2';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ var $ = root.jQuery || root.Zepto || root.ender;
+
+ // Set the JavaScript library that will be used for DOM manipulation and
+ // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+ // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+ // alternate JavaScript library (or a mock library for testing your views
+ // outside of a browser).
+ Backbone.setDomLibrary = function(lib) {
+ $ = lib;
+ };
+
+ // 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 `"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
+ // -----------------
+
+ // Regular expression used to split event strings
+ var eventSplitter = /\s+/;
+
+ // 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 one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) return this;
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ while (event = events.shift()) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ }
+
+ return this;
+ },
+
+ // 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 `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ while (event = events.shift()) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) continue;
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ }
+ }
+
+ 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(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ if (node = calls[event]) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ if (node = all) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) this.collection = options.collection;
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ 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,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: 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);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // 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"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ (options || (options = {})).unset = true;
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ (options || (options = {})).unset = true;
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, '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, value, options) {
+ var attrs, current;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) {
+ delete options.wait;
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+ if (options.wait) this.set(current, silentOptions);
+ 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 triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (this.isNew()) {
+ triggerDestroy();
+ return false;
+ }
+
+ options.success = function(resp) {
+ if (options.wait) triggerDestroy();
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ if (!options.wait) triggerDestroy();
+ 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 = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 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, xhr) {
+ 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.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // 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 (!arguments.length) 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, old = this._previousAttributes;
+ 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 (!arguments.length || !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);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. 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) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+ };
+
+ // 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); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ models.splice(dups[i], 1);
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[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);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // 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) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || 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) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return 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) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // 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 `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, '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) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) coll.add(nextModel, options);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', 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, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(model, options) {
+ options || (options = {});
+ if (!(model instanceof Model)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) model = false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ 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)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(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 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) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // 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;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // 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,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.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 || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+ 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._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = $('').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname == this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+ this.fragment = frag;
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+
+ // Backbone.View
+ // -------------
+
+ // 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');
+ this._configure(options || {});
+ 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'];
+
+ // 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 prefered 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 from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // 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 $) ? element : $(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 = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // 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.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // 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 = getValue(this, 'attributes') || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // 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.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.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 (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return $.ajax(_.extend(params, options));
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this);;
+
+/**
+ * Backbone localStorage Adapter
+ * https://github.com/jeromegn/Backbone.localStorage
+ */
+
+(function() {
+ // A simple module to replace `Backbone.sync` with *localStorage*-based
+ // persistence. Models are given GUIDS, and saved into a JSON object. Simple
+ // as that.
+
+ // Hold reference to Underscore.js and Backbone.js in the closure in order
+ // to make things work even if they are removed from the global namespace
+ var _ = this._;
+ var Backbone = this.Backbone;
+
+ // Generate four random hex digits.
+ function S4() {
+ return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
+ };
+
+ // Generate a pseudo-GUID by concatenating random hexadecimal.
+ function guid() {
+ return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
+ };
+
+ // Our Store is represented by a single JS object in *localStorage*. Create it
+ // with a meaningful name, like the name you'd give a table.
+ // window.Store is deprectated, use Backbone.LocalStorage instead
+ Backbone.LocalStorage = window.Store = function(name) {
+ this.name = name;
+ var store = this.localStorage().getItem(this.name);
+ this.records = (store && store.split(",")) || [];
+ };
+
+ _.extend(Backbone.LocalStorage.prototype, {
+
+ // Save the current state of the **Store** to *localStorage*.
+ save: function() {
+ this.localStorage().setItem(this.name, this.records.join(","));
+ },
+
+ // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
+ // have an id of it's own.
+ create: function(model) {
+ if (!model.id) {
+ model.id = guid();
+ model.set(model.idAttribute, model.id);
+ }
+ this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
+ this.records.push(model.id.toString());
+ this.save();
+ return model.toJSON();
+ },
+
+ // Update a model by replacing its copy in `this.data`.
+ update: function(model) {
+ this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
+ if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
+ return model.toJSON();
+ },
+
+ // Retrieve a model from `this.data` by id.
+ find: function(model) {
+ return JSON.parse(this.localStorage().getItem(this.name+"-"+model.id));
+ },
+
+ // Return the array of all models currently in storage.
+ findAll: function() {
+ return _(this.records).chain()
+ .map(function(id){return JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
+ .compact()
+ .value();
+ },
+
+ // Delete a model from `this.data`, returning it.
+ destroy: function(model) {
+ this.localStorage().removeItem(this.name+"-"+model.id);
+ this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
+ this.save();
+ return model;
+ },
+
+ localStorage: function() {
+ return localStorage;
+ }
+
+ });
+
+ // localSync delegate to the model or collection's
+ // *localStorage* property, which should be an instance of `Store`.
+ // window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
+ Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options, error) {
+ var store = model.localStorage || model.collection.localStorage;
+
+ // Backwards compatibility with Backbone <= 0.3.3
+ if (typeof options == 'function') {
+ options = {
+ success: options,
+ error: error
+ };
+ }
+
+ var resp;
+
+ switch (method) {
+ case "read": resp = model.id != undefined ? store.find(model) : store.findAll(); break;
+ case "create": resp = store.create(model); break;
+ case "update": resp = store.update(model); break;
+ case "delete": resp = store.destroy(model); break;
+ }
+
+ if (resp) {
+ options.success(resp);
+ } else {
+ options.error("Record not found");
+ }
+ };
+
+ Backbone.ajaxSync = Backbone.sync;
+
+ Backbone.getSyncMethod = function(model) {
+ if(model.localStorage || (model.collection && model.collection.localStorage))
+ {
+ return Backbone.localSync;
+ }
+
+ return Backbone.ajaxSync;
+ };
+
+ // Override 'Backbone.sync' to default to localSync,
+ // the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
+ Backbone.sync = function(method, model, options, error) {
+ return Backbone.getSyncMethod(model).apply(this, [method, model, options, error]);
+ };
+
+})();
+;
+
+// lib/handlebars/base.js
+
+/*jshint eqnull:true*/
+this.Handlebars = {};
+
+(function(Handlebars) {
+
+Handlebars.VERSION = "1.0.rc.1";
+
+Handlebars.helpers = {};
+Handlebars.partials = {};
+
+Handlebars.registerHelper = function(name, fn, inverse) {
+ if(inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+};
+
+Handlebars.registerPartial = function(name, str) {
+ this.partials[name] = str;
+};
+
+Handlebars.registerHelper('helperMissing', function(arg) {
+ if(arguments.length === 2) {
+ return undefined;
+ } else {
+ throw new Error("Could not find property '" + arg + "'");
+ }
+});
+
+var toString = Object.prototype.toString, functionType = "[object Function]";
+
+Handlebars.registerHelper('blockHelperMissing', function(context, options) {
+ var inverse = options.inverse || function() {}, fn = options.fn;
+
+
+ var ret = "";
+ var type = toString.call(context);
+
+ if(type === functionType) { context = context.call(this); }
+
+ if(context === true) {
+ return fn(this);
+ } else if(context === false || context == null) {
+ return inverse(this);
+ } else if(type === "[object Array]") {
+ if(context.length > 0) {
+ return Handlebars.helpers.each(context, options);
+ } else {
+ return inverse(this);
+ }
+ } else {
+ return fn(context);
+ }
+});
+
+Handlebars.K = function() {};
+
+Handlebars.createFrame = Object.create || function(object) {
+ Handlebars.K.prototype = object;
+ var obj = new Handlebars.K();
+ Handlebars.K.prototype = null;
+ return obj;
+};
+
+Handlebars.registerHelper('each', function(context, options) {
+ var fn = options.fn, inverse = options.inverse;
+ var ret = "", data;
+
+ if (options.data) {
+ data = Handlebars.createFrame(options.data);
+ }
+
+ if(context && context.length > 0) {
+ for(var i=0, j=context.length; i": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var badChars = /[&<>"'`]/g;
+ var possible = /[&<>"'`]/;
+
+ var escapeChar = function(chr) {
+ return escape[chr] || "&";
+ };
+
+ Handlebars.Utils = {
+ escapeExpression: function(string) {
+ // don't escape SafeStrings, since they're already safe
+ if (string instanceof Handlebars.SafeString) {
+ return string.toString();
+ } else if (string == null || string === false) {
+ return "";
+ }
+
+ if(!possible.test(string)) { return string; }
+ return string.replace(badChars, escapeChar);
+ },
+
+ isEmpty: function(value) {
+ if (typeof value === "undefined") {
+ return true;
+ } else if (value === null) {
+ return true;
+ } else if (value === false) {
+ return true;
+ } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+})();;
+// lib/handlebars/runtime.js
+Handlebars.VM = {
+ template: function(templateSpec) {
+ // Just add water
+ var container = {
+ escapeExpression: Handlebars.Utils.escapeExpression,
+ invokePartial: Handlebars.VM.invokePartial,
+ programs: [],
+ program: function(i, fn, data) {
+ var programWrapper = this.programs[i];
+ if(data) {
+ return Handlebars.VM.program(fn, data);
+ } else if(programWrapper) {
+ return programWrapper;
+ } else {
+ programWrapper = this.programs[i] = Handlebars.VM.program(fn);
+ return programWrapper;
+ }
+ },
+ programWithDepth: Handlebars.VM.programWithDepth,
+ noop: Handlebars.VM.noop
+ };
+
+ return function(context, options) {
+ options = options || {};
+ return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
+ };
+ },
+
+ programWithDepth: function(fn, data, $depth) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function(context, options) {
+ options = options || {};
+
+ return fn.apply(this, [context, options.data || data].concat(args));
+ };
+ },
+ program: function(fn, data) {
+ return function(context, options) {
+ options = options || {};
+
+ return fn(context, options.data || data);
+ };
+ },
+ noop: function() { return ""; },
+ invokePartial: function(partial, name, context, helpers, partials, data) {
+ var options = { helpers: helpers, partials: partials, data: data };
+
+ if(partial === undefined) {
+ throw new Handlebars.Exception("The partial " + name + " could not be found");
+ } else if(partial instanceof Function) {
+ return partial(context, options);
+ } else if (!Handlebars.compile) {
+ throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
+ } else {
+ partials[name] = Handlebars.compile(partial, {data: data !== undefined});
+ return partials[name](context, options);
+ }
+ }
+};
+
+Handlebars.template = Handlebars.VM.template;
+;
+;
+
+/*
+Chaplin 0.5.0.
+
+Chaplin may be freely distributed under the MIT license.
+For all details and documentation:
+http://github.com/chaplinjs/chaplin
+*/
+
+'use strict';
+
+var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __slice = [].slice;
+
+require.define({
+ 'jquery': function(require, exports, module) {
+ return module.exports = $;
+ },
+ 'underscore': function(require, exports, module) {
+ return module.exports = _;
+ },
+ 'backbone': function(require, exports, module) {
+ return module.exports = Backbone;
+ }
+});
+
+require.define({
+ 'chaplin/application': function(exports, require, module) {
+ var Application, Backbone, Dispatcher, EventBroker, Layout, Router, mediator;
+ Backbone = require('backbone');
+ mediator = require('chaplin/mediator');
+ Dispatcher = require('chaplin/dispatcher');
+ Layout = require('chaplin/views/layout');
+ Router = require('chaplin/lib/router');
+ EventBroker = require('chaplin/lib/event_broker');
+ return module.exports = Application = (function() {
+
+ function Application() {}
+
+ Application.extend = Backbone.Model.extend;
+
+ _(Application.prototype).extend(EventBroker);
+
+ Application.prototype.title = '';
+
+ Application.prototype.dispatcher = null;
+
+ Application.prototype.layout = null;
+
+ Application.prototype.router = null;
+
+ Application.prototype.initialize = function() {};
+
+ Application.prototype.initDispatcher = function(options) {
+ return this.dispatcher = new Dispatcher(options);
+ };
+
+ Application.prototype.initLayout = function(options) {
+ var _ref;
+ if (options == null) {
+ options = {};
+ }
+ if ((_ref = options.title) == null) {
+ options.title = this.title;
+ }
+ return this.layout = new Layout(options);
+ };
+
+ Application.prototype.initRouter = function(routes, options) {
+ this.router = new Router(options);
+ if (typeof routes === "function") {
+ routes(this.router.match);
+ }
+ return this.router.startHistory();
+ };
+
+ Application.prototype.disposed = false;
+
+ Application.prototype.dispose = function() {
+ var prop, properties, _i, _len;
+ if (this.disposed) {
+ return;
+ }
+ properties = ['dispatcher', 'layout', 'router'];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ if (!(this[prop] != null)) {
+ continue;
+ }
+ this[prop].dispose();
+ delete this[prop];
+ }
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Application;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/mediator': function(exports, require, module) {
+ var Backbone, mediator, support, utils, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ support = require('chaplin/lib/support');
+ utils = require('chaplin/lib/utils');
+ mediator = {};
+ mediator.subscribe = Backbone.Events.on;
+ mediator.unsubscribe = Backbone.Events.off;
+ mediator.publish = Backbone.Events.trigger;
+ mediator.on = mediator.subscribe;
+ mediator._callbacks = null;
+ utils.readonly(mediator, 'subscribe', 'unsubscribe', 'publish', 'on');
+ mediator.seal = function() {
+ if (support.propertyDescriptors && Object.seal) {
+ return Object.seal(mediator);
+ }
+ };
+ utils.readonly(mediator, 'seal');
+ return module.exports = mediator;
+ }
+});
+
+require.define({
+ 'chaplin/dispatcher': function(exports, require, module) {
+ var Backbone, Dispatcher, EventBroker, utils, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ utils = require('chaplin/lib/utils');
+ EventBroker = require('chaplin/lib/event_broker');
+ return module.exports = Dispatcher = (function() {
+
+ Dispatcher.extend = Backbone.Model.extend;
+
+ _(Dispatcher.prototype).extend(EventBroker);
+
+ Dispatcher.prototype.previousControllerName = null;
+
+ Dispatcher.prototype.currentControllerName = null;
+
+ Dispatcher.prototype.currentController = null;
+
+ Dispatcher.prototype.currentAction = null;
+
+ Dispatcher.prototype.currentParams = null;
+
+ Dispatcher.prototype.url = null;
+
+ function Dispatcher() {
+ this.initialize.apply(this, arguments);
+ }
+
+ Dispatcher.prototype.initialize = function(options) {
+ if (options == null) {
+ options = {};
+ }
+ this.settings = _(options).defaults({
+ controllerPath: 'controllers/',
+ controllerSuffix: '_controller'
+ });
+ this.subscribeEvent('matchRoute', this.matchRoute);
+ return this.subscribeEvent('!startupController', this.startupController);
+ };
+
+ Dispatcher.prototype.matchRoute = function(route, params) {
+ return this.startupController(route.controller, route.action, params);
+ };
+
+ Dispatcher.prototype.startupController = function(controllerName, action, params) {
+ var handler, isSameController;
+ if (action == null) {
+ action = 'index';
+ }
+ if (params == null) {
+ params = {};
+ }
+ if (params.changeURL !== false) {
+ params.changeURL = true;
+ }
+ if (params.forceStartup !== true) {
+ params.forceStartup = false;
+ }
+ isSameController = !params.forceStartup && this.currentControllerName === controllerName && this.currentAction === action && (!this.currentParams || _(params).isEqual(this.currentParams));
+ if (isSameController) {
+ return;
+ }
+ handler = _(this.controllerLoaded).bind(this, controllerName, action, params);
+ return this.loadController(controllerName, handler);
+ };
+
+ Dispatcher.prototype.loadController = function(controllerName, handler) {
+ var controllerFileName, path;
+ controllerFileName = utils.underscorize(controllerName) + this.settings.controllerSuffix;
+ path = this.settings.controllerPath + controllerFileName;
+ if (typeof define !== "undefined" && define !== null ? define.amd : void 0) {
+ return require([path], handler);
+ } else {
+ return handler(require(path));
+ }
+ };
+
+ Dispatcher.prototype.controllerLoaded = function(controllerName, action, params, ControllerConstructor) {
+ var controller, currentController, currentControllerName;
+ currentControllerName = this.currentControllerName || null;
+ currentController = this.currentController || null;
+ if (currentController) {
+ this.publishEvent('beforeControllerDispose', currentController);
+ currentController.dispose(params, controllerName);
+ }
+ controller = new ControllerConstructor(params, currentControllerName);
+ controller[action](params, currentControllerName);
+ if (controller.redirected) {
+ return;
+ }
+ this.previousControllerName = currentControllerName;
+ this.currentControllerName = controllerName;
+ this.currentController = controller;
+ this.currentAction = action;
+ this.currentParams = params;
+ this.adjustURL(controller, params);
+ return this.publishEvent('startupController', {
+ previousControllerName: this.previousControllerName,
+ controller: this.currentController,
+ controllerName: this.currentControllerName,
+ params: this.currentParams
+ });
+ };
+
+ Dispatcher.prototype.adjustURL = function(controller, params) {
+ var url;
+ if (params.path || params.path === '') {
+ url = params.path;
+ } else if (typeof controller.historyURL === 'function') {
+ url = controller.historyURL(params);
+ } else if (typeof controller.historyURL === 'string') {
+ url = controller.historyURL;
+ } else {
+ throw new Error('Dispatcher#adjustURL: controller for ' + ("" + this.currentControllerName + " does not provide a historyURL"));
+ }
+ if (params.changeURL) {
+ this.publishEvent('!router:changeURL', url);
+ }
+ return this.url = url;
+ };
+
+ Dispatcher.prototype.disposed = false;
+
+ Dispatcher.prototype.dispose = function() {
+ if (this.disposed) {
+ return;
+ }
+ this.unsubscribeAllEvents();
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Dispatcher;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/controllers/controller': function(exports, require, module) {
+ var Backbone, Controller, EventBroker, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ EventBroker = require('chaplin/lib/event_broker');
+ return module.exports = Controller = (function() {
+
+ Controller.extend = Backbone.Model.extend;
+
+ _(Controller.prototype).extend(EventBroker);
+
+ Controller.prototype.view = null;
+
+ Controller.prototype.currentId = null;
+
+ Controller.prototype.redirected = false;
+
+ function Controller() {
+ this.initialize.apply(this, arguments);
+ }
+
+ Controller.prototype.initialize = function() {};
+
+ Controller.prototype.redirectTo = function(arg1, action, params) {
+ this.redirected = true;
+ if (arguments.length === 1) {
+ return this.publishEvent('!router:route', arg1, function(routed) {
+ if (!routed) {
+ throw new Error('Controller#redirectTo: no route matched');
+ }
+ });
+ } else {
+ return this.publishEvent('!startupController', arg1, action, params);
+ }
+ };
+
+ Controller.prototype.disposed = false;
+
+ Controller.prototype.dispose = function() {
+ var obj, prop, properties, _i, _len;
+ if (this.disposed) {
+ return;
+ }
+ for (prop in this) {
+ if (!__hasProp.call(this, prop)) continue;
+ obj = this[prop];
+ if (obj && typeof obj.dispose === 'function') {
+ obj.dispose();
+ delete this[prop];
+ }
+ }
+ this.unsubscribeAllEvents();
+ properties = ['currentId', 'redirected'];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ delete this[prop];
+ }
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Controller;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/models/collection': function(exports, require, module) {
+ var Backbone, Collection, EventBroker, Model, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ EventBroker = require('chaplin/lib/event_broker');
+ Model = require('chaplin/models/model');
+ return module.exports = Collection = (function(_super) {
+
+ __extends(Collection, _super);
+
+ function Collection() {
+ return Collection.__super__.constructor.apply(this, arguments);
+ }
+
+ _(Collection.prototype).extend(EventBroker);
+
+ Collection.prototype.model = Model;
+
+ Collection.prototype.initDeferred = function() {
+ return _(this).extend($.Deferred());
+ };
+
+ Collection.prototype.serialize = function() {
+ var model, _i, _len, _ref, _results;
+ _ref = this.models;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ model = _ref[_i];
+ if (model instanceof Model) {
+ _results.push(model.serialize());
+ } else {
+ _results.push(model.toJSON());
+ }
+ }
+ return _results;
+ };
+
+ Collection.prototype.addAtomic = function(models, options) {
+ var direction, model;
+ if (options == null) {
+ options = {};
+ }
+ if (!models.length) {
+ return;
+ }
+ options.silent = true;
+ direction = typeof options.at === 'number' ? 'pop' : 'shift';
+ while (model = models[direction]()) {
+ this.add(model, options);
+ }
+ return this.trigger('reset');
+ };
+
+ Collection.prototype.update = function(models, options) {
+ var fingerPrint, i, ids, model, newFingerPrint, preexistent, _i, _ids, _len;
+ if (options == null) {
+ options = {};
+ }
+ fingerPrint = this.pluck('id').join();
+ ids = _(models).pluck('id');
+ newFingerPrint = ids.join();
+ if (newFingerPrint !== fingerPrint) {
+ _ids = _(ids);
+ i = this.models.length;
+ while (i--) {
+ model = this.models[i];
+ if (!_ids.include(model.id)) {
+ this.remove(model);
+ }
+ }
+ }
+ if (newFingerPrint !== fingerPrint || options.deep) {
+ for (i = _i = 0, _len = models.length; _i < _len; i = ++_i) {
+ model = models[i];
+ preexistent = this.get(model.id);
+ if (preexistent) {
+ if (options.deep) {
+ preexistent.set(model);
+ }
+ } else {
+ this.add(model, {
+ at: i
+ });
+ }
+ }
+ }
+ };
+
+ Collection.prototype.disposed = false;
+
+ Collection.prototype.dispose = function() {
+ var prop, properties, _i, _len;
+ if (this.disposed) {
+ return;
+ }
+ this.trigger('dispose', this);
+ this.reset([], {
+ silent: true
+ });
+ this.unsubscribeAllEvents();
+ this.off();
+ if (typeof this.reject === "function") {
+ this.reject();
+ }
+ properties = ['model', 'models', '_byId', '_byCid', '_callbacks'];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ delete this[prop];
+ }
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Collection;
+
+ })(Backbone.Collection);
+ }
+});
+
+require.define({
+ 'chaplin/models/model': function(exports, require, module) {
+ var Backbone, EventBroker, Model, utils, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ utils = require('chaplin/lib/utils');
+ EventBroker = require('chaplin/lib/event_broker');
+ return module.exports = Model = (function(_super) {
+ var serializeAttributes;
+
+ __extends(Model, _super);
+
+ function Model() {
+ return Model.__super__.constructor.apply(this, arguments);
+ }
+
+ _(Model.prototype).extend(EventBroker);
+
+ Model.prototype.initDeferred = function() {
+ return _(this).extend($.Deferred());
+ };
+
+ Model.prototype.getAttributes = function() {
+ return this.attributes;
+ };
+
+ serializeAttributes = function(model, attributes, modelStack) {
+ var delegator, item, key, value;
+ if (!modelStack) {
+ delegator = utils.beget(attributes);
+ modelStack = [model];
+ } else {
+ modelStack.push(model);
+ }
+ for (key in attributes) {
+ value = attributes[key];
+ if (value instanceof Backbone.Model) {
+ if (delegator == null) {
+ delegator = utils.beget(attributes);
+ }
+ delegator[key] = value === model || __indexOf.call(modelStack, value) >= 0 ? null : serializeAttributes(value, value.getAttributes(), modelStack);
+ } else if (value instanceof Backbone.Collection) {
+ if (delegator == null) {
+ delegator = utils.beget(attributes);
+ }
+ delegator[key] = (function() {
+ var _i, _len, _ref, _results;
+ _ref = value.models;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ item = _ref[_i];
+ _results.push(serializeAttributes(item, item.getAttributes(), modelStack));
+ }
+ return _results;
+ })();
+ }
+ }
+ modelStack.pop();
+ return delegator || attributes;
+ };
+
+ Model.prototype.serialize = function() {
+ return serializeAttributes(this, this.getAttributes());
+ };
+
+ Model.prototype.disposed = false;
+
+ Model.prototype.dispose = function() {
+ var prop, properties, _i, _len;
+ if (this.disposed) {
+ return;
+ }
+ this.trigger('dispose', this);
+ this.unsubscribeAllEvents();
+ this.off();
+ if (typeof this.reject === "function") {
+ this.reject();
+ }
+ properties = ['collection', 'attributes', 'changed', '_escapedAttributes', '_previousAttributes', '_silent', '_pending', '_callbacks'];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ delete this[prop];
+ }
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Model;
+
+ })(Backbone.Model);
+ }
+});
+
+require.define({
+ 'chaplin/views/layout': function(exports, require, module) {
+ var $, Backbone, EventBroker, Layout, utils, _;
+ $ = require('jquery');
+ _ = require('underscore');
+ Backbone = require('backbone');
+ utils = require('chaplin/lib/utils');
+ EventBroker = require('chaplin/lib/event_broker');
+ return module.exports = Layout = (function() {
+
+ Layout.extend = Backbone.Model.extend;
+
+ _(Layout.prototype).extend(EventBroker);
+
+ Layout.prototype.title = '';
+
+ Layout.prototype.events = {};
+
+ Layout.prototype.el = document;
+
+ Layout.prototype.$el = $(document);
+
+ Layout.prototype.cid = 'chaplin-layout';
+
+ function Layout() {
+ this.openLink = __bind(this.openLink, this);
+ this.initialize.apply(this, arguments);
+ }
+
+ Layout.prototype.initialize = function(options) {
+ if (options == null) {
+ options = {};
+ }
+ this.title = options.title;
+ this.settings = _(options).defaults({
+ titleTemplate: _.template("<%= subtitle %> \u2013 <%= title %>"),
+ openExternalToBlank: false,
+ routeLinks: 'a, .go-to',
+ skipRouting: '.noscript',
+ scrollTo: [0, 0]
+ });
+ this.subscribeEvent('beforeControllerDispose', this.hideOldView);
+ this.subscribeEvent('startupController', this.showNewView);
+ this.subscribeEvent('startupController', this.adjustTitle);
+ if (this.settings.routeLinks) {
+ this.startLinkRouting();
+ }
+ return this.delegateEvents();
+ };
+
+ Layout.prototype.delegateEvents = Backbone.View.prototype.delegateEvents;
+
+ Layout.prototype.undelegateEvents = Backbone.View.prototype.undelegateEvents;
+
+ Layout.prototype.hideOldView = function(controller) {
+ var scrollTo, view;
+ scrollTo = this.settings.scrollTo;
+ if (scrollTo) {
+ window.scrollTo(scrollTo[0], scrollTo[1]);
+ }
+ view = controller.view;
+ if (view) {
+ return view.$el.css('display', 'none');
+ }
+ };
+
+ Layout.prototype.showNewView = function(context) {
+ var view;
+ view = context.controller.view;
+ if (view) {
+ return view.$el.css({
+ display: 'block',
+ opacity: 1,
+ visibility: 'visible'
+ });
+ }
+ };
+
+ Layout.prototype.adjustTitle = function(context) {
+ var subtitle, title;
+ title = this.title || '';
+ subtitle = context.controller.title || '';
+ title = this.settings.titleTemplate({
+ title: title,
+ subtitle: subtitle
+ });
+ return setTimeout((function() {
+ return document.title = title;
+ }), 50);
+ };
+
+ Layout.prototype.startLinkRouting = function() {
+ if (this.settings.routeLinks) {
+ return $(document).on('click', this.settings.routeLinks, this.openLink);
+ }
+ };
+
+ Layout.prototype.stopLinkRouting = function() {
+ if (this.settings.routeLinks) {
+ return $(document).off('click', this.settings.routeLinks);
+ }
+ };
+
+ Layout.prototype.openLink = function(event) {
+ var $el, el, href, internal, isAnchor, path, skipRouting, type, _ref, _ref1;
+ if (utils.modifierKeyPressed(event)) {
+ return;
+ }
+ el = event.currentTarget;
+ $el = $(el);
+ isAnchor = el.nodeName === 'A';
+ href = $el.attr('href') || $el.data('href') || null;
+ if (href === null || href === void 0 || href === '' || href.charAt(0) === '#') {
+ return;
+ }
+ if (isAnchor && ($el.attr('target') === '_blank' || $el.attr('rel') === 'external' || ((_ref = el.protocol) !== 'http:' && _ref !== 'https:' && _ref !== 'file:'))) {
+ return;
+ }
+ skipRouting = this.settings.skipRouting;
+ type = typeof skipRouting;
+ if (type === 'function' && !skipRouting(href, el) || type === 'string' && $el.is(skipRouting)) {
+ return;
+ }
+ internal = !isAnchor || ((_ref1 = el.hostname) === location.hostname || _ref1 === '');
+ if (!internal) {
+ if (this.settings.openExternalToBlank) {
+ event.preventDefault();
+ window.open(el.href);
+ }
+ return;
+ }
+ if (isAnchor) {
+ path = el.pathname + el.search;
+ if (path.charAt(0) !== '/') {
+ path = "/" + path;
+ }
+ } else {
+ path = href;
+ }
+ this.publishEvent('!router:route', path, function(routed) {
+ if (routed) {
+ event.preventDefault();
+ } else if (!isAnchor) {
+ location.href = path;
+ }
+ });
+ };
+
+ Layout.prototype.disposed = false;
+
+ Layout.prototype.dispose = function() {
+ if (this.disposed) {
+ return;
+ }
+ this.stopLinkRouting();
+ this.unsubscribeAllEvents();
+ this.undelegateEvents();
+ delete this.title;
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Layout;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/views/view': function(exports, require, module) {
+ var $, Backbone, Collection, EventBroker, Model, View, utils, _;
+ $ = require('jquery');
+ _ = require('underscore');
+ Backbone = require('backbone');
+ utils = require('chaplin/lib/utils');
+ EventBroker = require('chaplin/lib/event_broker');
+ Model = require('chaplin/models/model');
+ Collection = require('chaplin/models/collection');
+ return module.exports = View = (function(_super) {
+
+ __extends(View, _super);
+
+ _(View.prototype).extend(EventBroker);
+
+ View.prototype.autoRender = false;
+
+ View.prototype.container = null;
+
+ View.prototype.containerMethod = 'append';
+
+ View.prototype.subviews = null;
+
+ View.prototype.subviewsByName = null;
+
+ View.prototype.wrapMethod = function(name) {
+ var func, instance;
+ instance = this;
+ func = instance[name];
+ instance["" + name + "IsWrapped"] = true;
+ return instance[name] = function() {
+ if (this.disposed) {
+ return false;
+ }
+ func.apply(instance, arguments);
+ instance["after" + (utils.upcase(name))].apply(instance, arguments);
+ return instance;
+ };
+ };
+
+ function View() {
+ if (this.initialize !== View.prototype.initialize) {
+ this.wrapMethod('initialize');
+ }
+ if (this.render !== View.prototype.render) {
+ this.wrapMethod('render');
+ } else {
+ this.render = _(this.render).bind(this);
+ }
+ View.__super__.constructor.apply(this, arguments);
+ }
+
+ View.prototype.initialize = function(options) {
+ var prop, _i, _len, _ref;
+ if (options) {
+ _ref = ['autoRender', 'container', 'containerMethod'];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ prop = _ref[_i];
+ if (options[prop] != null) {
+ this[prop] = options[prop];
+ }
+ }
+ }
+ this.subviews = [];
+ this.subviewsByName = {};
+ if (this.model || this.collection) {
+ this.modelBind('dispose', this.dispose);
+ }
+ if (!this.initializeIsWrapped) {
+ return this.afterInitialize();
+ }
+ };
+
+ View.prototype.afterInitialize = function() {
+ if (this.autoRender) {
+ return this.render();
+ }
+ };
+
+ View.prototype.delegate = function(eventType, second, third) {
+ var event, events, handler, list, selector;
+ if (typeof eventType !== 'string') {
+ throw new TypeError('View#delegate: first argument must be a string');
+ }
+ if (arguments.length === 2) {
+ handler = second;
+ } else if (arguments.length === 3) {
+ selector = second;
+ if (typeof selector !== 'string') {
+ throw new TypeError('View#delegate: ' + 'second argument must be a string');
+ }
+ handler = third;
+ } else {
+ throw new TypeError('View#delegate: ' + 'only two or three arguments are allowed');
+ }
+ if (typeof handler !== 'function') {
+ throw new TypeError('View#delegate: ' + 'handler argument must be function');
+ }
+ list = (function() {
+ var _i, _len, _ref, _results;
+ _ref = eventType.split(' ');
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ event = _ref[_i];
+ _results.push("" + event + ".delegate" + this.cid);
+ }
+ return _results;
+ }).call(this);
+ events = list.join(' ');
+ handler = _(handler).bind(this);
+ if (selector) {
+ this.$el.on(events, selector, handler);
+ } else {
+ this.$el.on(events, handler);
+ }
+ return handler;
+ };
+
+ View.prototype.undelegate = function() {
+ return this.$el.unbind(".delegate" + this.cid);
+ };
+
+ View.prototype.modelBind = function(type, handler) {
+ var modelOrCollection;
+ if (typeof type !== 'string') {
+ throw new TypeError('View#modelBind: ' + 'type must be a string');
+ }
+ if (typeof handler !== 'function') {
+ throw new TypeError('View#modelBind: ' + 'handler argument must be function');
+ }
+ modelOrCollection = this.model || this.collection;
+ if (!modelOrCollection) {
+ throw new TypeError('View#modelBind: no model or collection set');
+ }
+ modelOrCollection.off(type, handler, this);
+ return modelOrCollection.on(type, handler, this);
+ };
+
+ View.prototype.modelUnbind = function(type, handler) {
+ var modelOrCollection;
+ if (typeof type !== 'string') {
+ throw new TypeError('View#modelUnbind: ' + 'type argument must be a string');
+ }
+ if (typeof handler !== 'function') {
+ throw new TypeError('View#modelUnbind: ' + 'handler argument must be a function');
+ }
+ modelOrCollection = this.model || this.collection;
+ if (!modelOrCollection) {
+ return;
+ }
+ return modelOrCollection.off(type, handler);
+ };
+
+ View.prototype.modelUnbindAll = function() {
+ var modelOrCollection;
+ modelOrCollection = this.model || this.collection;
+ if (!modelOrCollection) {
+ return;
+ }
+ return modelOrCollection.off(null, null, this);
+ };
+
+ View.prototype.pass = function(attribute, selector) {
+ var _this = this;
+ return this.modelBind("change:" + attribute, function(model, value) {
+ var $el;
+ $el = _this.$(selector);
+ if ($el.is('input, textarea, select, button')) {
+ return $el.val(value);
+ } else {
+ return $el.text(value);
+ }
+ });
+ };
+
+ View.prototype.subview = function(name, view) {
+ if (name && view) {
+ this.removeSubview(name);
+ this.subviews.push(view);
+ this.subviewsByName[name] = view;
+ return view;
+ } else if (name) {
+ return this.subviewsByName[name];
+ }
+ };
+
+ View.prototype.removeSubview = function(nameOrView) {
+ var index, name, otherName, otherView, view, _ref;
+ if (!nameOrView) {
+ return;
+ }
+ if (typeof nameOrView === 'string') {
+ name = nameOrView;
+ view = this.subviewsByName[name];
+ } else {
+ view = nameOrView;
+ _ref = this.subviewsByName;
+ for (otherName in _ref) {
+ otherView = _ref[otherName];
+ if (view === otherView) {
+ name = otherName;
+ break;
+ }
+ }
+ }
+ if (!(name && view && view.dispose)) {
+ return;
+ }
+ view.dispose();
+ index = _(this.subviews).indexOf(view);
+ if (index > -1) {
+ this.subviews.splice(index, 1);
+ }
+ return delete this.subviewsByName[name];
+ };
+
+ View.prototype.getTemplateData = function() {
+ var items, model, modelOrCollection, templateData, _i, _len, _ref;
+ if (this.model) {
+ templateData = this.model instanceof Model ? this.model.serialize() : utils.beget(this.model.attributes);
+ } else if (this.collection) {
+ if (this.collection instanceof Collection) {
+ items = this.collection.serialize();
+ } else {
+ items = [];
+ _ref = this.collection.models;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ model = _ref[_i];
+ items.push(utils.beget(model.attributes));
+ }
+ }
+ templateData = {
+ items: items
+ };
+ } else {
+ templateData = {};
+ }
+ modelOrCollection = this.model || this.collection;
+ if (modelOrCollection) {
+ if (typeof modelOrCollection.state === 'function' && !('resolved' in templateData)) {
+ templateData.resolved = modelOrCollection.state() === 'resolved';
+ }
+ if (typeof modelOrCollection.isSynced === 'function' && !('synced' in templateData)) {
+ templateData.synced = modelOrCollection.isSynced();
+ }
+ }
+ return templateData;
+ };
+
+ View.prototype.getTemplateFunction = function() {
+ throw new Error('View#getTemplateFunction must be overridden');
+ };
+
+ View.prototype.render = function() {
+ var html, templateFunc;
+ if (this.disposed) {
+ return false;
+ }
+ templateFunc = this.getTemplateFunction();
+ if (typeof templateFunc === 'function') {
+ html = templateFunc(this.getTemplateData());
+ this.$el.empty().append(html);
+ }
+ if (!this.renderIsWrapped) {
+ this.afterRender();
+ }
+ return this;
+ };
+
+ View.prototype.afterRender = function() {
+ if (this.container) {
+ $(this.container)[this.containerMethod](this.el);
+ return this.trigger('addedToDOM');
+ }
+ };
+
+ View.prototype.disposed = false;
+
+ View.prototype.dispose = function() {
+ var prop, properties, subview, _i, _j, _len, _len1, _ref;
+ if (this.disposed) {
+ return;
+ }
+ _ref = this.subviews;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ subview = _ref[_i];
+ subview.dispose();
+ }
+ this.unsubscribeAllEvents();
+ this.modelUnbindAll();
+ this.off();
+ this.$el.remove();
+ properties = ['el', '$el', 'options', 'model', 'collection', 'subviews', 'subviewsByName', '_callbacks'];
+ for (_j = 0, _len1 = properties.length; _j < _len1; _j++) {
+ prop = properties[_j];
+ delete this[prop];
+ }
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return View;
+
+ })(Backbone.View);
+ }
+});
+
+require.define({
+ 'chaplin/views/collection_view': function(exports, require, module) {
+ var $, CollectionView, View, _;
+ $ = require('jquery');
+ _ = require('underscore');
+ View = require('chaplin/views/view');
+ return module.exports = CollectionView = (function(_super) {
+
+ __extends(CollectionView, _super);
+
+ function CollectionView() {
+ this.renderAllItems = __bind(this.renderAllItems, this);
+
+ this.showHideFallback = __bind(this.showHideFallback, this);
+
+ this.itemsResetted = __bind(this.itemsResetted, this);
+
+ this.itemRemoved = __bind(this.itemRemoved, this);
+
+ this.itemAdded = __bind(this.itemAdded, this);
+ return CollectionView.__super__.constructor.apply(this, arguments);
+ }
+
+ CollectionView.prototype.itemView = null;
+
+ CollectionView.prototype.autoRender = true;
+
+ CollectionView.prototype.renderItems = true;
+
+ CollectionView.prototype.animationDuration = 500;
+
+ CollectionView.prototype.useCssAnimation = false;
+
+ CollectionView.prototype.listSelector = null;
+
+ CollectionView.prototype.$list = null;
+
+ CollectionView.prototype.fallbackSelector = null;
+
+ CollectionView.prototype.$fallback = null;
+
+ CollectionView.prototype.loadingSelector = null;
+
+ CollectionView.prototype.$loading = null;
+
+ CollectionView.prototype.itemSelector = null;
+
+ CollectionView.prototype.filterer = null;
+
+ CollectionView.prototype.filterCallback = function(view, included) {
+ var display;
+ display = included ? '' : 'none';
+ return view.$el.stop(true, true).css('display', display);
+ };
+
+ CollectionView.prototype.visibleItems = null;
+
+ CollectionView.prototype.initialize = function(options) {
+ if (options == null) {
+ options = {};
+ }
+ CollectionView.__super__.initialize.apply(this, arguments);
+ this.visibleItems = [];
+ this.addCollectionListeners();
+ if (options.renderItems != null) {
+ this.renderItems = options.renderItems;
+ }
+ if (options.itemView != null) {
+ this.itemView = options.itemView;
+ }
+ if (options.filterer != null) {
+ return this.filter(options.filterer);
+ }
+ };
+
+ CollectionView.prototype.addCollectionListeners = function() {
+ this.modelBind('add', this.itemAdded);
+ this.modelBind('remove', this.itemRemoved);
+ return this.modelBind('reset', this.itemsResetted);
+ };
+
+ CollectionView.prototype.getTemplateFunction = function() {};
+
+ CollectionView.prototype.render = function() {
+ CollectionView.__super__.render.apply(this, arguments);
+ this.$list = this.listSelector ? this.$(this.listSelector) : this.$el;
+ this.initFallback();
+ this.initLoadingIndicator();
+ if (this.renderItems) {
+ return this.renderAllItems();
+ }
+ };
+
+ CollectionView.prototype.itemAdded = function(item, collection, options) {
+ if (options == null) {
+ options = {};
+ }
+ return this.renderAndInsertItem(item, options.index);
+ };
+
+ CollectionView.prototype.itemRemoved = function(item) {
+ return this.removeViewForItem(item);
+ };
+
+ CollectionView.prototype.itemsResetted = function() {
+ return this.renderAllItems();
+ };
+
+ CollectionView.prototype.initFallback = function() {
+ if (!this.fallbackSelector) {
+ return;
+ }
+ this.$fallback = this.$(this.fallbackSelector);
+ this.on('visibilityChange', this.showHideFallback);
+ this.modelBind('syncStateChange', this.showHideFallback);
+ return this.showHideFallback();
+ };
+
+ CollectionView.prototype.showHideFallback = function() {
+ var visible;
+ visible = this.visibleItems.length === 0 && (typeof this.collection.isSynced === 'function' ? this.collection.isSynced() : true);
+ return this.$fallback.css('display', visible ? 'block' : 'none');
+ };
+
+ CollectionView.prototype.initLoadingIndicator = function() {
+ if (!(this.loadingSelector && typeof this.collection.isSyncing === 'function')) {
+ return;
+ }
+ this.$loading = this.$(this.loadingSelector);
+ this.modelBind('syncStateChange', this.showHideLoadingIndicator);
+ return this.showHideLoadingIndicator();
+ };
+
+ CollectionView.prototype.showHideLoadingIndicator = function() {
+ var visible;
+ visible = this.collection.length === 0 && this.collection.isSyncing();
+ return this.$loading.css('display', visible ? 'block' : 'none');
+ };
+
+ CollectionView.prototype.getItemViews = function() {
+ var itemViews, name, view, _ref;
+ itemViews = {};
+ _ref = this.subviewsByName;
+ for (name in _ref) {
+ view = _ref[name];
+ if (name.slice(0, 9) === 'itemView:') {
+ itemViews[name.slice(9)] = view;
+ }
+ }
+ return itemViews;
+ };
+
+ CollectionView.prototype.filter = function(filterer, filterCallback) {
+ var included, index, item, view, _i, _len, _ref;
+ this.filterer = filterer;
+ if (filterCallback) {
+ this.filterCallback = filterCallback;
+ }
+ if (filterCallback == null) {
+ filterCallback = this.filterCallback;
+ }
+ if (!_(this.getItemViews()).isEmpty()) {
+ _ref = this.collection.models;
+ for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) {
+ item = _ref[index];
+ included = typeof filterer === 'function' ? filterer(item, index) : true;
+ view = this.subview("itemView:" + item.cid);
+ if (!view) {
+ throw new Error('CollectionView#filter: ' + ("no view found for " + item.cid));
+ }
+ this.filterCallback(view, included);
+ this.updateVisibleItems(view.model, included, false);
+ }
+ }
+ return this.trigger('visibilityChange', this.visibleItems);
+ };
+
+ CollectionView.prototype.renderAllItems = function() {
+ var cid, index, item, items, remainingViewsByCid, view, _i, _j, _len, _len1, _ref;
+ items = this.collection.models;
+ this.visibleItems = [];
+ remainingViewsByCid = {};
+ for (_i = 0, _len = items.length; _i < _len; _i++) {
+ item = items[_i];
+ view = this.subview("itemView:" + item.cid);
+ if (view) {
+ remainingViewsByCid[item.cid] = view;
+ }
+ }
+ _ref = this.getItemViews();
+ for (cid in _ref) {
+ if (!__hasProp.call(_ref, cid)) continue;
+ view = _ref[cid];
+ if (!(cid in remainingViewsByCid)) {
+ this.removeSubview("itemView:" + cid);
+ }
+ }
+ for (index = _j = 0, _len1 = items.length; _j < _len1; index = ++_j) {
+ item = items[index];
+ view = this.subview("itemView:" + item.cid);
+ if (view) {
+ this.insertView(item, view, index, false);
+ } else {
+ this.renderAndInsertItem(item, index);
+ }
+ }
+ if (!items.length) {
+ return this.trigger('visibilityChange', this.visibleItems);
+ }
+ };
+
+ CollectionView.prototype.renderAndInsertItem = function(item, index) {
+ var view;
+ view = this.renderItem(item);
+ return this.insertView(item, view, index);
+ };
+
+ CollectionView.prototype.renderItem = function(item) {
+ var view;
+ view = this.subview("itemView:" + item.cid);
+ if (!view) {
+ view = this.getView(item);
+ this.subview("itemView:" + item.cid, view);
+ }
+ view.render();
+ return view;
+ };
+
+ CollectionView.prototype.getView = function(model) {
+ if (this.itemView) {
+ return new this.itemView({
+ model: model
+ });
+ } else {
+ throw new Error('The CollectionView#itemView property ' + 'must be defined or the getView() must be overridden.');
+ }
+ };
+
+ CollectionView.prototype.insertView = function(item, view, index, enableAnimation) {
+ var $list, $next, $previous, $viewEl, children, included, length, position, viewEl,
+ _this = this;
+ if (index == null) {
+ index = null;
+ }
+ if (enableAnimation == null) {
+ enableAnimation = true;
+ }
+ position = typeof index === 'number' ? index : this.collection.indexOf(item);
+ included = typeof this.filterer === 'function' ? this.filterer(item, position) : true;
+ viewEl = view.el;
+ $viewEl = view.$el;
+ if (included) {
+ if (enableAnimation) {
+ if (this.useCssAnimation) {
+ $viewEl.addClass('animated-item-view');
+ } else {
+ $viewEl.css('opacity', 0);
+ }
+ }
+ } else {
+ this.filterCallback(view, included);
+ }
+ $list = this.$list;
+ children = this.itemSelector ? $list.children(this.itemSelector) : $list.children();
+ if (children.get(position) !== viewEl) {
+ length = children.length;
+ if (length === 0 || position === length) {
+ $list.append(viewEl);
+ } else {
+ if (position === 0) {
+ $next = children.eq(position);
+ $next.before(viewEl);
+ } else {
+ $previous = children.eq(position - 1);
+ $previous.after(viewEl);
+ }
+ }
+ }
+ view.trigger('addedToDOM');
+ this.updateVisibleItems(item, included);
+ if (enableAnimation && included) {
+ if (this.useCssAnimation) {
+ setTimeout(function() {
+ return $viewEl.addClass('animated-item-view-end');
+ }, 0);
+ } else {
+ $viewEl.animate({
+ opacity: 1
+ }, this.animationDuration);
+ }
+ }
+ };
+
+ CollectionView.prototype.removeViewForItem = function(item) {
+ this.updateVisibleItems(item, false);
+ return this.removeSubview("itemView:" + item.cid);
+ };
+
+ CollectionView.prototype.updateVisibleItems = function(item, includedInFilter, triggerEvent) {
+ var includedInVisibleItems, visibilityChanged, visibleItemsIndex;
+ if (triggerEvent == null) {
+ triggerEvent = true;
+ }
+ visibilityChanged = false;
+ visibleItemsIndex = _(this.visibleItems).indexOf(item);
+ includedInVisibleItems = visibleItemsIndex > -1;
+ if (includedInFilter && !includedInVisibleItems) {
+ this.visibleItems.push(item);
+ visibilityChanged = true;
+ } else if (!includedInFilter && includedInVisibleItems) {
+ this.visibleItems.splice(visibleItemsIndex, 1);
+ visibilityChanged = true;
+ }
+ if (visibilityChanged && triggerEvent) {
+ this.trigger('visibilityChange', this.visibleItems);
+ }
+ return visibilityChanged;
+ };
+
+ CollectionView.prototype.dispose = function() {
+ var prop, properties, _i, _len;
+ if (this.disposed) {
+ return;
+ }
+ properties = ['$list', '$fallback', '$loading', 'visibleItems'];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ delete this[prop];
+ }
+ return CollectionView.__super__.dispose.apply(this, arguments);
+ };
+
+ return CollectionView;
+
+ })(View);
+ }
+});
+
+require.define({
+ 'chaplin/lib/route': function(exports, require, module) {
+ var Backbone, Controller, EventBroker, Route, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ EventBroker = require('chaplin/lib/event_broker');
+ Controller = require('chaplin/controllers/controller');
+ return module.exports = Route = (function() {
+ var escapeRegExp, queryStringFieldSeparator, queryStringValueSeparator, reservedParams;
+
+ Route.extend = Backbone.Model.extend;
+
+ _(Route.prototype).extend(EventBroker);
+
+ reservedParams = ['path', 'changeURL'];
+
+ escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ queryStringFieldSeparator = '&';
+
+ queryStringValueSeparator = '=';
+
+ function Route(pattern, target, options) {
+ var _ref;
+ this.options = options != null ? options : {};
+ this.handler = __bind(this.handler, this);
+
+ this.addParamName = __bind(this.addParamName, this);
+
+ this.pattern = pattern;
+ _ref = target.split('#'), this.controller = _ref[0], this.action = _ref[1];
+ if (_(Controller.prototype).has(this.action)) {
+ throw new Error('Route: You should not use existing controller properties as action names');
+ }
+ this.createRegExp();
+ }
+
+ Route.prototype.createRegExp = function() {
+ var pattern;
+ if (_.isRegExp(this.pattern)) {
+ this.regExp = this.pattern;
+ return;
+ }
+ pattern = this.pattern.replace(escapeRegExp, '\\$&').replace(/(?::|\*)(\w+)/g, this.addParamName);
+ return this.regExp = RegExp("^" + pattern + "(?=\\?|$)");
+ };
+
+ Route.prototype.addParamName = function(match, paramName) {
+ var _ref;
+ if ((_ref = this.paramNames) == null) {
+ this.paramNames = [];
+ }
+ if (_(reservedParams).include(paramName)) {
+ throw new Error("Route#addParamName: parameter name " + paramName + " is reserved");
+ }
+ this.paramNames.push(paramName);
+ if (match.charAt(0) === ':') {
+ return '([^\/\?]+)';
+ } else {
+ return '(.*?)';
+ }
+ };
+
+ Route.prototype.test = function(path) {
+ var constraint, constraints, matched, name, params;
+ matched = this.regExp.test(path);
+ if (!matched) {
+ return false;
+ }
+ constraints = this.options.constraints;
+ if (constraints) {
+ params = this.extractParams(path);
+ for (name in constraints) {
+ if (!__hasProp.call(constraints, name)) continue;
+ constraint = constraints[name];
+ if (!constraint.test(params[name])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ Route.prototype.handler = function(path, options) {
+ var params;
+ params = this.buildParams(path, options);
+ return this.publishEvent('matchRoute', this, params);
+ };
+
+ Route.prototype.buildParams = function(path, options) {
+ var params, patternParams, queryParams;
+ params = {};
+ queryParams = this.extractQueryParams(path);
+ _(params).extend(queryParams);
+ patternParams = this.extractParams(path);
+ _(params).extend(patternParams);
+ _(params).extend(this.options.params);
+ params.changeURL = Boolean(options && options.changeURL);
+ params.path = path;
+ return params;
+ };
+
+ Route.prototype.extractParams = function(path) {
+ var index, match, matches, paramName, params, _i, _len, _ref;
+ params = {};
+ matches = this.regExp.exec(path);
+ _ref = matches.slice(1);
+ for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) {
+ match = _ref[index];
+ paramName = this.paramNames ? this.paramNames[index] : index;
+ params[paramName] = match;
+ }
+ return params;
+ };
+
+ Route.prototype.extractQueryParams = function(path) {
+ var current, field, matches, pair, pairs, params, queryString, regExp, value, _i, _len, _ref;
+ params = {};
+ regExp = /\?(.+?)(?=#|$)/;
+ matches = regExp.exec(path);
+ if (!matches) {
+ return params;
+ }
+ queryString = matches[1];
+ pairs = queryString.split(queryStringFieldSeparator);
+ for (_i = 0, _len = pairs.length; _i < _len; _i++) {
+ pair = pairs[_i];
+ if (!pair.length) {
+ continue;
+ }
+ _ref = pair.split(queryStringValueSeparator), field = _ref[0], value = _ref[1];
+ if (!field.length) {
+ continue;
+ }
+ field = decodeURIComponent(field);
+ value = decodeURIComponent(value);
+ current = params[field];
+ if (current) {
+ if (current.push) {
+ current.push(value);
+ } else {
+ params[field] = [current, value];
+ }
+ } else {
+ params[field] = value;
+ }
+ }
+ return params;
+ };
+
+ return Route;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/lib/router': function(exports, require, module) {
+ var Backbone, EventBroker, Route, Router, mediator, _;
+ _ = require('underscore');
+ Backbone = require('backbone');
+ mediator = require('chaplin/mediator');
+ EventBroker = require('chaplin/lib/event_broker');
+ Route = require('chaplin/lib/route');
+ return module.exports = Router = (function() {
+
+ Router.extend = Backbone.Model.extend;
+
+ _(Router.prototype).extend(EventBroker);
+
+ function Router(options) {
+ this.options = options != null ? options : {};
+ this.route = __bind(this.route, this);
+
+ this.match = __bind(this.match, this);
+
+ _(this.options).defaults({
+ pushState: true
+ });
+ this.subscribeEvent('!router:route', this.routeHandler);
+ this.subscribeEvent('!router:changeURL', this.changeURLHandler);
+ this.createHistory();
+ }
+
+ Router.prototype.createHistory = function() {
+ return Backbone.history || (Backbone.history = new Backbone.History());
+ };
+
+ Router.prototype.startHistory = function() {
+ return Backbone.history.start(this.options);
+ };
+
+ Router.prototype.stopHistory = function() {
+ if (Backbone.History.started) {
+ return Backbone.history.stop();
+ }
+ };
+
+ Router.prototype.match = function(pattern, target, options) {
+ var route;
+ if (options == null) {
+ options = {};
+ }
+ route = new Route(pattern, target, options);
+ Backbone.history.handlers.push({
+ route: route,
+ callback: route.handler
+ });
+ return route;
+ };
+
+ Router.prototype.route = function(path) {
+ var handler, _i, _len, _ref;
+ path = path.replace(/^(\/#|\/)/, '');
+ _ref = Backbone.history.handlers;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ handler = _ref[_i];
+ if (handler.route.test(path)) {
+ handler.callback(path, {
+ changeURL: true
+ });
+ return true;
+ }
+ }
+ return false;
+ };
+
+ Router.prototype.routeHandler = function(path, callback) {
+ var routed;
+ routed = this.route(path);
+ return typeof callback === "function" ? callback(routed) : void 0;
+ };
+
+ Router.prototype.changeURL = function(url) {
+ return Backbone.history.navigate(url, {
+ trigger: false
+ });
+ };
+
+ Router.prototype.changeURLHandler = function(url) {
+ return this.changeURL(url);
+ };
+
+ Router.prototype.disposed = false;
+
+ Router.prototype.dispose = function() {
+ if (this.disposed) {
+ return;
+ }
+ this.stopHistory();
+ delete Backbone.history;
+ this.unsubscribeAllEvents();
+ this.disposed = true;
+ return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
+ };
+
+ return Router;
+
+ })();
+ }
+});
+
+require.define({
+ 'chaplin/lib/delayer': function(exports, require, module) {
+ var Delayer;
+ Delayer = {
+ setTimeout: function(name, time, handler) {
+ var handle, wrappedHandler, _ref,
+ _this = this;
+ if ((_ref = this.timeouts) == null) {
+ this.timeouts = {};
+ }
+ this.clearTimeout(name);
+ wrappedHandler = function() {
+ delete _this.timeouts[name];
+ return handler();
+ };
+ handle = setTimeout(wrappedHandler, time);
+ this.timeouts[name] = handle;
+ return handle;
+ },
+ clearTimeout: function(name) {
+ if (!(this.timeouts && (this.timeouts[name] != null))) {
+ return;
+ }
+ clearTimeout(this.timeouts[name]);
+ delete this.timeouts[name];
+ },
+ clearAllTimeouts: function() {
+ var handle, name, _ref;
+ if (!this.timeouts) {
+ return;
+ }
+ _ref = this.timeouts;
+ for (name in _ref) {
+ handle = _ref[name];
+ this.clearTimeout(name);
+ }
+ },
+ setInterval: function(name, time, handler) {
+ var handle, _ref;
+ this.clearInterval(name);
+ if ((_ref = this.intervals) == null) {
+ this.intervals = {};
+ }
+ handle = setInterval(handler, time);
+ this.intervals[name] = handle;
+ return handle;
+ },
+ clearInterval: function(name) {
+ if (!(this.intervals && this.intervals[name])) {
+ return;
+ }
+ clearInterval(this.intervals[name]);
+ delete this.intervals[name];
+ },
+ clearAllIntervals: function() {
+ var handle, name, _ref;
+ if (!this.intervals) {
+ return;
+ }
+ _ref = this.intervals;
+ for (name in _ref) {
+ handle = _ref[name];
+ this.clearInterval(name);
+ }
+ },
+ clearDelayed: function() {
+ this.clearAllTimeouts();
+ this.clearAllIntervals();
+ }
+ };
+ if (typeof Object.freeze === "function") {
+ Object.freeze(Delayer);
+ }
+ return module.exports = Delayer;
+ }
+});
+
+require.define({
+ 'chaplin/lib/event_broker': function(exports, require, module) {
+ var EventBroker, mediator;
+ mediator = require('chaplin/mediator');
+ EventBroker = {
+ subscribeEvent: function(type, handler) {
+ if (typeof type !== 'string') {
+ throw new TypeError('EventBroker#subscribeEvent: ' + 'type argument must be a string');
+ }
+ if (typeof handler !== 'function') {
+ throw new TypeError('EventBroker#subscribeEvent: ' + 'handler argument must be a function');
+ }
+ mediator.unsubscribe(type, handler, this);
+ return mediator.subscribe(type, handler, this);
+ },
+ unsubscribeEvent: function(type, handler) {
+ if (typeof type !== 'string') {
+ throw new TypeError('EventBroker#unsubscribeEvent: ' + 'type argument must be a string');
+ }
+ if (typeof handler !== 'function') {
+ throw new TypeError('EventBroker#unsubscribeEvent: ' + 'handler argument must be a function');
+ }
+ return mediator.unsubscribe(type, handler);
+ },
+ unsubscribeAllEvents: function() {
+ return mediator.unsubscribe(null, null, this);
+ },
+ publishEvent: function() {
+ var args, type;
+ type = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+ if (typeof type !== 'string') {
+ throw new TypeError('EventBroker#publishEvent: ' + 'type argument must be a string');
+ }
+ return mediator.publish.apply(mediator, [type].concat(__slice.call(args)));
+ }
+ };
+ if (typeof Object.freeze === "function") {
+ Object.freeze(EventBroker);
+ }
+ return module.exports = EventBroker;
+ }
+});
+
+require.define({
+ 'chaplin/lib/support': function(exports, require, module) {
+ var support;
+ support = {
+ propertyDescriptors: (function() {
+ var o;
+ if (!(typeof Object.defineProperty === 'function' && typeof Object.defineProperties === 'function')) {
+ return false;
+ }
+ try {
+ o = {};
+ Object.defineProperty(o, 'foo', {
+ value: 'bar'
+ });
+ return o.foo === 'bar';
+ } catch (error) {
+ return false;
+ }
+ })()
+ };
+ return module.exports = support;
+ }
+});
+
+require.define({
+ 'chaplin/lib/sync_machine': function(exports, require, module) {
+ var STATE_CHANGE, SYNCED, SYNCING, SyncMachine, UNSYNCED, event, _fn, _i, _len, _ref;
+ UNSYNCED = 'unsynced';
+ SYNCING = 'syncing';
+ SYNCED = 'synced';
+ STATE_CHANGE = 'syncStateChange';
+ SyncMachine = {
+ _syncState: UNSYNCED,
+ _previousSyncState: null,
+ syncState: function() {
+ return this._syncState;
+ },
+ isUnsynced: function() {
+ return this._syncState === UNSYNCED;
+ },
+ isSynced: function() {
+ return this._syncState === SYNCED;
+ },
+ isSyncing: function() {
+ return this._syncState === SYNCING;
+ },
+ unsync: function() {
+ var _ref;
+ if ((_ref = this._syncState) === SYNCING || _ref === SYNCED) {
+ this._previousSync = this._syncState;
+ this._syncState = UNSYNCED;
+ this.trigger(this._syncState, this, this._syncState);
+ this.trigger(STATE_CHANGE, this, this._syncState);
+ }
+ },
+ beginSync: function() {
+ var _ref;
+ if ((_ref = this._syncState) === UNSYNCED || _ref === SYNCED) {
+ this._previousSync = this._syncState;
+ this._syncState = SYNCING;
+ this.trigger(this._syncState, this, this._syncState);
+ this.trigger(STATE_CHANGE, this, this._syncState);
+ }
+ },
+ finishSync: function() {
+ if (this._syncState === SYNCING) {
+ this._previousSync = this._syncState;
+ this._syncState = SYNCED;
+ this.trigger(this._syncState, this, this._syncState);
+ this.trigger(STATE_CHANGE, this, this._syncState);
+ }
+ },
+ abortSync: function() {
+ if (this._syncState === SYNCING) {
+ this._syncState = this._previousSync;
+ this._previousSync = this._syncState;
+ this.trigger(this._syncState, this, this._syncState);
+ this.trigger(STATE_CHANGE, this, this._syncState);
+ }
+ }
+ };
+ _ref = [UNSYNCED, SYNCING, SYNCED, STATE_CHANGE];
+ _fn = function(event) {
+ return SyncMachine[event] = function(callback, context) {
+ if (context == null) {
+ context = this;
+ }
+ this.on(event, callback, context);
+ if (this._syncState === event) {
+ return callback.call(context);
+ }
+ };
+ };
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ event = _ref[_i];
+ _fn(event);
+ }
+ if (typeof Object.freeze === "function") {
+ Object.freeze(SyncMachine);
+ }
+ return module.exports = SyncMachine;
+ }
+});
+
+require.define({
+ 'chaplin/lib/utils': function(exports, require, module) {
+ var support, utils;
+ support = require('chaplin/lib/support');
+ utils = {
+ beget: (function() {
+ var ctor;
+ if (typeof Object.create === 'function') {
+ return Object.create;
+ } else {
+ ctor = function() {};
+ return function(obj) {
+ ctor.prototype = obj;
+ return new ctor;
+ };
+ }
+ })(),
+ readonly: (function() {
+ var readonlyDescriptor;
+ if (support.propertyDescriptors) {
+ readonlyDescriptor = {
+ writable: false,
+ enumerable: true,
+ configurable: false
+ };
+ return function() {
+ var obj, prop, properties, _i, _len;
+ obj = arguments[0], properties = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+ for (_i = 0, _len = properties.length; _i < _len; _i++) {
+ prop = properties[_i];
+ readonlyDescriptor.value = obj[prop];
+ Object.defineProperty(obj, prop, readonlyDescriptor);
+ }
+ return true;
+ };
+ } else {
+ return function() {
+ return false;
+ };
+ }
+ })(),
+ upcase: function(str) {
+ return str.charAt(0).toUpperCase() + str.substring(1);
+ },
+ underscorize: function(string) {
+ return string.replace(/[A-Z]/g, function(char, index) {
+ return (index !== 0 ? '_' : '') + char.toLowerCase();
+ });
+ },
+ modifierKeyPressed: function(event) {
+ return event.shiftKey || event.altKey || event.ctrlKey || event.metaKey;
+ }
+ };
+ if (typeof Object.seal === "function") {
+ Object.seal(utils);
+ }
+ return module.exports = utils;
+ }
+});
+
+require.define({
+ 'chaplin': function(exports, require, module) {
+ var Application, Collection, CollectionView, Controller, Delayer, Dispatcher, EventBroker, Layout, Model, Route, Router, SyncMachine, View, mediator, support, utils;
+ Application = require('chaplin/application');
+ mediator = require('chaplin/mediator');
+ Dispatcher = require('chaplin/dispatcher');
+ Controller = require('chaplin/controllers/controller');
+ Collection = require('chaplin/models/collection');
+ Model = require('chaplin/models/model');
+ Layout = require('chaplin/views/layout');
+ View = require('chaplin/views/view');
+ CollectionView = require('chaplin/views/collection_view');
+ Route = require('chaplin/lib/route');
+ Router = require('chaplin/lib/router');
+ Delayer = require('chaplin/lib/delayer');
+ EventBroker = require('chaplin/lib/event_broker');
+ support = require('chaplin/lib/support');
+ SyncMachine = require('chaplin/lib/sync_machine');
+ utils = require('chaplin/lib/utils');
+ return module.exports = {
+ Application: Application,
+ mediator: mediator,
+ Dispatcher: Dispatcher,
+ Controller: Controller,
+ Collection: Collection,
+ Model: Model,
+ Layout: Layout,
+ View: View,
+ CollectionView: CollectionView,
+ Route: Route,
+ Router: Router,
+ Delayer: Delayer,
+ EventBroker: EventBroker,
+ support: support,
+ SyncMachine: SyncMachine,
+ utils: utils
+ };
+ }
+});
+;
+
diff --git a/labs/dependency-examples/chaplin-brunch/public/stylesheets/app.css b/labs/dependency-examples/chaplin-brunch/public/stylesheets/app.css
new file mode 100644
index 0000000000..1103e0acf4
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/public/stylesheets/app.css
@@ -0,0 +1,5 @@
+#todoapp.filter-active #todo-list .completed {
+ display: none; }
+
+#todoapp.filter-completed #todo-list .active {
+ display: none; }
diff --git a/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-0.9.2.js b/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-0.9.2.js
new file mode 100644
index 0000000000..0d78cf4030
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-0.9.2.js
@@ -0,0 +1,1431 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `global`
+ // on the server).
+ var root = this;
+
+ // 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 a local reference to slice/splice.
+ var slice = Array.prototype.slice;
+ var splice = Array.prototype.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.2';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ var $ = root.jQuery || root.Zepto || root.ender;
+
+ // Set the JavaScript library that will be used for DOM manipulation and
+ // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+ // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+ // alternate JavaScript library (or a mock library for testing your views
+ // outside of a browser).
+ Backbone.setDomLibrary = function(lib) {
+ $ = lib;
+ };
+
+ // 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 `"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
+ // -----------------
+
+ // Regular expression used to split event strings
+ var eventSplitter = /\s+/;
+
+ // 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 one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) return this;
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ while (event = events.shift()) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ }
+
+ return this;
+ },
+
+ // 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 `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ while (event = events.shift()) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) continue;
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ }
+ }
+
+ 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(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ if (node = calls[event]) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ if (node = all) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) this.collection = options.collection;
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ 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,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: 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);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // 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"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ (options || (options = {})).unset = true;
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ (options || (options = {})).unset = true;
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, '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, value, options) {
+ var attrs, current;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) {
+ delete options.wait;
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+ if (options.wait) this.set(current, silentOptions);
+ 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 triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (this.isNew()) {
+ triggerDestroy();
+ return false;
+ }
+
+ options.success = function(resp) {
+ if (options.wait) triggerDestroy();
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ if (!options.wait) triggerDestroy();
+ 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 = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 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, xhr) {
+ 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.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // 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 (!arguments.length) 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, old = this._previousAttributes;
+ 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 (!arguments.length || !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);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. 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) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+ };
+
+ // 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); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ models.splice(dups[i], 1);
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[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);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // 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) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || 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) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return 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) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // 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 `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, '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) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) coll.add(nextModel, options);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', 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, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(model, options) {
+ options || (options = {});
+ if (!(model instanceof Model)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) model = false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ 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)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(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 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) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // 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;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // 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,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.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 || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+ 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._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = $('').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname == this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+ this.fragment = frag;
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+
+ // Backbone.View
+ // -------------
+
+ // 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');
+ this._configure(options || {});
+ 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'];
+
+ // 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 prefered 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 from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // 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 $) ? element : $(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 = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // 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.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // 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 = getValue(this, 'attributes') || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // 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.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.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 (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return $.ajax(_.extend(params, options));
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this);
\ No newline at end of file
diff --git a/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-localStorage.js b/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-localStorage.js
new file mode 100644
index 0000000000..56021e5e72
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-localStorage.js
@@ -0,0 +1,136 @@
+/**
+ * Backbone localStorage Adapter
+ * https://github.com/jeromegn/Backbone.localStorage
+ */
+
+(function() {
+ // A simple module to replace `Backbone.sync` with *localStorage*-based
+ // persistence. Models are given GUIDS, and saved into a JSON object. Simple
+ // as that.
+
+ // Hold reference to Underscore.js and Backbone.js in the closure in order
+ // to make things work even if they are removed from the global namespace
+ var _ = this._;
+ var Backbone = this.Backbone;
+
+ // Generate four random hex digits.
+ function S4() {
+ return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
+ };
+
+ // Generate a pseudo-GUID by concatenating random hexadecimal.
+ function guid() {
+ return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
+ };
+
+ // Our Store is represented by a single JS object in *localStorage*. Create it
+ // with a meaningful name, like the name you'd give a table.
+ // window.Store is deprectated, use Backbone.LocalStorage instead
+ Backbone.LocalStorage = window.Store = function(name) {
+ this.name = name;
+ var store = this.localStorage().getItem(this.name);
+ this.records = (store && store.split(",")) || [];
+ };
+
+ _.extend(Backbone.LocalStorage.prototype, {
+
+ // Save the current state of the **Store** to *localStorage*.
+ save: function() {
+ this.localStorage().setItem(this.name, this.records.join(","));
+ },
+
+ // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
+ // have an id of it's own.
+ create: function(model) {
+ if (!model.id) {
+ model.id = guid();
+ model.set(model.idAttribute, model.id);
+ }
+ this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
+ this.records.push(model.id.toString());
+ this.save();
+ return model.toJSON();
+ },
+
+ // Update a model by replacing its copy in `this.data`.
+ update: function(model) {
+ this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
+ if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
+ return model.toJSON();
+ },
+
+ // Retrieve a model from `this.data` by id.
+ find: function(model) {
+ return JSON.parse(this.localStorage().getItem(this.name+"-"+model.id));
+ },
+
+ // Return the array of all models currently in storage.
+ findAll: function() {
+ return _(this.records).chain()
+ .map(function(id){return JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
+ .compact()
+ .value();
+ },
+
+ // Delete a model from `this.data`, returning it.
+ destroy: function(model) {
+ this.localStorage().removeItem(this.name+"-"+model.id);
+ this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
+ this.save();
+ return model;
+ },
+
+ localStorage: function() {
+ return localStorage;
+ }
+
+ });
+
+ // localSync delegate to the model or collection's
+ // *localStorage* property, which should be an instance of `Store`.
+ // window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
+ Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options, error) {
+ var store = model.localStorage || model.collection.localStorage;
+
+ // Backwards compatibility with Backbone <= 0.3.3
+ if (typeof options == 'function') {
+ options = {
+ success: options,
+ error: error
+ };
+ }
+
+ var resp;
+
+ switch (method) {
+ case "read": resp = model.id != undefined ? store.find(model) : store.findAll(); break;
+ case "create": resp = store.create(model); break;
+ case "update": resp = store.update(model); break;
+ case "delete": resp = store.destroy(model); break;
+ }
+
+ if (resp) {
+ options.success(resp);
+ } else {
+ options.error("Record not found");
+ }
+ };
+
+ Backbone.ajaxSync = Backbone.sync;
+
+ Backbone.getSyncMethod = function(model) {
+ if(model.localStorage || (model.collection && model.collection.localStorage))
+ {
+ return Backbone.localSync;
+ }
+
+ return Backbone.ajaxSync;
+ };
+
+ // Override 'Backbone.sync' to default to localSync,
+ // the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
+ Backbone.sync = function(method, model, options, error) {
+ return Backbone.getSyncMethod(model).apply(this, [method, model, options, error]);
+ };
+
+})();
diff --git a/labs/dependency-examples/chaplin-brunch/vendor/scripts/chaplin-0.5.0.coffee b/labs/dependency-examples/chaplin-brunch/vendor/scripts/chaplin-0.5.0.coffee
new file mode 100644
index 0000000000..903aec1c9b
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/vendor/scripts/chaplin-0.5.0.coffee
@@ -0,0 +1,2249 @@
+###
+Chaplin 0.5.0.
+
+Chaplin may be freely distributed under the MIT license.
+For all details and documentation:
+http://github.com/chaplinjs/chaplin
+###
+
+'use strict'
+
+require.define
+ 'jquery': (require, exports, module) -> module.exports = $
+ 'underscore': (require, exports, module) -> module.exports = _
+ 'backbone': (require, exports, module) -> module.exports = Backbone
+
+require.define 'chaplin/application': (exports, require, module) ->
+ Backbone = require 'backbone'
+ mediator = require 'chaplin/mediator'
+ Dispatcher = require 'chaplin/dispatcher'
+ Layout = require 'chaplin/views/layout'
+ Router = require 'chaplin/lib/router'
+ EventBroker = require 'chaplin/lib/event_broker'
+
+ # The application bootstrapper
+ # ----------------------------
+
+ module.exports = class Application
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # The site title used in the document title
+ title: ''
+
+ # The application instantiates these three core modules
+ dispatcher: null
+ layout: null
+ router: null
+
+ initialize: ->
+
+ initDispatcher: (options) ->
+ @dispatcher = new Dispatcher options
+
+ initLayout: (options = {}) ->
+ options.title ?= @title
+ @layout = new Layout options
+
+ # Instantiate the dispatcher
+ # --------------------------
+
+ # Pass the function typically returned by routes.coffee
+ initRouter: (routes, options) ->
+ # Save the reference for testing introspection only.
+ # Modules should communicate with each other via Pub/Sub.
+ @router = new Router options
+
+ # Register all routes declared in routes.coffee
+ routes? @router.match
+
+ # After registering the routes, start Backbone.history
+ @router.startHistory()
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ properties = ['dispatcher', 'layout', 'router']
+ for prop in properties when this[prop]?
+ this[prop].dispose()
+ delete this[prop]
+
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/mediator': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ support = require 'chaplin/lib/support'
+ utils = require 'chaplin/lib/utils'
+
+ # Mediator
+ # --------
+
+ # The mediator is a simple object all others modules use to communicate
+ # with each other. It implements the Publish/Subscribe pattern.
+ #
+ # Additionally, it holds objects which need to be shared between modules.
+ # In this case, a `user` property is created for getting the user object
+ # and a `setUser` method for setting the user.
+ #
+ # This module returns the singleton object. This is the
+ # application-wide mediator you might load into modules
+ # which need to talk to other modules using Publish/Subscribe.
+
+ # Start with a simple object
+ mediator = {}
+
+ # Publish / Subscribe
+ # -------------------
+
+ # Mixin event methods from Backbone.Events,
+ # create Publish/Subscribe aliases
+ mediator.subscribe = Backbone.Events.on
+ mediator.unsubscribe = Backbone.Events.off
+ mediator.publish = Backbone.Events.trigger
+
+ # The `on` method should not be used,
+ # it is kept only for purpose of compatibility with Backbone.
+ mediator.on = mediator.subscribe
+
+ # Initialize an empty callback list so we might seal the mediator later
+ mediator._callbacks = null
+
+ # Make properties readonly
+ utils.readonly mediator, 'subscribe', 'unsubscribe', 'publish', 'on'
+
+ # Sealing the mediator
+ # --------------------
+
+ # After adding all needed properties, you should seal the mediator
+ # using this method
+ mediator.seal = ->
+ # Prevent extensions and make all properties non-configurable
+ if support.propertyDescriptors and Object.seal
+ Object.seal mediator
+
+ # Make the method readonly
+ utils.readonly mediator, 'seal'
+
+ # Return our creation
+ module.exports = mediator
+
+require.define 'chaplin/dispatcher': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ utils = require 'chaplin/lib/utils'
+ EventBroker = require 'chaplin/lib/event_broker'
+
+ module.exports = class Dispatcher
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # The previous controller name
+ previousControllerName: null
+
+ # The current controller, its name, main view and parameters
+ currentControllerName: null
+ currentController: null
+ currentAction: null
+ currentParams: null
+
+ # The current URL
+ url: null
+
+ constructor: ->
+ @initialize arguments...
+
+ initialize: (options = {}) ->
+ # Merge the options
+ @settings = _(options).defaults
+ controllerPath: 'controllers/'
+ controllerSuffix: '_controller'
+
+ # Listen to global events
+ @subscribeEvent 'matchRoute', @matchRoute
+ @subscribeEvent '!startupController', @startupController
+
+ # Controller management
+ # Starting and disposing controllers
+ # ----------------------------------
+
+ # Handler for the global matchRoute event
+ matchRoute: (route, params) ->
+ @startupController route.controller, route.action, params
+
+ # Handler for the global !startupController event
+ #
+ # The standard flow is:
+ #
+ # 1. Test if it’s a new controller/action with new params
+ # 1. Hide the old view
+ # 2. Dispose the old controller
+ # 3. Instantiate the new controller, call the controller action
+ # 4. Show the new view
+ #
+ startupController: (controllerName, action = 'index', params = {}) ->
+ # Set default flags
+
+ # Whether to update the URL after controller startup
+ # Default to true unless explicitly set to false
+ if params.changeURL isnt false
+ params.changeURL = true
+
+ # Whether to force the controller startup even
+ # when current and new controllers and params match
+ # Default to false unless explicitly set to true
+ if params.forceStartup isnt true
+ params.forceStartup = false
+
+ # Check if the desired controller is already active
+ isSameController =
+ not params.forceStartup and
+ @currentControllerName is controllerName and
+ @currentAction is action and
+ # Deep parameters check is not nice but the simplest way for now
+ (not @currentParams or _(params).isEqual(@currentParams))
+
+ # Stop if it’s the same controller/action with the same params
+ return if isSameController
+
+ # Fetch the new controller, then go on
+ handler = _(@controllerLoaded).bind(this, controllerName, action, params)
+ @loadController controllerName, handler
+
+ # Load the constructor for a given controller name.
+ # The default implementation uses require() from a AMD module loader
+ # like RequireJS to fetch the constructor.
+ loadController: (controllerName, handler) ->
+ controllerFileName = utils.underscorize(controllerName) + @settings.controllerSuffix
+ path = @settings.controllerPath + controllerFileName
+ if define?.amd
+ require [path], handler
+ else
+ handler require path
+
+ # Handler for the controller lazy-loading
+ controllerLoaded: (controllerName, action, params, ControllerConstructor) ->
+
+ # Shortcuts for the old controller
+ currentControllerName = @currentControllerName or null
+ currentController = @currentController or null
+
+ # Dispose the current controller
+ if currentController
+ # Notify the rest of the world beforehand
+ @publishEvent 'beforeControllerDispose', currentController
+ # Passing the params and the new controller name
+ currentController.dispose params, controllerName
+
+ # Initialize the new controller
+ # Passing the params and the old controller name
+ controller = new ControllerConstructor params, currentControllerName
+
+ # Call the specific controller action
+ # Passing the params and the old controller name
+ controller[action] params, currentControllerName
+
+ # Stop if the action triggered a redirect
+ return if controller.redirected
+
+ # Save the new controller
+ @previousControllerName = currentControllerName
+ @currentControllerName = controllerName
+ @currentController = controller
+ @currentAction = action
+ @currentParams = params
+
+ @adjustURL controller, params
+
+ # We're done! Spread the word!
+ @publishEvent 'startupController',
+ previousControllerName: @previousControllerName
+ controller: @currentController
+ controllerName: @currentControllerName
+ params: @currentParams
+
+ # Change the URL to the new controller using the router
+ adjustURL: (controller, params) ->
+ if params.path or params.path is ''
+ # Just use the matched path
+ url = params.path
+
+ else if typeof controller.historyURL is 'function'
+ # Use controller.historyURL to get the URL
+ # If the property is a function, call it
+ url = controller.historyURL params
+
+ else if typeof controller.historyURL is 'string'
+ # If the property is a string, read it
+ url = controller.historyURL
+
+ else
+ throw new Error 'Dispatcher#adjustURL: controller for ' +
+ "#{@currentControllerName} does not provide a historyURL"
+
+ # Tell the router to actually change the current URL
+ @publishEvent '!router:changeURL', url if params.changeURL
+
+ # Save the URL
+ @url = url
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ @unsubscribeAllEvents()
+
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/controllers/controller': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ EventBroker = require 'chaplin/lib/event_broker'
+
+ module.exports = class Controller
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ view: null
+ currentId: null
+
+ # Internal flag which stores whether `redirectTo`
+ # was called in the current action
+ redirected: false
+
+ # You should set a `title` property and a `historyURL` property or method
+ # on the derived controller. Like this:
+ # title: 'foo'
+ # historyURL: 'foo'
+ # historyURL: ->
+
+ constructor: ->
+ @initialize arguments...
+
+ initialize: ->
+ # Empty per default
+
+ # Redirection
+ # -----------
+
+ redirectTo: (arg1, action, params) ->
+ @redirected = true
+ if arguments.length is 1
+ # URL was passed, try to route it
+ @publishEvent '!router:route', arg1, (routed) ->
+ unless routed
+ throw new Error 'Controller#redirectTo: no route matched'
+ else
+ # Assume controller and action names were passed
+ @publishEvent '!startupController', arg1, action, params
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ # Dispose and delete all members which are disposable
+ for own prop of this
+ obj = this[prop]
+ if obj and typeof obj.dispose is 'function'
+ obj.dispose()
+ delete this[prop]
+
+ # Unbind handlers of global events
+ @unsubscribeAllEvents()
+
+ # Remove properties which are not disposable
+ properties = ['currentId', 'redirected']
+ delete this[prop] for prop in properties
+
+ # Finished
+ @disposed = true
+
+ # You're frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/models/collection': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ EventBroker = require 'chaplin/lib/event_broker'
+ Model = require 'chaplin/models/model'
+
+ # Abstract class which extends the standard Backbone collection
+ # in order to add some functionality
+ module.exports = class Collection extends Backbone.Collection
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # Use the Chaplin model per default, not Backbone.Model
+ model: Model
+
+ # Mixin a Deferred
+ initDeferred: ->
+ _(this).extend $.Deferred()
+
+ # Serializes collection
+ serialize: ->
+ for model in @models
+ if model instanceof Model
+ # Use optimized Chaplin serialization
+ model.serialize()
+ else
+ # Fall back to unoptimized Backbone stuff
+ model.toJSON()
+
+ # Adds a collection atomically, i.e. throws no event until
+ # all members have been added
+ addAtomic: (models, options = {}) ->
+ return unless models.length
+ options.silent = true
+ direction = if typeof options.at is 'number' then 'pop' else 'shift'
+ while model = models[direction]()
+ @add model, options
+ @trigger 'reset'
+
+ # Updates a collection with a list of models
+ # Just like the reset method, but only adds new items and
+ # removes items which are not in the new list.
+ # Fires individual `add` and `remove` event instead of one `reset`.
+ #
+ # options:
+ # deep: Boolean flag to specify whether existing models
+ # should be updated with new values
+ update: (models, options = {}) ->
+ fingerPrint = @pluck('id').join()
+ ids = _(models).pluck('id')
+ newFingerPrint = ids.join()
+
+ # Only remove if ID fingerprints differ
+ if newFingerPrint isnt fingerPrint
+ # Remove items which are not in the new list
+ _ids = _(ids) # Underscore wrapper
+ i = @models.length
+ while i--
+ model = @models[i]
+ unless _ids.include model.id
+ @remove model
+
+ # Only add/update list if ID fingerprints differ
+ # or update is deep (member attributes)
+ if newFingerPrint isnt fingerPrint or options.deep
+ # Add items which are not yet in the list
+ for model, i in models
+ preexistent = @get model.id
+ if preexistent
+ # Update existing model
+ preexistent.set model if options.deep
+ else
+ # Insert new model
+ @add model, at: i
+
+ return
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ # Fire an event to notify associated views
+ @trigger 'dispose', this
+
+ # Empty the list silently, but do not dispose all models since
+ # they might be referenced elsewhere
+ @reset [], silent: true
+
+ # Unbind all global event handlers
+ @unsubscribeAllEvents()
+
+ # Remove all event handlers on this module
+ @off()
+
+ # If the model is a Deferred, reject it
+ # This does nothing if it was resolved before
+ @reject?()
+
+ # Remove model constructor reference, internal model lists
+ # and event handlers
+ properties = [
+ 'model',
+ 'models', '_byId', '_byCid',
+ '_callbacks'
+ ]
+ delete this[prop] for prop in properties
+
+ # Finished
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/models/model': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ utils = require 'chaplin/lib/utils'
+ EventBroker = require 'chaplin/lib/event_broker'
+
+ module.exports = class Model extends Backbone.Model
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # Mixin a Deferred
+ initDeferred: ->
+ _(this).extend $.Deferred()
+
+ # This method is used to get the attributes for the view template
+ # and might be overwritten by decorators which cannot create a
+ # proper `attributes` getter due to ECMAScript 3 limits.
+ getAttributes: ->
+ @attributes
+
+ # Private helper function for serializing attributes recursively,
+ # creating objects which delegate to the original attributes
+ # when a property needs to be overwritten.
+ serializeAttributes = (model, attributes, modelStack) ->
+ # Create a delegator on initial call
+ unless modelStack
+ delegator = utils.beget attributes
+ modelStack = [model]
+ else
+ # Add model to stack
+ modelStack.push model
+ # Map model/collection to their attributes
+ for key, value of attributes
+ if value instanceof Backbone.Model
+ # Don’t change the original attribute, create a property
+ # on the delegator which shadows the original attribute
+ delegator ?= utils.beget attributes
+ delegator[key] = if value is model or value in modelStack
+ # Nullify circular references
+ null
+ else
+ # Serialize recursively
+ serializeAttributes(
+ value, value.getAttributes(), modelStack
+ )
+ else if value instanceof Backbone.Collection
+ delegator ?= utils.beget attributes
+ delegator[key] = for item in value.models
+ serializeAttributes(
+ item, item.getAttributes(), modelStack
+ )
+
+ # Remove model from stack
+ modelStack.pop()
+ # Return the delegator if it was created, otherwise the plain attributes
+ delegator or attributes
+
+ # Return an object which delegates to the attributes
+ # (i.e. an object which has the attributes as prototype)
+ # so primitive values might be added and altered safely.
+ # Map models to their attributes, recursively.
+ serialize: ->
+ serializeAttributes this, @getAttributes()
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ # Fire an event to notify associated collections and views
+ @trigger 'dispose', this
+
+ # Unbind all global event handlers
+ @unsubscribeAllEvents()
+
+ # Remove all event handlers on this module
+ @off()
+
+ # If the model is a Deferred, reject it
+ # This does nothing if it was resolved before
+ @reject?()
+
+ # Remove the collection reference, internal attribute hashes
+ # and event handlers
+ properties = [
+ 'collection',
+ 'attributes', 'changed'
+ '_escapedAttributes', '_previousAttributes',
+ '_silent', '_pending',
+ '_callbacks'
+ ]
+ delete this[prop] for prop in properties
+
+ # Finished
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/views/layout': (exports, require, module) ->
+ $ = require 'jquery'
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ utils = require 'chaplin/lib/utils'
+ EventBroker = require 'chaplin/lib/event_broker'
+
+ module.exports = class Layout # This class does not extend View
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # The site title used in the document title.
+ # This should be set in your app-specific Application class
+ # and passed as an option
+ title: ''
+
+ # An hash to register events, like in Backbone.View
+ # It is only meant for events that are app-wide
+ # independent from any view
+ events: {}
+
+ # Register @el, @$el and @cid for delegating events
+ el: document
+ $el: $(document)
+ cid: 'chaplin-layout'
+
+ constructor: ->
+ @initialize arguments...
+
+ initialize: (options = {}) ->
+ @title = options.title
+ @settings = _(options).defaults
+ titleTemplate: _.template("<%= subtitle %> \u2013 <%= title %>")
+ openExternalToBlank: false
+ routeLinks: 'a, .go-to'
+ skipRouting: '.noscript'
+ # Per default, jump to the top of the page
+ scrollTo: [0, 0]
+
+ @subscribeEvent 'beforeControllerDispose', @hideOldView
+ @subscribeEvent 'startupController', @showNewView
+ @subscribeEvent 'startupController', @adjustTitle
+
+ # Set the app link routing
+ if @settings.routeLinks
+ @startLinkRouting()
+
+ # Set app wide event handlers
+ @delegateEvents()
+
+ # Take (un)delegateEvents from Backbone
+ # -------------------------------------
+ delegateEvents: Backbone.View::delegateEvents
+ undelegateEvents: Backbone.View::undelegateEvents
+
+ # Controller startup and disposal
+ # -------------------------------
+
+ # Handler for the global beforeControllerDispose event
+ hideOldView: (controller) ->
+ # Reset the scroll position
+ scrollTo = @settings.scrollTo
+ if scrollTo
+ window.scrollTo scrollTo[0], scrollTo[1]
+
+ # Hide the current view
+ view = controller.view
+ if view
+ view.$el.css 'display', 'none'
+
+ # Handler for the global startupController event
+ # Show the new view
+ showNewView: (context) ->
+ view = context.controller.view
+ if view
+ view.$el.css display: 'block', opacity: 1, visibility: 'visible'
+
+ # Handler for the global startupController event
+ # Change the document title to match the new controller
+ # Get the title from the title property of the current controller
+ adjustTitle: (context) ->
+ title = @title or ''
+ subtitle = context.controller.title or ''
+ title = @settings.titleTemplate {title, subtitle}
+
+ # Internet Explorer < 9 workaround
+ setTimeout (-> document.title = title), 50
+
+ # Automatic routing of internal links
+ # -----------------------------------
+
+ startLinkRouting: ->
+ if @settings.routeLinks
+ $(document).on 'click', @settings.routeLinks, @openLink
+
+ stopLinkRouting: ->
+ if @settings.routeLinks
+ $(document).off 'click', @settings.routeLinks
+
+ # Handle all clicks on A elements and try to route them internally
+ openLink: (event) =>
+ return if utils.modifierKeyPressed(event)
+
+ el = event.currentTarget
+ $el = $(el)
+ isAnchor = el.nodeName is 'A'
+
+ # Get the href and perform checks on it
+ href = $el.attr('href') or $el.data('href') or null
+
+ # Basic href checks
+ return if href is null or href is undefined or
+ # Technically an empty string is a valid relative URL
+ # but it doesn’t make sense to route it.
+ href is '' or
+ # Exclude fragment links
+ href.charAt(0) is '#'
+
+ # Checks for A elements
+ return if isAnchor and (
+ # Exclude links marked as external
+ $el.attr('target') is '_blank' or
+ $el.attr('rel') is 'external' or
+ # Exclude links to non-HTTP ressources
+ el.protocol not in ['http:', 'https:', 'file:']
+ )
+
+ # Apply skipRouting option
+ skipRouting = @settings.skipRouting
+ type = typeof skipRouting
+ return if type is 'function' and not skipRouting(href, el) or
+ type is 'string' and $el.is skipRouting
+
+ # Handle external links
+ internal = not isAnchor or el.hostname in [location.hostname, '']
+ unless internal
+ if @settings.openExternalToBlank
+ # Open external links normally in a new tab
+ event.preventDefault()
+ window.open el.href
+ return
+
+ if isAnchor
+ # Get the path with query string
+ path = el.pathname + el.search
+ # Leading slash for IE8
+ path = "/#{path}" if path.charAt(0) isnt '/'
+ else
+ path = href
+
+ # Pass to the router, try to route the path internally
+ @publishEvent '!router:route', path, (routed) ->
+ # Prevent default handling if the URL could be routed
+ if routed
+ event.preventDefault()
+ else unless isAnchor
+ location.href = path
+ return
+
+ return
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ @stopLinkRouting()
+ @unsubscribeAllEvents()
+ @undelegateEvents()
+
+ delete @title
+
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/views/view': (exports, require, module) ->
+ $ = require 'jquery'
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ utils = require 'chaplin/lib/utils'
+ EventBroker = require 'chaplin/lib/event_broker'
+ Model = require 'chaplin/models/model'
+ Collection = require 'chaplin/models/collection'
+
+ module.exports = class View extends Backbone.View
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ # Automatic rendering
+ # -------------------
+
+ # Flag whether to render the view automatically on initialization.
+ # As an alternative you might pass a `render` option to the constructor.
+ autoRender: false
+
+ # Automatic inserting into DOM
+ # ----------------------------
+
+ # View container element
+ # Set this property in a derived class to specify the container element.
+ # Normally this is a selector string but it might also be an element or
+ # jQuery object.
+ # The view is automatically inserted into the container when it’s rendered.
+ # As an alternative you might pass a `container` option to the constructor.
+ container: null
+
+ # Method which is used for adding the view to the DOM
+ # Like jQuery’s `html`, `prepend`, `append`, `after`, `before` etc.
+ containerMethod: 'append'
+
+ # Subviews
+ # --------
+
+ # List of subviews
+ subviews: null
+ subviewsByName: null
+
+ # Method wrapping to enable `afterRender` and `afterInitialize`
+ # -------------------------------------------------------------
+
+ # Wrap a method in order to call the corresponding
+ # `after-` method automatically
+ wrapMethod: (name) ->
+ instance = this
+ # Enclose the original function
+ func = instance[name]
+ # Set a flag
+ instance["#{name}IsWrapped"] = true
+ # Create the wrapper method
+ instance[name] = ->
+ # Stop if the view was already disposed
+ return false if @disposed
+ # Call the original method
+ func.apply instance, arguments
+ # Call the corresponding `after-` method
+ instance["after#{utils.upcase(name)}"] arguments...
+ # Return the view
+ instance
+
+ constructor: ->
+ # Wrap `initialize` so `afterInitialize` is called afterwards
+ # Only wrap if there is an overring method, otherwise we
+ # can call the `after-` method directly
+ unless @initialize is View::initialize
+ @wrapMethod 'initialize'
+
+ # Wrap `render` so `afterRender` is called afterwards
+ unless @render is View::render
+ @wrapMethod 'render'
+ else
+ # Otherwise just bind the `render` method
+ @render = _(@render).bind this
+
+ # Call Backbone’s constructor
+ super
+
+ initialize: (options) ->
+ # No super call here, Backbone’s `initialize` is a no-op
+
+ # Copy some options to instance properties
+ if options
+ for prop in ['autoRender', 'container', 'containerMethod']
+ if options[prop]?
+ @[prop] = options[prop]
+
+ # Initialize subviews
+ @subviews = []
+ @subviewsByName = {}
+
+ # Listen for disposal of the model
+ # If the model is disposed, automatically dispose the associated view
+ if @model or @collection
+ @modelBind 'dispose', @dispose
+
+ # Call `afterInitialize` if `initialize` was not wrapped
+ unless @initializeIsWrapped
+ @afterInitialize()
+
+ # This method is called after a specific `initialize` of a derived class
+ afterInitialize: ->
+ # Render automatically if set by options or instance property
+ @render() if @autoRender
+
+ # User input event handling
+ # -------------------------
+
+ # Event handling using event delegation
+ # Register a handler for a specific event type
+ # For the whole view:
+ # delegate(eventType, handler)
+ # e.g.
+ # @delegate('click', @clicked)
+ # For an element in the passing a selector:
+ # delegate(eventType, selector, handler)
+ # e.g.
+ # @delegate('click', 'button.confirm', @confirm)
+ delegate: (eventType, second, third) ->
+ if typeof eventType isnt 'string'
+ throw new TypeError 'View#delegate: first argument must be a string'
+
+ if arguments.length is 2
+ handler = second
+ else if arguments.length is 3
+ selector = second
+ if typeof selector isnt 'string'
+ throw new TypeError 'View#delegate: ' +
+ 'second argument must be a string'
+ handler = third
+ else
+ throw new TypeError 'View#delegate: ' +
+ 'only two or three arguments are allowed'
+
+ if typeof handler isnt 'function'
+ throw new TypeError 'View#delegate: ' +
+ 'handler argument must be function'
+
+ # Add an event namespace
+ list = ("#{event}.delegate#{@cid}" for event in eventType.split(' '))
+ events = list.join(' ')
+
+ # Bind the handler to the view
+ handler = _(handler).bind(this)
+
+ if selector
+ # Register handler
+ @$el.on events, selector, handler
+ else
+ # Register handler
+ @$el.on events, handler
+
+ # Return the bound handler
+ handler
+
+ # Remove all handlers registered with @delegate
+
+ undelegate: ->
+ @$el.unbind ".delegate#{@cid}"
+
+ # Model binding
+ # The following implementation resembles EventBroker
+ # --------------------------------------------------
+
+ # Bind to a model event
+ modelBind: (type, handler) ->
+ if typeof type isnt 'string'
+ throw new TypeError 'View#modelBind: ' +
+ 'type must be a string'
+ if typeof handler isnt 'function'
+ throw new TypeError 'View#modelBind: ' +
+ 'handler argument must be function'
+
+ # Get model/collection reference
+ modelOrCollection = @model or @collection
+ unless modelOrCollection
+ throw new TypeError 'View#modelBind: no model or collection set'
+
+ # Ensure that a handler isn’t registered twice
+ modelOrCollection.off type, handler, this
+
+ # Register model handler, force context to the view
+ modelOrCollection.on type, handler, this
+
+ # Unbind from a model event
+
+ modelUnbind: (type, handler) ->
+ if typeof type isnt 'string'
+ throw new TypeError 'View#modelUnbind: ' +
+ 'type argument must be a string'
+ if typeof handler isnt 'function'
+ throw new TypeError 'View#modelUnbind: ' +
+ 'handler argument must be a function'
+
+ # Get model/collection reference
+ modelOrCollection = @model or @collection
+ return unless modelOrCollection
+
+ # Remove model handler
+ modelOrCollection.off type, handler
+
+ # Unbind all recorded model event handlers
+ modelUnbindAll: ->
+ # Get model/collection reference
+ modelOrCollection = @model or @collection
+ return unless modelOrCollection
+
+ # Remove all handlers with a context of this view
+ modelOrCollection.off null, null, this
+
+ # Setup a simple one-way model-view binding
+ # Pass changed attribute values to specific elements in the view
+ # For form controls, the value is changed, otherwise the element
+ # text content is set to the model attribute value.
+ # Example: @pass 'attribute', '.selector'
+ pass: (attribute, selector) ->
+ @modelBind "change:#{attribute}", (model, value) =>
+ $el = @$(selector)
+ if $el.is('input, textarea, select, button')
+ $el.val value
+ else
+ $el.text value
+
+ # Subviews
+ # --------
+
+ # Getting or adding a subview
+ subview: (name, view) ->
+ if name and view
+ # Add the subview, ensure it’s unique
+ @removeSubview name
+ @subviews.push view
+ @subviewsByName[name] = view
+ view
+ else if name
+ # Get and return the subview by the given name
+ @subviewsByName[name]
+
+ # Removing a subview
+ removeSubview: (nameOrView) ->
+ return unless nameOrView
+
+ if typeof nameOrView is 'string'
+ # Name given, search for a subview by name
+ name = nameOrView
+ view = @subviewsByName[name]
+ else
+ # View instance given, search for the corresponding name
+ view = nameOrView
+ for otherName, otherView of @subviewsByName
+ if view is otherView
+ name = otherName
+ break
+
+ # Break if no view and name were found
+ return unless name and view and view.dispose
+
+ # Dispose the view
+ view.dispose()
+
+ # Remove the subview from the lists
+ index = _(@subviews).indexOf(view)
+ if index > -1
+ @subviews.splice index, 1
+ delete @subviewsByName[name]
+
+ # Rendering
+ # ---------
+
+ # Get the model/collection data for the templating function
+ # Uses optimized Chaplin serialization if available.
+ getTemplateData: ->
+ if @model
+ templateData = if @model instanceof Model
+ @model.serialize()
+ else
+ utils.beget @model.attributes
+ else if @collection
+ # Collection: Serialize all models
+ if @collection instanceof Collection
+ items = @collection.serialize()
+ else
+ items = []
+ for model in @collection.models
+ items.push utils.beget(model.attributes)
+ templateData = {items}
+ else
+ # Empty object
+ templateData = {}
+
+ modelOrCollection = @model or @collection
+ if modelOrCollection
+
+ # If the model/collection is a Deferred, add a `resolved` flag,
+ # but only if it’s not present yet
+ if typeof modelOrCollection.state is 'function' and
+ not ('resolved' of templateData)
+ templateData.resolved = modelOrCollection.state() is 'resolved'
+
+ # If the model/collection is a SyncMachine, add a `synced` flag,
+ # but only if it’s not present yet
+ if typeof modelOrCollection.isSynced is 'function' and
+ not ('synced' of templateData)
+ templateData.synced = modelOrCollection.isSynced()
+
+ templateData
+
+ # Returns the compiled template function
+ getTemplateFunction: ->
+ # Chaplin doesn’t define how you load and compile templates in order to
+ # render views. The example application uses Handlebars and RequireJS
+ # to load and compile templates on the client side. See the derived
+ # View class in the example application:
+ # https://github.com/chaplinjs/facebook-example/blob/master/coffee/views/base/view.coffee
+ #
+ # If you precompile templates to JavaScript functions on the server,
+ # you might just return a reference to that function.
+ # Several precompilers create a global `JST` hash which stores the
+ # template functions. You can get the function by the template name:
+ # JST[@templateName]
+
+ throw new Error 'View#getTemplateFunction must be overridden'
+
+ # Main render function
+ # This method is bound to the instance in the constructor (see above)
+ render: ->
+ # Do not render if the object was disposed
+ # (render might be called as an event handler which wasn’t
+ # removed correctly)
+ return false if @disposed
+
+ templateFunc = @getTemplateFunction()
+ if typeof templateFunc is 'function'
+
+ # Call the template function passing the template data
+ html = templateFunc @getTemplateData()
+
+ # Replace HTML
+ # ------------
+
+ # This is a workaround for an apparent issue with jQuery 1.7’s
+ # innerShiv feature. Using @$el.html(html) caused issues with
+ # HTML5-only tags in IE7 and IE8.
+ @$el.empty().append html
+
+ # Call `afterRender` if `render` was not wrapped
+ @afterRender() unless @renderIsWrapped
+
+ # Return the view
+ this
+
+ # This method is called after a specific `render` of a derived class
+ afterRender: ->
+ # Automatically append to DOM if the container element is set
+ if @container
+ # Append the view to the DOM
+ $(@container)[@containerMethod] @el
+ # Trigger an event
+ @trigger 'addedToDOM'
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ # Dispose subviews
+ subview.dispose() for subview in @subviews
+
+ # Unbind handlers of global events
+ @unsubscribeAllEvents()
+
+ # Unbind all model handlers
+ @modelUnbindAll()
+
+ # Remove all event handlers on this module
+ @off()
+
+ # Remove the topmost element from DOM. This also removes all event
+ # handlers from the element and all its children.
+ @$el.remove()
+
+ # Remove element references, options,
+ # model/collection references and subview lists
+ properties = [
+ 'el', '$el',
+ 'options', 'model', 'collection',
+ 'subviews', 'subviewsByName',
+ '_callbacks'
+ ]
+ delete this[prop] for prop in properties
+
+ # Finished
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/views/collection_view': (exports, require, module) ->
+ $ = require 'jquery'
+ _ = require 'underscore'
+ View = require 'chaplin/views/view'
+
+ # General class for rendering Collections.
+ # Derive this class and declare at least `itemView` or override
+ # `getView`. `getView` gets an item model and should instantiate
+ # and return a corresponding item view.
+ module.exports = class CollectionView extends View
+
+ # Configuration options
+ # ---------------------
+
+ # These options may be overwritten in derived classes.
+
+ # A class of item in collection.
+ # This property has to be overridden by a derived class.
+ itemView: null
+
+ # Automatic rendering
+
+ # Per default, render the view itself and all items on creation
+ autoRender: true
+ renderItems: true
+
+ # Animation
+
+ # When new items are added, their views are faded in.
+ # Animation duration in milliseconds (set to 0 to disable fade in)
+ animationDuration: 500
+
+ # By default, fading in is done by javascript function which can be
+ # slow on mobile devices. CSS animations are faster,
+ # but require user’s manual definitions.
+ # CSS classes used are: animated-item-view, animated-item-view-end.
+ useCssAnimation: false
+
+ # Selectors and Elements
+
+ # A collection view may have a template and use one of its child elements
+ # as the container of the item views. If you specify `listSelector`, the
+ # item views will be appended to this element. If empty, $el is used.
+ listSelector: null
+
+ # The actual element which is fetched using `listSelector`
+ $list: null
+
+ # Selector for a fallback element which is shown if the collection is empty.
+ fallbackSelector: null
+
+ # The actual element which is fetched using `fallbackSelector`
+ $fallback: null
+
+ # Selector for a loading indicator element which is shown
+ # while the collection is syncing.
+ loadingSelector: null
+
+ # The actual element which is fetched using `loadingSelector`
+ $loading: null
+
+ # Selector which identifies child elements belonging to collection
+ # If empty, all children of $list are considered
+ itemSelector: null
+
+ # Filtering
+
+ # The filter function, if any
+ filterer: null
+
+ # A function that will be executed after each filter.
+ # Hides excluded items by default.
+ filterCallback: (view, included) ->
+ display = if included then '' else 'none'
+ view.$el.stop(true, true).css('display', display)
+
+ # View lists
+
+ # Track a list of the visible views
+ visibleItems: null
+
+ # Initialization
+ # --------------
+
+ initialize: (options = {}) ->
+ super
+
+ # Initialize list for visible items
+ @visibleItems = []
+
+ # Start observing the collection
+ @addCollectionListeners()
+
+ # Apply options
+ @renderItems = options.renderItems if options.renderItems?
+ @itemView = options.itemView if options.itemView?
+ @filter options.filterer if options.filterer?
+
+ # Binding of collection listeners
+ addCollectionListeners: ->
+ @modelBind 'add', @itemAdded
+ @modelBind 'remove', @itemRemoved
+ @modelBind 'reset', @itemsResetted
+
+ # Rendering
+ # ---------
+
+ # In contrast to normal views, a template is not mandatory
+ # for CollectionViews. Provide an empty `getTemplateFunction`.
+ getTemplateFunction: ->
+
+ # Main render method (should be called only once)
+ render: ->
+ super
+
+ # Set the $list property with the actual list container
+ @$list = if @listSelector then @$(@listSelector) else @$el
+
+ @initFallback()
+ @initLoadingIndicator()
+
+ # Render all items
+ @renderAllItems() if @renderItems
+
+ # Adding / Removing
+ # -----------------
+
+ # When an item is added, create a new view and insert it
+ itemAdded: (item, collection, options = {}) =>
+ @renderAndInsertItem item, options.index
+
+ # When an item is removed, remove the corresponding view from DOM and caches
+ itemRemoved: (item) =>
+ @removeViewForItem item
+
+ # When all items are resetted, render all anew
+ itemsResetted: =>
+ @renderAllItems()
+
+ # Fallback message when the collection is empty
+ # ---------------------------------------------
+
+ initFallback: ->
+ return unless @fallbackSelector
+
+ # Set the $fallback property
+ @$fallback = @$(@fallbackSelector)
+
+ # Listen for visible items changes
+ @on 'visibilityChange', @showHideFallback
+
+ # Listen for sync events on the collection
+ @modelBind 'syncStateChange', @showHideFallback
+
+ # Set visibility initially
+ @showHideFallback()
+
+ # Show fallback if no item is visible and the collection is synced
+ showHideFallback: =>
+ visible = @visibleItems.length is 0 and (
+ if typeof @collection.isSynced is 'function'
+ # Collection is a SyncMachine
+ @collection.isSynced()
+ else
+ # Assume it is synced
+ true
+ )
+ @$fallback.css 'display', if visible then 'block' else 'none'
+
+ # Loading indicator
+ # -----------------
+
+ initLoadingIndicator: ->
+ # The loading indicator only works for Collections
+ # which are SyncMachines.
+ return unless @loadingSelector and
+ typeof @collection.isSyncing is 'function'
+
+ # Set the $loading property
+ @$loading = @$(@loadingSelector)
+
+ # Listen for sync events on the collection
+ @modelBind 'syncStateChange', @showHideLoadingIndicator
+
+ # Set visibility initially
+ @showHideLoadingIndicator()
+
+ showHideLoadingIndicator: ->
+ # Only show the loading indicator if the collection is empty.
+ # Otherwise loading more items in order to append them would
+ # show the loading indicator. If you want the indicator to
+ # show up in this case, you need to overwrite this method to
+ # disable the check.
+ visible = @collection.length is 0 and @collection.isSyncing()
+ @$loading.css 'display', if visible then 'block' else 'none'
+
+ # Filtering
+ # ---------
+
+ # Filters only child item views from all current subviews.
+ getItemViews: ->
+ itemViews = {}
+ for name, view of @subviewsByName when name.slice(0, 9) is 'itemView:'
+ itemViews[name.slice(9)] = view
+ itemViews
+
+ # Applies a filter to the collection view.
+ # Expects an iterator function as first parameter
+ # which need to return true or false.
+ # Optional filter callback which is called to
+ # show/hide the view or mark it otherwise as filtered.
+ filter: (filterer, filterCallback) ->
+ # Save the filterer and filterCallback functions
+ @filterer = filterer
+ @filterCallback = filterCallback if filterCallback
+ filterCallback ?= @filterCallback
+
+ # Show/hide existing views
+ unless _(@getItemViews()).isEmpty()
+ for item, index in @collection.models
+
+ # Apply filter to the item
+ included = if typeof filterer is 'function'
+ filterer item, index
+ else
+ true
+
+ # Show/hide the view accordingly
+ view = @subview "itemView:#{item.cid}"
+ # A view has not been created for this item yet
+ unless view
+ throw new Error 'CollectionView#filter: ' +
+ "no view found for #{item.cid}"
+
+ # Show/hide or mark the view accordingly
+ @filterCallback view, included
+
+ # Update visibleItems list, but do not trigger an event immediately
+ @updateVisibleItems view.model, included, false
+
+ # Trigger a combined `visibilityChange` event
+ @trigger 'visibilityChange', @visibleItems
+
+ # Item view rendering
+ # -------------------
+
+ # Render and insert all items
+ renderAllItems: =>
+ items = @collection.models
+
+ # Reset visible items
+ @visibleItems = []
+
+ # Collect remaining views
+ remainingViewsByCid = {}
+ for item in items
+ view = @subview "itemView:#{item.cid}"
+ if view
+ # View remains
+ remainingViewsByCid[item.cid] = view
+
+ # Remove old views of items not longer in the list
+ for own cid, view of @getItemViews() when cid not of remainingViewsByCid
+ # Remove the view
+ @removeSubview "itemView:#{cid}"
+
+ # Re-insert remaining items; render and insert new items
+ for item, index in items
+ # Check if view was already created
+ view = @subview "itemView:#{item.cid}"
+ if view
+ # Re-insert the view
+ @insertView item, view, index, false
+ else
+ # Create a new view, render and insert it
+ @renderAndInsertItem item, index
+
+ # If no view was created, trigger `visibilityChange` event manually
+ unless items.length
+ @trigger 'visibilityChange', @visibleItems
+
+ # Render the view for an item
+ renderAndInsertItem: (item, index) ->
+ view = @renderItem item
+ @insertView item, view, index
+
+ # Instantiate and render an item using the `viewsByCid` hash as a cache
+ renderItem: (item) ->
+ # Get the existing view
+ view = @subview "itemView:#{item.cid}"
+
+ # Instantiate a new view if necessary
+ unless view
+ view = @getView item
+ # Save the view in the subviews
+ @subview "itemView:#{item.cid}", view
+
+ # Render in any case
+ view.render()
+
+ view
+
+ # Returns an instance of the view class. Override this
+ # method to use several item view constructors depending
+ # on the model type or data.
+ getView: (model) ->
+ if @itemView
+ new @itemView {model}
+ else
+ throw new Error 'The CollectionView#itemView property ' +
+ 'must be defined or the getView() must be overridden.'
+
+ # Inserts a view into the list at the proper position
+ insertView: (item, view, index = null, enableAnimation = true) ->
+ # Get the insertion offset
+ position = if typeof index is 'number'
+ index
+ else
+ @collection.indexOf item
+
+ # Is the item included in the filter?
+ included = if typeof @filterer is 'function'
+ @filterer item, position
+ else
+ true
+
+ # Get the view’s top element
+ viewEl = view.el
+ $viewEl = view.$el
+
+ if included
+ # Make view transparent if animation is enabled
+ if enableAnimation
+ if @useCssAnimation
+ $viewEl.addClass 'animated-item-view'
+ else
+ $viewEl.css 'opacity', 0
+ else
+ # Hide the view if it’s filtered
+ @filterCallback view, included
+
+ # Insert the view into the list
+ $list = @$list
+
+ # Get the children which originate from item views
+ children = if @itemSelector
+ $list.children @itemSelector
+ else
+ $list.children()
+
+ # Check if it needs to be inserted
+ unless children.get(position) is viewEl
+ length = children.length
+ if length is 0 or position is length
+ # Insert at the end
+ $list.append viewEl
+ else
+ # Insert at the right position
+ if position is 0
+ $next = children.eq position
+ $next.before viewEl
+ else
+ $previous = children.eq position - 1
+ $previous.after viewEl
+
+ # Tell the view that it was added to the DOM
+ view.trigger 'addedToDOM'
+
+ # Update the list of visible items, trigger a `visibilityChange` event
+ @updateVisibleItems item, included
+
+ # Fade the view in if it was made transparent before
+ if enableAnimation and included
+ if @useCssAnimation
+ # Wait for DOM state change.
+ setTimeout =>
+ $viewEl.addClass 'animated-item-view-end'
+ , 0
+ else
+ $viewEl.animate {opacity: 1}, @animationDuration
+
+ return
+
+ # Remove the view for an item
+ removeViewForItem: (item) ->
+ # Remove item from visibleItems list, trigger a `visibilityChange` event
+ @updateVisibleItems item, false
+ @removeSubview "itemView:#{item.cid}"
+
+ # List of visible items
+ # ---------------------
+
+ # Update visibleItems list and trigger a `visibilityChanged` event
+ # if an item changed its visibility
+ updateVisibleItems: (item, includedInFilter, triggerEvent = true) ->
+ visibilityChanged = false
+
+ visibleItemsIndex = _(@visibleItems).indexOf item
+ includedInVisibleItems = visibleItemsIndex > -1
+
+ if includedInFilter and not includedInVisibleItems
+ # Add item to the visible items list
+ @visibleItems.push item
+ visibilityChanged = true
+
+ else if not includedInFilter and includedInVisibleItems
+ # Remove item from the visible items list
+ @visibleItems.splice visibleItemsIndex, 1
+ visibilityChanged = true
+
+ # Trigger a `visibilityChange` event if the visible items changed
+ if visibilityChanged and triggerEvent
+ @trigger 'visibilityChange', @visibleItems
+
+ visibilityChanged
+
+ # Disposal
+ # --------
+
+ dispose: ->
+ return if @disposed
+
+ # Remove jQuery objects, item view cache and visible items list
+ properties = [
+ '$list', '$fallback', '$loading',
+ 'visibleItems'
+ ]
+ delete this[prop] for prop in properties
+
+ # Self-disposal
+ super
+
+require.define 'chaplin/lib/route': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ EventBroker = require 'chaplin/lib/event_broker'
+ Controller = require 'chaplin/controllers/controller'
+
+ module.exports = class Route
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ reservedParams = ['path', 'changeURL']
+ # Taken from Backbone.Router
+ escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
+
+ queryStringFieldSeparator = '&'
+ queryStringValueSeparator = '='
+
+ # Create a route for a URL pattern and a controller action
+ # e.g. new Route '/users/:id', 'users#show'
+ constructor: (pattern, target, @options = {}) ->
+ # Save the raw pattern
+ @pattern = pattern
+
+ # Separate target into controller and controller action
+ [@controller, @action] = target.split('#')
+
+ # Check if the action is a reserved name
+ if _(Controller.prototype).has @action
+ throw new Error 'Route: You should not use existing controller properties as action names'
+
+ @createRegExp()
+
+ createRegExp: ->
+ if _.isRegExp(@pattern)
+ @regExp = @pattern
+ return
+
+ pattern = @pattern
+ # Escape magic characters
+ .replace(escapeRegExp, '\\$&')
+ # Replace named parameters, collecting their names
+ .replace(/(?::|\*)(\w+)/g, @addParamName)
+
+ # Create the actual regular expression
+ # Match until the end of the URL or the begin of query string
+ @regExp = ///^#{pattern}(?=\?|$)///
+
+ addParamName: (match, paramName) =>
+ @paramNames ?= []
+ # Test if parameter name is reserved
+ if _(reservedParams).include(paramName)
+ throw new Error "Route#addParamName: parameter name #{paramName} is reserved"
+ # Save parameter name
+ @paramNames.push paramName
+ # Replace with a character class
+ if match.charAt(0) is ':'
+ # Regexp for :foo
+ '([^\/\?]+)'
+ else
+ # Regexp for *foo
+ '(.*?)'
+
+ # Test if the route matches to a path (called by Backbone.History#loadUrl)
+ test: (path) ->
+ # Test the main RegExp
+ matched = @regExp.test path
+ return false unless matched
+
+ # Apply the parameter constraints
+ constraints = @options.constraints
+ if constraints
+ params = @extractParams path
+ for own name, constraint of constraints
+ unless constraint.test(params[name])
+ return false
+
+ return true
+
+ # The handler which is called by Backbone.History when the route matched.
+ # It is also called by Router#follow which might pass options
+ handler: (path, options) =>
+ # Build params hash
+ params = @buildParams path, options
+
+ # Publish a global matchRoute event passing the route and the params
+ @publishEvent 'matchRoute', this, params
+
+ # Create a proper Rails-like params hash, not an array like Backbone
+ # `matches` and `additionalParams` arguments are optional
+ buildParams: (path, options) ->
+ params = {}
+
+ # Add params from query string
+ queryParams = @extractQueryParams path
+ _(params).extend queryParams
+
+ # Add named params from pattern matches
+ patternParams = @extractParams path
+ _(params).extend patternParams
+
+ # Add additional params from options
+ # (they might overwrite params extracted from URL)
+ _(params).extend @options.params
+
+ # Add a `changeURL` param whether to change the URL after routing
+ # Defaults to false unless explicitly set in options
+ params.changeURL = Boolean(options and options.changeURL)
+
+ # Add a `path param with the whole path match
+ params.path = path
+
+ params
+
+ # Extract named parameters from the URL path
+ extractParams: (path) ->
+ params = {}
+
+ # Apply the regular expression
+ matches = @regExp.exec path
+
+ # Fill the hash using the paramNames and the matches
+ for match, index in matches.slice(1)
+ paramName = if @paramNames then @paramNames[index] else index
+ params[paramName] = match
+
+ params
+
+ # Extract parameters from the query string
+ extractQueryParams: (path) ->
+ params = {}
+
+ regExp = /\?(.+?)(?=#|$)/
+ matches = regExp.exec path
+ return params unless matches
+
+ queryString = matches[1]
+ pairs = queryString.split queryStringFieldSeparator
+ for pair in pairs
+ continue unless pair.length
+ [field, value] = pair.split queryStringValueSeparator
+ continue unless field.length
+ field = decodeURIComponent field
+ value = decodeURIComponent value
+ current = params[field]
+ if current
+ # Handle multiple params with same name:
+ # Aggregate them in an array
+ if current.push
+ # Add the existing array
+ current.push value
+ else
+ # Create a new array
+ params[field] = [current, value]
+ else
+ params[field] = value
+
+ params
+
+require.define 'chaplin/lib/router': (exports, require, module) ->
+ _ = require 'underscore'
+ Backbone = require 'backbone'
+ mediator = require 'chaplin/mediator'
+ EventBroker = require 'chaplin/lib/event_broker'
+ Route = require 'chaplin/lib/route'
+
+ # The router which is a replacement for Backbone.Router.
+ # Like the standard router, it creates a Backbone.History
+ # instance and registers routes on it.
+
+ module.exports = class Router # This class does not extend Backbone.Router
+
+ # Borrow the static extend method from Backbone
+ @extend = Backbone.Model.extend
+
+ # Mixin an EventBroker
+ _(@prototype).extend EventBroker
+
+ constructor: (@options = {}) ->
+ _(@options).defaults
+ pushState: true
+
+ @subscribeEvent '!router:route', @routeHandler
+ @subscribeEvent '!router:changeURL', @changeURLHandler
+
+ @createHistory()
+
+ # Create a Backbone.History instance
+ createHistory: ->
+ Backbone.history or= new Backbone.History()
+
+ startHistory: ->
+ # Start the Backbone.History instance to start routing
+ # This should be called after all routes have been registered
+ Backbone.history.start @options
+
+ # Stop the current Backbone.History instance from observing URL changes
+ stopHistory: ->
+ Backbone.history.stop() if Backbone.History.started
+
+ # Connect an address with a controller action
+ # Directly create a route on the Backbone.History instance
+ match: (pattern, target, options = {}) =>
+ # Create the route
+ route = new Route pattern, target, options
+ # Register the route at the Backbone.History instance.
+ # Don’t use Backbone.history.route here because it calls
+ # handlers.unshift, inserting the handler at the top of the list.
+ # Since we want routes to match in the order they were specified,
+ # we’re appending the route at the end.
+ Backbone.history.handlers.push {route, callback: route.handler}
+ route
+
+ # Route a given URL path manually, returns whether a route matched
+ # This looks quite like Backbone.History::loadUrl but it
+ # accepts an absolute URL with a leading slash (e.g. /foo)
+ # and passes a changeURL param to the callback function.
+ route: (path) =>
+ # Remove leading hash or slash
+ path = path.replace /^(\/#|\/)/, ''
+ for handler in Backbone.history.handlers
+ if handler.route.test(path)
+ handler.callback path, changeURL: true
+ return true
+ false
+
+ # Handler for the global !router:route event
+ routeHandler: (path, callback) ->
+ routed = @route path
+ callback? routed
+
+ # Change the current URL, add a history entry.
+ # Do not trigger any routes (which is Backbone’s
+ # default behavior, but added for clarity)
+ changeURL: (url) ->
+ Backbone.history.navigate url, trigger: false
+
+ # Handler for the global !router:changeURL event
+ changeURLHandler: (url) ->
+ @changeURL url
+
+ # Disposal
+ # --------
+
+ disposed: false
+
+ dispose: ->
+ return if @disposed
+
+ # Stop Backbone.History instance and remove it
+ @stopHistory()
+ delete Backbone.history
+
+ @unsubscribeAllEvents()
+
+ # Finished
+ @disposed = true
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? this
+
+require.define 'chaplin/lib/delayer': (exports, require, module) ->
+
+ # Delayer
+ # -------
+ #
+ # Add functionality to set unique, named timeouts and intervals
+ # so they can be cleared afterwards when disposing the object.
+ # This is especially useful in your custom View class which inherits
+ # from the standard Chaplin.View.
+ #
+ # Mixin this object to add the delayer capability to any object:
+ # _(object).extend Delayer
+ #
+ # Or to a prototype of a class:
+ # _(@prototype).extend Delayer
+ #
+ # In the dispose method, call `clearDelayed` to remove all pending
+ # timeouts and running intervals:
+ #
+ # dispose: ->
+ # return if @disposed
+ # @clearDelayed()
+ # super
+
+ Delayer =
+
+ setTimeout: (name, time, handler) ->
+ @timeouts ?= {}
+ @clearTimeout name
+ wrappedHandler = =>
+ delete @timeouts[name]
+ handler()
+ handle = setTimeout wrappedHandler, time
+ @timeouts[name] = handle
+ handle
+
+ clearTimeout: (name) ->
+ return unless @timeouts and @timeouts[name]?
+ clearTimeout @timeouts[name]
+ delete @timeouts[name]
+ return
+
+ clearAllTimeouts: ->
+ return unless @timeouts
+ for name, handle of @timeouts
+ @clearTimeout name
+ return
+
+ setInterval: (name, time, handler) ->
+ @clearInterval name
+ @intervals ?= {}
+ handle = setInterval handler, time
+ @intervals[name] = handle
+ handle
+
+ clearInterval: (name) ->
+ return unless @intervals and @intervals[name]
+ clearInterval @intervals[name]
+ delete @intervals[name]
+ return
+
+ clearAllIntervals: ->
+ return unless @intervals
+ for name, handle of @intervals
+ @clearInterval name
+ return
+
+ clearDelayed: ->
+ @clearAllTimeouts()
+ @clearAllIntervals()
+ return
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? Delayer
+
+ module.exports = Delayer
+
+require.define 'chaplin/lib/event_broker': (exports, require, module) ->
+ mediator = require 'chaplin/mediator'
+
+ # Add functionality to subscribe and publish to global
+ # Publish/Subscribe events so they can be removed afterwards
+ # when disposing the object.
+ #
+ # Mixin this object to add the subscriber capability to any object:
+ # _(object).extend EventBroker
+ # Or to a prototype of a class:
+ # _(@prototype).extend EventBroker
+ #
+ # Since Backbone 0.9.2 this abstraction just serves the purpose
+ # that a handler cannot be registered twice for the same event.
+
+ EventBroker =
+
+ subscribeEvent: (type, handler) ->
+ if typeof type isnt 'string'
+ throw new TypeError 'EventBroker#subscribeEvent: ' +
+ 'type argument must be a string'
+ if typeof handler isnt 'function'
+ throw new TypeError 'EventBroker#subscribeEvent: ' +
+ 'handler argument must be a function'
+
+ # Ensure that a handler isn’t registered twice
+ mediator.unsubscribe type, handler, this
+
+ # Register global handler, force context to the subscriber
+ mediator.subscribe type, handler, this
+
+ unsubscribeEvent: (type, handler) ->
+ if typeof type isnt 'string'
+ throw new TypeError 'EventBroker#unsubscribeEvent: ' +
+ 'type argument must be a string'
+ if typeof handler isnt 'function'
+ throw new TypeError 'EventBroker#unsubscribeEvent: ' +
+ 'handler argument must be a function'
+
+ # Remove global handler
+ mediator.unsubscribe type, handler
+
+ # Unbind all global handlers
+ unsubscribeAllEvents: ->
+ # Remove all handlers with a context of this subscriber
+ mediator.unsubscribe null, null, this
+
+ publishEvent: (type, args...) ->
+ if typeof type isnt 'string'
+ throw new TypeError 'EventBroker#publishEvent: ' +
+ 'type argument must be a string'
+
+ # Publish global handler
+ mediator.publish type, args...
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? EventBroker
+
+ module.exports = EventBroker
+
+require.define 'chaplin/lib/support': (exports, require, module) ->
+
+ # Feature detection
+ # -----------------
+
+ support =
+
+ # Test for defineProperty support
+ # (IE 8 knows the method but will throw an exception)
+ propertyDescriptors: do ->
+ unless typeof Object.defineProperty is 'function' and
+ typeof Object.defineProperties is 'function'
+ return false
+ try
+ o = {}
+ Object.defineProperty o, 'foo', value: 'bar'
+ return o.foo is 'bar'
+ catch error
+ return false
+
+ module.exports = support
+
+require.define 'chaplin/lib/sync_machine': (exports, require, module) ->
+
+ # Simple finite state machine for synchronization of models/collections
+ # Three states: unsynced, syncing and synced
+ # Several transitions between them
+ # Fires Backbone events on every transition
+ # (unsynced, syncing, synced; syncStateChange)
+ # Provides shortcut methods to call handlers when a given state is reached
+ # (named after the events above)
+
+ UNSYNCED = 'unsynced'
+ SYNCING = 'syncing'
+ SYNCED = 'synced'
+
+ STATE_CHANGE = 'syncStateChange'
+
+ SyncMachine =
+
+ _syncState: UNSYNCED
+ _previousSyncState: null
+
+ # Get the current state
+ # ---------------------
+
+ syncState: ->
+ @_syncState
+
+ isUnsynced: ->
+ @_syncState is UNSYNCED
+
+ isSynced: ->
+ @_syncState is SYNCED
+
+ isSyncing: ->
+ @_syncState is SYNCING
+
+ # Transitions
+ # -----------
+
+ unsync: ->
+ if @_syncState in [SYNCING, SYNCED]
+ @_previousSync = @_syncState
+ @_syncState = UNSYNCED
+ @trigger @_syncState, this, @_syncState
+ @trigger STATE_CHANGE, this, @_syncState
+ # when UNSYNCED do nothing
+ return
+
+ beginSync: ->
+ if @_syncState in [UNSYNCED, SYNCED]
+ @_previousSync = @_syncState
+ @_syncState = SYNCING
+ @trigger @_syncState, this, @_syncState
+ @trigger STATE_CHANGE, this, @_syncState
+ # when SYNCING do nothing
+ return
+
+ finishSync: ->
+ if @_syncState is SYNCING
+ @_previousSync = @_syncState
+ @_syncState = SYNCED
+ @trigger @_syncState, this, @_syncState
+ @trigger STATE_CHANGE, this, @_syncState
+ # when SYNCED, UNSYNCED do nothing
+ return
+
+ abortSync: ->
+ if @_syncState is SYNCING
+ @_syncState = @_previousSync
+ @_previousSync = @_syncState
+ @trigger @_syncState, this, @_syncState
+ @trigger STATE_CHANGE, this, @_syncState
+ # when UNSYNCED, SYNCED do nothing
+ return
+
+ # Create shortcut methods to bind a handler to a state change
+ # -----------------------------------------------------------
+
+ for event in [UNSYNCED, SYNCING, SYNCED, STATE_CHANGE]
+ do (event) ->
+ SyncMachine[event] = (callback, context = @) ->
+ @on event, callback, context
+ callback.call(context) if @_syncState is event
+
+ # You’re frozen when your heart’s not open
+ Object.freeze? SyncMachine
+
+ module.exports = SyncMachine
+
+require.define 'chaplin/lib/utils': (exports, require, module) ->
+ support = require 'chaplin/lib/support'
+
+ # Utilities
+ # ---------
+
+ utils =
+
+ # Object Helpers
+ # --------------
+
+ # Prototypal delegation. Create an object which delegates
+ # to another object.
+ beget: do ->
+ if typeof Object.create is 'function'
+ Object.create
+ else
+ ctor = ->
+ (obj) ->
+ ctor:: = obj
+ new ctor
+
+ # Make properties readonly and not configurable
+ # using ECMAScript 5 property descriptors
+ readonly: do ->
+ if support.propertyDescriptors
+ readonlyDescriptor =
+ writable: false
+ enumerable: true
+ configurable: false
+ (obj, properties...) ->
+ for prop in properties
+ readonlyDescriptor.value = obj[prop]
+ Object.defineProperty obj, prop, readonlyDescriptor
+ true
+ else
+ ->
+ false
+
+ # String Helpers
+ # --------------
+
+ # Upcase the first character
+ upcase: (str) ->
+ str.charAt(0).toUpperCase() + str.substring(1)
+
+ # underScoreHelper -> under_score_helper
+ underscorize: (string) ->
+ string.replace /[A-Z]/g, (char, index) ->
+ (if index isnt 0 then '_' else '') + char.toLowerCase()
+
+ # Event handling helpers
+ # ----------------------
+
+ # Returns whether a modifier key is pressed during a keypress or mouse click
+ modifierKeyPressed: (event) ->
+ event.shiftKey or event.altKey or event.ctrlKey or event.metaKey
+
+ # Finish
+ # ------
+
+ # Seal the utils object
+ Object.seal? utils
+
+ module.exports = utils
+
+require.define 'chaplin': (exports, require, module) ->
+ Application = require 'chaplin/application'
+ mediator = require 'chaplin/mediator'
+ Dispatcher = require 'chaplin/dispatcher'
+ Controller = require 'chaplin/controllers/controller'
+ Collection = require 'chaplin/models/collection'
+ Model = require 'chaplin/models/model'
+ Layout = require 'chaplin/views/layout'
+ View = require 'chaplin/views/view'
+ CollectionView = require 'chaplin/views/collection_view'
+ Route = require 'chaplin/lib/route'
+ Router = require 'chaplin/lib/router'
+ Delayer = require 'chaplin/lib/delayer'
+ EventBroker = require 'chaplin/lib/event_broker'
+ support = require 'chaplin/lib/support'
+ SyncMachine = require 'chaplin/lib/sync_machine'
+ utils = require 'chaplin/lib/utils'
+
+ module.exports = {
+ Application,
+ mediator,
+ Dispatcher,
+ Controller,
+ Collection,
+ Model,
+ Layout,
+ View,
+ CollectionView,
+ Route,
+ Router,
+ Delayer,
+ EventBroker,
+ support,
+ SyncMachine,
+ utils
+ }
\ No newline at end of file
diff --git a/labs/dependency-examples/chaplin-brunch/vendor/scripts/console-helper.js b/labs/dependency-examples/chaplin-brunch/vendor/scripts/console-helper.js
new file mode 100644
index 0000000000..9fd4e3dbb7
--- /dev/null
+++ b/labs/dependency-examples/chaplin-brunch/vendor/scripts/console-helper.js
@@ -0,0 +1,11 @@
+// Make it safe to do console.log() always.
+(function (con) {
+ var method;
+ var dummy = function() {};
+ var methods = ('assert,count,debug,dir,dirxml,error,exception,group,' +
+ 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
+ 'time,timeEnd,trace,warn').split(',');
+ while (method = methods.pop()) {
+ con[method] = con[method] || dummy;
+ }
+})(window.console = window.console || {});