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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in frontend.gemspec
4
+ gemspec
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ require "frontend/version"
2
+
3
+ module Frontend
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Frontend
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ #= require_self
2
+ #= require ./vendor/handlebars
3
+ #= require ./vendor/handlebars
4
+ #= require ./vendor/backrub
5
+ #= require ./vendor/relational
@@ -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()