event_cal 1.0

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