spine-rails 0.1.0 → 0.1.1

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