spinebox 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.textile +26 -0
- data/Rakefile +2 -0
- data/bin/spinebox +32 -0
- data/lib/spinebox/base.rb +10 -0
- data/lib/spinebox/command.rb +53 -0
- data/lib/spinebox/compiler.rb +10 -0
- data/lib/spinebox/config.rb +22 -0
- data/lib/spinebox/generator.rb +24 -0
- data/lib/spinebox/routes.rb +41 -0
- data/lib/spinebox/templates/Gemfile +3 -0
- data/lib/spinebox/templates/Rakefile +0 -0
- data/lib/spinebox/templates/app/assets/images/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/controllers/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/index.js.coffee +6 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/models/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/app/views/.gitignore +0 -0
- data/lib/spinebox/templates/app/assets/javascripts/application.js +14 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/jquery.js +4 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/json2.js +485 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/ajax.coffee +208 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/list.coffee +43 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/local.coffee +16 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/manager.coffee +83 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/relation.coffee +144 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/route.coffee +145 -0
- data/lib/spinebox/templates/app/assets/javascripts/lib/spine/spine.coffee +537 -0
- data/lib/spinebox/templates/app/assets/stylesheets/application.css.scss +13 -0
- data/lib/spinebox/templates/config/config.rb +7 -0
- data/lib/spinebox/templates/config/routes.rb +13 -0
- data/lib/spinebox/templates/config.ru +3 -0
- data/lib/spinebox/templates/public/index.html +14 -0
- data/lib/spinebox/version.rb +3 -0
- data/lib/spinebox.rb +19 -0
- data/spec/base_spec.rb +9 -0
- data/spec/command_spec.rb +23 -0
- data/spec/config_spec.rb +29 -0
- data/spec/generator_spec.rb +25 -0
- data/spec/helpers.rb +9 -0
- data/spec/routes_spec.rb +14 -0
- data/spinebox.gemspec +17 -0
- 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
|