frontend 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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()
|