frontend 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +45 -0
- data/frontend.gemspec +23 -0
- data/lib/frontend.rb +6 -0
- data/lib/frontend/version.rb +3 -0
- data/vendor/assets/javascripts/frontend.js.coffee +5 -0
- data/vendor/assets/javascripts/vendor/backbone.localStorage.js +1 -0
- data/vendor/assets/javascripts/vendor/backrub.js.coffee +523 -0
- data/vendor/assets/javascripts/vendor/handlebars.js +1493 -0
- data/vendor/assets/javascripts/vendor/relational.js +1108 -0
- data/vendor/assets/stylesheets/bootstrap.css.scss +2 -0
- data/vendor/assets/stylesheets/bootstrap/bootstrap.scss +25 -0
- data/vendor/assets/stylesheets/bootstrap/forms.css.scss +368 -0
- data/vendor/assets/stylesheets/bootstrap/patterns.scss +769 -0
- data/vendor/assets/stylesheets/bootstrap/preboot.css.scss +275 -0
- data/vendor/assets/stylesheets/bootstrap/reset.scss +146 -0
- data/vendor/assets/stylesheets/bootstrap/scaffolding.scss +204 -0
- data/vendor/assets/stylesheets/bootstrap/tables.scss +148 -0
- data/vendor/assets/stylesheets/bootstrap/type.scss +192 -0
- metadata +90 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# FrontEnd
|
2
|
+
A Rails 3.1 engine which provides Javascript Libraries and Stylesheets for rapidly prototyping advanced frontends.
|
3
|
+
|
4
|
+
## Javascript libraries
|
5
|
+
- jQuery
|
6
|
+
- jQuery UI
|
7
|
+
- Backbone
|
8
|
+
- Backbone.localStorage
|
9
|
+
- BackRub
|
10
|
+
- Handlebars.js
|
11
|
+
- Backbone.relational
|
12
|
+
|
13
|
+
## Stylesheets
|
14
|
+
- Normaliser.css
|
15
|
+
- Twitter Bootstrap (in SCSS)
|
16
|
+
- Compass
|
17
|
+
|
18
|
+
## Generators
|
19
|
+
We also include generators for Backbone Models, Views, Routers and a Backbone style scaffold, as well as installation generators.
|
20
|
+
|
21
|
+
# Installation
|
22
|
+
In your Gemfile:
|
23
|
+
|
24
|
+
gem 'frontend'
|
25
|
+
|
26
|
+
Automatic install of everything:
|
27
|
+
|
28
|
+
rails g frontend:install
|
29
|
+
|
30
|
+
Limited install options:
|
31
|
+
|
32
|
+
rails g frontend:bootstrap
|
33
|
+
rails g frontend:backbone
|
34
|
+
rails g frontend:backbone:relational
|
35
|
+
rails g frontend:backbone:localstorage
|
36
|
+
|
37
|
+
## Contributing
|
38
|
+
- Fork it
|
39
|
+
- Commit
|
40
|
+
- Test
|
41
|
+
- Issue pull request
|
42
|
+
- Grab a beer
|
43
|
+
|
44
|
+
# Authors
|
45
|
+
- Ivan Vanderbyl
|
data/frontend.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "frontend/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "frontend"
|
7
|
+
s.version = Frontend::VERSION
|
8
|
+
s.authors = ["Ivan Vanderbyl"]
|
9
|
+
s.email = ["ivanvanderbyl@me.com"]
|
10
|
+
s.homepage = "http://github.com/ivanvanderbyl/frontend"
|
11
|
+
s.summary = %q{A Rails 3.1 engine which provides Javascript libraries and CSS frameworks for rapidly prototyping advanced frontends}
|
12
|
+
s.description = %q{Includes: jQuery, Backbone, Backbone.localStorage, Backrub, Handlebars, Twitter Bootstrap (SCSS) and more.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
# s.add_development_dependency "rspec"
|
21
|
+
s.add_runtime_dependency "rails", '~> 3.1.0'
|
22
|
+
s.add_runtime_dependency "rails-backbone", '~> 0.5.3'
|
23
|
+
end
|
data/lib/frontend.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
function S4(){return(((1+Math.random())*65536)|0).toString(16).substring(1)}function guid(){return(S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())}window.Store=function(b){this.name=b;var a=localStorage.getItem(this.name);this.records=(a&&a.split(","))||[]};_.extend(Store.prototype,{save:function(){localStorage.setItem(this.name,this.records.join(","))},create:function(a){if(!a.id){a.id=a.attributes.id=guid()}localStorage.setItem(this.name+"-"+a.id,JSON.stringify(a));this.records.push(a.id);this.save();return a},update:function(a){localStorage.setItem(this.name+"-"+a.id,JSON.stringify(a));if(!_.include(this.records,a.id)){this.records.push(a.id)}this.save();return a},find:function(a){return JSON.parse(localStorage.getItem(this.name+"-"+a.id))},findAll:function(){return _.map(this.records,function(a){return JSON.parse(localStorage.getItem(this.name+"-"+a))},this)},destroy:function(a){localStorage.removeItem(this.name+"-"+a.id);this.records=_.reject(this.records,function(b){return b==a.id});this.save();return a}});Backbone.sync=function(f,c,e,b){var d;var a=c.localStorage||c.collection.localStorage;switch(f){case"read":d=c.id?a.find(c):a.findAll();break;case"create":d=a.create(c);break;case"update":d=a.update(c);break;case"delete":d=a.destroy(c);break}if(d){e(d)}else{b("Record not found")}};
|
@@ -0,0 +1,523 @@
|
|
1
|
+
Backrub =
|
2
|
+
_Genuine :
|
3
|
+
nameLookup : Handlebars.JavaScriptCompiler.prototype.nameLookup
|
4
|
+
mustache : Handlebars.Compiler.prototype.mustache
|
5
|
+
#
|
6
|
+
# Get a path within the base object (or window if undefined)
|
7
|
+
#
|
8
|
+
_getPath : (path, base, wrap)->
|
9
|
+
wrap = wrap || false
|
10
|
+
base = base || window
|
11
|
+
prev = base
|
12
|
+
throw new Error "Path is undefined or null" if path == null or path is undefined
|
13
|
+
parts = path.split(".")
|
14
|
+
_.each parts, (p)->
|
15
|
+
prev = base
|
16
|
+
base = if p is "" then base else base[p]
|
17
|
+
if not base?
|
18
|
+
throw new Error "cannot find given path '#{path}'"
|
19
|
+
return {}
|
20
|
+
if typeof( base ) is "function" and wrap
|
21
|
+
_.bind( base, prev )
|
22
|
+
else
|
23
|
+
base
|
24
|
+
|
25
|
+
#
|
26
|
+
# Resolve a value properly in the model
|
27
|
+
#
|
28
|
+
# Backbone models are not plain javascript object so you cannot
|
29
|
+
# simply follow the path, you need to call get on the model.
|
30
|
+
#
|
31
|
+
_resolveValue : (attr, model)->
|
32
|
+
parts = attr.split(/\.(.+)?/)
|
33
|
+
if parts.length > 1
|
34
|
+
model = Backrub._resolveValue(parts[0], model)
|
35
|
+
attr = parts[1]
|
36
|
+
model_info = Backrub._resolveIsModel attr, model
|
37
|
+
if model_info.is_model
|
38
|
+
model_info.model.get(model_info.attr)
|
39
|
+
else if model_info.is_model is null
|
40
|
+
attr
|
41
|
+
else
|
42
|
+
value = try
|
43
|
+
Backrub._getPath model_info.attr, model_info.model, true
|
44
|
+
catch error
|
45
|
+
|
46
|
+
if typeof( value ) is "function" then value() else value
|
47
|
+
|
48
|
+
#
|
49
|
+
# Determine if the attribute is a model attribute or view attribute
|
50
|
+
# If the attribute is preceded by @ it is considered a model attr.
|
51
|
+
#
|
52
|
+
_resolveIsModel : (attr, model)->
|
53
|
+
is_model = false
|
54
|
+
attr = if attr and (attr.charAt?(0) is "@")
|
55
|
+
is_model = true
|
56
|
+
model = model.model
|
57
|
+
attr.substring(1)
|
58
|
+
else if attr and model and model.get and model.get(attr) isnt undefined
|
59
|
+
is_model = true
|
60
|
+
attr
|
61
|
+
else if attr and model.model and model.model.get and model.model.get(attr) isnt undefined
|
62
|
+
is_model = true
|
63
|
+
model = model.model
|
64
|
+
attr
|
65
|
+
else if model[attr] isnt undefined
|
66
|
+
attr
|
67
|
+
else
|
68
|
+
model = null
|
69
|
+
is_model = null
|
70
|
+
attr
|
71
|
+
|
72
|
+
#
|
73
|
+
# return an object with a convenient bind method that check the
|
74
|
+
# presence of a model
|
75
|
+
#
|
76
|
+
is_model: is_model
|
77
|
+
attr: attr
|
78
|
+
model: model
|
79
|
+
bind: (callback)->
|
80
|
+
if model and model.bind
|
81
|
+
model.bind "change:#{attr}", callback
|
82
|
+
|
83
|
+
#
|
84
|
+
# Used by if and unless helpers to render the and listen changes
|
85
|
+
#
|
86
|
+
_bindIf : (attr, context)->
|
87
|
+
if context
|
88
|
+
view = Backrub._createBindView( attr, this, context)
|
89
|
+
|
90
|
+
model_info = Backrub._resolveIsModel attr, this
|
91
|
+
|
92
|
+
model_info.bind ->
|
93
|
+
if context.data.exec.isAlive()
|
94
|
+
view.rerender()
|
95
|
+
context.data.exec.makeAlive()
|
96
|
+
#setup the render to check for truth of the value
|
97
|
+
view.render = ->
|
98
|
+
fn = if Backrub._resolveValue( @attr, @model ) then context.fn else context.inverse
|
99
|
+
new Handlebars.SafeString @span( fn(@model, {data:context.data}) )
|
100
|
+
|
101
|
+
view.render()
|
102
|
+
else
|
103
|
+
throw new Error "No block is provided!"
|
104
|
+
|
105
|
+
|
106
|
+
#
|
107
|
+
#
|
108
|
+
#
|
109
|
+
_bindAttr : (attrs, context, model)->
|
110
|
+
id = _.uniqueId('ba')
|
111
|
+
outAttrs = []
|
112
|
+
self = model || this
|
113
|
+
#go thru every attributes in the hash
|
114
|
+
_.each attrs, (attr, k)->
|
115
|
+
model_info = Backrub._resolveIsModel attr, self
|
116
|
+
value = Backrub._resolveValue attr, self
|
117
|
+
outAttrs.push "#{k}=\"#{value}\""
|
118
|
+
|
119
|
+
#handle change events
|
120
|
+
model_info.bind ->
|
121
|
+
if context.data.exec.isAlive()
|
122
|
+
el = $("[data-baid='#{id}']")
|
123
|
+
if el.length is 0
|
124
|
+
model_info.model.unbind "change#{model_info.attr}"
|
125
|
+
else
|
126
|
+
el.attr k, Backrub._resolveValue attr, self
|
127
|
+
|
128
|
+
if outAttrs.length > 0
|
129
|
+
outAttrs.push "data-baid=\"#{id}\""
|
130
|
+
|
131
|
+
new Handlebars.SafeString outAttrs.join(" ")
|
132
|
+
|
133
|
+
#
|
134
|
+
# Create a backbone view with the specified prototype.
|
135
|
+
# It adds _span_, _live_, _textAttributes_ and _bvid_ attributes
|
136
|
+
# to the view.
|
137
|
+
#
|
138
|
+
_createView : (viewProto, options)->
|
139
|
+
|
140
|
+
v = new viewProto(options)
|
141
|
+
throw new Error "Cannot instantiate view" if !v
|
142
|
+
v._ensureElement = Backrub._BindView.prototype._ensureElement
|
143
|
+
v.span = Backrub._BindView.prototype.span
|
144
|
+
v.live = Backrub._BindView.prototype.live
|
145
|
+
v.textAttributes = Backrub._BindView.prototype.textAttributes
|
146
|
+
v.bvid = "#{_.uniqueId('bv')}"
|
147
|
+
return v
|
148
|
+
|
149
|
+
#
|
150
|
+
# Create a bind view and parse the hash properly
|
151
|
+
#
|
152
|
+
_createBindView : (attr, model, context)->
|
153
|
+
view = new Backrub._BindView
|
154
|
+
attr : attr
|
155
|
+
model : model
|
156
|
+
context: context
|
157
|
+
prevThis: model
|
158
|
+
context.data.exec.addView view
|
159
|
+
|
160
|
+
if context.hash
|
161
|
+
view.tagName = context.hash.tag || view.tagName
|
162
|
+
delete context.hash.tag
|
163
|
+
view.attributes = context.hash
|
164
|
+
|
165
|
+
view
|
166
|
+
|
167
|
+
#
|
168
|
+
# Lightweight span based view to encapsulate the different helpers
|
169
|
+
# A unique id is used to retrieve the view for update
|
170
|
+
#
|
171
|
+
_BindView : Backbone.View.extend
|
172
|
+
tagName : "span"
|
173
|
+
#
|
174
|
+
# _ensureElement is a noop to avoid creating tons of elements for nothing while
|
175
|
+
# building the template.
|
176
|
+
#
|
177
|
+
_ensureElement: ->
|
178
|
+
null
|
179
|
+
live : -> $("[data-bvid='#{@bvid}']")
|
180
|
+
initialize: ->
|
181
|
+
_.bindAll this, "render", "rerender", "span", "live", "value", "textAttributes"
|
182
|
+
@bvid = "#{_.uniqueId('bv')}"
|
183
|
+
@attr = @options.attr
|
184
|
+
@prevThis = @options.prevThis
|
185
|
+
@hbContext = @options.context
|
186
|
+
value: ->
|
187
|
+
Backrub._resolveValue @attr, @model
|
188
|
+
textAttributes: ->
|
189
|
+
@attributes = @attributes || @options.attributes || {}
|
190
|
+
@attributes.id = @id if !(@attributes.id) && @id
|
191
|
+
@attributes.class = @className if !@attributes.class && @className
|
192
|
+
Backrub._bindAttr(@attributes, @hbContext, @prevThis || this).string
|
193
|
+
span: (inner)->
|
194
|
+
"<#{@tagName} #{@textAttributes()} data-bvid=\"#{@bvid}\">#{inner}</#{@tagName}>"
|
195
|
+
rerender : ->
|
196
|
+
@live().replaceWith @render().string
|
197
|
+
render : ->
|
198
|
+
new Handlebars.SafeString @span( @value() )
|
199
|
+
|
200
|
+
#
|
201
|
+
# See handlebars code. This override mustache so that
|
202
|
+
# it will call the bind helper to resolve single value
|
203
|
+
# mustaches.
|
204
|
+
#
|
205
|
+
Handlebars.Compiler.prototype.mustache = (mustache)->
|
206
|
+
if mustache.params.length || mustache.hash
|
207
|
+
Backrub._Genuine.mustache.call(this, mustache);
|
208
|
+
else
|
209
|
+
id = new Handlebars.AST.IdNode(['bind']);
|
210
|
+
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
|
211
|
+
Backrub._Genuine.mustache.call(this, mustache);
|
212
|
+
|
213
|
+
#
|
214
|
+
# See handlebars code.
|
215
|
+
#
|
216
|
+
Handlebars.JavaScriptCompiler.prototype.nameLookup = (parent, name, type)->
|
217
|
+
if type is 'context'
|
218
|
+
"\"#{name}\""
|
219
|
+
else
|
220
|
+
Backrub._Genuine.nameLookup.call(this, parent, name, type)
|
221
|
+
|
222
|
+
#
|
223
|
+
# Call this within the initialize function of your View, Controller, Model.
|
224
|
+
# It will look at all the attributes for _base_ that have been marked as
|
225
|
+
# dependent on some _event_ and make sure a change:attribute_name event
|
226
|
+
# will be trigger on the object defined by the _path_
|
227
|
+
#
|
228
|
+
Backbone.dependencies = (onHash, base)->
|
229
|
+
base = base || this
|
230
|
+
throw new Error "Not a Backbone.Event object" if !base.trigger and !base.bind
|
231
|
+
setupEvent = (event, path)->
|
232
|
+
parts = event.split(" ")
|
233
|
+
attr = parts[0]
|
234
|
+
object = Backrub._getPath(path, base)
|
235
|
+
for e in parts[1..]
|
236
|
+
object?.bind e, ->
|
237
|
+
base.trigger "change:#{attr}"
|
238
|
+
|
239
|
+
for event, path of onHash
|
240
|
+
setupEvent(event, path)
|
241
|
+
|
242
|
+
#
|
243
|
+
# Setup dependencies in the backbone prototype for nice syntax
|
244
|
+
#
|
245
|
+
for proto in [Backbone.Model.prototype, Backbone.Router.prototype, Backbone.Collection.prototype, Backbone.View.prototype]
|
246
|
+
_.extend proto, {dependencies: Backbone.dependencies}
|
247
|
+
|
248
|
+
|
249
|
+
Backbone.Backrub = (template)->
|
250
|
+
_.bindAll @, "addView", "render", "makeAlive", "isAlive"
|
251
|
+
@compiled = Handlebars.compile( template, {data: true, stringParams: true} )
|
252
|
+
@_createdViews = {}
|
253
|
+
@_aliveViews = {}
|
254
|
+
@_alive = false
|
255
|
+
return @
|
256
|
+
|
257
|
+
_.extend Backbone.Backrub.prototype,
|
258
|
+
#
|
259
|
+
# Execute a templae given some options
|
260
|
+
#
|
261
|
+
render: (options)->
|
262
|
+
self = this
|
263
|
+
@compiled(options, {data:{exec : @}})
|
264
|
+
|
265
|
+
#
|
266
|
+
# Make Alive will properly handle the delgation of
|
267
|
+
# events based on Backbone conventions. By default,
|
268
|
+
# it will use the body element to find created elements
|
269
|
+
# but you can also give a base element to query from.
|
270
|
+
# This is useful when your template is appended to a
|
271
|
+
# DOM element that wasn't inserted into the page yet.
|
272
|
+
#
|
273
|
+
makeAlive: (base)->
|
274
|
+
base = base || $("body")
|
275
|
+
query = []
|
276
|
+
currentViews = @_createdViews
|
277
|
+
@_createdViews = {}
|
278
|
+
|
279
|
+
_.each currentViews, (view, bvid)->
|
280
|
+
query.push "[data-bvid='#{bvid}']"
|
281
|
+
|
282
|
+
@_alive = true
|
283
|
+
self = @
|
284
|
+
$(query.join( "," ), base).each ->
|
285
|
+
el = $(@)
|
286
|
+
view = currentViews[el.attr( "data-bvid" )]
|
287
|
+
view.el = el
|
288
|
+
view.delegateEvents()
|
289
|
+
view.alive?.call(view)
|
290
|
+
#move alive views away for other makeAlive passes
|
291
|
+
_.extend @_aliveViews, currentViews
|
292
|
+
|
293
|
+
|
294
|
+
isAlive: ->
|
295
|
+
@_alive
|
296
|
+
|
297
|
+
#
|
298
|
+
# Internal API to add view to the context
|
299
|
+
#
|
300
|
+
addView : (view)->
|
301
|
+
@_createdViews[view.bvid] = view
|
302
|
+
|
303
|
+
#
|
304
|
+
# Internal API to remove view formt he tracking list
|
305
|
+
#
|
306
|
+
removeView : (view)->
|
307
|
+
delete @_createdViews[view.bvid]
|
308
|
+
delete @_aliveViews[view.bvid]
|
309
|
+
delete view
|
310
|
+
|
311
|
+
#
|
312
|
+
# A simple Backbone.View to wrap around the Backbone.Backrub API
|
313
|
+
# You can use this view as any other view within backbone. Call
|
314
|
+
# render as you would normally
|
315
|
+
#
|
316
|
+
Backbone.TemplateView = Backbone.View.extend
|
317
|
+
initialize: (options)->
|
318
|
+
@template = @template || options.template
|
319
|
+
throw new Error "Template is missing" if !@template
|
320
|
+
@compile = new Backbone.Backrub(@template)
|
321
|
+
|
322
|
+
render : ->
|
323
|
+
try
|
324
|
+
$(@el).html @compile.render @
|
325
|
+
@compile.makeAlive @el
|
326
|
+
catch e
|
327
|
+
console.error e.stack
|
328
|
+
@el
|
329
|
+
|
330
|
+
#
|
331
|
+
# View helper
|
332
|
+
# You can reference your backbone views in the template to
|
333
|
+
# add extra logic and events. Use this helper with the view
|
334
|
+
# name (as accessible within the window object). You can give it
|
335
|
+
# a hash with the usual backbone options (model, id, etc.)
|
336
|
+
# The render method is redefined. A rendered event is sent but
|
337
|
+
# within the templating loop (elements are not on the document yet)
|
338
|
+
#
|
339
|
+
Handlebars.registerHelper "view", (viewName, context)->
|
340
|
+
execContext = context.data.exec
|
341
|
+
view = Backrub._getPath(viewName)
|
342
|
+
resolvedOptions = {}
|
343
|
+
for key, val of context.hash
|
344
|
+
resolvedOptions[key] = Backrub._resolveValue(val, this) ? val
|
345
|
+
|
346
|
+
v = Backrub._createView view, resolvedOptions
|
347
|
+
execContext.addView v
|
348
|
+
v.render = ()->
|
349
|
+
new Handlebars.SafeString @span( context(@, {data:context.data}) )
|
350
|
+
v.render(v)
|
351
|
+
|
352
|
+
|
353
|
+
#
|
354
|
+
# Bind helper
|
355
|
+
# Bind a value from the view or the model to the template.
|
356
|
+
# When the value changes ("change:_attribute_" events) only this part
|
357
|
+
# of the template will be rerendered. Bind will create a new <span>
|
358
|
+
# node to keep track of what to refresh.
|
359
|
+
#
|
360
|
+
Handlebars.registerHelper "bind", (attrName, context)->
|
361
|
+
execContext = context.data.exec
|
362
|
+
view = Backrub._createBindView( attrName, this, context )
|
363
|
+
|
364
|
+
model_info = Backrub._resolveIsModel attrName, this
|
365
|
+
model_info.bind ->
|
366
|
+
if execContext.isAlive()
|
367
|
+
view.rerender()
|
368
|
+
execContext.makeAlive()
|
369
|
+
new Handlebars.SafeString view.render()
|
370
|
+
|
371
|
+
#
|
372
|
+
# Bind attributes helper
|
373
|
+
# This helper is used to bind attributes to an HTML element in
|
374
|
+
# your template. Bind attributes will create a data-baid to keep
|
375
|
+
# track of the element for further updates.
|
376
|
+
#
|
377
|
+
Handlebars.registerHelper "bindAttr", (context)->
|
378
|
+
_.bind(Backrub._bindAttr, this)(context.hash, context)
|
379
|
+
|
380
|
+
#
|
381
|
+
# Bounded if
|
382
|
+
# A if/else statement that will listen for changes and update
|
383
|
+
# accordingly. Uses bind so a <span> will be created
|
384
|
+
#
|
385
|
+
Handlebars.registerHelper "if", (attr, context )->
|
386
|
+
_.bind(Backrub._bindIf, this)( attr, context)
|
387
|
+
|
388
|
+
#
|
389
|
+
# Bounded unless
|
390
|
+
# A unless/else statement that will listen for changes and update
|
391
|
+
# accordingly. Uses bind so a <span> will be created
|
392
|
+
#
|
393
|
+
Handlebars.registerHelper "unless", (attr, context)->
|
394
|
+
fn = context.fn
|
395
|
+
inverse = context.inverse
|
396
|
+
context.fn = inverse
|
397
|
+
context.inverse = fn
|
398
|
+
|
399
|
+
_.bind(Backrub._bindIf, this)( attr, context )
|
400
|
+
|
401
|
+
#
|
402
|
+
# Bounded each
|
403
|
+
# A each helper to iterate on a Backbone Collection and
|
404
|
+
# update any refresh/add/remove events. <span> will be created
|
405
|
+
# for the overall collection and each of its items to keep track
|
406
|
+
# of what to refresh.
|
407
|
+
# Do not know what will happen with sorting...
|
408
|
+
#
|
409
|
+
Handlebars.registerHelper "collection", (attr, context)->
|
410
|
+
execContext = context.data.exec
|
411
|
+
collection = Backrub._resolveValue attr, this
|
412
|
+
if not collection.each?
|
413
|
+
throw new Error "not a backbone collection!"
|
414
|
+
|
415
|
+
options = context.hash
|
416
|
+
colViewPath = options?.colView
|
417
|
+
colView = Backrub._getPath(colViewPath) if colViewPath
|
418
|
+
colTagName = options?.colTag || "ul"
|
419
|
+
|
420
|
+
itemViewPath = options?.itemView
|
421
|
+
itemView = Backrub._getPath(itemViewPath) if itemViewPath
|
422
|
+
itemTagName = options?.itemTag || "li"
|
423
|
+
|
424
|
+
# filter col/items arguments
|
425
|
+
# TODO would it be possible to use bindAttr for col/item attributes
|
426
|
+
colAtts = {}
|
427
|
+
itemAtts = {}
|
428
|
+
_.each options, (v, k) ->
|
429
|
+
return if k.indexOf("Tag") > 0 or k.indexOf("View") > 0
|
430
|
+
if k.indexOf( "col") is 0
|
431
|
+
colAtts[k.substring(3).toLowerCase()] = v
|
432
|
+
else if k.indexOf( "item" ) is 0
|
433
|
+
itemAtts[k.substring(4).toLowerCase()] = v
|
434
|
+
|
435
|
+
view = if colView
|
436
|
+
Backrub._createView colView,
|
437
|
+
model: collection
|
438
|
+
attributes: colAtts
|
439
|
+
context: context
|
440
|
+
tagName : if options?.colTag then colTagName else colView.prototype.tagName
|
441
|
+
else
|
442
|
+
new Backrub._BindView
|
443
|
+
tagName: colTagName
|
444
|
+
attributes: colAtts
|
445
|
+
attr : attr
|
446
|
+
model : this
|
447
|
+
context: context
|
448
|
+
execContext.addView view
|
449
|
+
|
450
|
+
views = {}
|
451
|
+
|
452
|
+
#
|
453
|
+
# Item view setup closure
|
454
|
+
#
|
455
|
+
item_view = (m)->
|
456
|
+
mview = if itemView
|
457
|
+
Backrub._createView itemView,
|
458
|
+
model: m
|
459
|
+
attributes: itemAtts
|
460
|
+
context: context
|
461
|
+
tagName : if options?.itemTag then itemTagName else itemView.prototype.tagName
|
462
|
+
else
|
463
|
+
new Backrub._BindView
|
464
|
+
tagName: itemTagName
|
465
|
+
attributes: itemAtts
|
466
|
+
model: m
|
467
|
+
context: context
|
468
|
+
execContext.addView mview
|
469
|
+
|
470
|
+
#
|
471
|
+
# Render the item view using the template
|
472
|
+
#
|
473
|
+
mview.render = ()-> @span context(@, {data:context.data})
|
474
|
+
return mview
|
475
|
+
|
476
|
+
#
|
477
|
+
# Container view setup closure
|
478
|
+
#
|
479
|
+
setup = (col, mainView, childViews) ->
|
480
|
+
# create all childs
|
481
|
+
col.each (m)->
|
482
|
+
mview = item_view m
|
483
|
+
childViews[m.cid] = mview
|
484
|
+
|
485
|
+
#
|
486
|
+
# Rendering for the main view simply calls render of the child
|
487
|
+
# and wrap this with the container view element.
|
488
|
+
#
|
489
|
+
mainView.render = ->
|
490
|
+
rendered = _.map childViews, (v)->
|
491
|
+
v.render()
|
492
|
+
new Handlebars.SafeString @span( rendered.join("\n") )
|
493
|
+
|
494
|
+
setup(collection, view, views)
|
495
|
+
|
496
|
+
collection.bind "reset", ()->
|
497
|
+
if execContext.isAlive()
|
498
|
+
# dump everything and resetup the view
|
499
|
+
# Call make alive to keep track of new views.
|
500
|
+
views = {}
|
501
|
+
setup(collection, view, views)
|
502
|
+
view.rerender()
|
503
|
+
execContext.makeAlive()
|
504
|
+
collection.bind "add", (m)->
|
505
|
+
if execContext.isAlive()
|
506
|
+
# create the new view as needed
|
507
|
+
# Call make alive to keep track of new views.
|
508
|
+
mview = item_view m
|
509
|
+
views[m.cid] = mview
|
510
|
+
if options.prepend isnt undefined
|
511
|
+
view.live().prepend(mview.render())
|
512
|
+
else
|
513
|
+
view.live().append(mview.render())
|
514
|
+
execContext.makeAlive()
|
515
|
+
collection.bind "remove", (m)->
|
516
|
+
if execContext.isAlive()
|
517
|
+
# remove the view associated with the model
|
518
|
+
# Stop tracking this view.
|
519
|
+
mview = views[m.cid]
|
520
|
+
mview.live().remove()
|
521
|
+
execContext.removeView mview
|
522
|
+
|
523
|
+
view.render()
|