flipper-ui 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +17 -0
  5. data/Guardfile +26 -0
  6. data/LICENSE +22 -0
  7. data/README.md +101 -0
  8. data/Rakefile +7 -0
  9. data/examples/basic.ru +44 -0
  10. data/examples/flipper.html +14 -0
  11. data/examples/flipper.png +0 -0
  12. data/flipper-ui.gemspec +21 -0
  13. data/lib/flipper-ui.rb +1 -0
  14. data/lib/flipper/ui.rb +23 -0
  15. data/lib/flipper/ui/action.rb +172 -0
  16. data/lib/flipper/ui/action_collection.rb +20 -0
  17. data/lib/flipper/ui/actions/features.rb +21 -0
  18. data/lib/flipper/ui/actions/file.rb +17 -0
  19. data/lib/flipper/ui/actions/gate.rb +143 -0
  20. data/lib/flipper/ui/actions/index.rb +17 -0
  21. data/lib/flipper/ui/assets/javascripts/application.coffee +305 -0
  22. data/lib/flipper/ui/assets/javascripts/spine/ajax.coffee +223 -0
  23. data/lib/flipper/ui/assets/javascripts/spine/list.coffee +43 -0
  24. data/lib/flipper/ui/assets/javascripts/spine/local.coffee +16 -0
  25. data/lib/flipper/ui/assets/javascripts/spine/manager.coffee +83 -0
  26. data/lib/flipper/ui/assets/javascripts/spine/relation.coffee +148 -0
  27. data/lib/flipper/ui/assets/javascripts/spine/route.coffee +146 -0
  28. data/lib/flipper/ui/assets/javascripts/spine/spine.coffee +542 -0
  29. data/lib/flipper/ui/assets/javascripts/spine/version +1 -0
  30. data/lib/flipper/ui/assets/stylesheets/application.scss +237 -0
  31. data/lib/flipper/ui/decorators/feature.rb +37 -0
  32. data/lib/flipper/ui/decorators/gate.rb +36 -0
  33. data/lib/flipper/ui/error.rb +10 -0
  34. data/lib/flipper/ui/eruby.rb +11 -0
  35. data/lib/flipper/ui/middleware.rb +66 -0
  36. data/lib/flipper/ui/public/css/application.css +183 -0
  37. data/lib/flipper/ui/public/css/images/animated-overlay.gif +0 -0
  38. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  39. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  40. data/lib/flipper/ui/public/css/images/ui-bg_flat_10_000000_40x100.png +0 -0
  41. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  42. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  43. data/lib/flipper/ui/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  44. data/lib/flipper/ui/public/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  45. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  46. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  47. data/lib/flipper/ui/public/css/images/ui-icons_222222_256x240.png +0 -0
  48. data/lib/flipper/ui/public/css/images/ui-icons_228ef1_256x240.png +0 -0
  49. data/lib/flipper/ui/public/css/images/ui-icons_ef8c08_256x240.png +0 -0
  50. data/lib/flipper/ui/public/css/images/ui-icons_ffd27a_256x240.png +0 -0
  51. data/lib/flipper/ui/public/css/images/ui-icons_ffffff_256x240.png +0 -0
  52. data/lib/flipper/ui/public/css/jquery-ui-1.10.3.slider.min.css +5 -0
  53. data/lib/flipper/ui/public/images/logo.png +0 -0
  54. data/lib/flipper/ui/public/images/remove.png +0 -0
  55. data/lib/flipper/ui/public/js/application.js +544 -0
  56. data/lib/flipper/ui/public/js/handlebars.js +1992 -0
  57. data/lib/flipper/ui/public/js/jquery-ui-1.10.3.slider.min.js +6 -0
  58. data/lib/flipper/ui/public/js/jquery.js +9555 -0
  59. data/lib/flipper/ui/public/js/jquery.min.js +4 -0
  60. data/lib/flipper/ui/public/js/jquery.min.map +1 -0
  61. data/lib/flipper/ui/public/js/spine/ajax.js +320 -0
  62. data/lib/flipper/ui/public/js/spine/list.js +72 -0
  63. data/lib/flipper/ui/public/js/spine/local.js +29 -0
  64. data/lib/flipper/ui/public/js/spine/manager.js +157 -0
  65. data/lib/flipper/ui/public/js/spine/relation.js +260 -0
  66. data/lib/flipper/ui/public/js/spine/route.js +223 -0
  67. data/lib/flipper/ui/public/js/spine/spine.js +927 -0
  68. data/lib/flipper/ui/util.rb +12 -0
  69. data/lib/flipper/ui/version.rb +5 -0
  70. data/lib/flipper/ui/views/index.erb +9 -0
  71. data/lib/flipper/ui/views/layout.erb +161 -0
  72. data/script/bootstrap +21 -0
  73. data/script/server +19 -0
  74. data/script/test +30 -0
  75. data/spec/flipper/ui/decorators/feature_spec.rb +59 -0
  76. data/spec/flipper/ui/decorators/gate_spec.rb +47 -0
  77. data/spec/flipper/ui/util_spec.rb +18 -0
  78. data/spec/flipper/ui_spec.rb +470 -0
  79. data/spec/helper.rb +35 -0
  80. metadata +168 -0
@@ -0,0 +1,146 @@
1
+ Spine = @Spine or require('spine')
2
+ $ = Spine.$
3
+
4
+ hashStrip = /^#*/
5
+ namedParam = /:([\w\d]+)/g
6
+ splatParam = /\*([\w\d]+)/g
7
+ escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
8
+
9
+ class Spine.Route extends Spine.Module
10
+ @extend Spine.Events
11
+
12
+ @historySupport: window.history?.pushState?
13
+
14
+ @routes: []
15
+
16
+ @options:
17
+ trigger: true
18
+ history: false
19
+ shim: false
20
+
21
+ @add: (path, callback) ->
22
+ if (typeof path is 'object' and path not instanceof RegExp)
23
+ @add(key, value) for key, value of path
24
+ else
25
+ @routes.push(new @(path, callback))
26
+
27
+ @setup: (options = {}) ->
28
+ @options = $.extend({}, @options, options)
29
+
30
+ if (@options.history)
31
+ @history = @historySupport and @options.history
32
+
33
+ return if @options.shim
34
+
35
+ if @history
36
+ $(window).bind('popstate', @change)
37
+ else
38
+ $(window).bind('hashchange', @change)
39
+ @change()
40
+
41
+ @unbind: ->
42
+ return if @options.shim
43
+
44
+ if @history
45
+ $(window).unbind('popstate', @change)
46
+ else
47
+ $(window).unbind('hashchange', @change)
48
+
49
+ @navigate: (args...) ->
50
+ options = {}
51
+
52
+ lastArg = args[args.length - 1]
53
+ if typeof lastArg is 'object'
54
+ options = args.pop()
55
+ else if typeof lastArg is 'boolean'
56
+ options.trigger = args.pop()
57
+
58
+ options = $.extend({}, @options, options)
59
+
60
+ path = args.join('/')
61
+ return if @path is path
62
+ @path = path
63
+
64
+ @trigger('navigate', @path)
65
+
66
+ @matchRoute(@path, options) if options.trigger
67
+
68
+ return if options.shim
69
+
70
+ if @history
71
+ history.pushState({}, document.title, @path)
72
+ else
73
+ window.location.hash = @path
74
+
75
+ # Private
76
+
77
+ @getPath: ->
78
+ path = window.location.pathname
79
+ if path.substr(0,1) isnt '/'
80
+ path = '/' + path
81
+ path
82
+
83
+ @getHash: -> window.location.hash
84
+
85
+ @getFragment: -> @getHash().replace(hashStrip, '')
86
+
87
+ @getHost: ->
88
+ (document.location + '').replace(@getPath() + @getHash(), '')
89
+
90
+ @change: ->
91
+ path = if @history then @getPath() else @getFragment()
92
+ return if path is @path
93
+ @path = path
94
+ @matchRoute(@path)
95
+
96
+ @matchRoute: (path, options) ->
97
+ for route in @routes when route.match(path, options)
98
+ @trigger('change', route, path)
99
+ return route
100
+
101
+ constructor: (@path, @callback) ->
102
+ @names = []
103
+
104
+ if typeof path is 'string'
105
+ namedParam.lastIndex = 0
106
+ while (match = namedParam.exec(path)) != null
107
+ @names.push(match[1])
108
+
109
+ splatParam.lastIndex = 0
110
+ while (match = splatParam.exec(path)) != null
111
+ @names.push(match[1])
112
+
113
+ path = path.replace(escapeRegExp, '\\$&')
114
+ .replace(namedParam, '([^\/]*)')
115
+ .replace(splatParam, '(.*?)')
116
+
117
+ @route = new RegExp("^#{path}$")
118
+ else
119
+ @route = path
120
+
121
+ match: (path, options = {}) ->
122
+ match = @route.exec(path)
123
+ return false unless match
124
+ options.match = match
125
+ params = match.slice(1)
126
+
127
+ if @names.length
128
+ for param, i in params
129
+ options[@names[i]] = param
130
+
131
+ @callback.call(null, options) isnt false
132
+
133
+ # Coffee-script bug
134
+ Spine.Route.change = Spine.Route.proxy(Spine.Route.change)
135
+
136
+ Spine.Controller.include
137
+ route: (path, callback) ->
138
+ Spine.Route.add(path, @proxy(callback))
139
+
140
+ routes: (routes) ->
141
+ @route(key, value) for key, value of routes
142
+
143
+ navigate: ->
144
+ Spine.Route.navigate.apply(Spine.Route, arguments)
145
+
146
+ module?.exports = Spine.Route
@@ -0,0 +1,542 @@
1
+ Events =
2
+ bind: (ev, callback) ->
3
+ evs = ev.split(' ')
4
+ calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}
5
+
6
+ for name in evs
7
+ calls[name] or= []
8
+ calls[name].push(callback)
9
+ this
10
+
11
+ one: (ev, callback) ->
12
+ @bind ev, ->
13
+ @unbind(ev, arguments.callee)
14
+ callback.apply(this, arguments)
15
+
16
+ trigger: (args...) ->
17
+ ev = args.shift()
18
+
19
+ list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
20
+ return unless list
21
+
22
+ for callback in list
23
+ if callback.apply(this, args) is false
24
+ break
25
+ true
26
+
27
+ unbind: (ev, callback) ->
28
+ unless ev
29
+ @_callbacks = {}
30
+ return this
31
+
32
+ list = @_callbacks?[ev]
33
+ return this unless list
34
+
35
+ unless callback
36
+ delete @_callbacks[ev]
37
+ return this
38
+
39
+ for cb, i in list when cb is callback
40
+ list = list.slice()
41
+ list.splice(i, 1)
42
+ @_callbacks[ev] = list
43
+ break
44
+ this
45
+
46
+ Log =
47
+ trace: true
48
+
49
+ logPrefix: '(App)'
50
+
51
+ log: (args...) ->
52
+ return unless @trace
53
+ if @logPrefix then args.unshift(@logPrefix)
54
+ console?.log?(args...)
55
+ this
56
+
57
+ moduleKeywords = ['included', 'extended']
58
+
59
+ class Module
60
+ @include: (obj) ->
61
+ throw new Error('include(obj) requires obj') unless obj
62
+ for key, value of obj when key not in moduleKeywords
63
+ @::[key] = value
64
+ obj.included?.apply(this)
65
+ this
66
+
67
+ @extend: (obj) ->
68
+ throw new Error('extend(obj) requires obj') unless obj
69
+ for key, value of obj when key not in moduleKeywords
70
+ @[key] = value
71
+ obj.extended?.apply(this)
72
+ this
73
+
74
+ @proxy: (func) ->
75
+ => func.apply(this, arguments)
76
+
77
+ proxy: (func) ->
78
+ => func.apply(this, arguments)
79
+
80
+ constructor: ->
81
+ @init?(arguments...)
82
+
83
+ class Model extends Module
84
+ @extend Events
85
+
86
+ @records: {}
87
+ @crecords: {}
88
+ @attributes: []
89
+
90
+ @configure: (name, attributes...) ->
91
+ @className = name
92
+ @records = {}
93
+ @crecords = {}
94
+ @attributes = attributes if attributes.length
95
+ @attributes and= makeArray(@attributes)
96
+ @attributes or= []
97
+ @unbind()
98
+ this
99
+
100
+ @toString: -> "#{@className}(#{@attributes.join(", ")})"
101
+
102
+ @find: (id) ->
103
+ record = @records[id]
104
+ if !record and ("#{id}").match(/c-\d+/)
105
+ return @findCID(id)
106
+ throw new Error("\"#{@className}\" model could not find a record for the ID \"#{id}\"") unless record
107
+ record.clone()
108
+
109
+ @findCID: (cid) ->
110
+ record = @crecords[cid]
111
+ throw new Error("\"#{@className}\" model could not find a record for the ID \"#{id}\"") unless record
112
+ record.clone()
113
+
114
+ @exists: (id) ->
115
+ try
116
+ return @find(id)
117
+ catch e
118
+ return false
119
+
120
+ @refresh: (values, options = {}) ->
121
+ if options.clear
122
+ @records = {}
123
+ @crecords = {}
124
+
125
+ records = @fromJSON(values)
126
+ records = [records] unless isArray(records)
127
+
128
+ for record in records
129
+ record.id or= record.cid
130
+ @records[record.id] = record
131
+ @crecords[record.cid] = record
132
+
133
+ @trigger('refresh', @cloneArray(records))
134
+ this
135
+
136
+ @select: (callback) ->
137
+ result = (record for id, record of @records when callback(record))
138
+ @cloneArray(result)
139
+
140
+ @findByAttribute: (name, value) ->
141
+ for id, record of @records
142
+ if record[name] is value
143
+ return record.clone()
144
+ null
145
+
146
+ @findAllByAttribute: (name, value) ->
147
+ @select (item) ->
148
+ item[name] is value
149
+
150
+ @each: (callback) ->
151
+ for key, value of @records
152
+ callback(value.clone())
153
+
154
+ @all: ->
155
+ @cloneArray(@recordsValues())
156
+
157
+ @first: ->
158
+ record = @recordsValues()[0]
159
+ record?.clone()
160
+
161
+ @last: ->
162
+ values = @recordsValues()
163
+ record = values[values.length - 1]
164
+ record?.clone()
165
+
166
+ @count: ->
167
+ @recordsValues().length
168
+
169
+ @deleteAll: ->
170
+ for key, value of @records
171
+ delete @records[key]
172
+
173
+ @destroyAll: ->
174
+ for key, value of @records
175
+ @records[key].destroy()
176
+
177
+ @update: (id, atts, options) ->
178
+ @find(id).updateAttributes(atts, options)
179
+
180
+ @create: (atts, options) ->
181
+ record = new @(atts)
182
+ record.save(options)
183
+
184
+ @destroy: (id, options) ->
185
+ @find(id).destroy(options)
186
+
187
+ @change: (callbackOrParams) ->
188
+ if typeof callbackOrParams is 'function'
189
+ @bind('change', callbackOrParams)
190
+ else
191
+ @trigger('change', callbackOrParams)
192
+
193
+ @fetch: (callbackOrParams) ->
194
+ if typeof callbackOrParams is 'function'
195
+ @bind('fetch', callbackOrParams)
196
+ else
197
+ @trigger('fetch', callbackOrParams)
198
+
199
+ @toJSON: ->
200
+ @recordsValues()
201
+
202
+ @fromJSON: (objects) ->
203
+ return unless objects
204
+ if typeof objects is 'string'
205
+ objects = JSON.parse(objects)
206
+ if isArray(objects)
207
+ (new @(value) for value in objects)
208
+ else
209
+ new @(objects)
210
+
211
+ @fromForm: ->
212
+ (new this).fromForm(arguments...)
213
+
214
+ # Private
215
+
216
+ @recordsValues: ->
217
+ result = []
218
+ for key, value of @records
219
+ result.push(value)
220
+ result
221
+
222
+ @cloneArray: (array) ->
223
+ (value.clone() for value in array)
224
+
225
+ @idCounter: 0
226
+
227
+ @uid: (prefix = '') ->
228
+ uid = prefix + @idCounter++
229
+ uid = @uid(prefix) if @exists(uid)
230
+ uid
231
+
232
+ # Instance
233
+
234
+ constructor: (atts) ->
235
+ super
236
+ @load atts if atts
237
+ @cid = @constructor.uid('c-')
238
+
239
+ isNew: ->
240
+ not @exists()
241
+
242
+ isValid: ->
243
+ not @validate()
244
+
245
+ validate: ->
246
+
247
+ load: (atts) ->
248
+ for key, value of atts
249
+ if typeof @[key] is 'function'
250
+ @[key](value)
251
+ else
252
+ @[key] = value
253
+ this
254
+
255
+ attributes: ->
256
+ result = {}
257
+ for key in @constructor.attributes when key of this
258
+ if typeof @[key] is 'function'
259
+ result[key] = @[key]()
260
+ else
261
+ result[key] = @[key]
262
+ result.id = @id if @id
263
+ result
264
+
265
+ eql: (rec) ->
266
+ !!(rec and rec.constructor is @constructor and
267
+ (rec.cid is @cid) or (rec.id and rec.id is @id))
268
+
269
+ save: (options = {}) ->
270
+ unless options.validate is false
271
+ error = @validate()
272
+ if error
273
+ @trigger('error', error)
274
+ return false
275
+
276
+ @trigger('beforeSave', options)
277
+ record = if @isNew() then @create(options) else @update(options)
278
+ @stripCloneAttrs()
279
+ @trigger('save', options)
280
+ record
281
+
282
+ stripCloneAttrs: ->
283
+ return if @hasOwnProperty 'cid' # Make sure it's not the raw object
284
+ for own key, value of @
285
+ delete @[key] if @constructor.attributes.indexOf(key) > -1
286
+ this
287
+
288
+ updateAttribute: (name, value, options) ->
289
+ @[name] = value
290
+ @save(options)
291
+
292
+ updateAttributes: (atts, options) ->
293
+ @load(atts)
294
+ @save(options)
295
+
296
+ changeID: (id) ->
297
+ records = @constructor.records
298
+ records[id] = records[@id]
299
+ delete records[@id]
300
+ @id = id
301
+ @save()
302
+
303
+ destroy: (options = {}) ->
304
+ @trigger('beforeDestroy', options)
305
+ delete @constructor.records[@id]
306
+ delete @constructor.crecords[@cid]
307
+ @destroyed = true
308
+ @trigger('destroy', options)
309
+ @trigger('change', 'destroy', options)
310
+ @unbind()
311
+ this
312
+
313
+ dup: (newRecord) ->
314
+ result = new @constructor(@attributes())
315
+ if newRecord is false
316
+ result.cid = @cid
317
+ else
318
+ delete result.id
319
+ result
320
+
321
+ clone: ->
322
+ createObject(this)
323
+
324
+ reload: ->
325
+ return this if @isNew()
326
+ original = @constructor.find(@id)
327
+ @load(original.attributes())
328
+ original
329
+
330
+ toJSON: ->
331
+ @attributes()
332
+
333
+ toString: ->
334
+ "<#{@constructor.className} (#{JSON.stringify(this)})>"
335
+
336
+ fromForm: (form) ->
337
+ result = {}
338
+ for key in $(form).serializeArray()
339
+ result[key.name] = key.value
340
+ @load(result)
341
+
342
+ exists: ->
343
+ @id && @id of @constructor.records
344
+
345
+ # Private
346
+
347
+ update: (options) ->
348
+ @trigger('beforeUpdate', options)
349
+ records = @constructor.records
350
+ records[@id].load @attributes()
351
+ clone = records[@id].clone()
352
+ clone.trigger('update', options)
353
+ clone.trigger('change', 'update', options)
354
+ clone
355
+
356
+ create: (options) ->
357
+ @trigger('beforeCreate', options)
358
+ @id = @cid unless @id
359
+
360
+ record = @dup(false)
361
+ @constructor.records[@id] = record
362
+ @constructor.crecords[@cid] = record
363
+
364
+ clone = record.clone()
365
+ clone.trigger('create', options)
366
+ clone.trigger('change', 'create', options)
367
+ clone
368
+
369
+ bind: (events, callback) ->
370
+ @constructor.bind events, binder = (record) =>
371
+ if record && @eql(record)
372
+ callback.apply(this, arguments)
373
+ @constructor.bind 'unbind', unbinder = (record) =>
374
+ if record && @eql(record)
375
+ @constructor.unbind(events, binder)
376
+ @constructor.unbind('unbind', unbinder)
377
+ binder
378
+
379
+ one: (events, callback) ->
380
+ binder = @bind events, =>
381
+ @constructor.unbind(events, binder)
382
+ callback.apply(this, arguments)
383
+
384
+ trigger: (args...) ->
385
+ args.splice(1, 0, this)
386
+ @constructor.trigger(args...)
387
+
388
+ unbind: ->
389
+ @trigger('unbind')
390
+
391
+ class Controller extends Module
392
+ @include Events
393
+ @include Log
394
+
395
+ eventSplitter: /^(\S+)\s*(.*)$/
396
+ tag: 'div'
397
+
398
+ constructor: (options) ->
399
+ @options = options
400
+
401
+ for key, value of @options
402
+ @[key] = value
403
+
404
+ @el = document.createElement(@tag) unless @el
405
+ @el = $(@el)
406
+ @$el = @el
407
+
408
+ @el.addClass(@className) if @className
409
+ @el.attr(@attributes) if @attributes
410
+
411
+ @events = @constructor.events unless @events
412
+ @elements = @constructor.elements unless @elements
413
+
414
+ @delegateEvents(@events) if @events
415
+ @refreshElements() if @elements
416
+
417
+ super
418
+
419
+ release: =>
420
+ @trigger 'release'
421
+ @el.remove()
422
+ @unbind()
423
+
424
+ $: (selector) -> $(selector, @el)
425
+
426
+ delegateEvents: (events) ->
427
+ for key, method of events
428
+
429
+ if typeof(method) is 'function'
430
+ # Always return true from event handlers
431
+ method = do (method) => =>
432
+ method.apply(this, arguments)
433
+ true
434
+ else
435
+ unless @[method]
436
+ throw new Error("#{method} doesn't exist")
437
+
438
+ method = do (method) => =>
439
+ @[method].apply(this, arguments)
440
+ true
441
+
442
+ match = key.match(@eventSplitter)
443
+ eventName = match[1]
444
+ selector = match[2]
445
+
446
+ if selector is ''
447
+ @el.bind(eventName, method)
448
+ else
449
+ @el.delegate(selector, eventName, method)
450
+
451
+ refreshElements: ->
452
+ for key, value of @elements
453
+ @[value] = @$(key)
454
+
455
+ delay: (func, timeout) ->
456
+ setTimeout(@proxy(func), timeout || 0)
457
+
458
+ html: (element) ->
459
+ @el.html(element.el or element)
460
+ @refreshElements()
461
+ @el
462
+
463
+ append: (elements...) ->
464
+ elements = (e.el or e for e in elements)
465
+ @el.append(elements...)
466
+ @refreshElements()
467
+ @el
468
+
469
+ appendTo: (element) ->
470
+ @el.appendTo(element.el or element)
471
+ @refreshElements()
472
+ @el
473
+
474
+ prepend: (elements...) ->
475
+ elements = (e.el or e for e in elements)
476
+ @el.prepend(elements...)
477
+ @refreshElements()
478
+ @el
479
+
480
+ replace: (element) ->
481
+ [previous, @el] = [@el, $(element.el or element)]
482
+ previous.replaceWith(@el)
483
+ @delegateEvents(@events)
484
+ @refreshElements()
485
+ @el
486
+
487
+ # Utilities & Shims
488
+
489
+ $ = window?.jQuery or window?.Zepto or (element) -> element
490
+
491
+ createObject = Object.create or (o) ->
492
+ Func = ->
493
+ Func.prototype = o
494
+ new Func()
495
+
496
+ isArray = (value) ->
497
+ Object::toString.call(value) is '[object Array]'
498
+
499
+ isBlank = (value) ->
500
+ return true unless value
501
+ return false for key of value
502
+ true
503
+
504
+ makeArray = (args) ->
505
+ Array::slice.call(args, 0)
506
+
507
+ # Globals
508
+
509
+ Spine = @Spine = {}
510
+ module?.exports = Spine
511
+
512
+ Spine.version = '1.0.8'
513
+ Spine.isArray = isArray
514
+ Spine.isBlank = isBlank
515
+ Spine.$ = $
516
+ Spine.Events = Events
517
+ Spine.Log = Log
518
+ Spine.Module = Module
519
+ Spine.Controller = Controller
520
+ Spine.Model = Model
521
+
522
+ # Global events
523
+
524
+ Module.extend.call(Spine, Events)
525
+
526
+ # JavaScript compatability
527
+
528
+ Module.create = Module.sub =
529
+ Controller.create = Controller.sub =
530
+ Model.sub = (instances, statics) ->
531
+ class Result extends this
532
+ Result.include(instances) if instances
533
+ Result.extend(statics) if statics
534
+ Result.unbind?()
535
+ Result
536
+
537
+ Model.setup = (name, attributes = []) ->
538
+ class Instance extends this
539
+ Instance.configure(name, attributes...)
540
+ Instance
541
+
542
+ Spine.Class = Module