spine-rails 0.1.0 → 0.1.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/README.md CHANGED
@@ -14,10 +14,19 @@ This gem does two things:
14
14
 
15
15
  ### Installation
16
16
 
17
- In your Gemfile, add this line:
17
+ In your Gemfile, add the following lines:
18
18
 
19
+ gem "json2-rails"
19
20
  gem "spine-rails"
20
21
 
22
+ Make sure that your Gemfile also includes
23
+
24
+ gem "jquery-rails"
25
+
26
+ Or if your using Zepto
27
+
28
+ gem "zepto-rails"
29
+
21
30
  Then run the following commands:
22
31
 
23
32
  bundle install
@@ -71,7 +80,7 @@ The generator will create views in `hamljs`, `eco` or `ejs` format, depending on
71
80
 
72
81
  ## Example Usage
73
82
 
74
- Created a new Rails 3.1 application called `blog`.
83
+ Created a new Rails application called `blog`.
75
84
 
76
85
  rails new blog
77
86
 
@@ -120,4 +129,4 @@ Also if you want to have some useful helpers to bridge the gap between Spine and
120
129
 
121
130
  ## Attributions
122
131
 
123
- This plugin was made by [Alex MacCaw](http://alexmaccaw.co.uk) with major contributions from [Dmytrii Nagirniak](https://github.com/dnagir). It's under the same license as [Spine](http://spinejs.com) (MIT).
132
+ This plugin was made by [Alex MacCaw](http://alexmaccaw.co.uk) with major contributions from [Dmytrii Nagirniak](https://github.com/dnagir) [Zohar Arad] (https:://github.com/zohararad) and [Ben Haines] (http://github.com/benhainez). It's under the same license as [Spine](http://spinejs.com) (MIT).
data/build/update.sh ADDED
@@ -0,0 +1,9 @@
1
+ TAG=v1.1.0
2
+ FILES=( ajax list local manager relation route )
3
+ curl -s -o vendor/assets/javascripts/spine.coffee https://raw.github.com/spine/spine/$TAG/src/spine.coffee
4
+
5
+ for i in "${FILES[@]}"
6
+ do
7
+ :
8
+ curl -s -o vendor/assets/javascripts/spine/$i.coffee https://raw.github.com/spine/spine/$TAG/src/$i.coffee
9
+ done
@@ -7,7 +7,7 @@ module Spine
7
7
  source_root File.expand_path("../templates", __FILE__)
8
8
  desc "Generate a Spine model with configured fields"
9
9
 
10
- argument :fields, :desc => 'List of model attributes', :type => :array, :banner => 'field1 field2'
10
+ argument :fields, desc: 'List of model attributes', type: :array, banner: 'field1 field2'
11
11
 
12
12
  def create_model
13
13
  template "model.coffee.erb", "app/assets/javascripts/#{app_name}/models/#{file_name}.js.coffee"
@@ -4,40 +4,40 @@ module Spine
4
4
  module Generators
5
5
  class NewGenerator < ::Rails::Generators::Base
6
6
  source_root File.expand_path("../templates", __FILE__)
7
-
7
+
8
8
  desc "This generator installs Spine #{Spine::Rails::SPINE_VERSION} as part of assets pipeline"
9
-
10
- class_option :app, :type => :string, :default => "app", :desc => "app name"
11
-
9
+
10
+ class_option :app, type: :string, default: "app", desc: "app name"
11
+
12
12
  def app_name
13
13
  options[:app]
14
14
  end
15
-
15
+
16
16
  def app_class
17
17
  app_name.camelize
18
18
  end
19
-
19
+
20
20
  def create_dir_layout
21
21
  %W{models views controllers lib}.each do |dir|
22
- empty_directory "app/assets/javascripts/#{app_name}/#{dir}"
23
- create_file "app/assets/javascripts/#{app_name}/#{dir}/.gitkeep"
22
+ empty_directory "app/assets/javascripts/#{app_name}/#{dir}"
23
+ create_file "app/assets/javascripts/#{app_name}/#{dir}/.keep"
24
24
  end
25
25
  end
26
-
26
+
27
27
  def create_app_file
28
28
  template "index.coffee.erb", "app/assets/javascripts/#{app_name}/index.js.coffee"
29
29
  end
30
-
30
+
31
31
  def create_lib_file
32
- template "view.coffee.erb", "app/assets/javascripts/#{app_name}/lib/view.js.coffee"
32
+ template "view.coffee.erb", "app/assets/javascripts/#{app_name}/lib/view.js.coffee"
33
33
  end
34
-
34
+
35
35
  def add_spine_app_to_application
36
36
  source = "app/assets/javascripts/application.js"
37
37
  content = File.read(source)
38
-
38
+
39
39
  if content.include?("//= require_tree .")
40
- inject_into_file source, :before => "//= require_tree ." do
40
+ inject_into_file source, before: "//= require_tree ." do
41
41
  "//= require #{app_name}\n"
42
42
  end
43
43
  else
@@ -7,11 +7,11 @@ module Spine
7
7
  source_root File.expand_path("../templates", __FILE__)
8
8
  desc "Generate a Spine scaffold with configured fields"
9
9
 
10
- argument :fields, :desc => "List of model attributes", :type => :array, :banner => "field1 field2"
11
-
10
+ argument :fields, desc: "List of model attributes", type: :array, banner: "field1 field2"
11
+
12
12
  def create_scaffold
13
13
  raise("The 'eco' gem is required; add it to the Gemfile") unless defined?(::Eco)
14
-
14
+
15
15
  generate "spine:model #{model_name} #{fields.join(" ")} --app #{app_name}"
16
16
  template "controller.coffee.erb", "app/assets/javascripts/#{app_name}/controllers/#{controller_name}.js.coffee"
17
17
  template "edit.jst.erb", "app/assets/javascripts/#{app_name}/views/#{controller_name}/edit.jst.eco"
@@ -19,9 +19,9 @@ module Spine
19
19
  template "new.jst.erb", "app/assets/javascripts/#{app_name}/views/#{controller_name}/new.jst.eco"
20
20
  template "show.jst.erb", "app/assets/javascripts/#{app_name}/views/#{controller_name}/show.jst.eco"
21
21
  end
22
-
22
+
23
23
  protected
24
-
24
+
25
25
  def model_name
26
26
  file_name.singularize
27
27
  end
@@ -1,4 +1,4 @@
1
- $ = jQuery.sub()
1
+ $ = Spine.$
2
2
  <%= model_class %> = <%= app_class %>.<%= model_class %>
3
3
 
4
4
  $.fn.item = ->
@@ -1,18 +1,18 @@
1
1
  module Spine
2
2
  module Generators
3
3
  class Base < ::Rails::Generators::NamedBase
4
- class_option :app, :type => :string, :default => "app", :desc => "app name"
5
-
4
+ class_option :app, type: :string, default: "app", desc: "app name"
5
+
6
6
  protected
7
-
7
+
8
8
  def class_name
9
9
  (class_path + [file_name]).map!{ |m| m.camelize }.join('')
10
10
  end
11
-
11
+
12
12
  def app_name
13
13
  options[:app]
14
14
  end
15
-
15
+
16
16
  def app_class
17
17
  app_name.camelize
18
18
  end
@@ -1,6 +1,6 @@
1
1
  module Spine
2
2
  module Rails
3
- VERSION = "0.1.0"
4
- SPINE_VERSION = "1.0.6"
3
+ VERSION = "0.1.1"
4
+ SPINE_VERSION = "1.1.0"
5
5
  end
6
6
  end
data/spine-rails.gemspec CHANGED
@@ -15,8 +15,9 @@ Gem::Specification.new do |s|
15
15
  s.rubyforge_project = "spine-rails"
16
16
 
17
17
  s.add_dependency "rails", ">= 3.1.0"
18
+ s.add_dependency "json2-rails", ">= 0.0.2"
18
19
  s.add_development_dependency "bundler"
19
-
20
+
20
21
  s.files = `git ls-files`.split("\n")
21
22
  s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
22
23
  s.require_path = 'lib'
@@ -0,0 +1,640 @@
1
+ Events =
2
+ bind: (ev, callback) ->
3
+ evs = ev.split(' ')
4
+ calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}
5
+ for name in evs
6
+ calls[name] or= []
7
+ calls[name].push(callback)
8
+ this
9
+
10
+ one: (ev, callback) ->
11
+ @bind ev, handler = ->
12
+ @unbind(ev, handler)
13
+ callback.apply(this, arguments)
14
+
15
+ trigger: (args...) ->
16
+ ev = args.shift()
17
+ list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
18
+ return unless list
19
+ for callback in list
20
+ if callback.apply(this, args) is false
21
+ break
22
+ true
23
+
24
+ listenTo: (obj, ev, callback) ->
25
+ obj.bind(ev, callback)
26
+ @listeningTo or= []
27
+ @listeningTo.push obj
28
+ this
29
+
30
+ listenToOnce: (obj, ev, callback) ->
31
+ listeningToOnce = @listeningToOnce or = []
32
+ listeningToOnce.push obj
33
+ obj.one ev, ->
34
+ idx = listeningToOnce.indexOf(obj)
35
+ listeningToOnce.splice(idx, 1) unless idx is -1
36
+ callback.apply(this, arguments)
37
+ this
38
+
39
+ stopListening: (obj, ev, callback) ->
40
+ if arguments.length is 0
41
+ retain = []
42
+ for listeningTo in [@listeningTo, @listeningToOnce]
43
+ continue unless listeningTo
44
+ for obj in listeningTo when not (obj in retain)
45
+ obj.unbind()
46
+ retain.push(obj)
47
+ @listeningTo = undefined
48
+ @listeningToOnce = undefined
49
+
50
+ else if obj
51
+ obj.unbind(ev, callback) if ev
52
+ obj.unbind() unless ev
53
+ for listeningTo in [@listeningTo, @listeningToOnce]
54
+ continue unless listeningTo
55
+ idx = listeningTo.indexOf(obj)
56
+ listeningTo.splice(idx, 1) unless idx is -1
57
+
58
+ unbind: (ev, callback) ->
59
+ if arguments.length is 0
60
+ @_callbacks = {}
61
+ return this
62
+ return this unless ev
63
+ evs = ev.split(' ')
64
+ for name in evs
65
+ list = @_callbacks?[name]
66
+ continue unless list
67
+ unless callback
68
+ delete @_callbacks[name]
69
+ continue
70
+ for cb, i in list when (cb is callback)
71
+ list = list.slice()
72
+ list.splice(i, 1)
73
+ @_callbacks[name] = list
74
+ break
75
+ this
76
+
77
+ Events.on = Events.bind
78
+ Events.off = Events.unbind
79
+
80
+ Log =
81
+ trace: true
82
+
83
+ logPrefix: '(App)'
84
+
85
+ log: (args...) ->
86
+ return unless @trace
87
+ if @logPrefix then args.unshift(@logPrefix)
88
+ console?.log?(args...)
89
+ this
90
+
91
+ moduleKeywords = ['included', 'extended']
92
+
93
+ class Module
94
+ @include: (obj) ->
95
+ throw new Error('include(obj) requires obj') unless obj
96
+ for key, value of obj when key not in moduleKeywords
97
+ @::[key] = value
98
+ obj.included?.apply(this)
99
+ this
100
+
101
+ @extend: (obj) ->
102
+ throw new Error('extend(obj) requires obj') unless obj
103
+ for key, value of obj when key not in moduleKeywords
104
+ @[key] = value
105
+ obj.extended?.apply(this)
106
+ this
107
+
108
+ @proxy: (func) ->
109
+ => func.apply(this, arguments)
110
+
111
+ proxy: (func) ->
112
+ => func.apply(this, arguments)
113
+
114
+ constructor: ->
115
+ @init?(arguments...)
116
+
117
+ class Model extends Module
118
+ @extend Events
119
+
120
+ @records: []
121
+ @irecords: {}
122
+ @crecords: {}
123
+ @attributes: []
124
+
125
+ @configure: (name, attributes...) ->
126
+ @className = name
127
+ @deleteAll()
128
+ @attributes = attributes if attributes.length
129
+ @attributes and= makeArray(@attributes)
130
+ @attributes or= []
131
+ @unbind()
132
+ this
133
+
134
+ @toString: -> "#{@className}(#{@attributes.join(", ")})"
135
+
136
+ @find: (id) ->
137
+ record = @exists(id)
138
+ throw new Error("\"#{@className}\" model could not find a record for the ID \"#{id}\"") unless record
139
+ return record
140
+
141
+ @exists: (id) ->
142
+ (@records[id] ? @irecords[id])?.clone()
143
+
144
+ @refresh: (values, options = {}) ->
145
+ if options.clear
146
+ @deleteAll()
147
+
148
+ records = @fromJSON(values)
149
+ records = [records] unless isArray(records)
150
+
151
+ for record in records
152
+ if record.id and @irecords[record.id]
153
+ @records[@records.indexOf(@irecords[record.id])] = record
154
+ else
155
+ record.id or= record.cid
156
+ @records.push(record)
157
+ @irecords[record.id] = record
158
+ @crecords[record.cid] = record
159
+
160
+ @sort()
161
+
162
+ result = @cloneArray(records)
163
+ @trigger('refresh', @cloneArray(records))
164
+ result
165
+
166
+ @select: (callback) ->
167
+ (record.clone() for record in @records when callback(record))
168
+
169
+ @findByAttribute: (name, value) ->
170
+ for record in @records
171
+ if record[name] is value
172
+ return record.clone()
173
+ null
174
+
175
+ @findAllByAttribute: (name, value) ->
176
+ @select (item) ->
177
+ item[name] is value
178
+
179
+ @each: (callback) ->
180
+ callback(record.clone()) for record in @records
181
+
182
+ @all: ->
183
+ @cloneArray(@records)
184
+
185
+ @first: ->
186
+ @records[0]?.clone()
187
+
188
+ @last: ->
189
+ @records[@records.length - 1]?.clone()
190
+
191
+ @count: ->
192
+ @records.length
193
+
194
+ @deleteAll: ->
195
+ @records = []
196
+ @irecords = {}
197
+ @crecords = {}
198
+
199
+ @destroyAll: (options) ->
200
+ record.destroy(options) for record in @records
201
+
202
+ @update: (id, atts, options) ->
203
+ @find(id).updateAttributes(atts, options)
204
+
205
+ @create: (atts, options) ->
206
+ record = new @(atts)
207
+ record.save(options)
208
+
209
+ @destroy: (id, options) ->
210
+ @find(id).destroy(options)
211
+
212
+ @change: (callbackOrParams) ->
213
+ if typeof callbackOrParams is 'function'
214
+ @bind('change', callbackOrParams)
215
+ else
216
+ @trigger('change', arguments...)
217
+
218
+ @fetch: (callbackOrParams) ->
219
+ if typeof callbackOrParams is 'function'
220
+ @bind('fetch', callbackOrParams)
221
+ else
222
+ @trigger('fetch', arguments...)
223
+
224
+ @toJSON: ->
225
+ @records
226
+
227
+ @fromJSON: (objects) ->
228
+ return unless objects
229
+ if typeof objects is 'string'
230
+ objects = JSON.parse(objects)
231
+ if isArray(objects)
232
+ (new @(value) for value in objects)
233
+ else
234
+ new @(objects)
235
+
236
+ @fromForm: ->
237
+ (new this).fromForm(arguments...)
238
+
239
+ @sort: ->
240
+ if @comparator
241
+ @records.sort @comparator
242
+ @records
243
+
244
+ # Private
245
+
246
+ @cloneArray: (array) ->
247
+ (value.clone() for value in array)
248
+
249
+ @idCounter: 0
250
+
251
+ @uid: (prefix = '') ->
252
+ uid = prefix + @idCounter++
253
+ uid = @uid(prefix) if @exists(uid)
254
+ uid
255
+
256
+ # Instance
257
+
258
+ constructor: (atts) ->
259
+ super
260
+ @load atts if atts
261
+ @cid = @constructor.uid('c-')
262
+
263
+ isNew: ->
264
+ not @exists()
265
+
266
+ isValid: ->
267
+ not @validate()
268
+
269
+ validate: ->
270
+
271
+ load: (atts) ->
272
+ if atts.id then @id = atts.id
273
+ for key, value of atts
274
+ if atts.hasOwnProperty(key) and typeof @[key] is 'function'
275
+ @[key](value)
276
+ else
277
+ @[key] = value
278
+ this
279
+
280
+ attributes: ->
281
+ result = {}
282
+ for key in @constructor.attributes when key of this
283
+ if typeof @[key] is 'function'
284
+ result[key] = @[key]()
285
+ else
286
+ result[key] = @[key]
287
+ result.id = @id if @id
288
+ result
289
+
290
+ eql: (rec) ->
291
+ !!(rec and rec.constructor is @constructor and
292
+ (rec.cid is @cid) or (rec.id and rec.id is @id))
293
+
294
+ save: (options = {}) ->
295
+ unless options.validate is false
296
+ error = @validate()
297
+ if error
298
+ @trigger('error', error)
299
+ return false
300
+
301
+ @trigger('beforeSave', options)
302
+ record = if @isNew() then @create(options) else @update(options)
303
+ @stripCloneAttrs()
304
+ @trigger('save', options)
305
+ record
306
+
307
+ stripCloneAttrs: ->
308
+ return if @hasOwnProperty 'cid' # Make sure it's not the raw object
309
+ for own key, value of @
310
+ delete @[key] if @constructor.attributes.indexOf(key) > -1
311
+ this
312
+
313
+ updateAttribute: (name, value, options) ->
314
+ atts = {}
315
+ atts[name] = value
316
+ @updateAttributes(atts, options)
317
+
318
+ updateAttributes: (atts, options) ->
319
+ @load(atts)
320
+ @save(options)
321
+
322
+ changeID: (id) ->
323
+ records = @constructor.irecords
324
+ records[id] = records[@id]
325
+ delete records[@id]
326
+ @id = id
327
+ @save()
328
+
329
+ destroy: (options = {}) ->
330
+ @trigger('beforeDestroy', options)
331
+
332
+ # Remove record from model
333
+ records = @constructor.records.slice(0)
334
+ for record, i in records when @eql(record)
335
+ records.splice(i, 1)
336
+ break
337
+ @constructor.records = records
338
+
339
+ # Remove the ID and CID
340
+ delete @constructor.irecords[@id]
341
+ delete @constructor.crecords[@cid]
342
+
343
+ @destroyed = true
344
+ @trigger('destroy', options)
345
+ @trigger('change', 'destroy', options)
346
+ if @listeningTo
347
+ @stopListening()
348
+ @unbind()
349
+ this
350
+
351
+ dup: (newRecord) ->
352
+ result = new @constructor(@attributes())
353
+ if newRecord is false
354
+ result.cid = @cid
355
+ else
356
+ delete result.id
357
+ result
358
+
359
+ clone: ->
360
+ createObject(this)
361
+
362
+ reload: ->
363
+ return this if @isNew()
364
+ original = @constructor.find(@id)
365
+ @load(original.attributes())
366
+ original
367
+
368
+ toJSON: ->
369
+ @attributes()
370
+
371
+ toString: ->
372
+ "<#{@constructor.className} (#{JSON.stringify(this)})>"
373
+
374
+ fromForm: (form) ->
375
+ result = {}
376
+ for key in $(form).serializeArray()
377
+ result[key.name] = key.value
378
+ @load(result)
379
+
380
+ exists: ->
381
+ @constructor.exists(@id)
382
+
383
+ # Private
384
+
385
+ update: (options) ->
386
+ @trigger('beforeUpdate', options)
387
+
388
+ records = @constructor.irecords
389
+ records[@id].load @attributes()
390
+
391
+ @constructor.sort()
392
+
393
+ clone = records[@id].clone()
394
+ clone.trigger('update', options)
395
+ clone.trigger('change', 'update', options)
396
+ clone
397
+
398
+ create: (options) ->
399
+ @trigger('beforeCreate', options)
400
+ @id = @cid unless @id
401
+
402
+ record = @dup(false)
403
+ @constructor.records.push(record)
404
+ @constructor.irecords[@id] = record
405
+ @constructor.crecords[@cid] = record
406
+
407
+ @constructor.sort()
408
+
409
+ clone = record.clone()
410
+ clone.trigger('create', options)
411
+ clone.trigger('change', 'create', options)
412
+ clone
413
+
414
+ bind: (events, callback) ->
415
+ @constructor.bind events, binder = (record) =>
416
+ if record && @eql(record)
417
+ callback.apply(this, arguments)
418
+ # create a wrapper function to be called with 'unbind' for each event
419
+ for singleEvent in events.split(' ')
420
+ do (singleEvent) =>
421
+ @constructor.bind "unbind", unbinder = (record, event, cb) =>
422
+ if record && @eql(record)
423
+ return if event and event isnt singleEvent
424
+ return if cb and cb isnt callback
425
+ @constructor.unbind(singleEvent, binder)
426
+ @constructor.unbind("unbind", unbinder)
427
+ this
428
+
429
+ one: (events, callback) ->
430
+ @bind events, handler = =>
431
+ @unbind(events, handler)
432
+ callback.apply(this, arguments)
433
+
434
+ trigger: (args...) ->
435
+ args.splice(1, 0, this)
436
+ @constructor.trigger(args...)
437
+
438
+ listenTo: (obj, events, callback) ->
439
+ obj.bind events, callback
440
+ @listeningTo or= []
441
+ @listeningTo.push(obj)
442
+
443
+ listenToOnce: (obj, events, callback) ->
444
+ listeningToOnce = @listeningToOnce or= []
445
+ listeningToOnce.push obj
446
+ obj.bind events, handler = =>
447
+ idx = listeningToOnce.indexOf(obj)
448
+ listeningToOnce.splice(idx, 1) unless idx is -1
449
+ obj.unbind(events, handler)
450
+ callback.apply(obj, arguments)
451
+
452
+ stopListening: (obj, events, callback) ->
453
+ if arguments.length is 0
454
+ retain = []
455
+ for listeningTo in [@listeningTo, @listeningToOnce]
456
+ continue unless listeningTo
457
+ for obj in @listeningTo when not (obj in retain)
458
+ obj.unbind()
459
+ retain.push(obj)
460
+ @listeningTo = undefined
461
+ @listeningToOnce = undefined
462
+ return
463
+
464
+ if obj
465
+ obj.unbind() unless events
466
+ obj.unbind(events, callback) if events
467
+ for listeningTo in [@listeningTo, @listeningToOnce]
468
+ continue unless listeningTo
469
+ idx = listeningTo.indexOf(obj)
470
+ listeningTo.splice(idx, 1) unless idx is -1
471
+
472
+ unbind: (events, callback) ->
473
+ if arguments.length is 0
474
+ @trigger('unbind')
475
+ else if events
476
+ for event in events.split(' ')
477
+ @trigger('unbind', event, callback)
478
+
479
+ Model::on = Model::bind
480
+ Model::off = Model::unbind
481
+
482
+ class Controller extends Module
483
+ @include Events
484
+ @include Log
485
+
486
+ eventSplitter: /^(\S+)\s*(.*)$/
487
+ tag: 'div'
488
+
489
+ constructor: (options) ->
490
+ @options = options
491
+
492
+ for key, value of @options
493
+ @[key] = value
494
+
495
+ @el = document.createElement(@tag) unless @el
496
+ @el = $(@el)
497
+ @$el = @el
498
+
499
+ @el.addClass(@className) if @className
500
+ @el.attr(@attributes) if @attributes
501
+
502
+ @events = @constructor.events unless @events
503
+ @elements = @constructor.elements unless @elements
504
+
505
+ context = @
506
+ while parent_prototype = context.constructor.__super__
507
+ @events = $.extend({}, parent_prototype.events, @events) if parent_prototype.events
508
+ @elements = $.extend({}, parent_prototype.elements, @elements) if parent_prototype.elements
509
+ context = parent_prototype
510
+
511
+ @delegateEvents(@events) if @events
512
+ @refreshElements() if @elements
513
+
514
+ super
515
+
516
+ release: =>
517
+ @trigger 'release', this
518
+ @el.remove()
519
+ @unbind()
520
+ @stopListening()
521
+
522
+ $: (selector) -> $(selector, @el)
523
+
524
+ delegateEvents: (events) ->
525
+ for key, method of events
526
+
527
+ if typeof(method) is 'function'
528
+ # Always return true from event handlers
529
+ method = do (method) => =>
530
+ method.apply(this, arguments)
531
+ true
532
+ else
533
+ unless @[method]
534
+ throw new Error("#{method} doesn't exist")
535
+
536
+ method = do (method) => =>
537
+ @[method].apply(this, arguments)
538
+ true
539
+
540
+ match = key.match(@eventSplitter)
541
+ eventName = match[1]
542
+ selector = match[2]
543
+
544
+ if selector is ''
545
+ @el.bind(eventName, method)
546
+ else
547
+ @el.delegate(selector, eventName, method)
548
+
549
+ refreshElements: ->
550
+ for key, value of @elements
551
+ @[value] = @$(key)
552
+
553
+ delay: (func, timeout) ->
554
+ setTimeout(@proxy(func), timeout || 0)
555
+
556
+ html: (element) ->
557
+ @el.html(element.el or element)
558
+ @refreshElements()
559
+ @el
560
+
561
+ append: (elements...) ->
562
+ elements = (e.el or e for e in elements)
563
+ @el.append(elements...)
564
+ @refreshElements()
565
+ @el
566
+
567
+ appendTo: (element) ->
568
+ @el.appendTo(element.el or element)
569
+ @refreshElements()
570
+ @el
571
+
572
+ prepend: (elements...) ->
573
+ elements = (e.el or e for e in elements)
574
+ @el.prepend(elements...)
575
+ @refreshElements()
576
+ @el
577
+
578
+ replace: (element) ->
579
+ [previous, @el] = [@el, $(element.el or element)]
580
+ previous.replaceWith(@el)
581
+ @delegateEvents(@events)
582
+ @refreshElements()
583
+ @el
584
+
585
+ # Utilities & Shims
586
+
587
+ $ = window?.jQuery or window?.Zepto or (element) -> element
588
+
589
+ createObject = Object.create or (o) ->
590
+ Func = ->
591
+ Func.prototype = o
592
+ new Func()
593
+
594
+ isArray = (value) ->
595
+ Object::toString.call(value) is '[object Array]'
596
+
597
+ isBlank = (value) ->
598
+ return true unless value
599
+ return false for key of value
600
+ true
601
+
602
+ makeArray = (args) ->
603
+ Array::slice.call(args, 0)
604
+
605
+ # Globals
606
+
607
+ Spine = @Spine = {}
608
+ module?.exports = Spine
609
+
610
+ Spine.version = '1.1.0'
611
+ Spine.isArray = isArray
612
+ Spine.isBlank = isBlank
613
+ Spine.$ = $
614
+ Spine.Events = Events
615
+ Spine.Log = Log
616
+ Spine.Module = Module
617
+ Spine.Controller = Controller
618
+ Spine.Model = Model
619
+
620
+ # Global events
621
+
622
+ Module.extend.call(Spine, Events)
623
+
624
+ # JavaScript compatability
625
+
626
+ Module.create = Module.sub =
627
+ Controller.create = Controller.sub =
628
+ Model.sub = (instances, statics) ->
629
+ class Result extends this
630
+ Result.include(instances) if instances
631
+ Result.extend(statics) if statics
632
+ Result.unbind?()
633
+ Result
634
+
635
+ Model.setup = (name, attributes = []) ->
636
+ class Instance extends this
637
+ Instance.configure(name, attributes...)
638
+ Instance
639
+
640
+ Spine.Class = Module