spinebox 0.0.1

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