├── .gitignore
├── .rspec
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── assets
│ ├── javascripts
│ │ ├── application.js.coffee
│ │ ├── foo.js
│ │ ├── main.js.coffee
│ │ ├── models
│ │ │ └── todo.coffee
│ │ └── views
│ │ │ ├── helpers.coffee
│ │ │ └── todo_view.coffee
│ └── stylesheets
│ │ ├── .gitkeep
│ │ ├── application.css
│ │ ├── scaffold.css
│ │ └── todos.css
├── controllers
│ ├── application_controller.rb
│ └── todos_controller.rb
├── helpers
│ └── application_helper.rb
├── models
│ └── todo.rb
└── views
│ ├── layouts
│ └── application.html.haml
│ └── todos
│ └── index.html.haml
├── config.ru
├── config
├── application.rb
├── assets.yml
├── boot.rb
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── backbone.rb
│ ├── backtrace_silencers.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── secret_token.rb
│ └── session_store.rb
├── locales
│ └── en.yml
└── routes.rb
├── db
├── migrate
│ └── 20101104122721_create_todos.rb
├── schema.rb
└── seeds.rb
├── doc
└── README_FOR_APP
├── lib
└── tasks
│ └── .gitkeep
├── public
├── 404.html
├── 422.html
├── 500.html
├── SpecRunner.html
├── favicon.ico
├── images
│ └── destroy.png
└── robots.txt
├── script
└── rails
├── spec
├── assets
│ └── javascripts
│ │ ├── spec_helper.coffee
│ │ ├── specs.js.coffee
│ │ └── todo_spec.coffee
├── javascripts
│ └── support
│ │ ├── jasmine.yml
│ │ ├── jasmine_config.rb
│ │ └── jasmine_runner.rb
├── models
│ └── todo_spec.rb
└── spec_helper.rb
└── vendor
├── assets
├── javascripts
│ ├── backbone.js
│ ├── handlebars.js
│ ├── jasmine-html.js
│ ├── jasmine.js
│ ├── jquery.js
│ └── underscore.js
└── stylesheets
│ └── jasmine.css
└── plugins
└── .gitkeep
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle
2 | .livereload
3 | .rvmrc
4 | .DS_Store
5 | db/*.sqlite3
6 | log/*.log
7 | tmp/**/*
8 | /public/javascripts/compiled
9 | /spec/javascripts/compiled
10 | .redcar
11 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'http://rubygems.org'
2 |
3 | gem 'rails', '>=3.1.rc'
4 |
5 | gem 'rake', '0.8.7'
6 |
7 | gem 'sqlite3', :require => 'sqlite3'
8 |
9 | gem 'haml'
10 | gem 'jquery-rails'
11 | gem 'json'
12 | gem 'sass'
13 | gem 'coffee-script'
14 | gem 'uglifier'
15 |
16 | group :development, :test do
17 | gem 'awesome_print', :require => 'ap'
18 | gem 'rspec-rails', '~> 2.0'
19 | gem 'jasmine'
20 | end
21 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | actionmailer (3.1.0.rc1)
5 | actionpack (= 3.1.0.rc1)
6 | mail (~> 2.3.0)
7 | actionpack (3.1.0.rc1)
8 | activemodel (= 3.1.0.rc1)
9 | activesupport (= 3.1.0.rc1)
10 | builder (~> 3.0.0)
11 | erubis (~> 2.7.0)
12 | i18n (~> 0.6.0beta1)
13 | rack (~> 1.3.0.beta2)
14 | rack-cache (~> 1.0.1)
15 | rack-mount (~> 0.8.1)
16 | rack-test (~> 0.6.0)
17 | sprockets (~> 2.0.0.beta.5)
18 | tzinfo (~> 0.3.27)
19 | activemodel (3.1.0.rc1)
20 | activesupport (= 3.1.0.rc1)
21 | bcrypt-ruby (~> 2.1.4)
22 | builder (~> 3.0.0)
23 | i18n (~> 0.6.0beta1)
24 | activerecord (3.1.0.rc1)
25 | activemodel (= 3.1.0.rc1)
26 | activesupport (= 3.1.0.rc1)
27 | arel (~> 2.1.1)
28 | tzinfo (~> 0.3.27)
29 | activeresource (3.1.0.rc1)
30 | activemodel (= 3.1.0.rc1)
31 | activesupport (= 3.1.0.rc1)
32 | activesupport (3.1.0.rc1)
33 | multi_json (~> 1.0)
34 | arel (2.1.1)
35 | awesome_print (0.4.0)
36 | bcrypt-ruby (2.1.4)
37 | builder (3.0.0)
38 | childprocess (0.1.9)
39 | ffi (~> 1.0.6)
40 | coffee-script (2.2.0)
41 | coffee-script-source
42 | execjs
43 | coffee-script-source (1.1.1)
44 | diff-lcs (1.1.2)
45 | erubis (2.7.0)
46 | execjs (1.1.2)
47 | multi_json (~> 1.0)
48 | ffi (1.0.9)
49 | haml (3.1.1)
50 | hike (1.0.0)
51 | i18n (0.6.0)
52 | jasmine (1.0.2.1)
53 | json_pure (>= 1.4.3)
54 | rack (>= 1.1)
55 | rspec (>= 1.3.1)
56 | selenium-webdriver (>= 0.1.3)
57 | jquery-rails (1.0.9)
58 | railties (~> 3.0)
59 | thor (~> 0.14)
60 | json (1.5.1)
61 | json_pure (1.5.1)
62 | mail (2.3.0)
63 | i18n (>= 0.4.0)
64 | mime-types (~> 1.16)
65 | treetop (~> 1.4.8)
66 | mime-types (1.16)
67 | multi_json (1.0.3)
68 | polyglot (0.3.1)
69 | rack (1.3.0)
70 | rack-cache (1.0.2)
71 | rack (>= 0.4)
72 | rack-mount (0.8.1)
73 | rack (>= 1.0.0)
74 | rack-ssl (1.3.2)
75 | rack
76 | rack-test (0.6.0)
77 | rack (>= 1.0)
78 | rails (3.1.0.rc1)
79 | actionmailer (= 3.1.0.rc1)
80 | actionpack (= 3.1.0.rc1)
81 | activerecord (= 3.1.0.rc1)
82 | activeresource (= 3.1.0.rc1)
83 | activesupport (= 3.1.0.rc1)
84 | bundler (~> 1.0)
85 | railties (= 3.1.0.rc1)
86 | railties (3.1.0.rc1)
87 | actionpack (= 3.1.0.rc1)
88 | activesupport (= 3.1.0.rc1)
89 | rack-ssl (~> 1.3.2)
90 | rake (>= 0.8.7)
91 | thor (~> 0.14.6)
92 | rake (0.8.7)
93 | rspec (2.6.0)
94 | rspec-core (~> 2.6.0)
95 | rspec-expectations (~> 2.6.0)
96 | rspec-mocks (~> 2.6.0)
97 | rspec-core (2.6.3)
98 | rspec-expectations (2.6.0)
99 | diff-lcs (~> 1.1.2)
100 | rspec-mocks (2.6.0)
101 | rspec-rails (2.6.1)
102 | actionpack (~> 3.0)
103 | activesupport (~> 3.0)
104 | railties (~> 3.0)
105 | rspec (~> 2.6.0)
106 | rubyzip (0.9.4)
107 | sass (3.1.2)
108 | selenium-webdriver (0.2.1)
109 | childprocess (>= 0.1.7)
110 | ffi (>= 1.0.7)
111 | json_pure
112 | rubyzip
113 | sprockets (2.0.0.beta.9)
114 | hike (~> 1.0)
115 | rack (~> 1.0)
116 | tilt (~> 1.1, != 1.3.0)
117 | sqlite3 (1.3.3)
118 | thor (0.14.6)
119 | tilt (1.3.2)
120 | treetop (1.4.9)
121 | polyglot (>= 0.3.1)
122 | tzinfo (0.3.27)
123 | uglifier (0.5.4)
124 | execjs (>= 0.3.0)
125 | multi_json (>= 1.0.2)
126 |
127 | PLATFORMS
128 | ruby
129 |
130 | DEPENDENCIES
131 | awesome_print
132 | coffee-script
133 | haml
134 | jasmine
135 | jquery-rails
136 | json
137 | rails (>= 3.1.rc)
138 | rake (= 0.8.7)
139 | rspec-rails (~> 2.0)
140 | sass
141 | sqlite3
142 | uglifier
143 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Overview
2 | ========
3 |
4 | This is sample Rails application that demonstrates usage of
5 |
6 | * [Backbone.js](http://documentcloud.github.com/backbone/)
7 | * [CoffeeScript](http://jashkenas.github.com/coffee-script/)
8 | * [Jasmine](http://pivotal.github.com/jasmine/)
9 | * Rails 3.1 asset pipeline
10 |
11 | This code is from the demo used during [RailsWayCon 2011](http://railswaycon.com/2011/sessions#session-17838) conference presentation [Rails-like JavaScript using CoffeeScript, Backbone.js and Jasmine](http://www.slideshare.net/rsim/railslike-javascript-using-coffeescript-backbonejs-and-jasmine-8196890).
12 | Source code is based on original [Todos application](http://documentcloud.github.com/backbone/docs/todos.html) by [Jérôme Gravel-Niquet](https://github.com/jeromegn).
13 |
14 | In this fork, I've moved it around and upgraded it to rails 3.1
15 |
16 | Running application
17 | ===================
18 |
19 | Install gems, run migrations and start application with
20 |
21 | bundle install
22 | rake db:migrate
23 | rails server
24 |
25 | And then visit `http://localhost:3000`.
26 |
27 | Running tests
28 | =============
29 |
30 | Run `rake jasmine` and then visit `http://localhost:8888` to execute Jasmine tests and see results.
31 |
32 | Application source code
33 | =======================
34 |
35 | Backbone.js models and views written in CoffeeScript are located in `app/assets/javascripts` directory and Jasmine tests are located in `spec/coffeescripts` directory.
36 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 | require 'rake'
6 |
7 | BackboneTodo::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js.coffee:
--------------------------------------------------------------------------------
1 | window.TodoApp = {}
2 |
3 | $ ->
4 | TodoApp.appView = new TodoApp.AppView collection: new TodoApp.TodoList
5 |
--------------------------------------------------------------------------------
/app/assets/javascripts/foo.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/app/assets/javascripts/foo.js
--------------------------------------------------------------------------------
/app/assets/javascripts/main.js.coffee:
--------------------------------------------------------------------------------
1 | #= require jquery
2 | #= require underscore
3 | #= require backbone
4 | #= require handlebars
5 | #= require application
6 | #= require_tree .
--------------------------------------------------------------------------------
/app/assets/javascripts/models/todo.coffee:
--------------------------------------------------------------------------------
1 | # Todo Model
2 | # ----------
3 |
4 | # Our basic **Todo** model has `content`, `order`, and `done` attributes.
5 | class TodoApp.Todo extends Backbone.Model
6 |
7 | # If you don't provide a todo, one will be provided for you.
8 | EMPTY: "empty todo..."
9 |
10 | # Ensure that each todo created has `content`.
11 | initialize: ->
12 | unless @get "content"
13 | @set content: @EMPTY
14 |
15 | # Toggle the `done` state of this todo item.
16 | toggle: ->
17 | @save done: not @get "done"
18 |
19 |
20 | # Todo Collection
21 | # ---------------
22 | #
23 | # The collection of todos is backed by TodosController
24 | #
25 | class TodoApp.TodoList extends Backbone.Collection
26 |
27 | # Reference to this collection's model.
28 | model: TodoApp.Todo
29 |
30 | # Save all of the todo items under the `"todos"` namespace.
31 | url: '/todos'
32 |
33 | # Filter down the list of all todo items that are finished.
34 | done: ->
35 | @filter (todo) -> todo.get 'done'
36 |
37 | # Filter down the list to only todo items that are still not finished.
38 | remaining: ->
39 | @without this.done()...
40 |
41 | # We keep the Todos in sequential order, despite being saved by unordered
42 | # GUID in the database. This generates the next order number for new items.
43 | nextOrder: ->
44 | if @length then @last().get('order') + 1 else 1
45 |
46 | # Todos are sorted by their original insertion order.
47 | comparator: (todo) ->
48 | todo.get 'order'
49 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/helpers.coffee:
--------------------------------------------------------------------------------
1 | # Compile and store template function from DOM element with specified selector
2 | TodoApp.template = (selector) ->
3 | template = null
4 | ->
5 | template ?= Handlebars.compile(if selector.charAt(0) == "#" then $(selector).html() else selector)
6 | template.apply this, arguments
7 |
8 | Handlebars.registerHelper 'debug', (ctx) ->
9 | context = typeof ctx == "function" ? ctx.call this : ctx
10 | console.log context
11 |
12 |
13 | Handlebars.registerHelper 'pluralize', (count, word, fn) ->
14 | if count != 1 then word+"s" else word
15 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/todo_view.coffee:
--------------------------------------------------------------------------------
1 | # Todo Item View
2 | # --------------
3 |
4 | # The DOM element for a todo item...
5 | class TodoApp.TodoView extends Backbone.View
6 | # ... is a list tag.
7 | tagName: "li"
8 |
9 | # Cache the template function for a single item.
10 | template: TodoApp.template '#item-template'
11 |
12 | # The DOM events specific to an item.
13 | events:
14 | "click .check" : "toggleDone"
15 | "dblclick div.todo-content" : "edit"
16 | "click span.todo-destroy" : "destroy"
17 | "keypress .todo-input" : "updateOnEnter"
18 |
19 | # The TodoView listens for changes to its model, re-rendering. Since there's
20 | # a one-to-one correspondence between a **Todo** and a **TodoView** in this
21 | # app, we set a direct reference on the model for convenience.
22 | initialize: ->
23 | _.bindAll this, 'render', 'close'
24 | @model.bind 'change', @render
25 | @model.bind 'destroy', => @remove()
26 |
27 | # Re-render the contents of the todo item.
28 | render: ->
29 | $(@el).html @template @model.toJSON()
30 | @setContent()
31 | this
32 |
33 | # To avoid XSS (not that it would be harmful in this particular app),
34 | # we use `jQuery.text` to set the contents of the todo item.
35 | setContent: ->
36 | content = @model.get 'content'
37 | @$('.todo-content').text content
38 | @input = @$('.todo-input')
39 | @input.blur @close
40 | @input.val content
41 |
42 | # Toggle the `"done"` state of the model.
43 | toggleDone: ->
44 | @model.toggle()
45 |
46 | # Switch this view into `"editing"` mode, displaying the input field.
47 | edit: ->
48 | $(@el).addClass "editing"
49 | @input.focus()
50 |
51 | # Close the `"editing"` mode, saving changes to the todo.
52 | close: ->
53 | @model.save content: @input.val()
54 | $(@el).removeClass "editing"
55 |
56 | # If you hit `enter`, we're through editing the item.
57 | updateOnEnter: (e) ->
58 | @close() if e.keyCode == 13
59 |
60 | # Destroy the model.
61 | destroy: ->
62 | @model.destroy()
63 |
64 |
65 | # The Application
66 | # ---------------
67 |
68 | # Our overall **AppView** is the top-level piece of UI.
69 | class TodoApp.AppView extends Backbone.View
70 |
71 | # Instead of generating a new element, bind to the existing skeleton of
72 | # the App already present in the HTML.
73 | el: "#todoapp"
74 |
75 | # Our template for the line of statistics at the bottom of the app.
76 | statsTemplate: TodoApp.template '#stats-template'
77 |
78 | # Delegated events for creating new items, and clearing completed ones.
79 | events:
80 | "keypress #new-todo" : "createOnEnter"
81 | "keyup #new-todo" : "showTooltip"
82 | "click .todo-clear a" : "clearCompleted"
83 |
84 | # At initialization we bind to the relevant events on the `Todos`
85 | # collection, when items are added or changed. Kick things off by
86 | # loading any preexisting todos that might be saved
87 | initialize: ->
88 | _.bindAll this, 'addOne', 'addAll', 'renderStats'
89 |
90 | @input = @$("#new-todo")
91 |
92 | @collection.bind 'add', @addOne
93 | @collection.bind 'refresh', @addAll
94 | @collection.bind 'all', @renderStats
95 |
96 | @collection.fetch()
97 |
98 | # Re-rendering the App just means refreshing the statistics -- the rest
99 | # of the app doesn't change.
100 | renderStats: ->
101 | @$('#todo-stats').html @statsTemplate
102 | total: @collection.length
103 | done: @collection.done().length
104 | remaining: @collection.remaining().length
105 |
106 | # Add a single todo item to the list by creating a view for it, and
107 | # appending its element to the `
`.
108 | addOne: (todo) ->
109 | view = new TodoApp.TodoView model: todo
110 | @$("#todo-list").append view.render().el
111 |
112 | # Add all items in the collection at once.
113 | addAll: ->
114 | @collection.each @addOne
115 |
116 | # Generate the attributes for a new Todo item.
117 | newAttributes: ->
118 | content: @input.val()
119 | order: @collection.nextOrder()
120 | done: false
121 |
122 | # If you hit return in the main input field, create new **Todo** model,
123 | # persisting it to server.
124 | createOnEnter: (e) ->
125 | if e.keyCode == 13
126 | @collection.create @newAttributes()
127 | @input.val ''
128 |
129 | # Clear all done todo items, destroying their views and models.
130 | clearCompleted: ->
131 | todo.destroy() for todo in @collection.done()
132 | false
133 |
134 | # Lazily show the tooltip that tells you to press `enter` to save
135 | # a new todo item, after one second.
136 | showTooltip: (e) ->
137 | tooltip = @$(".ui-tooltip-top")
138 | val = @input.val()
139 | tooltip.fadeOut()
140 | clearTimeout @tooltipTimeout if @tooltipTimeout
141 | unless val == '' or val == @input.attr 'placeholder'
142 | @tooltipTimeout = _.delay ->
143 | tooltip.show().fadeIn()
144 | , 1000
145 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/app/assets/stylesheets/.gitkeep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory
3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4 | * the top of the compiled file, but it's generally better to create a new file per style scope.
5 | *= require_self
6 | *= require_tree .
7 | */
--------------------------------------------------------------------------------
/app/assets/stylesheets/scaffold.css:
--------------------------------------------------------------------------------
1 | body { background-color: #fff; color: #333; }
2 |
3 | body, p, ol, ul, td {
4 | font-family: verdana, arial, helvetica, sans-serif;
5 | font-size: 13px;
6 | line-height: 18px;
7 | }
8 |
9 | pre {
10 | background-color: #eee;
11 | padding: 10px;
12 | font-size: 11px;
13 | }
14 |
15 | a { color: #000; }
16 | a:visited { color: #666; }
17 | a:hover { color: #fff; background-color:#000; }
18 |
19 | div.field, div.actions {
20 | margin-bottom: 10px;
21 | }
22 |
23 | #notice {
24 | color: green;
25 | }
26 |
27 | .field_with_errors {
28 | padding: 2px;
29 | background-color: red;
30 | display: table;
31 | }
32 |
33 | #error_explanation {
34 | width: 450px;
35 | border: 2px solid red;
36 | padding: 7px;
37 | padding-bottom: 0;
38 | margin-bottom: 20px;
39 | background-color: #f0f0f0;
40 | }
41 |
42 | #error_explanation h2 {
43 | text-align: left;
44 | font-weight: bold;
45 | padding: 5px 5px 5px 15px;
46 | font-size: 12px;
47 | margin: -7px;
48 | margin-bottom: 0px;
49 | background-color: #c00;
50 | color: #fff;
51 | }
52 |
53 | #error_explanation ul li {
54 | font-size: 12px;
55 | list-style: square;
56 | }
57 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/todos.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, font, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | dl, dt, dd, ol, ul, li,
7 | fieldset, form, label, legend,
8 | table, caption, tbody, tfoot, thead, tr, th, td {
9 | margin: 0;
10 | padding: 0;
11 | border: 0;
12 | outline: 0;
13 | font-weight: inherit;
14 | font-style: inherit;
15 | font-size: 100%;
16 | font-family: inherit;
17 | vertical-align: baseline;
18 | }
19 | body {
20 | line-height: 1;
21 | color: black;
22 | background: white;
23 | }
24 | ol, ul {
25 | list-style: none;
26 | }
27 | a img {
28 | border: none;
29 | }
30 |
31 | html {
32 | background: #eeeeee;
33 | }
34 | body {
35 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
36 | font-size: 14px;
37 | line-height: 1.4em;
38 | background: #eeeeee;
39 | color: #333333;
40 | }
41 |
42 | #todoapp {
43 | width: 480px;
44 | margin: 0 auto 40px;
45 | background: white;
46 | padding: 20px;
47 | -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
48 | -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
49 | -o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
50 | box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
51 | }
52 | #todoapp h1 {
53 | font-size: 36px;
54 | font-weight: bold;
55 | text-align: center;
56 | padding: 20px 0 30px 0;
57 | line-height: 1;
58 | }
59 |
60 | #create-todo {
61 | position: relative;
62 | }
63 | #create-todo input {
64 | width: 466px;
65 | font-size: 24px;
66 | font-family: inherit;
67 | line-height: 1.4em;
68 | border: 0;
69 | outline: none;
70 | padding: 6px;
71 | border: 1px solid #999999;
72 | -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
73 | -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
74 | -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
75 | box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
76 | }
77 | #create-todo input::-webkit-input-placeholder {
78 | font-style: italic;
79 | }
80 | #create-todo span {
81 | position: absolute;
82 | z-index: 999;
83 | width: 170px;
84 | left: 50%;
85 | margin-left: -85px;
86 | }
87 |
88 | #todo-list {
89 | margin-top: 10px;
90 | }
91 | #todo-list li {
92 | padding: 12px 20px 11px 0;
93 | position: relative;
94 | font-size: 24px;
95 | line-height: 1.1em;
96 | border-bottom: 1px solid #cccccc;
97 | }
98 | #todo-list li:after {
99 | content: "\0020";
100 | display: block;
101 | height: 0;
102 | clear: both;
103 | overflow: hidden;
104 | visibility: hidden;
105 | }
106 | #todo-list li.editing {
107 | padding: 0;
108 | border-bottom: 0;
109 | }
110 | #todo-list .editing .display,
111 | #todo-list .edit {
112 | display: none;
113 | }
114 | #todo-list .editing .edit {
115 | display: block;
116 | }
117 | #todo-list .editing input {
118 | width: 444px;
119 | font-size: 24px;
120 | font-family: inherit;
121 | margin: 0;
122 | line-height: 1.6em;
123 | border: 0;
124 | outline: none;
125 | padding: 10px 7px 0px 27px;
126 | border: 1px solid #999999;
127 | -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
128 | -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
129 | -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
130 | box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
131 | }
132 | #todo-list .check {
133 | position: relative;
134 | top: 9px;
135 | margin: 0 10px 0 7px;
136 | float: left;
137 | }
138 | #todo-list .done .todo-content {
139 | text-decoration: line-through;
140 | color: #777777;
141 | }
142 | #todo-list .todo-destroy {
143 | position: absolute;
144 | right: 5px;
145 | top: 14px;
146 | display: none;
147 | cursor: pointer;
148 | width: 20px;
149 | height: 20px;
150 | background: url(/images/destroy.png) no-repeat 0 0;
151 | }
152 | #todo-list li:hover .todo-destroy {
153 | display: block;
154 | }
155 | #todo-list .todo-destroy:hover {
156 | background-position: 0 -20px;
157 | }
158 |
159 | #todo-stats {
160 | *zoom: 1;
161 | margin-top: 10px;
162 | color: #777777;
163 | }
164 | #todo-stats:after {
165 | content: "\0020";
166 | display: block;
167 | height: 0;
168 | clear: both;
169 | overflow: hidden;
170 | visibility: hidden;
171 | }
172 | #todo-stats .todo-count {
173 | float: left;
174 | }
175 | #todo-stats .todo-count .number {
176 | font-weight: bold;
177 | color: #333333;
178 | }
179 | #todo-stats .todo-clear {
180 | float: right;
181 | }
182 | #todo-stats .todo-clear a {
183 | color: #777777;
184 | font-size: 12px;
185 | }
186 | #todo-stats .todo-clear a:visited {
187 | color: #777777;
188 | }
189 | #todo-stats .todo-clear a:hover {
190 | color: #336699;
191 | }
192 |
193 | #instructions {
194 | width: 520px;
195 | margin: 10px auto;
196 | color: #777777;
197 | text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
198 | text-align: center;
199 | }
200 | #instructions a {
201 | color: #336699;
202 | }
203 |
204 | #credits {
205 | width: 520px;
206 | margin: 30px auto;
207 | color: #999;
208 | text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
209 | text-align: center;
210 | }
211 | #credits a {
212 | color: #888;
213 | }
214 |
215 |
216 | /*
217 | * François 'cahnory' Germain
218 | */
219 | .ui-tooltip, .ui-tooltip-top, .ui-tooltip-right, .ui-tooltip-bottom, .ui-tooltip-left {
220 | color:#ffffff;
221 | cursor:normal;
222 | display:-moz-inline-stack;
223 | display:inline-block;
224 | font-size:12px;
225 | font-family:arial;
226 | padding:.5em 1em;
227 | position:relative;
228 | text-align:center;
229 | text-shadow:0 -1px 1px #111111;
230 | -webkit-border-top-left-radius:4px ;
231 | -webkit-border-top-right-radius:4px ;
232 | -webkit-border-bottom-right-radius:4px ;
233 | -webkit-border-bottom-left-radius:4px ;
234 | -khtml-border-top-left-radius:4px ;
235 | -khtml-border-top-right-radius:4px ;
236 | -khtml-border-bottom-right-radius:4px ;
237 | -khtml-border-bottom-left-radius:4px ;
238 | -moz-border-radius-topleft:4px ;
239 | -moz-border-radius-topright:4px ;
240 | -moz-border-radius-bottomright:4px ;
241 | -moz-border-radius-bottomleft:4px ;
242 | border-top-left-radius:4px ;
243 | border-top-right-radius:4px ;
244 | border-bottom-right-radius:4px ;
245 | border-bottom-left-radius:4px ;
246 | -o-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
247 | -moz-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
248 | -khtml-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
249 | -webkit-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
250 | box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
251 | background-color:#3b3b3b;
252 | background-image:-moz-linear-gradient(top,#555555,#222222);
253 | background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555555),color-stop(1,#222222));
254 | filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
255 | -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
256 | }
257 | .ui-tooltip:after, .ui-tooltip-top:after, .ui-tooltip-right:after, .ui-tooltip-bottom:after, .ui-tooltip-left:after {
258 | content:"\25B8";
259 | display:block;
260 | font-size:2em;
261 | height:0;
262 | line-height:0;
263 | position:absolute;
264 | }
265 | .ui-tooltip:after, .ui-tooltip-bottom:after {
266 | color:#2a2a2a;
267 | bottom:0;
268 | left:1px;
269 | text-align:center;
270 | text-shadow:1px 0 2px #000000;
271 | -o-transform:rotate(90deg);
272 | -moz-transform:rotate(90deg);
273 | -khtml-transform:rotate(90deg);
274 | -webkit-transform:rotate(90deg);
275 | width:100%;
276 | }
277 | .ui-tooltip-top:after {
278 | bottom:auto;
279 | color:#4f4f4f;
280 | left:-2px;
281 | top:0;
282 | text-align:center;
283 | text-shadow:none;
284 | -o-transform:rotate(-90deg);
285 | -moz-transform:rotate(-90deg);
286 | -khtml-transform:rotate(-90deg);
287 | -webkit-transform:rotate(-90deg);
288 | width:100%;
289 | }
290 | .ui-tooltip-right:after {
291 | color:#222222;
292 | right:-0.375em;
293 | top:50%;
294 | margin-top:-.05em;
295 | text-shadow:0 1px 2px #000000;
296 | -o-transform:rotate(0);
297 | -moz-transform:rotate(0);
298 | -khtml-transform:rotate(0);
299 | -webkit-transform:rotate(0);
300 | }
301 | .ui-tooltip-left:after {
302 | color:#222222;
303 | left:-0.375em;
304 | top:50%;
305 | margin-top:.1em;
306 | text-shadow:0 -1px 2px #000000;
307 | -o-transform:rotate(180deg);
308 | -moz-transform:rotate(180deg);
309 | -khtml-transform:rotate(180deg);
310 | -webkit-transform:rotate(180deg);
311 | }
312 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/todos_controller.rb:
--------------------------------------------------------------------------------
1 | class TodosController < ApplicationController
2 | respond_to :html, :json
3 |
4 | def index
5 | respond_to do |format|
6 | format.html
7 | format.json { respond_with Todo.all }
8 | end
9 | end
10 |
11 | def show
12 | respond_with Todo.find(params[:id])
13 | end
14 |
15 | def create
16 | todo = Todo.new(params)
17 | if todo.save
18 | respond_with todo
19 | else
20 | render :json => todo.errors, :status => :unprocessable_entity
21 | end
22 | end
23 |
24 | def update
25 | todo = Todo.find(params[:id])
26 |
27 | if todo.update_attributes(params)
28 | respond_with todo
29 | else
30 | render :json => todo.errors, :status => :unprocessable_entity
31 | end
32 | end
33 |
34 | def destroy
35 | todo = Todo.find(params[:id])
36 | todo.destroy
37 | render :json => nil
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/todo.rb:
--------------------------------------------------------------------------------
1 | class Todo < ActiveRecord::Base
2 | attr_accessible :content, :order, :done
3 |
4 | validates_presence_of :content
5 |
6 | end
7 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %title Backbone Demo: Todos
5 | = stylesheet_link_tag "application"
6 | = javascript_include_tag "main"
7 | %body
8 | = yield
9 |
--------------------------------------------------------------------------------
/app/views/todos/index.html.haml:
--------------------------------------------------------------------------------
1 | #todoapp
2 | .title
3 | %h1 Todos
4 | .content
5 | #create-todo
6 | %input#new-todo{:placeholder => "What needs to be done?", :type => "text"}/
7 | %span.ui-tooltip-top{:style => "display:none;"} Press Enter to save this task
8 | #todos
9 | %ul#todo-list
10 | #todo-stats
11 | %ul#instructions
12 | %li Double-click to edit a todo.
13 |
14 | %script#item-template{:type => "text/html"}
15 | .todo{:class => "{{#done}}done{{/done}}"}
16 | .display
17 | %input{:class => "check", :type => "checkbox", :"{{#done}}checked{{/done}}" => true}
18 | .todo-content
19 | %span.todo-destroy
20 | .edit
21 | %input.todo-input{:type => "text", :value => ""}
22 |
23 | %script#stats-template{:type => "text/html"}
24 | {{#if total}}
25 | %span.todo-count
26 | %span.number {{remaining}}
27 | %span.word {{pluralize remaining "item"}}
28 | left.
29 | {{/if}}
30 | {{#if done}}
31 | %span.todo-clear
32 | %a{:href => "#"}
33 | Clear
34 | %span.number-done {{done}}
35 | completed
36 | %span.word-done {{pluralize done "item"}}
37 | {{/if}}
38 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run BackboneTodo::Application
5 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # If you have a Gemfile, require the gems listed there, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(:default, Rails.env) if defined?(Bundler)
8 |
9 | module BackboneTodo
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Custom directories with classes and modules you want to be autoloadable.
16 | # config.autoload_paths += %W(#{config.root}/extras)
17 |
18 | # Only load the plugins named here, in the order given (default is alphabetical).
19 | # :all can be used as a placeholder for all plugins not explicitly named.
20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
21 |
22 | # Activate observers that should always be running.
23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
24 |
25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
27 | # config.time_zone = 'Central Time (US & Canada)'
28 |
29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
31 | # config.i18n.default_locale = :de
32 |
33 | # JavaScript files you want as :defaults (application.js is always included).
34 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
35 |
36 | # Configure the default encoding used in templates for Ruby 1.9.
37 | config.encoding = "utf-8"
38 |
39 | # Configure sensitive parameters which will be filtered from the log file.
40 | config.filter_parameters += [:password]
41 |
42 | config.generators do |g|
43 | g.test_framework :rspec, :fixture => false,
44 | :view_specs => false, :controller_specs => false, :helper_specs => false,
45 | :route_specs => false, :request_specs => false
46 | end
47 |
48 | config.assets.enabled = true
49 | config.assets.paths << Rails.root.join("spec", "assets", "javascripts").to_s
50 | config.assets.paths << Rails.root.join("spec", "assets", "stylesheets").to_s
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/config/assets.yml:
--------------------------------------------------------------------------------
1 | javascripts:
2 | all:
3 | - public/javascripts/vendor/jquery.js
4 | - public/javascripts/vendor/underscore.js
5 | - public/javascripts/vendor/backbone.js
6 | - public/javascripts/vendor/handlebars.js
7 | - public/javascripts/compiled/application.js
8 | - public/javascripts/compiled/views/helpers.js
9 | - public/javascripts/compiled/models/*.js
10 | - public/javascripts/compiled/views/*.js
11 |
12 | stylesheets:
13 | all:
14 | - public/stylesheets/todos.css
15 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # Set up gems listed in the Gemfile.
4 | gemfile = File.expand_path('../../Gemfile', __FILE__)
5 | begin
6 | ENV['BUNDLE_GEMFILE'] = gemfile
7 | require 'bundler'
8 | Bundler.setup
9 | rescue Bundler::GemNotFound => e
10 | STDERR.puts e.message
11 | STDERR.puts "Try running `bundle install`."
12 | exit!
13 | end if File.exist?(gemfile)
14 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3-ruby (not necessary on OS X Leopard)
3 | development:
4 | adapter: sqlite3
5 | database: db/development.sqlite3
6 | pool: 5
7 | timeout: 5000
8 |
9 | # Warning: The database defined as "test" will be erased and
10 | # re-generated from your development database when you run "rake".
11 | # Do not set this db to the same as development or production.
12 | test:
13 | adapter: sqlite3
14 | database: db/test.sqlite3
15 | pool: 5
16 | timeout: 5000
17 |
18 | production:
19 | adapter: sqlite3
20 | database: db/production.sqlite3
21 | pool: 5
22 | timeout: 5000
23 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | BackboneTodo::Application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | BackboneTodo::Application.configure do
2 | # Settings specified here will take precedence over those in config/environment.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the webserver when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | # config.action_view.debug_rjs = true
15 | config.action_controller.perform_caching = false
16 |
17 | # Don't care if the mailer can't send
18 | config.action_mailer.raise_delivery_errors = false
19 |
20 | # Print deprecation notices to the Rails logger
21 | config.active_support.deprecation = :log
22 |
23 | # Only use best-standards-support built into browsers
24 | config.action_dispatch.best_standards_support = :builtin
25 | end
26 |
27 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | BackboneTodo::Application.configure do
2 | # Settings specified here will take precedence over those in config/environment.rb
3 |
4 | # The production environment is meant for finished, "live" apps.
5 | # Code is not reloaded between requests
6 | config.cache_classes = true
7 |
8 | # Full error reports are disabled and caching is turned on
9 | config.consider_all_requests_local = false
10 | config.action_controller.perform_caching = true
11 |
12 | # Specifies the header that your server uses for sending files
13 | config.action_dispatch.x_sendfile_header = "X-Sendfile"
14 |
15 | # For nginx:
16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
17 |
18 | # If you have no front-end server that supports something like X-Sendfile,
19 | # just comment this out and Rails will serve the files
20 |
21 | # See everything in the log (default is :info)
22 | # config.log_level = :debug
23 |
24 | # Use a different logger for distributed setups
25 | # config.logger = SyslogLogger.new
26 |
27 | # Use a different cache store in production
28 | # config.cache_store = :mem_cache_store
29 |
30 | # Disable Rails's static asset server
31 | # In production, Apache or nginx will already do this
32 | config.serve_static_assets = false
33 |
34 | # Enable serving of images, stylesheets, and javascripts from an asset server
35 | # config.action_controller.asset_host = "http://assets.example.com"
36 |
37 | # Disable delivery errors, bad email addresses will be ignored
38 | # config.action_mailer.raise_delivery_errors = false
39 |
40 | # Enable threaded mode
41 | # config.threadsafe!
42 |
43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
44 | # the I18n.default_locale when a translation can not be found)
45 | config.i18n.fallbacks = true
46 |
47 | # Send deprecation notices to registered listeners
48 | config.active_support.deprecation = :notify
49 | end
50 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | BackboneTodo::Application.configure do
2 | # Settings specified here will take precedence over those in config/environment.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Log error messages when you accidentally call methods on nil.
11 | config.whiny_nils = true
12 |
13 | # Show full error reports and disable caching
14 | config.consider_all_requests_local = true
15 | config.action_controller.perform_caching = false
16 |
17 | # Raise exceptions instead of rendering exception templates
18 | config.action_dispatch.show_exceptions = false
19 |
20 | # Disable request forgery protection in test environment
21 | config.action_controller.allow_forgery_protection = false
22 |
23 | # Tell Action Mailer not to deliver emails to the real world.
24 | # The :test delivery method accumulates sent emails in the
25 | # ActionMailer::Base.deliveries array.
26 | config.action_mailer.delivery_method = :test
27 |
28 | # Use SQL instead of Active Record's schema dumper when creating the test database.
29 | # This is necessary if your schema can't be completely dumped by the schema dumper,
30 | # like if you have constraints or database-specific column types
31 | # config.active_record.schema_format = :sql
32 |
33 | # Print deprecation notices to the stderr
34 | config.active_support.deprecation = :stderr
35 | end
36 |
--------------------------------------------------------------------------------
/config/initializers/backbone.rb:
--------------------------------------------------------------------------------
1 | # for Backbone.js
2 | ActiveRecord::Base.include_root_in_json = false
3 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | BackboneTodo::Application.config.secret_token = '9422d2583d9d5d904ef9dac500548208637b43057a9bffefdb33f79c34b395958a2730041646bc4dfd63da0d8acee64ef11a8ac13dd5dfd31642b1d7ffc65c3e'
8 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | BackboneTodo::Application.config.session_store :cookie_store, :key => '_backbone_todo_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rake db:sessions:create")
8 | # BackboneTodo::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | BackboneTodo::Application.routes.draw do
2 | root :to => "todos#index"
3 |
4 | resources :todos
5 |
6 | # The priority is based upon order of creation:
7 | # first created -> highest priority.
8 |
9 | # Sample of regular route:
10 | # match 'products/:id' => 'catalog#view'
11 | # Keep in mind you can assign values other than :controller and :action
12 |
13 | # Sample of named route:
14 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
15 | # This route can be invoked with purchase_url(:id => product.id)
16 |
17 | # Sample resource route (maps HTTP verbs to controller actions automatically):
18 | # resources :products
19 |
20 | # Sample resource route with options:
21 | # resources :products do
22 | # member do
23 | # get 'short'
24 | # post 'toggle'
25 | # end
26 | #
27 | # collection do
28 | # get 'sold'
29 | # end
30 | # end
31 |
32 | # Sample resource route with sub-resources:
33 | # resources :products do
34 | # resources :comments, :sales
35 | # resource :seller
36 | # end
37 |
38 | # Sample resource route with more complex sub-resources
39 | # resources :products do
40 | # resources :comments
41 | # resources :sales do
42 | # get 'recent', :on => :collection
43 | # end
44 | # end
45 |
46 | # Sample resource route within a namespace:
47 | # namespace :admin do
48 | # # Directs /admin/products/* to Admin::ProductsController
49 | # # (app/controllers/admin/products_controller.rb)
50 | # resources :products
51 | # end
52 |
53 | # You can have the root of your site routed with "root"
54 | # just remember to delete public/index.html.
55 | # root :to => "welcome#index"
56 |
57 | # See how all your routes lay out with "rake routes"
58 |
59 | # This is a legacy wild controller route that's not recommended for RESTful applications.
60 | # Note: This route will make all actions in every controller accessible via GET requests.
61 | # match ':controller(/:action(/:id(.:format)))'
62 | end
63 |
--------------------------------------------------------------------------------
/db/migrate/20101104122721_create_todos.rb:
--------------------------------------------------------------------------------
1 | class CreateTodos < ActiveRecord::Migration
2 | def self.up
3 | create_table :todos do |t|
4 | t.string :content
5 | t.integer :order
6 | t.boolean :done
7 |
8 | t.timestamps
9 | end
10 | end
11 |
12 | def self.down
13 | drop_table :todos
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended to check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(:version => 20101104122721) do
14 |
15 | create_table "todos", :force => true do |t|
16 | t.string "content"
17 | t.integer "order"
18 | t.boolean "done"
19 | t.datetime "created_at"
20 | t.datetime "updated_at"
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7 | # Mayor.create(:name => 'Daley', :city => cities.first)
8 |
--------------------------------------------------------------------------------
/doc/README_FOR_APP:
--------------------------------------------------------------------------------
1 | Use this README file to introduce your application and point to useful places in the API for learning more.
2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
3 |
--------------------------------------------------------------------------------
/lib/tasks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/lib/tasks/.gitkeep
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Jasmine Test Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/destroy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/public/images/destroy.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-Agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/assets/javascripts/spec_helper.coffee:
--------------------------------------------------------------------------------
1 | beforeEach ->
2 | @addMatchers
3 | toBeEmpty: -> this.actual.length == 0
4 |
5 | toInclude: (value) -> _.include @actual, value
6 |
--------------------------------------------------------------------------------
/spec/assets/javascripts/specs.js.coffee:
--------------------------------------------------------------------------------
1 | #= require main
2 | #= require jasmine
3 | #= require jasmine-html
4 | #= require spec_helper
5 | #= require_tree .
--------------------------------------------------------------------------------
/spec/assets/javascripts/todo_spec.coffee:
--------------------------------------------------------------------------------
1 | describe "Todo", ->
2 | todo = null
3 | ajaxCall = (param) -> jQuery.ajax.mostRecentCall.args[0][param]
4 |
5 | beforeEach ->
6 | todo = new TodoApp.Todo
7 | todos = new TodoApp.TodoList [todo]
8 |
9 | it "should initialize with empty content", ->
10 | expect(todo.get "content").toEqual "empty todo..."
11 |
12 | it "should initialize as not done", ->
13 | expect(todo.get "done").toBeFalsy()
14 |
15 | it "should save after toggle", ->
16 | spyOn jQuery, "ajax"
17 | todo.toggle()
18 | expect(ajaxCall "url").toEqual "/todos"
19 | expect(todo.get "done").toBeTruthy()
20 |
21 | describe "TodoList", ->
22 | attributes = [
23 | content: "First"
24 | done: true
25 | ,
26 | content: "Second"
27 | ]
28 | todos = null
29 |
30 | beforeEach ->
31 | todos = new TodoApp.TodoList attributes
32 |
33 | it "should return done todos", ->
34 | expect(_.invoke todos.done(), "toJSON").toEqual [attributes[0]]
35 |
36 | it "should return remaining todos", ->
37 | expect(_.invoke todos.remaining(), "toJSON").toEqual [attributes[1]]
38 |
--------------------------------------------------------------------------------
/spec/javascripts/support/jasmine.yml:
--------------------------------------------------------------------------------
1 | # src_files
2 | #
3 | # Return an array of filepaths relative to src_dir to include before jasmine specs.
4 | # Default: []
5 | #
6 | # EXAMPLE:
7 | #
8 | # src_files:
9 | # - lib/source1.js
10 | # - lib/source2.js
11 | # - dist/**/*.js
12 | #
13 | src_files:
14 | - public/javascripts/vendor/jquery.js
15 | - public/javascripts/vendor/underscore.js
16 | - public/javascripts/vendor/backbone.js
17 | - public/javascripts/vendor/handlebars.js
18 | - public/javascripts/compiled/application.js
19 | - public/javascripts/compiled/models/*.js
20 |
21 | # stylesheets
22 | #
23 | # Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
24 | # Default: []
25 | #
26 | # EXAMPLE:
27 | #
28 | # stylesheets:
29 | # - css/style.css
30 | # - stylesheets/*.css
31 | #
32 | stylesheets:
33 | - stylesheets/**/*.css
34 |
35 | # helpers
36 | #
37 | # Return an array of filepaths relative to spec_dir to include before jasmine specs.
38 | # Default: ["helpers/**/*.js"]
39 | #
40 | # EXAMPLE:
41 | #
42 | # helpers:
43 | # - helpers/**/*.js
44 | #
45 | helpers:
46 | - compiled/helpers/**/*.js
47 | - helpers/**/*.js
48 |
49 | # spec_files
50 | #
51 | # Return an array of filepaths relative to spec_dir to include.
52 | # Default: ["**/*[sS]pec.js"]
53 | #
54 | # EXAMPLE:
55 | #
56 | # spec_files:
57 | # - **/*[sS]pec.js
58 | #
59 | spec_files:
60 | - **/*[sS]pec.js
61 |
62 | # src_dir
63 | #
64 | # Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
65 | # Default: project root
66 | #
67 | # EXAMPLE:
68 | #
69 | # src_dir: public
70 | #
71 | src_dir:
72 |
73 | # spec_dir
74 | #
75 | # Spec directory path. Your spec_files must be returned relative to this path.
76 | # Default: spec/javascripts
77 | #
78 | # EXAMPLE:
79 | #
80 | # spec_dir: spec/javascripts
81 | #
82 | spec_dir: spec/javascripts
83 |
--------------------------------------------------------------------------------
/spec/javascripts/support/jasmine_config.rb:
--------------------------------------------------------------------------------
1 | require 'barista'
2 | require 'logger'
3 |
4 | require File.join(Rails.root, 'config/initializers/barista_config')
5 | Barista.configure do |c|
6 | c.env = 'test'
7 | c.logger = Logger.new(STDOUT)
8 | c.logger.level = Logger::INFO
9 | c.before_compilation do |path|
10 | relative_path = Pathname(path).relative_path_from(Rails.root)
11 | c.logger.info "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] Barista: Compiling #{relative_path}"
12 | end
13 | end
14 | Barista.setup_defaults
15 |
16 | module Jasmine
17 | def self.app(config)
18 | Barista::Framework.register 'jasmine', File.expand_path('../coffeescripts', config.spec_dir)
19 | Barista::Framework['jasmine'].instance_variable_set('@output_root', Pathname(config.spec_dir).join('compiled'))
20 |
21 | Rack::Builder.app do
22 | use Rack::Head
23 |
24 | map('/run.html') { run Jasmine::Redirect.new('/') }
25 | map('/__suite__') { run Barista::Filter.new(Jasmine::FocusedSuite.new(config)) }
26 |
27 | map('/__JASMINE_ROOT__') { run Rack::File.new(Jasmine.root) }
28 | map(config.spec_path) { run Rack::File.new(config.spec_dir) }
29 | map(config.root_path) { run Rack::File.new(config.project_root) }
30 |
31 | map('/favicon.ico') { run Rack::File.new(File.join(Rails.root, 'public', 'favicon.ico')) }
32 |
33 | map('/') do
34 | run Rack::Cascade.new([
35 | Rack::URLMap.new('/' => Rack::File.new(config.src_dir)),
36 | Barista::Filter.new(Jasmine::RunAdapter.new(config))
37 | ])
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/javascripts/support/jasmine_runner.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
2 |
3 | require 'rubygems'
4 | require 'jasmine'
5 | jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
6 | require jasmine_config_overrides if File.exists?(jasmine_config_overrides)
7 |
8 | jasmine_config = Jasmine::Config.new
9 | spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
10 |
11 | should_stop = false
12 |
13 | Spec::Runner.configure do |config|
14 | config.after(:suite) do
15 | spec_builder.stop if should_stop
16 | end
17 | end
18 |
19 | spec_builder.start
20 | should_stop = true
21 | spec_builder.declare_suites
--------------------------------------------------------------------------------
/spec/models/todo_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Todo do
4 | describe "validations" do
5 | it "should not be valid without content" do
6 | todo = Todo.new
7 | todo.should_not be_valid
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require File.expand_path("../../config/environment", __FILE__)
4 | require 'rspec/rails'
5 |
6 | # Requires supporting ruby files with custom matchers and macros, etc,
7 | # in spec/support/ and its subdirectories.
8 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
9 |
10 | RSpec.configure do |config|
11 | # == Mock Framework
12 | #
13 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
14 | #
15 | # config.mock_with :mocha
16 | # config.mock_with :flexmock
17 | # config.mock_with :rr
18 | config.mock_with :rspec
19 |
20 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
21 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
22 |
23 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
24 | # examples within a transaction, remove the following line or assign false
25 | # instead of true.
26 | config.use_transactional_fixtures = true
27 | end
28 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/backbone.js:
--------------------------------------------------------------------------------
1 | // Backbone.js 0.3.3
2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Backbone may be freely distributed under the MIT license.
4 | // For all details and documentation:
5 | // http://documentcloud.github.com/backbone
6 |
7 | (function(){
8 |
9 | // Initial Setup
10 | // -------------
11 |
12 | // Save a reference to the global object.
13 | var root = this;
14 |
15 | // Save the previous value of the `Backbone` variable.
16 | var previousBackbone = root.Backbone;
17 |
18 | // The top-level namespace. All public Backbone classes and modules will
19 | // be attached to this. Exported for both CommonJS and the browser.
20 | var Backbone;
21 | if (typeof exports !== 'undefined') {
22 | Backbone = exports;
23 | } else {
24 | Backbone = root.Backbone = {};
25 | }
26 |
27 | // Current version of the library. Keep in sync with `package.json`.
28 | Backbone.VERSION = '0.3.3';
29 |
30 | // Require Underscore, if we're on the server, and it's not already present.
31 | var _ = root._;
32 | if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
33 |
34 | // For Backbone's purposes, either jQuery or Zepto owns the `$` variable.
35 | var $ = root.jQuery || root.Zepto;
36 |
37 | // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
38 | // to its previous owner. Returns a reference to this Backbone object.
39 | Backbone.noConflict = function() {
40 | root.Backbone = previousBackbone;
41 | return this;
42 | };
43 |
44 | // Turn on `emulateHTTP` to use support legacy HTTP servers. Setting this option will
45 | // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
46 | // `X-Http-Method-Override` header.
47 | Backbone.emulateHTTP = false;
48 |
49 | // Turn on `emulateJSON` to support legacy servers that can't deal with direct
50 | // `application/json` requests ... will encode the body as
51 | // `application/x-www-form-urlencoded` instead and will send the model in a
52 | // form param named `model`.
53 | Backbone.emulateJSON = false;
54 |
55 | // Backbone.Events
56 | // -----------------
57 |
58 | // A module that can be mixed in to *any object* in order to provide it with
59 | // custom events. You may `bind` or `unbind` a callback function to an event;
60 | // `trigger`-ing an event fires all callbacks in succession.
61 | //
62 | // var object = {};
63 | // _.extend(object, Backbone.Events);
64 | // object.bind('expand', function(){ alert('expanded'); });
65 | // object.trigger('expand');
66 | //
67 | Backbone.Events = {
68 |
69 | // Bind an event, specified by a string name, `ev`, to a `callback` function.
70 | // Passing `"all"` will bind the callback to all events fired.
71 | bind : function(ev, callback) {
72 | var calls = this._callbacks || (this._callbacks = {});
73 | var list = this._callbacks[ev] || (this._callbacks[ev] = []);
74 | list.push(callback);
75 | return this;
76 | },
77 |
78 | // Remove one or many callbacks. If `callback` is null, removes all
79 | // callbacks for the event. If `ev` is null, removes all bound callbacks
80 | // for all events.
81 | unbind : function(ev, callback) {
82 | var calls;
83 | if (!ev) {
84 | this._callbacks = {};
85 | } else if (calls = this._callbacks) {
86 | if (!callback) {
87 | calls[ev] = [];
88 | } else {
89 | var list = calls[ev];
90 | if (!list) return this;
91 | for (var i = 0, l = list.length; i < l; i++) {
92 | if (callback === list[i]) {
93 | list.splice(i, 1);
94 | break;
95 | }
96 | }
97 | }
98 | }
99 | return this;
100 | },
101 |
102 | // Trigger an event, firing all bound callbacks. Callbacks are passed the
103 | // same arguments as `trigger` is, apart from the event name.
104 | // Listening for `"all"` passes the true event name as the first argument.
105 | trigger : function(ev) {
106 | var list, calls, i, l;
107 | if (!(calls = this._callbacks)) return this;
108 | if (calls[ev]) {
109 | list = calls[ev].slice(0);
110 | for (i = 0, l = list.length; i < l; i++) {
111 | list[i].apply(this, Array.prototype.slice.call(arguments, 1));
112 | }
113 | }
114 | if (calls['all']) {
115 | list = calls['all'].slice(0);
116 | for (i = 0, l = list.length; i < l; i++) {
117 | list[i].apply(this, arguments);
118 | }
119 | }
120 | return this;
121 | }
122 |
123 | };
124 |
125 | // Backbone.Model
126 | // --------------
127 |
128 | // Create a new model, with defined attributes. A client id (`cid`)
129 | // is automatically generated and assigned for you.
130 | Backbone.Model = function(attributes, options) {
131 | var defaults;
132 | attributes || (attributes = {});
133 | if (defaults = this.defaults) {
134 | if (_.isFunction(defaults)) defaults = defaults();
135 | attributes = _.extend({}, defaults, attributes);
136 | }
137 | this.attributes = {};
138 | this._escapedAttributes = {};
139 | this.cid = _.uniqueId('c');
140 | this.set(attributes, {silent : true});
141 | this._changed = false;
142 | this._previousAttributes = _.clone(this.attributes);
143 | if (options && options.collection) this.collection = options.collection;
144 | this.initialize(attributes, options);
145 | };
146 |
147 | // Attach all inheritable methods to the Model prototype.
148 | _.extend(Backbone.Model.prototype, Backbone.Events, {
149 |
150 | // A snapshot of the model's previous attributes, taken immediately
151 | // after the last `"change"` event was fired.
152 | _previousAttributes : null,
153 |
154 | // Has the item been changed since the last `"change"` event?
155 | _changed : false,
156 |
157 | // The default name for the JSON `id` attribute is `"id"`. MongoDB and
158 | // CouchDB users may want to set this to `"_id"`.
159 | idAttribute : 'id',
160 |
161 | // Initialize is an empty function by default. Override it with your own
162 | // initialization logic.
163 | initialize : function(){},
164 |
165 | // Return a copy of the model's `attributes` object.
166 | toJSON : function() {
167 | return _.clone(this.attributes);
168 | },
169 |
170 | // Get the value of an attribute.
171 | get : function(attr) {
172 | return this.attributes[attr];
173 | },
174 |
175 | // Get the HTML-escaped value of an attribute.
176 | escape : function(attr) {
177 | var html;
178 | if (html = this._escapedAttributes[attr]) return html;
179 | var val = this.attributes[attr];
180 | return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
181 | },
182 |
183 | // Returns `true` if the attribute contains a value that is not null
184 | // or undefined.
185 | has : function(attr) {
186 | return this.attributes[attr] != null;
187 | },
188 |
189 | // Set a hash of model attributes on the object, firing `"change"` unless you
190 | // choose to silence it.
191 | set : function(attrs, options) {
192 |
193 | // Extract attributes and options.
194 | options || (options = {});
195 | if (!attrs) return this;
196 | if (attrs.attributes) attrs = attrs.attributes;
197 | var now = this.attributes, escaped = this._escapedAttributes;
198 |
199 | // Run validation.
200 | if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
201 |
202 | // Check for changes of `id`.
203 | if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
204 |
205 | // Update attributes.
206 | for (var attr in attrs) {
207 | var val = attrs[attr];
208 | if (!_.isEqual(now[attr], val)) {
209 | now[attr] = val;
210 | delete escaped[attr];
211 | this._changed = true;
212 | if (!options.silent) this.trigger('change:' + attr, this, val, options);
213 | }
214 | }
215 |
216 | // Fire the `"change"` event, if the model has been changed.
217 | if (!options.silent && this._changed) this.change(options);
218 | return this;
219 | },
220 |
221 | // Remove an attribute from the model, firing `"change"` unless you choose
222 | // to silence it. `unset` is a noop if the attribute doesn't exist.
223 | unset : function(attr, options) {
224 | if (!(attr in this.attributes)) return this;
225 | options || (options = {});
226 | var value = this.attributes[attr];
227 |
228 | // Run validation.
229 | var validObj = {};
230 | validObj[attr] = void 0;
231 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
232 |
233 | // Remove the attribute.
234 | delete this.attributes[attr];
235 | delete this._escapedAttributes[attr];
236 | if (attr == this.idAttribute) delete this.id;
237 | this._changed = true;
238 | if (!options.silent) {
239 | this.trigger('change:' + attr, this, void 0, options);
240 | this.change(options);
241 | }
242 | return this;
243 | },
244 |
245 | // Clear all attributes on the model, firing `"change"` unless you choose
246 | // to silence it.
247 | clear : function(options) {
248 | options || (options = {});
249 | var old = this.attributes;
250 |
251 | // Run validation.
252 | var validObj = {};
253 | for (attr in old) validObj[attr] = void 0;
254 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
255 |
256 | this.attributes = {};
257 | this._escapedAttributes = {};
258 | this._changed = true;
259 | if (!options.silent) {
260 | for (attr in old) {
261 | this.trigger('change:' + attr, this, void 0, options);
262 | }
263 | this.change(options);
264 | }
265 | return this;
266 | },
267 |
268 | // Fetch the model from the server. If the server's representation of the
269 | // model differs from its current attributes, they will be overriden,
270 | // triggering a `"change"` event.
271 | fetch : function(options) {
272 | options || (options = {});
273 | var model = this;
274 | var success = options.success;
275 | options.success = function(resp) {
276 | if (!model.set(model.parse(resp), options)) return false;
277 | if (success) success(model, resp);
278 | };
279 | options.error = wrapError(options.error, model, options);
280 | (this.sync || Backbone.sync).call(this, 'read', this, options);
281 | return this;
282 | },
283 |
284 | // Set a hash of model attributes, and sync the model to the server.
285 | // If the server returns an attributes hash that differs, the model's
286 | // state will be `set` again.
287 | save : function(attrs, options) {
288 | options || (options = {});
289 | if (attrs && !this.set(attrs, options)) return false;
290 | var model = this;
291 | var success = options.success;
292 | options.success = function(resp) {
293 | if (!model.set(model.parse(resp), options)) return false;
294 | if (success) success(model, resp);
295 | };
296 | options.error = wrapError(options.error, model, options);
297 | var method = this.isNew() ? 'create' : 'update';
298 | (this.sync || Backbone.sync).call(this, method, this, options);
299 | return this;
300 | },
301 |
302 | // Destroy this model on the server. Upon success, the model is removed
303 | // from its collection, if it has one.
304 | destroy : function(options) {
305 | options || (options = {});
306 | var model = this;
307 | var success = options.success;
308 | options.success = function(resp) {
309 | model.trigger('destroy', model, model.collection, options);
310 | if (success) success(model, resp);
311 | };
312 | options.error = wrapError(options.error, model, options);
313 | (this.sync || Backbone.sync).call(this, 'delete', this, options);
314 | return this;
315 | },
316 |
317 | // Default URL for the model's representation on the server -- if you're
318 | // using Backbone's restful methods, override this to change the endpoint
319 | // that will be called.
320 | url : function() {
321 | var base = getUrl(this.collection) || this.urlRoot || urlError();
322 | if (this.isNew()) return base;
323 | return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
324 | },
325 |
326 | // **parse** converts a response into the hash of attributes to be `set` on
327 | // the model. The default implementation is just to pass the response along.
328 | parse : function(resp) {
329 | return resp;
330 | },
331 |
332 | // Create a new model with identical attributes to this one.
333 | clone : function() {
334 | return new this.constructor(this);
335 | },
336 |
337 | // A model is new if it has never been saved to the server, and has a negative
338 | // ID.
339 | isNew : function() {
340 | return !this.id;
341 | },
342 |
343 | // Call this method to manually fire a `change` event for this model.
344 | // Calling this will cause all objects observing the model to update.
345 | change : function(options) {
346 | this.trigger('change', this, options);
347 | this._previousAttributes = _.clone(this.attributes);
348 | this._changed = false;
349 | },
350 |
351 | // Determine if the model has changed since the last `"change"` event.
352 | // If you specify an attribute name, determine if that attribute has changed.
353 | hasChanged : function(attr) {
354 | if (attr) return this._previousAttributes[attr] != this.attributes[attr];
355 | return this._changed;
356 | },
357 |
358 | // Return an object containing all the attributes that have changed, or false
359 | // if there are no changed attributes. Useful for determining what parts of a
360 | // view need to be updated and/or what attributes need to be persisted to
361 | // the server.
362 | changedAttributes : function(now) {
363 | now || (now = this.attributes);
364 | var old = this._previousAttributes;
365 | var changed = false;
366 | for (var attr in now) {
367 | if (!_.isEqual(old[attr], now[attr])) {
368 | changed = changed || {};
369 | changed[attr] = now[attr];
370 | }
371 | }
372 | return changed;
373 | },
374 |
375 | // Get the previous value of an attribute, recorded at the time the last
376 | // `"change"` event was fired.
377 | previous : function(attr) {
378 | if (!attr || !this._previousAttributes) return null;
379 | return this._previousAttributes[attr];
380 | },
381 |
382 | // Get all of the attributes of the model at the time of the previous
383 | // `"change"` event.
384 | previousAttributes : function() {
385 | return _.clone(this._previousAttributes);
386 | },
387 |
388 | // Run validation against a set of incoming attributes, returning `true`
389 | // if all is well. If a specific `error` callback has been passed,
390 | // call that instead of firing the general `"error"` event.
391 | _performValidation : function(attrs, options) {
392 | var error = this.validate(attrs);
393 | if (error) {
394 | if (options.error) {
395 | options.error(this, error);
396 | } else {
397 | this.trigger('error', this, error, options);
398 | }
399 | return false;
400 | }
401 | return true;
402 | }
403 |
404 | });
405 |
406 | // Backbone.Collection
407 | // -------------------
408 |
409 | // Provides a standard collection class for our sets of models, ordered
410 | // or unordered. If a `comparator` is specified, the Collection will maintain
411 | // its models in sort order, as they're added and removed.
412 | Backbone.Collection = function(models, options) {
413 | options || (options = {});
414 | if (options.comparator) {
415 | this.comparator = options.comparator;
416 | delete options.comparator;
417 | }
418 | _.bindAll(this, '_onModelEvent', '_removeReference');
419 | this._reset();
420 | if (models) this.refresh(models, {silent: true});
421 | this.initialize(models, options);
422 | };
423 |
424 | // Define the Collection's inheritable methods.
425 | _.extend(Backbone.Collection.prototype, Backbone.Events, {
426 |
427 | // The default model for a collection is just a **Backbone.Model**.
428 | // This should be overridden in most cases.
429 | model : Backbone.Model,
430 |
431 | // Initialize is an empty function by default. Override it with your own
432 | // initialization logic.
433 | initialize : function(){},
434 |
435 | // The JSON representation of a Collection is an array of the
436 | // models' attributes.
437 | toJSON : function() {
438 | return this.map(function(model){ return model.toJSON(); });
439 | },
440 |
441 | // Add a model, or list of models to the set. Pass **silent** to avoid
442 | // firing the `added` event for every new model.
443 | add : function(models, options) {
444 | if (_.isArray(models)) {
445 | for (var i = 0, l = models.length; i < l; i++) {
446 | this._add(models[i], options);
447 | }
448 | } else {
449 | this._add(models, options);
450 | }
451 | return this;
452 | },
453 |
454 | // Remove a model, or a list of models from the set. Pass silent to avoid
455 | // firing the `removed` event for every model removed.
456 | remove : function(models, options) {
457 | if (_.isArray(models)) {
458 | for (var i = 0, l = models.length; i < l; i++) {
459 | this._remove(models[i], options);
460 | }
461 | } else {
462 | this._remove(models, options);
463 | }
464 | return this;
465 | },
466 |
467 | // Get a model from the set by id.
468 | get : function(id) {
469 | if (id == null) return null;
470 | return this._byId[id.id != null ? id.id : id];
471 | },
472 |
473 | // Get a model from the set by client id.
474 | getByCid : function(cid) {
475 | return cid && this._byCid[cid.cid || cid];
476 | },
477 |
478 | // Get the model at the given index.
479 | at: function(index) {
480 | return this.models[index];
481 | },
482 |
483 | // Force the collection to re-sort itself. You don't need to call this under normal
484 | // circumstances, as the set will maintain sort order as each item is added.
485 | sort : function(options) {
486 | options || (options = {});
487 | if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
488 | this.models = this.sortBy(this.comparator);
489 | if (!options.silent) this.trigger('refresh', this, options);
490 | return this;
491 | },
492 |
493 | // Pluck an attribute from each model in the collection.
494 | pluck : function(attr) {
495 | return _.map(this.models, function(model){ return model.get(attr); });
496 | },
497 |
498 | // When you have more items than you want to add or remove individually,
499 | // you can refresh the entire set with a new list of models, without firing
500 | // any `added` or `removed` events. Fires `refresh` when finished.
501 | refresh : function(models, options) {
502 | models || (models = []);
503 | options || (options = {});
504 | this.each(this._removeReference);
505 | this._reset();
506 | this.add(models, {silent: true});
507 | if (!options.silent) this.trigger('refresh', this, options);
508 | return this;
509 | },
510 |
511 | // Fetch the default set of models for this collection, refreshing the
512 | // collection when they arrive. If `add: true` is passed, appends the
513 | // models to the collection instead of refreshing.
514 | fetch : function(options) {
515 | options || (options = {});
516 | var collection = this;
517 | var success = options.success;
518 | options.success = function(resp) {
519 | collection[options.add ? 'add' : 'refresh'](collection.parse(resp), options);
520 | if (success) success(collection, resp);
521 | };
522 | options.error = wrapError(options.error, collection, options);
523 | (this.sync || Backbone.sync).call(this, 'read', this, options);
524 | return this;
525 | },
526 |
527 | // Create a new instance of a model in this collection. After the model
528 | // has been created on the server, it will be added to the collection.
529 | create : function(model, options) {
530 | var coll = this;
531 | options || (options = {});
532 | if (!(model instanceof Backbone.Model)) {
533 | var attrs = model;
534 | model = new this.model(null, {collection: coll});
535 | if (!model.set(attrs)) return false;
536 | } else {
537 | model.collection = coll;
538 | }
539 | var success = options.success;
540 | options.success = function(nextModel, resp) {
541 | coll.add(nextModel);
542 | if (success) success(nextModel, resp);
543 | };
544 | return model.save(null, options);
545 | },
546 |
547 | // **parse** converts a response into a list of models to be added to the
548 | // collection. The default implementation is just to pass it through.
549 | parse : function(resp) {
550 | return resp;
551 | },
552 |
553 | // Proxy to _'s chain. Can't be proxied the same way the rest of the
554 | // underscore methods are proxied because it relies on the underscore
555 | // constructor.
556 | chain: function () {
557 | return _(this.models).chain();
558 | },
559 |
560 | // Reset all internal state. Called when the collection is refreshed.
561 | _reset : function(options) {
562 | this.length = 0;
563 | this.models = [];
564 | this._byId = {};
565 | this._byCid = {};
566 | },
567 |
568 | // Internal implementation of adding a single model to the set, updating
569 | // hash indexes for `id` and `cid` lookups.
570 | _add : function(model, options) {
571 | options || (options = {});
572 | if (!(model instanceof Backbone.Model)) {
573 | model = new this.model(model, {collection: this});
574 | }
575 | var already = this.getByCid(model);
576 | if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
577 | this._byId[model.id] = model;
578 | this._byCid[model.cid] = model;
579 | if (!model.collection) {
580 | model.collection = this;
581 | }
582 | var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
583 | this.models.splice(index, 0, model);
584 | model.bind('all', this._onModelEvent);
585 | this.length++;
586 | if (!options.silent) model.trigger('add', model, this, options);
587 | return model;
588 | },
589 |
590 | // Internal implementation of removing a single model from the set, updating
591 | // hash indexes for `id` and `cid` lookups.
592 | _remove : function(model, options) {
593 | options || (options = {});
594 | model = this.getByCid(model) || this.get(model);
595 | if (!model) return null;
596 | delete this._byId[model.id];
597 | delete this._byCid[model.cid];
598 | this.models.splice(this.indexOf(model), 1);
599 | this.length--;
600 | if (!options.silent) model.trigger('remove', model, this, options);
601 | this._removeReference(model);
602 | return model;
603 | },
604 |
605 | // Internal method to remove a model's ties to a collection.
606 | _removeReference : function(model) {
607 | if (this == model.collection) {
608 | delete model.collection;
609 | }
610 | model.unbind('all', this._onModelEvent);
611 | },
612 |
613 | // Internal method called every time a model in the set fires an event.
614 | // Sets need to update their indexes when models change ids. All other
615 | // events simply proxy through. "add" and "remove" events that originate
616 | // in other collections are ignored.
617 | _onModelEvent : function(ev, model, collection, options) {
618 | if ((ev == 'add' || ev == 'remove') && collection != this) return;
619 | if (ev == 'destroy') {
620 | this._remove(model, options);
621 | }
622 | if (ev === 'change:' + model.idAttribute) {
623 | delete this._byId[model.previous(model.idAttribute)];
624 | this._byId[model.id] = model;
625 | }
626 | this.trigger.apply(this, arguments);
627 | }
628 |
629 | });
630 |
631 | // Underscore methods that we want to implement on the Collection.
632 | var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
633 | 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
634 | 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
635 | 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty'];
636 |
637 | // Mix in each Underscore method as a proxy to `Collection#models`.
638 | _.each(methods, function(method) {
639 | Backbone.Collection.prototype[method] = function() {
640 | return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
641 | };
642 | });
643 |
644 | // Backbone.Controller
645 | // -------------------
646 |
647 | // Controllers map faux-URLs to actions, and fire events when routes are
648 | // matched. Creating a new one sets its `routes` hash, if not set statically.
649 | Backbone.Controller = function(options) {
650 | options || (options = {});
651 | if (options.routes) this.routes = options.routes;
652 | this._bindRoutes();
653 | this.initialize(options);
654 | };
655 |
656 | // Cached regular expressions for matching named param parts and splatted
657 | // parts of route strings.
658 | var namedParam = /:([\w\d]+)/g;
659 | var splatParam = /\*([\w\d]+)/g;
660 | var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
661 |
662 | // Set up all inheritable **Backbone.Controller** properties and methods.
663 | _.extend(Backbone.Controller.prototype, Backbone.Events, {
664 |
665 | // Initialize is an empty function by default. Override it with your own
666 | // initialization logic.
667 | initialize : function(){},
668 |
669 | // Manually bind a single named route to a callback. For example:
670 | //
671 | // this.route('search/:query/p:num', 'search', function(query, num) {
672 | // ...
673 | // });
674 | //
675 | route : function(route, name, callback) {
676 | Backbone.history || (Backbone.history = new Backbone.History);
677 | if (!_.isRegExp(route)) route = this._routeToRegExp(route);
678 | Backbone.history.route(route, _.bind(function(fragment) {
679 | var args = this._extractParameters(route, fragment);
680 | callback.apply(this, args);
681 | this.trigger.apply(this, ['route:' + name].concat(args));
682 | }, this));
683 | },
684 |
685 | // Simple proxy to `Backbone.history` to save a fragment into the history,
686 | // without triggering routes.
687 | saveLocation : function(fragment) {
688 | Backbone.history.saveLocation(fragment);
689 | },
690 |
691 | // Bind all defined routes to `Backbone.history`. We have to reverse the
692 | // order of the routes here to support behavior where the most general
693 | // routes can be defined at the bottom of the route map.
694 | _bindRoutes : function() {
695 | if (!this.routes) return;
696 | var routes = [];
697 | for (var route in this.routes) {
698 | routes.unshift([route, this.routes[route]]);
699 | }
700 | for (var i = 0, l = routes.length; i < l; i++) {
701 | this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
702 | }
703 | },
704 |
705 | // Convert a route string into a regular expression, suitable for matching
706 | // against the current location fragment.
707 | _routeToRegExp : function(route) {
708 | route = route.replace(escapeRegExp, "\\$&")
709 | .replace(namedParam, "([^\/]*)")
710 | .replace(splatParam, "(.*?)");
711 | return new RegExp('^' + route + '$');
712 | },
713 |
714 | // Given a route, and a URL fragment that it matches, return the array of
715 | // extracted parameters.
716 | _extractParameters : function(route, fragment) {
717 | return route.exec(fragment).slice(1);
718 | }
719 |
720 | });
721 |
722 | // Backbone.History
723 | // ----------------
724 |
725 | // Handles cross-browser history management, based on URL hashes. If the
726 | // browser does not support `onhashchange`, falls back to polling.
727 | Backbone.History = function() {
728 | this.handlers = [];
729 | this.fragment = this.getFragment();
730 | _.bindAll(this, 'checkUrl');
731 | };
732 |
733 | // Cached regex for cleaning hashes.
734 | var hashStrip = /^#*/;
735 |
736 | // Has the history handling already been started?
737 | var historyStarted = false;
738 |
739 | // Set up all inheritable **Backbone.History** properties and methods.
740 | _.extend(Backbone.History.prototype, {
741 |
742 | // The default interval to poll for hash changes, if necessary, is
743 | // twenty times a second.
744 | interval: 50,
745 |
746 | // Get the cross-browser normalized URL fragment.
747 | getFragment : function(loc) {
748 | return (loc || window.location).hash.replace(hashStrip, '');
749 | },
750 |
751 | // Start the hash change handling, returning `true` if the current URL matches
752 | // an existing route, and `false` otherwise.
753 | start : function() {
754 | if (historyStarted) throw new Error("Backbone.history has already been started");
755 | var docMode = document.documentMode;
756 | var oldIE = ($.browser.msie && (!docMode || docMode <= 7));
757 | if (oldIE) {
758 | this.iframe = $('').hide().appendTo('body')[0].contentWindow;
759 | }
760 | if ('onhashchange' in window && !oldIE) {
761 | $(window).bind('hashchange', this.checkUrl);
762 | } else {
763 | setInterval(this.checkUrl, this.interval);
764 | }
765 | historyStarted = true;
766 | return this.loadUrl();
767 | },
768 |
769 | // Add a route to be tested when the hash changes. Routes added later may
770 | // override previous routes.
771 | route : function(route, callback) {
772 | this.handlers.unshift({route : route, callback : callback});
773 | },
774 |
775 | // Checks the current URL to see if it has changed, and if it has,
776 | // calls `loadUrl`, normalizing across the hidden iframe.
777 | checkUrl : function() {
778 | var current = this.getFragment();
779 | if (current == this.fragment && this.iframe) {
780 | current = this.getFragment(this.iframe.location);
781 | }
782 | if (current == this.fragment ||
783 | current == decodeURIComponent(this.fragment)) return false;
784 | if (this.iframe) {
785 | window.location.hash = this.iframe.location.hash = current;
786 | }
787 | this.loadUrl();
788 | },
789 |
790 | // Attempt to load the current URL fragment. If a route succeeds with a
791 | // match, returns `true`. If no defined routes matches the fragment,
792 | // returns `false`.
793 | loadUrl : function() {
794 | var fragment = this.fragment = this.getFragment();
795 | var matched = _.any(this.handlers, function(handler) {
796 | if (handler.route.test(fragment)) {
797 | handler.callback(fragment);
798 | return true;
799 | }
800 | });
801 | return matched;
802 | },
803 |
804 | // Save a fragment into the hash history. You are responsible for properly
805 | // URL-encoding the fragment in advance. This does not trigger
806 | // a `hashchange` event.
807 | saveLocation : function(fragment) {
808 | fragment = (fragment || '').replace(hashStrip, '');
809 | if (this.fragment == fragment) return;
810 | window.location.hash = this.fragment = fragment;
811 | if (this.iframe && (fragment != this.getFragment(this.iframe.location))) {
812 | this.iframe.document.open().close();
813 | this.iframe.location.hash = fragment;
814 | }
815 | }
816 |
817 | });
818 |
819 | // Backbone.View
820 | // -------------
821 |
822 | // Creating a Backbone.View creates its initial element outside of the DOM,
823 | // if an existing element is not provided...
824 | Backbone.View = function(options) {
825 | this.cid = _.uniqueId('view');
826 | this._configure(options || {});
827 | this._ensureElement();
828 | this.delegateEvents();
829 | this.initialize(options);
830 | };
831 |
832 | // Element lookup, scoped to DOM elements within the current view.
833 | // This should be prefered to global lookups, if you're dealing with
834 | // a specific view.
835 | var selectorDelegate = function(selector) {
836 | return $(selector, this.el);
837 | };
838 |
839 | // Cached regex to split keys for `delegate`.
840 | var eventSplitter = /^(\w+)\s*(.*)$/;
841 |
842 | // List of view options to be merged as properties.
843 | var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
844 |
845 | // Set up all inheritable **Backbone.View** properties and methods.
846 | _.extend(Backbone.View.prototype, Backbone.Events, {
847 |
848 | // The default `tagName` of a View's element is `"div"`.
849 | tagName : 'div',
850 |
851 | // Attach the `selectorDelegate` function as the `$` property.
852 | $ : selectorDelegate,
853 |
854 | // Initialize is an empty function by default. Override it with your own
855 | // initialization logic.
856 | initialize : function(){},
857 |
858 | // **render** is the core function that your view should override, in order
859 | // to populate its element (`this.el`), with the appropriate HTML. The
860 | // convention is for **render** to always return `this`.
861 | render : function() {
862 | return this;
863 | },
864 |
865 | // Remove this view from the DOM. Note that the view isn't present in the
866 | // DOM by default, so calling this method may be a no-op.
867 | remove : function() {
868 | $(this.el).remove();
869 | return this;
870 | },
871 |
872 | // For small amounts of DOM Elements, where a full-blown template isn't
873 | // needed, use **make** to manufacture elements, one at a time.
874 | //
875 | // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
876 | //
877 | make : function(tagName, attributes, content) {
878 | var el = document.createElement(tagName);
879 | if (attributes) $(el).attr(attributes);
880 | if (content) $(el).html(content);
881 | return el;
882 | },
883 |
884 | // Set callbacks, where `this.callbacks` is a hash of
885 | //
886 | // *{"event selector": "callback"}*
887 | //
888 | // {
889 | // 'mousedown .title': 'edit',
890 | // 'click .button': 'save'
891 | // }
892 | //
893 | // pairs. Callbacks will be bound to the view, with `this` set properly.
894 | // Uses event delegation for efficiency.
895 | // Omitting the selector binds the event to `this.el`.
896 | // This only works for delegate-able events: not `focus`, `blur`, and
897 | // not `change`, `submit`, and `reset` in Internet Explorer.
898 | delegateEvents : function(events) {
899 | if (!(events || (events = this.events))) return;
900 | $(this.el).unbind('.delegateEvents' + this.cid);
901 | for (var key in events) {
902 | var methodName = events[key];
903 | var match = key.match(eventSplitter);
904 | var eventName = match[1], selector = match[2];
905 | var method = _.bind(this[methodName], this);
906 | eventName += '.delegateEvents' + this.cid;
907 | if (selector === '') {
908 | $(this.el).bind(eventName, method);
909 | } else {
910 | $(this.el).delegate(selector, eventName, method);
911 | }
912 | }
913 | },
914 |
915 | // Performs the initial configuration of a View with a set of options.
916 | // Keys with special meaning *(model, collection, id, className)*, are
917 | // attached directly to the view.
918 | _configure : function(options) {
919 | if (this.options) options = _.extend({}, this.options, options);
920 | for (var i = 0, l = viewOptions.length; i < l; i++) {
921 | var attr = viewOptions[i];
922 | if (options[attr]) this[attr] = options[attr];
923 | }
924 | this.options = options;
925 | },
926 |
927 | // Ensure that the View has a DOM element to render into.
928 | // If `this.el` is a string, pass it through `$()`, take the first
929 | // matching element, and re-assign it to `el`. Otherwise, create
930 | // an element from the `id`, `className` and `tagName` proeprties.
931 | _ensureElement : function() {
932 | if (!this.el) {
933 | var attrs = this.attributes || {};
934 | if (this.id) attrs.id = this.id;
935 | if (this.className) attrs['class'] = this.className;
936 | this.el = this.make(this.tagName, attrs);
937 | } else if (_.isString(this.el)) {
938 | this.el = $(this.el).get(0);
939 | }
940 | }
941 |
942 | });
943 |
944 | // The self-propagating extend function that Backbone classes use.
945 | var extend = function (protoProps, classProps) {
946 | var child = inherits(this, protoProps, classProps);
947 | child.extend = this.extend;
948 | return child;
949 | };
950 |
951 | // Set up inheritance for the model, collection, and view.
952 | Backbone.Model.extend = Backbone.Collection.extend =
953 | Backbone.Controller.extend = Backbone.View.extend = extend;
954 |
955 | // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
956 | var methodMap = {
957 | 'create': 'POST',
958 | 'update': 'PUT',
959 | 'delete': 'DELETE',
960 | 'read' : 'GET'
961 | };
962 |
963 | // Backbone.sync
964 | // -------------
965 |
966 | // Override this function to change the manner in which Backbone persists
967 | // models to the server. You will be passed the type of request, and the
968 | // model in question. By default, uses makes a RESTful Ajax request
969 | // to the model's `url()`. Some possible customizations could be:
970 | //
971 | // * Use `setTimeout` to batch rapid-fire updates into a single request.
972 | // * Send up the models as XML instead of JSON.
973 | // * Persist models via WebSockets instead of Ajax.
974 | //
975 | // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
976 | // as `POST`, with a `_method` parameter containing the true HTTP method,
977 | // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
978 | // `application/json` with the model in a param named `model`.
979 | // Useful when interfacing with server-side languages like **PHP** that make
980 | // it difficult to read the body of `PUT` requests.
981 | Backbone.sync = function(method, model, options) {
982 | var type = methodMap[method];
983 |
984 | // Default JSON-request options.
985 | var params = _.extend({
986 | type: type,
987 | contentType: 'application/json',
988 | dataType: 'json',
989 | processData: false
990 | }, options);
991 |
992 | // Ensure that we have a URL.
993 | if (!params.url) {
994 | params.url = getUrl(model) || urlError();
995 | }
996 |
997 | // Ensure that we have the appropriate request data.
998 | if (!params.data && model && (method == 'create' || method == 'update')) {
999 | params.data = JSON.stringify(model.toJSON());
1000 | }
1001 |
1002 | // For older servers, emulate JSON by encoding the request into an HTML-form.
1003 | if (Backbone.emulateJSON) {
1004 | params.contentType = 'application/x-www-form-urlencoded';
1005 | params.processData = true;
1006 | params.data = params.data ? {model : params.data} : {};
1007 | }
1008 |
1009 | // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1010 | // And an `X-HTTP-Method-Override` header.
1011 | if (Backbone.emulateHTTP) {
1012 | if (type === 'PUT' || type === 'DELETE') {
1013 | if (Backbone.emulateJSON) params.data._method = type;
1014 | params.type = 'POST';
1015 | params.beforeSend = function(xhr) {
1016 | xhr.setRequestHeader('X-HTTP-Method-Override', type);
1017 | };
1018 | }
1019 | }
1020 |
1021 | // Make the request.
1022 | $.ajax(params);
1023 | };
1024 |
1025 | // Helpers
1026 | // -------
1027 |
1028 | // Shared empty constructor function to aid in prototype-chain creation.
1029 | var ctor = function(){};
1030 |
1031 | // Helper function to correctly set up the prototype chain, for subclasses.
1032 | // Similar to `goog.inherits`, but uses a hash of prototype properties and
1033 | // class properties to be extended.
1034 | var inherits = function(parent, protoProps, staticProps) {
1035 | var child;
1036 |
1037 | // The constructor function for the new subclass is either defined by you
1038 | // (the "constructor" property in your `extend` definition), or defaulted
1039 | // by us to simply call `super()`.
1040 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
1041 | child = protoProps.constructor;
1042 | } else {
1043 | child = function(){ return parent.apply(this, arguments); };
1044 | }
1045 |
1046 | // Inherit class (static) properties from parent.
1047 | _.extend(child, parent);
1048 |
1049 | // Set the prototype chain to inherit from `parent`, without calling
1050 | // `parent`'s constructor function.
1051 | ctor.prototype = parent.prototype;
1052 | child.prototype = new ctor();
1053 |
1054 | // Add prototype properties (instance properties) to the subclass,
1055 | // if supplied.
1056 | if (protoProps) _.extend(child.prototype, protoProps);
1057 |
1058 | // Add static properties to the constructor function, if supplied.
1059 | if (staticProps) _.extend(child, staticProps);
1060 |
1061 | // Correctly set child's `prototype.constructor`.
1062 | child.prototype.constructor = child;
1063 |
1064 | // Set a convenience property in case the parent's prototype is needed later.
1065 | child.__super__ = parent.prototype;
1066 |
1067 | return child;
1068 | };
1069 |
1070 | // Helper function to get a URL from a Model or Collection as a property
1071 | // or as a function.
1072 | var getUrl = function(object) {
1073 | if (!(object && object.url)) return null;
1074 | return _.isFunction(object.url) ? object.url() : object.url;
1075 | };
1076 |
1077 | // Throw an error when a URL is needed, and none is supplied.
1078 | var urlError = function() {
1079 | throw new Error("A 'url' property or function must be specified");
1080 | };
1081 |
1082 | // Wrap an optional error callback with a fallback error event.
1083 | var wrapError = function(onError, model, options) {
1084 | return function(resp) {
1085 | if (onError) {
1086 | onError(model, resp, options);
1087 | } else {
1088 | model.trigger('error', model, resp, options);
1089 | }
1090 | };
1091 | };
1092 |
1093 | // Helper function to escape a string for HTML rendering.
1094 | var escapeHTML = function(string) {
1095 | return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"');
1096 | };
1097 |
1098 | }).call(this);
1099 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/handlebars.js:
--------------------------------------------------------------------------------
1 | // lib/handlebars/parser.js
2 | /* Jison generated parser */
3 | var handlebars = (function(){
4 | var parser = {trace: function trace() { },
5 | yy: {},
6 | symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"param":27,"STRING":28,"hashSegments":29,"hashSegment":30,"ID":31,"EQUALS":32,"pathSegments":33,"SEP":34,"$accept":0,"$end":1},
7 | terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"STRING",31:"ID",32:"EQUALS",34:"SEP"},
8 | productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[25,2],[25,1],[27,1],[27,1],[26,1],[29,2],[29,1],[30,3],[30,3],[21,1],[33,3],[33,1]],
9 | performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$) {
10 |
11 | var $0 = $$.length - 1;
12 | switch (yystate) {
13 | case 1: return $$[$0-1]
14 | break;
15 | case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0])
16 | break;
17 | case 3: this.$ = new yy.ProgramNode($$[$0])
18 | break;
19 | case 4: this.$ = new yy.ProgramNode([])
20 | break;
21 | case 5: this.$ = [$$[$0]]
22 | break;
23 | case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
24 | break;
25 | case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0])
26 | break;
27 | case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0])
28 | break;
29 | case 9: this.$ = $$[$0]
30 | break;
31 | case 10: this.$ = $$[$0]
32 | break;
33 | case 11: this.$ = new yy.ContentNode($$[$0])
34 | break;
35 | case 12: this.$ = new yy.CommentNode($$[$0])
36 | break;
37 | case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
38 | break;
39 | case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
40 | break;
41 | case 15: this.$ = $$[$0-1]
42 | break;
43 | case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
44 | break;
45 | case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true)
46 | break;
47 | case 18: this.$ = new yy.PartialNode($$[$0-1])
48 | break;
49 | case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1])
50 | break;
51 | case 20:
52 | break;
53 | case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]
54 | break;
55 | case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null]
56 | break;
57 | case 23: this.$ = [[$$[$0-1]], $$[$0]]
58 | break;
59 | case 24: this.$ = [[$$[$0]], null]
60 | break;
61 | case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
62 | break;
63 | case 26: this.$ = [$$[$0]]
64 | break;
65 | case 27: this.$ = $$[$0]
66 | break;
67 | case 28: this.$ = new yy.StringNode($$[$0])
68 | break;
69 | case 29: this.$ = new yy.HashNode($$[$0])
70 | break;
71 | case 30: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
72 | break;
73 | case 31: this.$ = [$$[$0]]
74 | break;
75 | case 32: this.$ = [$$[$0-2], $$[$0]]
76 | break;
77 | case 33: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]
78 | break;
79 | case 34: this.$ = new yy.IdNode($$[$0])
80 | break;
81 | case 35: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
82 | break;
83 | case 36: this.$ = [$$[$0]]
84 | break;
85 | }
86 | },
87 | table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,31:[1,25],33:24},{17:26,21:23,31:[1,25],33:24},{17:27,21:23,31:[1,25],33:24},{17:28,21:23,31:[1,25],33:24},{21:29,31:[1,25],33:24},{1:[2,1]},{6:30,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,31],21:23,31:[1,25],33:24},{10:32,20:[1,33]},{10:34,20:[1,33]},{18:[1,35]},{18:[2,24],21:40,25:36,26:37,27:38,28:[1,41],29:39,30:42,31:[1,43],33:24},{18:[2,34],28:[2,34],31:[2,34],34:[1,44]},{18:[2,36],28:[2,36],31:[2,36],34:[2,36]},{18:[1,45]},{18:[1,46]},{18:[1,47]},{18:[1,48],21:49,31:[1,25],33:24},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:50,31:[1,25],33:24},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:40,26:51,27:52,28:[1,41],29:39,30:42,31:[1,43],33:24},{18:[2,23]},{18:[2,26],28:[2,26],31:[2,26]},{18:[2,29],30:53,31:[1,54]},{18:[2,27],28:[2,27],31:[2,27]},{18:[2,28],28:[2,28],31:[2,28]},{18:[2,31],31:[2,31]},{18:[2,36],28:[2,36],31:[2,36],32:[1,55],34:[2,36]},{31:[1,56]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,57]},{18:[1,58]},{18:[2,21]},{18:[2,25],28:[2,25],31:[2,25]},{18:[2,30],31:[2,30]},{32:[1,55]},{21:59,28:[1,60],31:[1,25],33:24},{18:[2,35],28:[2,35],31:[2,35],34:[2,35]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,32],31:[2,32]},{18:[2,33],31:[2,33]}],
88 | defaultActions: {16:[2,1],37:[2,23],51:[2,21]},
89 | parseError: function parseError(str, hash) {
90 | throw new Error(str);
91 | },
92 | parse: function parse(input) {
93 | var self = this,
94 | stack = [0],
95 | vstack = [null], // semantic value stack
96 | table = this.table,
97 | yytext = '',
98 | yylineno = 0,
99 | yyleng = 0,
100 | recovering = 0,
101 | TERROR = 2,
102 | EOF = 1;
103 |
104 | //this.reductionCount = this.shiftCount = 0;
105 |
106 | this.lexer.setInput(input);
107 | this.lexer.yy = this.yy;
108 | this.yy.lexer = this.lexer;
109 |
110 | if (typeof this.yy.parseError === 'function')
111 | this.parseError = this.yy.parseError;
112 |
113 | function popStack (n) {
114 | stack.length = stack.length - 2*n;
115 | vstack.length = vstack.length - n;
116 | }
117 |
118 | function lex() {
119 | var token;
120 | token = self.lexer.lex() || 1; // $end = 1
121 | // if token isn't its numeric value, convert
122 | if (typeof token !== 'number') {
123 | token = self.symbols_[token] || token;
124 | }
125 | return token;
126 | };
127 |
128 | var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
129 | while (true) {
130 | // retreive state number from top of stack
131 | state = stack[stack.length-1];
132 |
133 | // use default actions if available
134 | if (this.defaultActions[state]) {
135 | action = this.defaultActions[state];
136 | } else {
137 | if (symbol == null)
138 | symbol = lex();
139 | // read action for current state and first input
140 | action = table[state] && table[state][symbol];
141 | }
142 |
143 | // handle parse error
144 | if (typeof action === 'undefined' || !action.length || !action[0]) {
145 |
146 | if (!recovering) {
147 | // Report error
148 | expected = [];
149 | for (p in table[state]) if (this.terminals_[p] && p > 2) {
150 | expected.push("'"+this.terminals_[p]+"'");
151 | }
152 | var errStr = '';
153 | if (this.lexer.showPosition) {
154 | errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+'\nExpecting '+expected.join(', ');
155 | } else {
156 | errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
157 | (symbol == 1 /*EOF*/ ? "end of input" :
158 | ("'"+(this.terminals_[symbol] || symbol)+"'"));
159 | }
160 | this.parseError(errStr,
161 | {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, expected: expected});
162 | }
163 |
164 | // just recovered from another error
165 | if (recovering == 3) {
166 | if (symbol == EOF) {
167 | throw new Error(errStr || 'Parsing halted.');
168 | }
169 |
170 | // discard current lookahead and grab another
171 | yyleng = this.lexer.yyleng;
172 | yytext = this.lexer.yytext;
173 | yylineno = this.lexer.yylineno;
174 | symbol = lex();
175 | }
176 |
177 | // try to recover from error
178 | while (1) {
179 | // check for error recovery rule in this state
180 | if ((TERROR.toString()) in table[state]) {
181 | break;
182 | }
183 | if (state == 0) {
184 | throw new Error(errStr || 'Parsing halted.');
185 | }
186 | popStack(1);
187 | state = stack[stack.length-1];
188 | }
189 |
190 | preErrorSymbol = symbol; // save the lookahead token
191 | symbol = TERROR; // insert generic error symbol as new lookahead
192 | state = stack[stack.length-1];
193 | action = table[state] && table[state][TERROR];
194 | recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
195 | }
196 |
197 | // this shouldn't happen, unless resolve defaults are off
198 | if (action[0] instanceof Array && action.length > 1) {
199 | throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
200 | }
201 |
202 | switch (action[0]) {
203 |
204 | case 1: // shift
205 | //this.shiftCount++;
206 |
207 | stack.push(symbol);
208 | vstack.push(this.lexer.yytext);
209 | stack.push(action[1]); // push state
210 | symbol = null;
211 | if (!preErrorSymbol) { // normal execution/no error
212 | yyleng = this.lexer.yyleng;
213 | yytext = this.lexer.yytext;
214 | yylineno = this.lexer.yylineno;
215 | if (recovering > 0)
216 | recovering--;
217 | } else { // error just occurred, resume old lookahead f/ before error
218 | symbol = preErrorSymbol;
219 | preErrorSymbol = null;
220 | }
221 | break;
222 |
223 | case 2: // reduce
224 | //this.reductionCount++;
225 |
226 | len = this.productions_[action[1]][1];
227 |
228 | // perform semantic action
229 | yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
230 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack);
231 |
232 | if (typeof r !== 'undefined') {
233 | return r;
234 | }
235 |
236 | // pop off stack
237 | if (len) {
238 | stack = stack.slice(0,-1*len*2);
239 | vstack = vstack.slice(0, -1*len);
240 | }
241 |
242 | stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
243 | vstack.push(yyval.$);
244 | // goto new state = table[STATE][NONTERMINAL]
245 | newState = table[stack[stack.length-2]][stack[stack.length-1]];
246 | stack.push(newState);
247 | break;
248 |
249 | case 3: // accept
250 | return true;
251 | }
252 |
253 | }
254 |
255 | return true;
256 | }};/* Jison generated lexer */
257 | var lexer = (function(){var lexer = ({EOF:1,
258 | parseError:function parseError(str, hash) {
259 | if (this.yy.parseError) {
260 | this.yy.parseError(str, hash);
261 | } else {
262 | throw new Error(str);
263 | }
264 | },
265 | setInput:function (input) {
266 | this._input = input;
267 | this._more = this._less = this.done = false;
268 | this.yylineno = this.yyleng = 0;
269 | this.yytext = this.matched = this.match = '';
270 | this.conditionStack = ['INITIAL'];
271 | return this;
272 | },
273 | input:function () {
274 | var ch = this._input[0];
275 | this.yytext+=ch;
276 | this.yyleng++;
277 | this.match+=ch;
278 | this.matched+=ch;
279 | var lines = ch.match(/\n/);
280 | if (lines) this.yylineno++;
281 | this._input = this._input.slice(1);
282 | return ch;
283 | },
284 | unput:function (ch) {
285 | this._input = ch + this._input;
286 | return this;
287 | },
288 | more:function () {
289 | this._more = true;
290 | return this;
291 | },
292 | pastInput:function () {
293 | var past = this.matched.substr(0, this.matched.length - this.match.length);
294 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
295 | },
296 | upcomingInput:function () {
297 | var next = this.match;
298 | if (next.length < 20) {
299 | next += this._input.substr(0, 20-next.length);
300 | }
301 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
302 | },
303 | showPosition:function () {
304 | var pre = this.pastInput();
305 | var c = new Array(pre.length + 1).join("-");
306 | return pre + this.upcomingInput() + "\n" + c+"^";
307 | },
308 | next:function () {
309 | if (this.done) {
310 | return this.EOF;
311 | }
312 | if (!this._input) this.done = true;
313 |
314 | var token,
315 | match,
316 | lines;
317 | if (!this._more) {
318 | this.yytext = '';
319 | this.match = '';
320 | }
321 | var rules = this._currentRules();
322 | for (var i=0;i < rules.length; i++) {
323 | match = this._input.match(this.rules[rules[i]]);
324 | if (match) {
325 | lines = match[0].match(/\n/g);
326 | if (lines) this.yylineno += lines.length;
327 | this.yytext += match[0];
328 | this.match += match[0];
329 | this.matches = match;
330 | this.yyleng = this.yytext.length;
331 | this._more = false;
332 | this._input = this._input.slice(match[0].length);
333 | this.matched += match[0];
334 | token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
335 | if (token) return token;
336 | else return;
337 | }
338 | }
339 | if (this._input === "") {
340 | return this.EOF;
341 | } else {
342 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
343 | {text: "", token: null, line: this.yylineno});
344 | }
345 | },
346 | lex:function lex() {
347 | var r = this.next();
348 | if (typeof r !== 'undefined') {
349 | return r;
350 | } else {
351 | return this.lex();
352 | }
353 | },
354 | begin:function begin(condition) {
355 | this.conditionStack.push(condition);
356 | },
357 | popState:function popState() {
358 | return this.conditionStack.pop();
359 | },
360 | _currentRules:function _currentRules() {
361 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
362 | }});
363 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
364 |
365 | YYSTATE=YY_START
366 | switch($avoiding_name_collisions) {
367 | case 0: this.begin("mu"); if (yy_.yytext) return 14;
368 | break;
369 | case 1: return 14;
370 | break;
371 | case 2: return 24;
372 | break;
373 | case 3: return 16;
374 | break;
375 | case 4: return 20;
376 | break;
377 | case 5: return 19;
378 | break;
379 | case 6: return 19;
380 | break;
381 | case 7: return 23;
382 | break;
383 | case 8: return 23;
384 | break;
385 | case 9: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.begin("INITIAL"); return 15;
386 | break;
387 | case 10: return 22;
388 | break;
389 | case 11: return 32;
390 | break;
391 | case 12: return 31;
392 | break;
393 | case 13: return 31;
394 | break;
395 | case 14: return 34;
396 | break;
397 | case 15: /*ignore whitespace*/
398 | break;
399 | case 16: this.begin("INITIAL"); return 18;
400 | break;
401 | case 17: this.begin("INITIAL"); return 18;
402 | break;
403 | case 18: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
404 | break;
405 | case 19: return 31;
406 | break;
407 | case 20: return 'INVALID';
408 | break;
409 | case 21: return 5;
410 | break;
411 | }
412 | };
413 | lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^[a-zA-Z0-9_-]+(?=[=} /.])/,/^./,/^$/];
414 | lexer.conditions = {"mu":{"rules":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21],"inclusive":false},"INITIAL":{"rules":[0,1,21],"inclusive":true}};return lexer;})()
415 | parser.lexer = lexer;
416 | return parser;
417 | })();
418 | if (typeof require !== 'undefined') {
419 | exports.parser = handlebars;
420 | exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
421 | exports.main = function commonjsMain(args) {
422 | if (!args[1])
423 | throw new Error('Usage: '+args[0]+' FILE');
424 | if (typeof process !== 'undefined') {
425 | var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
426 | } else {
427 | var cwd = require("file").path(require("file").cwd());
428 | var source = cwd.join(args[1]).read({charset: "utf-8"});
429 | }
430 | return exports.parser.parse(source);
431 | }
432 | if (typeof module !== 'undefined' && require.main === module) {
433 | exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
434 | }
435 | };
436 | ;
437 | // lib/handlebars/base.js
438 | var Handlebars = {};
439 |
440 | Handlebars.VERSION = "1.0.beta.2";
441 |
442 | Handlebars.Parser = handlebars;
443 |
444 | Handlebars.parse = function(string) {
445 | Handlebars.Parser.yy = Handlebars.AST;
446 | return Handlebars.Parser.parse(string);
447 | };
448 |
449 | Handlebars.print = function(ast) {
450 | return new Handlebars.PrintVisitor().accept(ast);
451 | };
452 |
453 | Handlebars.helpers = {};
454 | Handlebars.partials = {};
455 |
456 | Handlebars.registerHelper = function(name, fn, inverse) {
457 | if(inverse) { fn.not = inverse; }
458 | this.helpers[name] = fn;
459 | };
460 |
461 | Handlebars.registerPartial = function(name, str) {
462 | this.partials[name] = str;
463 | };
464 |
465 | Handlebars.registerHelper('helperMissing', function(arg) {
466 | if(arguments.length === 2) {
467 | return undefined;
468 | } else {
469 | throw new Error("Could not find property '" + arg + "'");
470 | }
471 | });
472 |
473 | Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
474 | inverse = inverse || function() {};
475 |
476 | var ret = "";
477 | var type = Object.prototype.toString.call(context);
478 |
479 | if(type === "[object Function]") {
480 | context = context();
481 | }
482 |
483 | if(context === true) {
484 | return fn(this);
485 | } else if(context === false || context == null) {
486 | return inverse(this);
487 | } else if(type === "[object Array]") {
488 | if(context.length > 0) {
489 | for(var i=0, j=context.length; i 0) {
507 | for(var i=0, j=context.length; i": ">"
656 | };
657 |
658 | var badChars = /&(?!\w+;)|[<>]/g;
659 | var possible = /[&<>]/;
660 |
661 | var escapeChar = function(chr) {
662 | return escape[chr] || "&"
663 | };
664 |
665 | Handlebars.Utils = {
666 | escapeExpression: function(string) {
667 | // don't escape SafeStrings, since they're already safe
668 | if (string instanceof Handlebars.SafeString) {
669 | return string.toString();
670 | } else if (string == null || string === false) {
671 | return "";
672 | }
673 |
674 | if(!possible.test(string)) { return string; }
675 | return string.replace(badChars, escapeChar);
676 | },
677 |
678 | isEmpty: function(value) {
679 | if (typeof value === "undefined") {
680 | return true;
681 | } else if (value === null) {
682 | return true;
683 | } else if (value === false) {
684 | return true;
685 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
686 | return true;
687 | } else {
688 | return false;
689 | }
690 | }
691 | };
692 | })();;
693 | // lib/handlebars/compiler.js
694 | Handlebars.Compiler = function() {};
695 | Handlebars.JavaScriptCompiler = function() {};
696 |
697 | (function(Compiler, JavaScriptCompiler) {
698 | Compiler.OPCODE_MAP = {
699 | appendContent: 1,
700 | getContext: 2,
701 | lookupWithHelpers: 3,
702 | lookup: 4,
703 | append: 5,
704 | invokeMustache: 6,
705 | appendEscaped: 7,
706 | pushString: 8,
707 | truthyOrFallback: 9,
708 | functionOrFallback: 10,
709 | invokeProgram: 11,
710 | invokePartial: 12,
711 | push: 13,
712 | invokeInverse: 14,
713 | assignToHash: 15,
714 | pushStringParam: 16
715 | };
716 |
717 | Compiler.MULTI_PARAM_OPCODES = {
718 | appendContent: 1,
719 | getContext: 1,
720 | lookupWithHelpers: 1,
721 | lookup: 1,
722 | invokeMustache: 2,
723 | pushString: 1,
724 | truthyOrFallback: 1,
725 | functionOrFallback: 1,
726 | invokeProgram: 2,
727 | invokePartial: 1,
728 | push: 1,
729 | invokeInverse: 1,
730 | assignToHash: 1,
731 | pushStringParam: 1
732 | };
733 |
734 | Compiler.DISASSEMBLE_MAP = {};
735 |
736 | for(var prop in Compiler.OPCODE_MAP) {
737 | var value = Compiler.OPCODE_MAP[prop];
738 | Compiler.DISASSEMBLE_MAP[value] = prop;
739 | }
740 |
741 | Compiler.multiParamSize = function(code) {
742 | return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
743 | };
744 |
745 | Compiler.prototype = {
746 | compiler: Compiler,
747 |
748 | disassemble: function() {
749 | var opcodes = this.opcodes, opcode, nextCode;
750 | var out = [], str, name, value;
751 |
752 | for(var i=0, l=opcodes.length; i 0) {
1092 | this.source[0] = this.source[0] + ", " + locals.join(", ");
1093 | }
1094 |
1095 | this.source[0] = this.source[0] + ";";
1096 |
1097 | this.source.push("return buffer;");
1098 |
1099 | var params = ["Handlebars", "context", "helpers", "partials"];
1100 |
1101 | if(this.options.data) { params.push("data"); }
1102 |
1103 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1355 | return "stack" + this.stackSlot;
1356 | },
1357 |
1358 | popStack: function() {
1359 | return "stack" + this.stackSlot--;
1360 | },
1361 |
1362 | topStack: function() {
1363 | return "stack" + this.stackSlot;
1364 | },
1365 |
1366 | quotedString: function(str) {
1367 | return '"' + str
1368 | .replace(/\\/g, '\\\\')
1369 | .replace(/"/g, '\\"')
1370 | .replace(/\n/g, '\\n')
1371 | .replace(/\r/g, '\\r') + '"';
1372 | }
1373 | };
1374 |
1375 | var reservedWords = ("break case catch continue default delete do else finally " +
1376 | "for function if in instanceof new return switch this throw " +
1377 | "try typeof var void while with null true false").split(" ");
1378 |
1379 | compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1380 |
1381 | for(var i=0, l=reservedWords.length; i 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount == 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) {
166 | if (console.log.apply) {
167 | console.log.apply(console, arguments);
168 | } else {
169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
170 | }
171 | }
172 | };
173 |
174 | jasmine.TrivialReporter.prototype.getLocation = function() {
175 | return this.document.location;
176 | };
177 |
178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
179 | var paramMap = {};
180 | var params = this.getLocation().search.substring(1).split('&');
181 | for (var i = 0; i < params.length; i++) {
182 | var p = params[i].split('=');
183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
184 | }
185 |
186 | if (!paramMap["spec"]) return true;
187 | return spec.getFullName().indexOf(paramMap["spec"]) == 0;
188 | };
189 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.1.6
2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 |
9 | (function() {
10 |
11 | // Baseline setup
12 | // --------------
13 |
14 | // Establish the root object, `window` in the browser, or `global` on the server.
15 | var root = this;
16 |
17 | // Save the previous value of the `_` variable.
18 | var previousUnderscore = root._;
19 |
20 | // Establish the object that gets returned to break out of a loop iteration.
21 | var breaker = {};
22 |
23 | // Save bytes in the minified (but not gzipped) version:
24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys,
46 | nativeBind = FuncProto.bind;
47 |
48 | // Create a safe reference to the Underscore object for use below.
49 | var _ = function(obj) { return new wrapper(obj); };
50 |
51 | // Export the Underscore object for **CommonJS**, with backwards-compatibility
52 | // for the old `require()` API. If we're not in CommonJS, add `_` to the
53 | // global object.
54 | if (typeof module !== 'undefined' && module.exports) {
55 | module.exports = _;
56 | _._ = _;
57 | } else {
58 | root._ = _;
59 | }
60 |
61 | // Current version.
62 | _.VERSION = '1.1.6';
63 |
64 | // Collection Functions
65 | // --------------------
66 |
67 | // The cornerstone, an `each` implementation, aka `forEach`.
68 | // Handles objects implementing `forEach`, arrays, and raw objects.
69 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
70 | var each = _.each = _.forEach = function(obj, iterator, context) {
71 | if (obj == null) return;
72 | if (nativeForEach && obj.forEach === nativeForEach) {
73 | obj.forEach(iterator, context);
74 | } else if (_.isNumber(obj.length)) {
75 | for (var i = 0, l = obj.length; i < l; i++) {
76 | if (iterator.call(context, obj[i], i, obj) === breaker) return;
77 | }
78 | } else {
79 | for (var key in obj) {
80 | if (hasOwnProperty.call(obj, key)) {
81 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
82 | }
83 | }
84 | }
85 | };
86 |
87 | // Return the results of applying the iterator to each element.
88 | // Delegates to **ECMAScript 5**'s native `map` if available.
89 | _.map = function(obj, iterator, context) {
90 | var results = [];
91 | if (obj == null) return results;
92 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
93 | each(obj, function(value, index, list) {
94 | results[results.length] = iterator.call(context, value, index, list);
95 | });
96 | return results;
97 | };
98 |
99 | // **Reduce** builds up a single result from a list of values, aka `inject`,
100 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
101 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
102 | var initial = memo !== void 0;
103 | if (obj == null) obj = [];
104 | if (nativeReduce && obj.reduce === nativeReduce) {
105 | if (context) iterator = _.bind(iterator, context);
106 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
107 | }
108 | each(obj, function(value, index, list) {
109 | if (!initial && index === 0) {
110 | memo = value;
111 | initial = true;
112 | } else {
113 | memo = iterator.call(context, memo, value, index, list);
114 | }
115 | });
116 | if (!initial) throw new TypeError("Reduce of empty array with no initial value");
117 | return memo;
118 | };
119 |
120 | // The right-associative version of reduce, also known as `foldr`.
121 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
122 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
123 | if (obj == null) obj = [];
124 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
125 | if (context) iterator = _.bind(iterator, context);
126 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
127 | }
128 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
129 | return _.reduce(reversed, iterator, memo, context);
130 | };
131 |
132 | // Return the first value which passes a truth test. Aliased as `detect`.
133 | _.find = _.detect = function(obj, iterator, context) {
134 | var result;
135 | any(obj, function(value, index, list) {
136 | if (iterator.call(context, value, index, list)) {
137 | result = value;
138 | return true;
139 | }
140 | });
141 | return result;
142 | };
143 |
144 | // Return all the elements that pass a truth test.
145 | // Delegates to **ECMAScript 5**'s native `filter` if available.
146 | // Aliased as `select`.
147 | _.filter = _.select = function(obj, iterator, context) {
148 | var results = [];
149 | if (obj == null) return results;
150 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
151 | each(obj, function(value, index, list) {
152 | if (iterator.call(context, value, index, list)) results[results.length] = value;
153 | });
154 | return results;
155 | };
156 |
157 | // Return all the elements for which a truth test fails.
158 | _.reject = function(obj, iterator, context) {
159 | var results = [];
160 | if (obj == null) return results;
161 | each(obj, function(value, index, list) {
162 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
163 | });
164 | return results;
165 | };
166 |
167 | // Determine whether all of the elements match a truth test.
168 | // Delegates to **ECMAScript 5**'s native `every` if available.
169 | // Aliased as `all`.
170 | _.every = _.all = function(obj, iterator, context) {
171 | var result = true;
172 | if (obj == null) return result;
173 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
174 | each(obj, function(value, index, list) {
175 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
176 | });
177 | return result;
178 | };
179 |
180 | // Determine if at least one element in the object matches a truth test.
181 | // Delegates to **ECMAScript 5**'s native `some` if available.
182 | // Aliased as `any`.
183 | var any = _.some = _.any = function(obj, iterator, context) {
184 | iterator || (iterator = _.identity);
185 | var result = false;
186 | if (obj == null) return result;
187 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
188 | each(obj, function(value, index, list) {
189 | if (result = iterator.call(context, value, index, list)) return breaker;
190 | });
191 | return result;
192 | };
193 |
194 | // Determine if a given value is included in the array or object using `===`.
195 | // Aliased as `contains`.
196 | _.include = _.contains = function(obj, target) {
197 | var found = false;
198 | if (obj == null) return found;
199 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
200 | any(obj, function(value) {
201 | if (found = value === target) return true;
202 | });
203 | return found;
204 | };
205 |
206 | // Invoke a method (with arguments) on every item in a collection.
207 | _.invoke = function(obj, method) {
208 | var args = slice.call(arguments, 2);
209 | return _.map(obj, function(value) {
210 | return (method.call ? method || value : value[method]).apply(value, args);
211 | });
212 | };
213 |
214 | // Convenience version of a common use case of `map`: fetching a property.
215 | _.pluck = function(obj, key) {
216 | return _.map(obj, function(value){ return value[key]; });
217 | };
218 |
219 | // Return the maximum element or (element-based computation).
220 | _.max = function(obj, iterator, context) {
221 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
222 | var result = {computed : -Infinity};
223 | each(obj, function(value, index, list) {
224 | var computed = iterator ? iterator.call(context, value, index, list) : value;
225 | computed >= result.computed && (result = {value : value, computed : computed});
226 | });
227 | return result.value;
228 | };
229 |
230 | // Return the minimum element (or element-based computation).
231 | _.min = function(obj, iterator, context) {
232 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
233 | var result = {computed : Infinity};
234 | each(obj, function(value, index, list) {
235 | var computed = iterator ? iterator.call(context, value, index, list) : value;
236 | computed < result.computed && (result = {value : value, computed : computed});
237 | });
238 | return result.value;
239 | };
240 |
241 | // Sort the object's values by a criterion produced by an iterator.
242 | _.sortBy = function(obj, iterator, context) {
243 | return _.pluck(_.map(obj, function(value, index, list) {
244 | return {
245 | value : value,
246 | criteria : iterator.call(context, value, index, list)
247 | };
248 | }).sort(function(left, right) {
249 | var a = left.criteria, b = right.criteria;
250 | return a < b ? -1 : a > b ? 1 : 0;
251 | }), 'value');
252 | };
253 |
254 | // Use a comparator function to figure out at what index an object should
255 | // be inserted so as to maintain order. Uses binary search.
256 | _.sortedIndex = function(array, obj, iterator) {
257 | iterator || (iterator = _.identity);
258 | var low = 0, high = array.length;
259 | while (low < high) {
260 | var mid = (low + high) >> 1;
261 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
262 | }
263 | return low;
264 | };
265 |
266 | // Safely convert anything iterable into a real, live array.
267 | _.toArray = function(iterable) {
268 | if (!iterable) return [];
269 | if (iterable.toArray) return iterable.toArray();
270 | if (_.isArray(iterable)) return iterable;
271 | if (_.isArguments(iterable)) return slice.call(iterable);
272 | return _.values(iterable);
273 | };
274 |
275 | // Return the number of elements in an object.
276 | _.size = function(obj) {
277 | return _.toArray(obj).length;
278 | };
279 |
280 | // Array Functions
281 | // ---------------
282 |
283 | // Get the first element of an array. Passing **n** will return the first N
284 | // values in the array. Aliased as `head`. The **guard** check allows it to work
285 | // with `_.map`.
286 | _.first = _.head = function(array, n, guard) {
287 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
288 | };
289 |
290 | // Returns everything but the first entry of the array. Aliased as `tail`.
291 | // Especially useful on the arguments object. Passing an **index** will return
292 | // the rest of the values in the array from that index onward. The **guard**
293 | // check allows it to work with `_.map`.
294 | _.rest = _.tail = function(array, index, guard) {
295 | return slice.call(array, (index == null) || guard ? 1 : index);
296 | };
297 |
298 | // Get the last element of an array.
299 | _.last = function(array) {
300 | return array[array.length - 1];
301 | };
302 |
303 | // Trim out all falsy values from an array.
304 | _.compact = function(array) {
305 | return _.filter(array, function(value){ return !!value; });
306 | };
307 |
308 | // Return a completely flattened version of an array.
309 | _.flatten = function(array) {
310 | return _.reduce(array, function(memo, value) {
311 | if (_.isArray(value)) return memo.concat(_.flatten(value));
312 | memo[memo.length] = value;
313 | return memo;
314 | }, []);
315 | };
316 |
317 | // Return a version of the array that does not contain the specified value(s).
318 | _.without = function(array) {
319 | var values = slice.call(arguments, 1);
320 | return _.filter(array, function(value){ return !_.include(values, value); });
321 | };
322 |
323 | // Produce a duplicate-free version of the array. If the array has already
324 | // been sorted, you have the option of using a faster algorithm.
325 | // Aliased as `unique`.
326 | _.uniq = _.unique = function(array, isSorted) {
327 | return _.reduce(array, function(memo, el, i) {
328 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
329 | return memo;
330 | }, []);
331 | };
332 |
333 | // Produce an array that contains every item shared between all the
334 | // passed-in arrays.
335 | _.intersect = function(array) {
336 | var rest = slice.call(arguments, 1);
337 | return _.filter(_.uniq(array), function(item) {
338 | return _.every(rest, function(other) {
339 | return _.indexOf(other, item) >= 0;
340 | });
341 | });
342 | };
343 |
344 | // Zip together multiple lists into a single array -- elements that share
345 | // an index go together.
346 | _.zip = function() {
347 | var args = slice.call(arguments);
348 | var length = _.max(_.pluck(args, 'length'));
349 | var results = new Array(length);
350 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
351 | return results;
352 | };
353 |
354 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
355 | // we need this function. Return the position of the first occurrence of an
356 | // item in an array, or -1 if the item is not included in the array.
357 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
358 | // If the array is large and already in sort order, pass `true`
359 | // for **isSorted** to use binary search.
360 | _.indexOf = function(array, item, isSorted) {
361 | if (array == null) return -1;
362 | var i, l;
363 | if (isSorted) {
364 | i = _.sortedIndex(array, item);
365 | return array[i] === item ? i : -1;
366 | }
367 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
368 | for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
369 | return -1;
370 | };
371 |
372 |
373 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
374 | _.lastIndexOf = function(array, item) {
375 | if (array == null) return -1;
376 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
377 | var i = array.length;
378 | while (i--) if (array[i] === item) return i;
379 | return -1;
380 | };
381 |
382 | // Generate an integer Array containing an arithmetic progression. A port of
383 | // the native Python `range()` function. See
384 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
385 | _.range = function(start, stop, step) {
386 | if (arguments.length <= 1) {
387 | stop = start || 0;
388 | start = 0;
389 | }
390 | step = arguments[2] || 1;
391 |
392 | var len = Math.max(Math.ceil((stop - start) / step), 0);
393 | var idx = 0;
394 | var range = new Array(len);
395 |
396 | while(idx < len) {
397 | range[idx++] = start;
398 | start += step;
399 | }
400 |
401 | return range;
402 | };
403 |
404 | // Function (ahem) Functions
405 | // ------------------
406 |
407 | // Create a function bound to a given object (assigning `this`, and arguments,
408 | // optionally). Binding with arguments is also known as `curry`.
409 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
410 | // We check for `func.bind` first, to fail fast when `func` is undefined.
411 | _.bind = function(func, obj) {
412 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
413 | var args = slice.call(arguments, 2);
414 | return function() {
415 | return func.apply(obj, args.concat(slice.call(arguments)));
416 | };
417 | };
418 |
419 | // Bind all of an object's methods to that object. Useful for ensuring that
420 | // all callbacks defined on an object belong to it.
421 | _.bindAll = function(obj) {
422 | var funcs = slice.call(arguments, 1);
423 | if (funcs.length == 0) funcs = _.functions(obj);
424 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
425 | return obj;
426 | };
427 |
428 | // Memoize an expensive function by storing its results.
429 | _.memoize = function(func, hasher) {
430 | var memo = {};
431 | hasher || (hasher = _.identity);
432 | return function() {
433 | var key = hasher.apply(this, arguments);
434 | return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
435 | };
436 | };
437 |
438 | // Delays a function for the given number of milliseconds, and then calls
439 | // it with the arguments supplied.
440 | _.delay = function(func, wait) {
441 | var args = slice.call(arguments, 2);
442 | return setTimeout(function(){ return func.apply(func, args); }, wait);
443 | };
444 |
445 | // Defers a function, scheduling it to run after the current call stack has
446 | // cleared.
447 | _.defer = function(func) {
448 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
449 | };
450 |
451 | // Internal function used to implement `_.throttle` and `_.debounce`.
452 | var limit = function(func, wait, debounce) {
453 | var timeout;
454 | return function() {
455 | var context = this, args = arguments;
456 | var throttler = function() {
457 | timeout = null;
458 | func.apply(context, args);
459 | };
460 | if (debounce) clearTimeout(timeout);
461 | if (debounce || !timeout) timeout = setTimeout(throttler, wait);
462 | };
463 | };
464 |
465 | // Returns a function, that, when invoked, will only be triggered at most once
466 | // during a given window of time.
467 | _.throttle = function(func, wait) {
468 | return limit(func, wait, false);
469 | };
470 |
471 | // Returns a function, that, as long as it continues to be invoked, will not
472 | // be triggered. The function will be called after it stops being called for
473 | // N milliseconds.
474 | _.debounce = function(func, wait) {
475 | return limit(func, wait, true);
476 | };
477 |
478 | // Returns a function that will be executed at most one time, no matter how
479 | // often you call it. Useful for lazy initialization.
480 | _.once = function(func) {
481 | var ran = false, memo;
482 | return function() {
483 | if (ran) return memo;
484 | ran = true;
485 | return memo = func.apply(this, arguments);
486 | };
487 | };
488 |
489 | // Returns the first function passed as an argument to the second,
490 | // allowing you to adjust arguments, run code before and after, and
491 | // conditionally execute the original function.
492 | _.wrap = function(func, wrapper) {
493 | return function() {
494 | var args = [func].concat(slice.call(arguments));
495 | return wrapper.apply(this, args);
496 | };
497 | };
498 |
499 | // Returns a function that is the composition of a list of functions, each
500 | // consuming the return value of the function that follows.
501 | _.compose = function() {
502 | var funcs = slice.call(arguments);
503 | return function() {
504 | var args = slice.call(arguments);
505 | for (var i=funcs.length-1; i >= 0; i--) {
506 | args = [funcs[i].apply(this, args)];
507 | }
508 | return args[0];
509 | };
510 | };
511 |
512 | // Returns a function that will only be executed after being called N times.
513 | _.after = function(times, func) {
514 | return function() {
515 | if (--times < 1) { return func.apply(this, arguments); }
516 | };
517 | };
518 |
519 |
520 | // Object Functions
521 | // ----------------
522 |
523 | // Retrieve the names of an object's properties.
524 | // Delegates to **ECMAScript 5**'s native `Object.keys`
525 | _.keys = nativeKeys || function(obj) {
526 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
527 | var keys = [];
528 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
529 | return keys;
530 | };
531 |
532 | // Retrieve the values of an object's properties.
533 | _.values = function(obj) {
534 | return _.map(obj, _.identity);
535 | };
536 |
537 | // Return a sorted list of the function names available on the object.
538 | // Aliased as `methods`
539 | _.functions = _.methods = function(obj) {
540 | return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
541 | };
542 |
543 | // Extend a given object with all the properties in passed-in object(s).
544 | _.extend = function(obj) {
545 | each(slice.call(arguments, 1), function(source) {
546 | for (var prop in source) {
547 | if (source[prop] !== void 0) obj[prop] = source[prop];
548 | }
549 | });
550 | return obj;
551 | };
552 |
553 | // Fill in a given object with default properties.
554 | _.defaults = function(obj) {
555 | each(slice.call(arguments, 1), function(source) {
556 | for (var prop in source) {
557 | if (obj[prop] == null) obj[prop] = source[prop];
558 | }
559 | });
560 | return obj;
561 | };
562 |
563 | // Create a (shallow-cloned) duplicate of an object.
564 | _.clone = function(obj) {
565 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
566 | };
567 |
568 | // Invokes interceptor with the obj, and then returns obj.
569 | // The primary purpose of this method is to "tap into" a method chain, in
570 | // order to perform operations on intermediate results within the chain.
571 | _.tap = function(obj, interceptor) {
572 | interceptor(obj);
573 | return obj;
574 | };
575 |
576 | // Perform a deep comparison to check if two objects are equal.
577 | _.isEqual = function(a, b) {
578 | // Check object identity.
579 | if (a === b) return true;
580 | // Different types?
581 | var atype = typeof(a), btype = typeof(b);
582 | if (atype != btype) return false;
583 | // Basic equality test (watch out for coercions).
584 | if (a == b) return true;
585 | // One is falsy and the other truthy.
586 | if ((!a && b) || (a && !b)) return false;
587 | // Unwrap any wrapped objects.
588 | if (a._chain) a = a._wrapped;
589 | if (b._chain) b = b._wrapped;
590 | // One of them implements an isEqual()?
591 | if (a.isEqual) return a.isEqual(b);
592 | // Check dates' integer values.
593 | if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
594 | // Both are NaN?
595 | if (_.isNaN(a) && _.isNaN(b)) return false;
596 | // Compare regular expressions.
597 | if (_.isRegExp(a) && _.isRegExp(b))
598 | return a.source === b.source &&
599 | a.global === b.global &&
600 | a.ignoreCase === b.ignoreCase &&
601 | a.multiline === b.multiline;
602 | // If a is not an object by this point, we can't handle it.
603 | if (atype !== 'object') return false;
604 | // Check for different array lengths before comparing contents.
605 | if (a.length && (a.length !== b.length)) return false;
606 | // Nothing else worked, deep compare the contents.
607 | var aKeys = _.keys(a), bKeys = _.keys(b);
608 | // Different object sizes?
609 | if (aKeys.length != bKeys.length) return false;
610 | // Recursive comparison of contents.
611 | for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
612 | return true;
613 | };
614 |
615 | // Is a given array or object empty?
616 | _.isEmpty = function(obj) {
617 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
618 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
619 | return true;
620 | };
621 |
622 | // Is a given value a DOM element?
623 | _.isElement = function(obj) {
624 | return !!(obj && obj.nodeType == 1);
625 | };
626 |
627 | // Is a given value an array?
628 | // Delegates to ECMA5's native Array.isArray
629 | _.isArray = nativeIsArray || function(obj) {
630 | return toString.call(obj) === '[object Array]';
631 | };
632 |
633 | // Is a given variable an arguments object?
634 | _.isArguments = function(obj) {
635 | return !!(obj && hasOwnProperty.call(obj, 'callee'));
636 | };
637 |
638 | // Is a given value a function?
639 | _.isFunction = function(obj) {
640 | return !!(obj && obj.constructor && obj.call && obj.apply);
641 | };
642 |
643 | // Is a given value a string?
644 | _.isString = function(obj) {
645 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
646 | };
647 |
648 | // Is a given value a number?
649 | _.isNumber = function(obj) {
650 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
651 | };
652 |
653 | // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
654 | // that does not equal itself.
655 | _.isNaN = function(obj) {
656 | return obj !== obj;
657 | };
658 |
659 | // Is a given value a boolean?
660 | _.isBoolean = function(obj) {
661 | return obj === true || obj === false;
662 | };
663 |
664 | // Is a given value a date?
665 | _.isDate = function(obj) {
666 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
667 | };
668 |
669 | // Is the given value a regular expression?
670 | _.isRegExp = function(obj) {
671 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
672 | };
673 |
674 | // Is a given value equal to null?
675 | _.isNull = function(obj) {
676 | return obj === null;
677 | };
678 |
679 | // Is a given variable undefined?
680 | _.isUndefined = function(obj) {
681 | return obj === void 0;
682 | };
683 |
684 | // Utility Functions
685 | // -----------------
686 |
687 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
688 | // previous owner. Returns a reference to the Underscore object.
689 | _.noConflict = function() {
690 | root._ = previousUnderscore;
691 | return this;
692 | };
693 |
694 | // Keep the identity function around for default iterators.
695 | _.identity = function(value) {
696 | return value;
697 | };
698 |
699 | // Run a function **n** times.
700 | _.times = function (n, iterator, context) {
701 | for (var i = 0; i < n; i++) iterator.call(context, i);
702 | };
703 |
704 | // Add your own custom functions to the Underscore object, ensuring that
705 | // they're correctly added to the OOP wrapper as well.
706 | _.mixin = function(obj) {
707 | each(_.functions(obj), function(name){
708 | addToWrapper(name, _[name] = obj[name]);
709 | });
710 | };
711 |
712 | // Generate a unique integer id (unique within the entire client session).
713 | // Useful for temporary DOM ids.
714 | var idCounter = 0;
715 | _.uniqueId = function(prefix) {
716 | var id = idCounter++;
717 | return prefix ? prefix + id : id;
718 | };
719 |
720 | // By default, Underscore uses ERB-style template delimiters, change the
721 | // following template settings to use alternative delimiters.
722 | _.templateSettings = {
723 | evaluate : /<%([\s\S]+?)%>/g,
724 | interpolate : /<%=([\s\S]+?)%>/g
725 | };
726 |
727 | // JavaScript micro-templating, similar to John Resig's implementation.
728 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
729 | // and correctly escapes quotes within interpolated code.
730 | _.template = function(str, data) {
731 | var c = _.templateSettings;
732 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
733 | 'with(obj||{}){__p.push(\'' +
734 | str.replace(/\\/g, '\\\\')
735 | .replace(/'/g, "\\'")
736 | .replace(c.interpolate, function(match, code) {
737 | return "'," + code.replace(/\\'/g, "'") + ",'";
738 | })
739 | .replace(c.evaluate || null, function(match, code) {
740 | return "');" + code.replace(/\\'/g, "'")
741 | .replace(/[\r\n\t]/g, ' ') + "__p.push('";
742 | })
743 | .replace(/\r/g, '\\r')
744 | .replace(/\n/g, '\\n')
745 | .replace(/\t/g, '\\t')
746 | + "');}return __p.join('');";
747 | var func = new Function('obj', tmpl);
748 | return data ? func(data) : func;
749 | };
750 |
751 | // The OOP Wrapper
752 | // ---------------
753 |
754 | // If Underscore is called as a function, it returns a wrapped object that
755 | // can be used OO-style. This wrapper holds altered versions of all the
756 | // underscore functions. Wrapped objects may be chained.
757 | var wrapper = function(obj) { this._wrapped = obj; };
758 |
759 | // Expose `wrapper.prototype` as `_.prototype`
760 | _.prototype = wrapper.prototype;
761 |
762 | // Helper function to continue chaining intermediate results.
763 | var result = function(obj, chain) {
764 | return chain ? _(obj).chain() : obj;
765 | };
766 |
767 | // A method to easily add functions to the OOP wrapper.
768 | var addToWrapper = function(name, func) {
769 | wrapper.prototype[name] = function() {
770 | var args = slice.call(arguments);
771 | unshift.call(args, this._wrapped);
772 | return result(func.apply(_, args), this._chain);
773 | };
774 | };
775 |
776 | // Add all of the Underscore functions to the wrapper object.
777 | _.mixin(_);
778 |
779 | // Add all mutator Array functions to the wrapper.
780 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
781 | var method = ArrayProto[name];
782 | wrapper.prototype[name] = function() {
783 | method.apply(this._wrapped, arguments);
784 | return result(this._wrapped, this._chain);
785 | };
786 | });
787 |
788 | // Add all accessor Array functions to the wrapper.
789 | each(['concat', 'join', 'slice'], function(name) {
790 | var method = ArrayProto[name];
791 | wrapper.prototype[name] = function() {
792 | return result(method.apply(this._wrapped, arguments), this._chain);
793 | };
794 | });
795 |
796 | // Start chaining a wrapped Underscore object.
797 | wrapper.prototype.chain = function() {
798 | this._chain = true;
799 | return this;
800 | };
801 |
802 | // Extracts the result from a wrapped and chained object.
803 | wrapper.prototype.value = function() {
804 | return this._wrapped;
805 | };
806 |
807 | })();
808 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/vendor/plugins/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/vendor/plugins/.gitkeep
--------------------------------------------------------------------------------