event_cal 1.0

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 (32) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/Rakefile +21 -0
  3. data/app/views/event_calendar/_calendar.html.haml +9 -0
  4. data/app/views/event_calendar/_date.html.haml +10 -0
  5. data/app/views/event_calendar/_event_details.html.haml +4 -0
  6. data/lib/event_calendar/calendar.rb +50 -0
  7. data/lib/event_calendar/calendar_helper.rb +45 -0
  8. data/lib/event_calendar/engine.rb +15 -0
  9. data/lib/event_calendar/event.rb +16 -0
  10. data/lib/event_calendar/version.rb +3 -0
  11. data/lib/event_calendar.rb +4 -0
  12. data/lib/tasks/event_calendar_tasks.rake +4 -0
  13. data/vendor/assets/images/calendar_next.png +0 -0
  14. data/vendor/assets/images/calendar_prev.png +0 -0
  15. data/vendor/assets/images/icon_config.png +0 -0
  16. data/vendor/assets/javascripts/calendarApplication.js.coffee +11 -0
  17. data/vendor/assets/javascripts/controllers/calendarDatesController.js.coffee +32 -0
  18. data/vendor/assets/javascripts/controllers/calendarEventsController.js.coffee +19 -0
  19. data/vendor/assets/javascripts/event_calendar.js +1117 -0
  20. data/vendor/assets/javascripts/event_calendar_no_libs.js +191 -0
  21. data/vendor/assets/javascripts/lib/moment.js +1 -0
  22. data/vendor/assets/javascripts/lib/spine.js.coffee +535 -0
  23. data/vendor/assets/javascripts/lib/spinelib/ajax.js.coffee +223 -0
  24. data/vendor/assets/javascripts/lib/spinelib/list.js.coffee +43 -0
  25. data/vendor/assets/javascripts/lib/spinelib/local.js.coffee +16 -0
  26. data/vendor/assets/javascripts/lib/spinelib/manager.js.coffee +83 -0
  27. data/vendor/assets/javascripts/lib/spinelib/relation.js.coffee +144 -0
  28. data/vendor/assets/javascripts/lib/spinelib/route.js.coffee +151 -0
  29. data/vendor/assets/javascripts/models/calendarDate.js.coffee +7 -0
  30. data/vendor/assets/javascripts/models/calendarEvent.js.coffee +11 -0
  31. data/vendor/assets/stylesheets/event_calendar.css.scss +251 -0
  32. metadata +274 -0
@@ -0,0 +1,223 @@
1
+ Spine = @Spine or require('spine')
2
+ $ = Spine.$
3
+ Model = Spine.Model
4
+ Queue = $({})
5
+
6
+ Ajax =
7
+ getURL: (object) ->
8
+ object and object.url?() or object.url
9
+
10
+ enabled: true
11
+
12
+ disable: (callback) ->
13
+ if @enabled
14
+ @enabled = false
15
+ try
16
+ do callback
17
+ catch e
18
+ throw e
19
+ finally
20
+ @enabled = true
21
+ else
22
+ do callback
23
+
24
+ queue: (request) ->
25
+ if request then Queue.queue(request) else Queue.queue()
26
+
27
+ clearQueue: ->
28
+ @queue []
29
+
30
+ class Base
31
+ defaults:
32
+ contentType: 'application/json'
33
+ dataType: 'json'
34
+ processData: false
35
+ headers: {'X-Requested-With': 'XMLHttpRequest'}
36
+
37
+ queue: Ajax.queue
38
+
39
+ ajax: (params, defaults) ->
40
+ $.ajax @ajaxSettings(params, defaults)
41
+
42
+ ajaxQueue: (params, defaults) ->
43
+ jqXHR = null
44
+ deferred = $.Deferred()
45
+
46
+ promise = deferred.promise()
47
+ return promise unless Ajax.enabled
48
+
49
+ settings = @ajaxSettings(params, defaults)
50
+
51
+ request = (next) ->
52
+ jqXHR = $.ajax(settings)
53
+ .done(deferred.resolve)
54
+ .fail(deferred.reject)
55
+ .then(next, next)
56
+
57
+ promise.abort = (statusText) ->
58
+ return jqXHR.abort(statusText) if jqXHR
59
+ index = $.inArray(request, @queue())
60
+ @queue().splice(index, 1) if index > -1
61
+
62
+ deferred.rejectWith(
63
+ settings.context or settings,
64
+ [promise, statusText, '']
65
+ )
66
+ promise
67
+
68
+ @queue request
69
+ promise
70
+
71
+ ajaxSettings: (params, defaults) ->
72
+ $.extend({}, @defaults, defaults, params)
73
+
74
+ class Collection extends Base
75
+ constructor: (@model) ->
76
+
77
+ find: (id, params) ->
78
+ record = new @model(id: id)
79
+ @ajaxQueue(
80
+ params,
81
+ type: 'GET',
82
+ url: Ajax.getURL(record)
83
+ ).done(@recordsResponse)
84
+ .fail(@failResponse)
85
+
86
+ all: (params) ->
87
+ @ajaxQueue(
88
+ params,
89
+ type: 'GET',
90
+ url: Ajax.getURL(@model)
91
+ ).done(@recordsResponse)
92
+ .fail(@failResponse)
93
+
94
+ fetch: (params = {}, options = {}) ->
95
+ if id = params.id
96
+ delete params.id
97
+ @find(id, params).done (record) =>
98
+ @model.refresh(record, options)
99
+ else
100
+ @all(params).done (records) =>
101
+ @model.refresh(records, options)
102
+
103
+ # Private
104
+
105
+ recordsResponse: (data, status, xhr) =>
106
+ @model.trigger('ajaxSuccess', null, status, xhr)
107
+
108
+ failResponse: (xhr, statusText, error) =>
109
+ @model.trigger('ajaxError', null, xhr, statusText, error)
110
+
111
+ class Singleton extends Base
112
+ constructor: (@record) ->
113
+ @model = @record.constructor
114
+
115
+ reload: (params, options) ->
116
+ @ajaxQueue(
117
+ params,
118
+ type: 'GET'
119
+ url: Ajax.getURL(@record)
120
+ ).done(@recordResponse(options))
121
+ .fail(@failResponse(options))
122
+
123
+ create: (params, options) ->
124
+ @ajaxQueue(
125
+ params,
126
+ type: 'POST'
127
+ data: JSON.stringify(@record)
128
+ url: Ajax.getURL(@model)
129
+ ).done(@recordResponse(options))
130
+ .fail(@failResponse(options))
131
+
132
+ update: (params, options) ->
133
+ @ajaxQueue(
134
+ params,
135
+ type: 'PUT'
136
+ data: JSON.stringify(@record)
137
+ url: Ajax.getURL(@record)
138
+ ).done(@recordResponse(options))
139
+ .fail(@failResponse(options))
140
+
141
+ destroy: (params, options) ->
142
+ @ajaxQueue(
143
+ params,
144
+ type: 'DELETE'
145
+ url: Ajax.getURL(@record)
146
+ ).done(@recordResponse(options))
147
+ .fail(@failResponse(options))
148
+
149
+ # Private
150
+
151
+ recordResponse: (options = {}) =>
152
+ (data, status, xhr) =>
153
+ if Spine.isBlank(data)
154
+ data = false
155
+ else
156
+ data = @model.fromJSON(data)
157
+
158
+ Ajax.disable =>
159
+ if data
160
+ # ID change, need to do some shifting
161
+ if data.id and @record.id isnt data.id
162
+ @record.changeID(data.id)
163
+
164
+ # Update with latest data
165
+ @record.updateAttributes(data.attributes())
166
+
167
+ @record.trigger('ajaxSuccess', data, status, xhr)
168
+ options.success?.apply(@record) # Deprecated
169
+ options.done?.apply(@record)
170
+
171
+ failResponse: (options = {}) =>
172
+ (xhr, statusText, error) =>
173
+ @record.trigger('ajaxError', xhr, statusText, error)
174
+ options.error?.apply(@record) # Deprecated
175
+ options.fail?.apply(@record)
176
+
177
+ # Ajax endpoint
178
+ Model.host = ''
179
+
180
+ Include =
181
+ ajax: -> new Singleton(this)
182
+
183
+ url: (args...) ->
184
+ url = Ajax.getURL(@constructor)
185
+ url += '/' unless url.charAt(url.length - 1) is '/'
186
+ url += encodeURIComponent(@id)
187
+ args.unshift(url)
188
+ args.join('/')
189
+
190
+ Extend =
191
+ ajax: -> new Collection(this)
192
+
193
+ url: (args...) ->
194
+ args.unshift(@className.toLowerCase() + 's')
195
+ args.unshift(Model.host)
196
+ args.join('/')
197
+
198
+ Model.Ajax =
199
+ extended: ->
200
+ @fetch @ajaxFetch
201
+ @change @ajaxChange
202
+
203
+ @extend Extend
204
+ @include Include
205
+
206
+ # Private
207
+
208
+ ajaxFetch: ->
209
+ @ajax().fetch(arguments...)
210
+
211
+ ajaxChange: (record, type, options = {}) ->
212
+ return if options.ajax is false
213
+ record.ajax()[type](options.ajax, options)
214
+
215
+ Model.Ajax.Methods =
216
+ extended: ->
217
+ @extend Extend
218
+ @include Include
219
+
220
+ # Globals
221
+ Ajax.defaults = Base::defaults
222
+ Spine.Ajax = Ajax
223
+ module?.exports = Ajax
@@ -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,151 @@
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
+ return if @options.shim
43
+
44
+ if @history
45
+ $(window).unbind('popstate', @change)
46
+ else
47
+ $(window).unbind('hashchange', @change)
48
+
49
+ @navigate: (args...) ->
50
+ options = {}
51
+
52
+ lastArg = args[args.length - 1]
53
+ if typeof lastArg is 'object'
54
+ options = args.pop()
55
+ else if typeof lastArg is 'boolean'
56
+ options.trigger = args.pop()
57
+
58
+ options = $.extend({}, @options, options)
59
+
60
+ path = args.join('/')
61
+ return if @path is path
62
+ @path = path
63
+
64
+ @trigger('navigate', @path)
65
+
66
+ @matchRoute(@path, options) if options.trigger
67
+
68
+ return if options.shim
69
+
70
+ if @history
71
+ history.pushState(
72
+ {},
73
+ document.title,
74
+ @path
75
+ )
76
+ else
77
+ window.location.hash = @path
78
+
79
+ # Private
80
+
81
+ @getPath: ->
82
+ path = window.location.pathname
83
+ if path.substr(0,1) isnt '/'
84
+ path = '/' + path
85
+ path
86
+
87
+ @getHash: -> window.location.hash
88
+
89
+ @getFragment: -> @getHash().replace(hashStrip, '')
90
+
91
+ @getHost: ->
92
+ (document.location + '').replace(@getPath() + @getHash(), '')
93
+
94
+ @change: ->
95
+ path = if @history then @getPath() else @getFragment()
96
+ return if path is @path
97
+ @path = path
98
+ @matchRoute(@path)
99
+
100
+ @matchRoute: (path, options) ->
101
+ for route in @routes
102
+ if route.match(path, options)
103
+ @trigger('change', route, path)
104
+ return route
105
+
106
+ constructor: (@path, @callback) ->
107
+ @names = []
108
+
109
+ if typeof path is 'string'
110
+ namedParam.lastIndex = 0
111
+ while (match = namedParam.exec(path)) != null
112
+ @names.push(match[1])
113
+
114
+ splatParam.lastIndex = 0
115
+ while (match = splatParam.exec(path)) != null
116
+ @names.push(match[1])
117
+
118
+ path = path.replace(escapeRegExp, '\\$&')
119
+ .replace(namedParam, '([^\/]*)')
120
+ .replace(splatParam, '(.*?)')
121
+
122
+ @route = new RegExp('^' + path + '$')
123
+ else
124
+ @route = path
125
+
126
+ match: (path, options = {}) ->
127
+ match = @route.exec(path)
128
+ return false unless match
129
+ options.match = match
130
+ params = match.slice(1)
131
+
132
+ if @names.length
133
+ for param, i in params
134
+ options[@names[i]] = param
135
+
136
+ @callback.call(null, options) isnt false
137
+
138
+ # Coffee-script bug
139
+ Spine.Route.change = Spine.Route.proxy(Spine.Route.change)
140
+
141
+ Spine.Controller.include
142
+ route: (path, callback) ->
143
+ Spine.Route.add(path, @proxy(callback))
144
+
145
+ routes: (routes) ->
146
+ @route(key, value) for key, value of routes
147
+
148
+ navigate: ->
149
+ Spine.Route.navigate.apply(Spine.Route, arguments)
150
+
151
+ module?.exports = Spine.Route
@@ -0,0 +1,7 @@
1
+ class CalendarDate extends Spine.Model
2
+ @configure 'CalendarDate', 'element', 'date', 'active'
3
+
4
+ @deactivateAllDates: () ->
5
+ CalendarDate.each((date) -> date.updateAttributes(active: false))
6
+
7
+ window.CalendarDate = CalendarDate
@@ -0,0 +1,11 @@
1
+ class CalendarEvent extends Spine.Model
2
+ @configure 'CalendarEvent', 'held_on', 'active', 'element'
3
+
4
+ @activateAllEventsOn: (date) ->
5
+ for event in CalendarEvent.findAllByAttribute('held_on', date)
6
+ event.updateAttributes(active: true)
7
+
8
+ @deactivateAllEvents: () ->
9
+ CalendarEvent.each((event) -> event.updateAttributes(active: false))
10
+
11
+ window.CalendarEvent = CalendarEvent