From 00ca0421638998655593d8e7321ecf6645b07860 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 11 Dec 2012 05:27:17 +0200 Subject: [PATCH] Add Chaplin app. --- .../chaplin-brunch/.editorconfig | 6 + .../chaplin-brunch/.gitignore | 5 + .../chaplin-brunch/README.md | 15 + .../chaplin-brunch/app/application.coffee | 67 + .../chaplin-brunch/app/assets/index.html | 36 + .../app/controllers/base/controller.coffee | 3 + .../app/controllers/footer-controller.coffee | 8 + .../app/controllers/header-controller.coffee | 8 + .../app/controllers/index-controller.coffee | 7 + .../app/controllers/todos-controller.coffee | 8 + .../chaplin-brunch/app/initialize.coffee | 6 + .../chaplin-brunch/app/lib/support.coffee | 13 + .../chaplin-brunch/app/lib/utils.coffee | 12 + .../chaplin-brunch/app/lib/view-helper.coffee | 39 + .../chaplin-brunch/app/mediator.coffee | 1 + .../app/models/base/collection.coffee | 9 + .../app/models/base/model.coffee | 5 + .../chaplin-brunch/app/models/todo.coffee | 16 + .../chaplin-brunch/app/models/todos.coffee | 18 + .../chaplin-brunch/app/routes.coffee | 3 + .../app/views/base/collection-view.coffee | 7 + .../chaplin-brunch/app/views/base/view.coffee | 7 + .../app/views/footer-view.coffee | 40 + .../app/views/header-view.coffee | 18 + .../chaplin-brunch/app/views/layout.coffee | 10 + .../app/views/templates/footer.hbs | 20 + .../app/views/templates/header.hbs | 2 + .../app/views/templates/todo.hbs | 6 + .../app/views/templates/todos.hbs | 3 + .../chaplin-brunch/app/views/todo-view.coffee | 40 + .../app/views/todos-view.coffee | 31 + .../chaplin-brunch/config.coffee | 29 + .../chaplin-brunch/package.json | 30 + .../chaplin-brunch/public/index.html | 36 + .../chaplin-brunch/public/javascripts/app.js | 937 ++++ .../public/javascripts/vendor.js | 3855 +++++++++++++++++ .../chaplin-brunch/public/stylesheets/app.css | 5 + .../vendor/scripts/backbone-0.9.2.js | 1431 ++++++ .../vendor/scripts/backbone-localStorage.js | 136 + .../vendor/scripts/chaplin-0.5.0.coffee | 2249 ++++++++++ .../vendor/scripts/console-helper.js | 11 + 41 files changed, 9188 insertions(+) create mode 100644 labs/dependency-examples/chaplin-brunch/.editorconfig create mode 100644 labs/dependency-examples/chaplin-brunch/.gitignore create mode 100644 labs/dependency-examples/chaplin-brunch/README.md create mode 100644 labs/dependency-examples/chaplin-brunch/app/application.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/assets/index.html create mode 100644 labs/dependency-examples/chaplin-brunch/app/controllers/base/controller.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/controllers/footer-controller.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/controllers/header-controller.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/controllers/index-controller.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/controllers/todos-controller.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/initialize.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/lib/support.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/lib/utils.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/lib/view-helper.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/mediator.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/models/base/collection.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/models/base/model.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/models/todo.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/models/todos.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/routes.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/base/collection-view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/base/view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/footer-view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/header-view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/layout.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/templates/footer.hbs create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/templates/header.hbs create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/templates/todo.hbs create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/templates/todos.hbs create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/todo-view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/app/views/todos-view.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/config.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/package.json create mode 100644 labs/dependency-examples/chaplin-brunch/public/index.html create mode 100644 labs/dependency-examples/chaplin-brunch/public/javascripts/app.js create mode 100644 labs/dependency-examples/chaplin-brunch/public/javascripts/vendor.js create mode 100644 labs/dependency-examples/chaplin-brunch/public/stylesheets/app.css create mode 100644 labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-0.9.2.js create mode 100644 labs/dependency-examples/chaplin-brunch/vendor/scripts/backbone-localStorage.js create mode 100644 labs/dependency-examples/chaplin-brunch/vendor/scripts/chaplin-0.5.0.coffee create mode 100644 labs/dependency-examples/chaplin-brunch/vendor/scripts/console-helper.js 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 + + + 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 @@ +
+ + + +
+ 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 @@ + + + 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";}); +}}); + +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 \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 "\n\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 = $('