frontend 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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()