spinebox 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.
Files changed (44) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE +22 -0
  4. data/README.textile +26 -0
  5. data/Rakefile +2 -0
  6. data/bin/spinebox +32 -0
  7. data/lib/spinebox/base.rb +10 -0
  8. data/lib/spinebox/command.rb +53 -0
  9. data/lib/spinebox/compiler.rb +10 -0
  10. data/lib/spinebox/config.rb +22 -0
  11. data/lib/spinebox/generator.rb +24 -0
  12. data/lib/spinebox/routes.rb +41 -0
  13. data/lib/spinebox/templates/Gemfile +3 -0
  14. data/lib/spinebox/templates/Rakefile +0 -0
  15. data/lib/spinebox/templates/app/assets/images/.gitignore +0 -0
  16. data/lib/spinebox/templates/app/assets/javascripts/app/controllers/.gitignore +0 -0
  17. data/lib/spinebox/templates/app/assets/javascripts/app/index.js.coffee +6 -0
  18. data/lib/spinebox/templates/app/assets/javascripts/app/models/.gitignore +0 -0
  19. data/lib/spinebox/templates/app/assets/javascripts/app/views/.gitignore +0 -0
  20. data/lib/spinebox/templates/app/assets/javascripts/application.js +14 -0
  21. data/lib/spinebox/templates/app/assets/javascripts/lib/jquery.js +4 -0
  22. data/lib/spinebox/templates/app/assets/javascripts/lib/json2.js +485 -0
  23. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/ajax.coffee +208 -0
  24. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/list.coffee +43 -0
  25. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/local.coffee +16 -0
  26. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/manager.coffee +83 -0
  27. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/relation.coffee +144 -0
  28. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/route.coffee +145 -0
  29. data/lib/spinebox/templates/app/assets/javascripts/lib/spine/spine.coffee +537 -0
  30. data/lib/spinebox/templates/app/assets/stylesheets/application.css.scss +13 -0
  31. data/lib/spinebox/templates/config/config.rb +7 -0
  32. data/lib/spinebox/templates/config/routes.rb +13 -0
  33. data/lib/spinebox/templates/config.ru +3 -0
  34. data/lib/spinebox/templates/public/index.html +14 -0
  35. data/lib/spinebox/version.rb +3 -0
  36. data/lib/spinebox.rb +19 -0
  37. data/spec/base_spec.rb +9 -0
  38. data/spec/command_spec.rb +23 -0
  39. data/spec/config_spec.rb +29 -0
  40. data/spec/generator_spec.rb +25 -0
  41. data/spec/helpers.rb +9 -0
  42. data/spec/routes_spec.rb +14 -0
  43. data/spinebox.gemspec +17 -0
  44. metadata +97 -0
@@ -0,0 +1,43 @@
1
+ Spine = @Spine or require('spine')
2
+ $ = Spine.$
3
+
4
+ class Spine.List extends Spine.Controller
5
+ events:
6
+ 'click .item': 'click'
7
+
8
+ selectFirst: false
9
+
10
+ constructor: ->
11
+ super
12
+ @bind 'change', @change
13
+
14
+ template: ->
15
+ throw 'Override template'
16
+
17
+ change: (item) =>
18
+ @current = item
19
+
20
+ unless @current
21
+ @children().removeClass('active')
22
+ return
23
+
24
+ @children().removeClass('active')
25
+ $(@children().get(@items.indexOf(@current))).addClass('active')
26
+
27
+ render: (items) ->
28
+ @items = items if items
29
+ @html @template(@items)
30
+ @change @current
31
+ if @selectFirst
32
+ unless @children('.active').length
33
+ @children(':first').click()
34
+
35
+ children: (sel) ->
36
+ @el.children(sel)
37
+
38
+ click: (e) ->
39
+ item = @items[$(e.currentTarget).index()]
40
+ @trigger('change', item)
41
+ true
42
+
43
+ module?.exports = Spine.List
@@ -0,0 +1,16 @@
1
+ Spine = @Spine or require('spine')
2
+
3
+ Spine.Model.Local =
4
+ extended: ->
5
+ @change @saveLocal
6
+ @fetch @loadLocal
7
+
8
+ saveLocal: ->
9
+ result = JSON.stringify(@)
10
+ localStorage[@className] = result
11
+
12
+ loadLocal: ->
13
+ result = localStorage[@className]
14
+ @refresh(result or [], clear: true)
15
+
16
+ module?.exports = Spine.Model.Local
@@ -0,0 +1,83 @@
1
+ Spine = @Spine or require('spine')
2
+ $ = Spine.$
3
+
4
+ class Spine.Manager extends Spine.Module
5
+ @include Spine.Events
6
+
7
+ constructor: ->
8
+ @controllers = []
9
+ @bind 'change', @change
10
+ @add(arguments...)
11
+
12
+ add: (controllers...) ->
13
+ @addOne(cont) for cont in controllers
14
+
15
+ addOne: (controller) ->
16
+ controller.bind 'active', (args...) =>
17
+ @trigger('change', controller, args...)
18
+ controller.bind 'release', =>
19
+ @controllers.splice(@controllers.indexOf(controller), 1)
20
+
21
+ @controllers.push(controller)
22
+
23
+ deactivate: ->
24
+ @trigger('change', false, arguments...)
25
+
26
+ # Private
27
+
28
+ change: (current, args...) ->
29
+ for cont in @controllers
30
+ if cont is current
31
+ cont.activate(args...)
32
+ else
33
+ cont.deactivate(args...)
34
+
35
+ Spine.Controller.include
36
+ active: (args...) ->
37
+ if typeof args[0] is 'function'
38
+ @bind('active', args[0])
39
+ else
40
+ args.unshift('active')
41
+ @trigger(args...)
42
+ @
43
+
44
+ isActive: ->
45
+ @el.hasClass('active')
46
+
47
+ activate: ->
48
+ @el.addClass('active')
49
+ @
50
+
51
+ deactivate: ->
52
+ @el.removeClass('active')
53
+ @
54
+
55
+ class Spine.Stack extends Spine.Controller
56
+ controllers: {}
57
+ routes: {}
58
+
59
+ className: 'spine stack'
60
+
61
+ constructor: ->
62
+ super
63
+
64
+ @manager = new Spine.Manager
65
+
66
+ for key, value of @controllers
67
+ @[key] = new value(stack: @)
68
+ @add(@[key])
69
+
70
+ for key, value of @routes
71
+ do (key, value) =>
72
+ callback = value if typeof value is 'function'
73
+ callback or= => @[value].active(arguments...)
74
+ @route(key, callback)
75
+
76
+ @[@default].active() if @default
77
+
78
+ add: (controller) ->
79
+ @manager.add(controller)
80
+ @append(controller)
81
+
82
+ module?.exports = Spine.Manager
83
+ module?.exports.Stack = Spine.Stack
@@ -0,0 +1,144 @@
1
+ Spine = @Spine or require('spine')
2
+ isArray = Spine.isArray
3
+ require = @require or ((value) -> eval(value))
4
+
5
+ class Collection extends Spine.Module
6
+ constructor: (options = {}) ->
7
+ for key, value of options
8
+ @[key] = value
9
+
10
+ all: ->
11
+ @model.select (rec) => @associated(rec)
12
+
13
+ first: ->
14
+ @all()[0]
15
+
16
+ last: ->
17
+ values = @all()
18
+ values[values.length - 1]
19
+
20
+ find: (id) ->
21
+ records = @select (rec) =>
22
+ rec.id + '' is id + ''
23
+ throw('Unknown record') unless records[0]
24
+ records[0]
25
+
26
+ findAllByAttribute: (name, value) ->
27
+ @model.select (rec) =>
28
+ @associated(rec) and rec[name] is value
29
+
30
+ findByAttribute: (name, value) ->
31
+ @findAllByAttribute(name, value)[0]
32
+
33
+ select: (cb) ->
34
+ @model.select (rec) =>
35
+ @associated(rec) and cb(rec)
36
+
37
+ refresh: (values) ->
38
+ delete @model.records[record.id] for record in @all()
39
+ records = @model.fromJSON(values)
40
+
41
+ records = [records] unless isArray(records)
42
+
43
+ for record in records
44
+ record.newRecord = false
45
+ record[@fkey] = @record.id
46
+ @model.records[record.id] = record
47
+
48
+ @model.trigger('refresh', @model.cloneArray(records))
49
+
50
+ create: (record) ->
51
+ record[@fkey] = @record.id
52
+ @model.create(record)
53
+
54
+ # Private
55
+
56
+ associated: (record) ->
57
+ record[@fkey] is @record.id
58
+
59
+ class Instance extends Spine.Module
60
+ constructor: (options = {}) ->
61
+ for key, value of options
62
+ @[key] = value
63
+
64
+ exists: ->
65
+ @record[@fkey] and @model.exists(@record[@fkey])
66
+
67
+ update: (value) ->
68
+ unless value instanceof @model
69
+ value = new @model(value)
70
+ value.save() if value.isNew()
71
+ @record[@fkey] = value and value.id
72
+
73
+ class Singleton extends Spine.Module
74
+ constructor: (options = {}) ->
75
+ for key, value of options
76
+ @[key] = value
77
+
78
+ find: ->
79
+ @record.id and @model.findByAttribute(@fkey, @record.id)
80
+
81
+ update: (value) ->
82
+ unless value instanceof @model
83
+ value = @model.fromJSON(value)
84
+
85
+ value[@fkey] = @record.id
86
+ value.save()
87
+
88
+ singularize = (str) ->
89
+ str.replace(/s$/, '')
90
+
91
+ underscore = (str) ->
92
+ str.replace(/::/g, '/')
93
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
94
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
95
+ .replace(/-/g, '_')
96
+ .toLowerCase()
97
+
98
+ Spine.Model.extend
99
+ hasMany: (name, model, fkey) ->
100
+ fkey ?= "#{underscore(this.className)}_id"
101
+
102
+ association = (record) ->
103
+ model = require(model) if typeof model is 'string'
104
+
105
+ new Collection(
106
+ name: name, model: model,
107
+ record: record, fkey: fkey
108
+ )
109
+
110
+ @::[name] = (value) ->
111
+ association(@).refresh(value) if value?
112
+ association(@)
113
+
114
+ belongsTo: (name, model, fkey) ->
115
+ fkey ?= "#{singularize(name)}_id"
116
+
117
+ association = (record) ->
118
+ model = require(model) if typeof model is 'string'
119
+
120
+ new Instance(
121
+ name: name, model: model,
122
+ record: record, fkey: fkey
123
+ )
124
+
125
+ @::[name] = (value) ->
126
+ association(@).update(value) if value?
127
+ association(@).exists()
128
+
129
+ @attributes.push(fkey)
130
+
131
+ hasOne: (name, model, fkey) ->
132
+ fkey ?= "#{underscore(@className)}_id"
133
+
134
+ association = (record) ->
135
+ model = require(model) if typeof model is 'string'
136
+
137
+ new Singleton(
138
+ name: name, model: model,
139
+ record: record, fkey: fkey
140
+ )
141
+
142
+ @::[name] = (value) ->
143
+ association(@).update(value) if value?
144
+ association(@).find()
@@ -0,0 +1,145 @@
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 && @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
+ if @history
43
+ $(window).unbind('popstate', @change)
44
+ else
45
+ $(window).unbind('hashchange', @change)
46
+
47
+ @navigate: (args...) ->
48
+ options = {}
49
+
50
+ lastArg = args[args.length - 1]
51
+ if typeof lastArg is 'object'
52
+ options = args.pop()
53
+ else if typeof lastArg is 'boolean'
54
+ options.trigger = args.pop()
55
+
56
+ options = $.extend({}, @options, options)
57
+
58
+ path = args.join('/')
59
+ return if @path is path
60
+ @path = path
61
+
62
+ @trigger('navigate', @path)
63
+
64
+ @matchRoute(@path, options) if options.trigger
65
+
66
+ return if options.shim
67
+
68
+ if @history
69
+ history.pushState(
70
+ {},
71
+ document.title,
72
+ @path
73
+ )
74
+ else
75
+ window.location.hash = @path
76
+
77
+ # Private
78
+
79
+ @getPath: ->
80
+ path = window.location.pathname
81
+ if path.substr(0,1) isnt '/'
82
+ path = '/' + path
83
+ path
84
+
85
+ @getHash: -> window.location.hash
86
+
87
+ @getFragment: -> @getHash().replace(hashStrip, '')
88
+
89
+ @getHost: ->
90
+ (document.location + '').replace(@getPath() + @getHash(), '')
91
+
92
+ @change: ->
93
+ path = if @getFragment() isnt '' then @getFragment() else @getPath()
94
+ return if path is @path
95
+ @path = path
96
+ @matchRoute(@path)
97
+
98
+ @matchRoute: (path, options) ->
99
+ for route in @routes
100
+ if route.match(path, options)
101
+ @trigger('change', route, path)
102
+ return route
103
+
104
+ constructor: (@path, @callback) ->
105
+ @names = []
106
+
107
+ if typeof path is 'string'
108
+ namedParam.lastIndex = 0
109
+ while (match = namedParam.exec(path)) != null
110
+ @names.push(match[1])
111
+
112
+ path = path.replace(escapeRegExp, '\\$&')
113
+ .replace(namedParam, '([^\/]*)')
114
+ .replace(splatParam, '(.*?)')
115
+
116
+ @route = new RegExp('^' + path + '$')
117
+ else
118
+ @route = path
119
+
120
+ match: (path, options = {}) ->
121
+ match = @route.exec(path)
122
+ return false unless match
123
+ options.match = match
124
+ params = match.slice(1)
125
+
126
+ if @names.length
127
+ for param, i in params
128
+ options[@names[i]] = param
129
+
130
+ @callback.call(null, options) isnt false
131
+
132
+ # Coffee-script bug
133
+ Spine.Route.change = Spine.Route.proxy(Spine.Route.change)
134
+
135
+ Spine.Controller.include
136
+ route: (path, callback) ->
137
+ Spine.Route.add(path, @proxy(callback))
138
+
139
+ routes: (routes) ->
140
+ @route(key, value) for key, value of routes
141
+
142
+ navigate: ->
143
+ Spine.Route.navigate.apply(Spine.Route, arguments)
144
+
145
+ module?.exports = Spine.Route