droom 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 (207) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +23 -0
  3. data/Rakefile +27 -0
  4. data/app/assets/images/droom/arrows.png +0 -0
  5. data/app/assets/images/droom/big_icons.png +0 -0
  6. data/app/assets/images/droom/blueblob.png +0 -0
  7. data/app/assets/images/droom/close.png +0 -0
  8. data/app/assets/images/droom/cross.png +0 -0
  9. data/app/assets/images/droom/download.png +0 -0
  10. data/app/assets/images/droom/greyblob.png +0 -0
  11. data/app/assets/images/droom/help/goodreader.jpg +0 -0
  12. data/app/assets/images/droom/ical.png +0 -0
  13. data/app/assets/images/droom/icons.png +0 -0
  14. data/app/assets/images/droom/medium_icons.png +0 -0
  15. data/app/assets/images/droom/medium_object_icons.png +0 -0
  16. data/app/assets/images/droom/minimonth.png +0 -0
  17. data/app/assets/images/droom/minisymbols.png +0 -0
  18. data/app/assets/images/droom/object_icons.png +0 -0
  19. data/app/assets/images/droom/pinkblob.png +0 -0
  20. data/app/assets/images/droom/place_busy.png +0 -0
  21. data/app/assets/images/droom/place_quiet.png +0 -0
  22. data/app/assets/images/droom/plus_bullet.png +0 -0
  23. data/app/assets/images/droom/search.png +0 -0
  24. data/app/assets/images/droom/setup.png +0 -0
  25. data/app/assets/images/droom/small_go.png +0 -0
  26. data/app/assets/images/droom/smallbullet.png +0 -0
  27. data/app/assets/images/droom/smallspinner.gif +0 -0
  28. data/app/assets/images/droom/spinner.gif +0 -0
  29. data/app/assets/images/droom/spr_toolbar_icons_r2.png +0 -0
  30. data/app/assets/images/droom/tablesort.png +0 -0
  31. data/app/assets/images/droom/tick.png +0 -0
  32. data/app/assets/images/droom/tinysymbols.png +0 -0
  33. data/app/assets/images/droom/twister.png +0 -0
  34. data/app/assets/images/droom/vcard.png +0 -0
  35. data/app/assets/images/droom/venue_bullet.png +0 -0
  36. data/app/assets/javascripts/droom.js.coffee +88 -0
  37. data/app/assets/javascripts/droom/calendar.js.coffee +121 -0
  38. data/app/assets/javascripts/droom/drag_sort.js.coffee +22 -0
  39. data/app/assets/javascripts/droom/forms.js.coffee +746 -0
  40. data/app/assets/javascripts/droom/lib/extensions.js.coffee +60 -0
  41. data/app/assets/javascripts/droom/lib/jquery.animate-colors.js +109 -0
  42. data/app/assets/javascripts/droom/lib/jquery.cookie.js +71 -0
  43. data/app/assets/javascripts/droom/lib/jquery.sortable.js +97 -0
  44. data/app/assets/javascripts/droom/lib/kalendae.js +1692 -0
  45. data/app/assets/javascripts/droom/lib/modernizr.js +4 -0
  46. data/app/assets/javascripts/droom/lib/parser_rules/advanced.js +553 -0
  47. data/app/assets/javascripts/droom/lib/parser_rules/simple.js +32 -0
  48. data/app/assets/javascripts/droom/lib/wysihtml5.js +9550 -0
  49. data/app/assets/javascripts/droom/map.js.coffee +123 -0
  50. data/app/assets/javascripts/droom/sort.js.coffee +126 -0
  51. data/app/assets/javascripts/droom/suggester.js.coffee +230 -0
  52. data/app/assets/stylesheets/droom.css.sass +1151 -0
  53. data/app/assets/stylesheets/lib/_kalendae.css.sass +142 -0
  54. data/app/assets/stylesheets/lib/_toolbar.css.sass +192 -0
  55. data/app/controllers/droom/dashboard_controller.rb +27 -0
  56. data/app/controllers/droom/document_attachments_controller.rb +19 -0
  57. data/app/controllers/droom/documents_controller.rb +116 -0
  58. data/app/controllers/droom/engine_controller.rb +43 -0
  59. data/app/controllers/droom/events_controller.rb +120 -0
  60. data/app/controllers/droom/group_invitations_controller.rb +47 -0
  61. data/app/controllers/droom/groups_controller.rb +67 -0
  62. data/app/controllers/droom/invitations_controller.rb +43 -0
  63. data/app/controllers/droom/memberships_controller.rb +47 -0
  64. data/app/controllers/droom/pages_controller.rb +61 -0
  65. data/app/controllers/droom/people_controller.rb +92 -0
  66. data/app/controllers/droom/suggestions_controller.rb +58 -0
  67. data/app/controllers/droom/venues_controller.rb +39 -0
  68. data/app/helpers/droom/droom_helper.rb +74 -0
  69. data/app/models/droom/agenda_category.rb +8 -0
  70. data/app/models/droom/category.rb +27 -0
  71. data/app/models/droom/document.rb +110 -0
  72. data/app/models/droom/document_attachment.rb +71 -0
  73. data/app/models/droom/document_link.rb +31 -0
  74. data/app/models/droom/event.rb +409 -0
  75. data/app/models/droom/event_set.rb +6 -0
  76. data/app/models/droom/group.rb +66 -0
  77. data/app/models/droom/group_invitation.rb +23 -0
  78. data/app/models/droom/invitation.rb +30 -0
  79. data/app/models/droom/membership.rb +27 -0
  80. data/app/models/droom/page.rb +26 -0
  81. data/app/models/droom/person.rb +302 -0
  82. data/app/models/droom/personal_document.rb +98 -0
  83. data/app/models/droom/recurrence_rule.rb +82 -0
  84. data/app/models/droom/venue.rb +125 -0
  85. data/app/views/droom/dashboard/_marginalia.html.haml +3 -0
  86. data/app/views/droom/dashboard/_my_future_events.html.haml +9 -0
  87. data/app/views/droom/dashboard/_my_group_documents.html.haml +6 -0
  88. data/app/views/droom/dashboard/_my_past_events.haml +6 -0
  89. data/app/views/droom/dashboard/index.html.haml +5 -0
  90. data/app/views/droom/documents/_created.html.haml +2 -0
  91. data/app/views/droom/documents/_document.html.haml +14 -0
  92. data/app/views/droom/documents/_document_line.html.haml +2 -0
  93. data/app/views/droom/documents/_documents_list.html.haml +31 -0
  94. data/app/views/droom/documents/_documents_table.html.haml +13 -0
  95. data/app/views/droom/documents/_event_document_form.html.haml +15 -0
  96. data/app/views/droom/documents/_form.html.haml +22 -0
  97. data/app/views/droom/documents/_listing.html.haml +8 -0
  98. data/app/views/droom/documents/_suggested.html.haml +9 -0
  99. data/app/views/droom/documents/_table_document.html.haml +31 -0
  100. data/app/views/droom/documents/edit.html.haml +1 -0
  101. data/app/views/droom/documents/index.html.haml +29 -0
  102. data/app/views/droom/documents/new.html.haml +1 -0
  103. data/app/views/droom/errors/bang.html.haml +12 -0
  104. data/app/views/droom/errors/not_allowed.html.haml +12 -0
  105. data/app/views/droom/errors/not_found.html.haml +12 -0
  106. data/app/views/droom/events/_attachment.html.haml +1 -0
  107. data/app/views/droom/events/_attachment_list.html.haml +4 -0
  108. data/app/views/droom/events/_calendar.html.haml +54 -0
  109. data/app/views/droom/events/_created.html.haml +2 -0
  110. data/app/views/droom/events/_event.html.haml +77 -0
  111. data/app/views/droom/events/_event_line.html.haml +13 -0
  112. data/app/views/droom/events/_events.html.haml +2 -0
  113. data/app/views/droom/events/_form.html.haml +35 -0
  114. data/app/views/droom/events/_invitations.html.haml +20 -0
  115. data/app/views/droom/events/_other_page_parts.html.haml +0 -0
  116. data/app/views/droom/events/_popup_event.html.haml +6 -0
  117. data/app/views/droom/events/_suggested.html.haml +12 -0
  118. data/app/views/droom/events/_views.html.haml +7 -0
  119. data/app/views/droom/events/edit.html.haml +1 -0
  120. data/app/views/droom/events/index.html.haml +12 -0
  121. data/app/views/droom/events/index.rss.builder +20 -0
  122. data/app/views/droom/events/new.html.haml +1 -0
  123. data/app/views/droom/events/show.html.haml +4 -0
  124. data/app/views/droom/group_invitations/_attending_groups.html.haml +8 -0
  125. data/app/views/droom/group_invitations/_created.html.haml +3 -0
  126. data/app/views/droom/group_invitations/_form.html.haml +4 -0
  127. data/app/views/droom/group_invitations/new.html.haml +1 -0
  128. data/app/views/droom/groups/_created.html.haml +3 -0
  129. data/app/views/droom/groups/_form.html.haml +12 -0
  130. data/app/views/droom/groups/_group.html.haml +11 -0
  131. data/app/views/droom/groups/_groups.html.haml +3 -0
  132. data/app/views/droom/groups/edit.html.haml +1 -0
  133. data/app/views/droom/groups/index.html.haml +7 -0
  134. data/app/views/droom/groups/show.html.haml +13 -0
  135. data/app/views/droom/invitations/_attending_people.html.haml +8 -0
  136. data/app/views/droom/invitations/_created.html.haml +3 -0
  137. data/app/views/droom/invitations/_form.html.haml +5 -0
  138. data/app/views/droom/invitations/new.html.haml +1 -0
  139. data/app/views/droom/memberships/_button.html.haml +11 -0
  140. data/app/views/droom/memberships/_created.html.haml +4 -0
  141. data/app/views/droom/memberships/_form.html.haml +7 -0
  142. data/app/views/droom/memberships/_member.html.haml +33 -0
  143. data/app/views/droom/memberships/_memberships.html.haml +4 -0
  144. data/app/views/droom/pages/_contents.html.haml +10 -0
  145. data/app/views/droom/pages/_form.html.haml +36 -0
  146. data/app/views/droom/pages/_full_page.html.haml +17 -0
  147. data/app/views/droom/pages/_page.html.haml +5 -0
  148. data/app/views/droom/pages/_pages.html.haml +2 -0
  149. data/app/views/droom/pages/admin.html.haml +24 -0
  150. data/app/views/droom/pages/edit.html.haml +1 -0
  151. data/app/views/droom/pages/index.html.haml +10 -0
  152. data/app/views/droom/pages/new.html.haml +4 -0
  153. data/app/views/droom/pages/show.html.haml +5 -0
  154. data/app/views/droom/people/_created.html.haml +6 -0
  155. data/app/views/droom/people/_form.html.haml +40 -0
  156. data/app/views/droom/people/_people.html.haml +29 -0
  157. data/app/views/droom/people/_person.html.haml +34 -0
  158. data/app/views/droom/people/_suggested.html.haml +9 -0
  159. data/app/views/droom/people/edit.html.haml +1 -0
  160. data/app/views/droom/people/index.html.haml +11 -0
  161. data/app/views/droom/people/new.html.haml +1 -0
  162. data/app/views/droom/people/show.html.haml +1 -0
  163. data/app/views/droom/shared/_calendar_and_search.html.haml +2 -0
  164. data/app/views/droom/shared/_calendar_holder.haml +3 -0
  165. data/app/views/droom/shared/_controls.html.haml +8 -0
  166. data/app/views/droom/shared/_navigation.html.haml +8 -0
  167. data/app/views/droom/shared/_search_form.html.haml +4 -0
  168. data/app/views/droom/shared/_suggestions.html.haml +11 -0
  169. data/app/views/droom/shared/_toolbar.html.haml +17 -0
  170. data/app/views/droom/venues/_suggested.html.haml +6 -0
  171. data/app/views/droom/venues/index.html.haml +6 -0
  172. data/app/views/droom/venues/show.html.haml +21 -0
  173. data/app/views/layouts/droom/application.html.haml +36 -0
  174. data/config/initializers/dav.rb +2 -0
  175. data/config/initializers/paperclip.rb +26 -0
  176. data/config/initializers/snail.rb +2 -0
  177. data/config/locales/en.yml +191 -0
  178. data/config/routes.rb +51 -0
  179. data/db/migrate/20120910075016_create_droom_data.rb +134 -0
  180. data/db/migrate/20120917095804_agenda_sections.rb +13 -0
  181. data/db/migrate/20120918121352_add_postal_address_to_people.rb +10 -0
  182. data/db/migrate/20121009075049_give_groups_descriptions.rb +5 -0
  183. data/db/migrate/20121009105244_more_names.rb +5 -0
  184. data/db/migrate/20121009145944_event_agenda_sections.rb +13 -0
  185. data/db/migrate/20121011091230_create_group_invitations.rb +10 -0
  186. data/db/migrate/20121012144720_give_people_positions.rb +5 -0
  187. data/db/migrate/20121012154558_help_pages.rb +13 -0
  188. data/db/migrate/20121012163201_category_slugs.rb +5 -0
  189. data/db/migrate/20121101160102_document_links.rb +14 -0
  190. data/db/migrate/20121101181247_people_visibility.rb +7 -0
  191. data/db/migrate/20121102094738_shy_people.rb +6 -0
  192. data/db/migrate/20121102095856_visibility_defaults.rb +8 -0
  193. data/lib/droom.rb +73 -0
  194. data/lib/droom/dav_resource.rb +36 -0
  195. data/lib/droom/engine.rb +8 -0
  196. data/lib/droom/helpers.rb +25 -0
  197. data/lib/droom/monkeys.rb +15 -0
  198. data/lib/droom/renderers.rb +13 -0
  199. data/lib/droom/validators.rb +5 -0
  200. data/lib/droom/version.rb +3 -0
  201. data/lib/generators/droom/dashboard/dashboard_generator.rb +16 -0
  202. data/lib/generators/droom/install/USAGE +9 -0
  203. data/lib/generators/droom/install/install_generator.rb +15 -0
  204. data/lib/generators/droom/install/templates/droom_initializer.rb +6 -0
  205. data/lib/generators/droom/views/views_generator.rb +9 -0
  206. data/lib/tasks/droom_tasks.rake +4 -0
  207. metadata +635 -0
@@ -0,0 +1,123 @@
1
+ jQuery ($) ->
2
+ $.infowindows = []
3
+
4
+ class Map
5
+ constructor: (element) ->
6
+ @container = $(element)
7
+ lat = @container.attr('data-lat') || 22.280147
8
+ lng = @container.attr('data-lng') || 114.158302
9
+ zoom = @container.attr('data-zoom') || 12
10
+ @map = new google.maps.Map element,
11
+ center: new google.maps.LatLng lat, lng
12
+ zoom: parseInt(zoom)
13
+ mapTypeId: google.maps.MapTypeId.ROADMAP
14
+ mapTypeControlOptions:
15
+ style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
16
+
17
+ getMap: () =>
18
+ @map
19
+
20
+ class Point
21
+ constructor: (point, map) ->
22
+ @_lat = point.lat
23
+ @_lng = point.lng
24
+ @_map = map
25
+ @_events = point.events
26
+ @_postcode = point.postcode
27
+ @id = point.id
28
+ @_name = point.name
29
+ @_address = point.address
30
+ @_editable = $.ed
31
+ @_marker = new google.maps.Marker
32
+ position: new google.maps.LatLng @_lat, @_lng
33
+ map: @_map
34
+ icon: $.set_icon(@_events.length)
35
+ draggable: $.map_editable
36
+ @infowindow()
37
+ google.maps.event.addListener @_marker, "click", @click
38
+ google.maps.event.addListener @_marker, "dragstart", @dragstart
39
+ google.maps.event.addListener @_marker, "dragend", @dragend
40
+ @_infowindow.open(@_map) if parseInt($.urlParam("id"), 10) == @id
41
+
42
+
43
+ infowindow: () =>
44
+ content = $("<div class='window'><h2>#{@_name}</h2>#{@_address.replace(/\n/g, ",")}<div class='window_venue_events'></div></div>")
45
+ $.each @_events, (i, evt) =>
46
+ content.find('.window_venue_events').append "<li><a href='/events/#{evt.id}'>#{evt.name}</a> <span class='pale'>#{evt.datestring}</span></li>"
47
+ @_infowindow = new google.maps.InfoWindow
48
+ position: new google.maps.LatLng @_lat, @_lng
49
+ content: content.prop('outerHTML')
50
+ marker: @_marker
51
+ $.infowindows.push @_infowindow
52
+
53
+ click: (e) =>
54
+ $.each $.infowindows, (i, iw) =>
55
+ iw.close()
56
+ @_infowindow.open(@_map)
57
+
58
+ dragstart: (e) =>
59
+ @_infowindow?.close()
60
+
61
+ dragend: (e) =>
62
+ position = @_marker.getPosition()
63
+ $.ajax
64
+ type: 'POST'
65
+ url: "/venues/#{@id}",
66
+ data:
67
+ _method: "PUT"
68
+ venue:
69
+ lat: position.lat()
70
+ lng: position.lng()
71
+ dataType: 'json'
72
+ success: () ->
73
+ console.log "venue updated"
74
+
75
+
76
+ $.fn.init_map = () ->
77
+ @each ->
78
+ $.gmap = new Map(@).getMap()
79
+ $.map_editable = $(@).hasClass('editable')
80
+ if $(@).hasClass('small')
81
+ $(@).show_venue()
82
+ else
83
+ $(@).show_venues()
84
+
85
+ $.fn.show_venues = () ->
86
+ @each ->
87
+ map = $.gmap
88
+ if src = $(@).attr("data-url")
89
+ $.getJSON src, (response) =>
90
+ bounds = new google.maps.LatLngBounds()
91
+ $.each response, (i, venue) =>
92
+ latlng = new google.maps.LatLng venue.lat, venue.lng
93
+ bounds.extend latlng
94
+ venue = new Point venue, map
95
+
96
+ temporary_zoom_limiter = google.maps.event.addListener map, 'bounds_changed', ->
97
+ map.setZoom(17) if map.getZoom() > 17
98
+ google.maps.event.removeListener(temporary_zoom_limiter);
99
+
100
+ map.fitBounds bounds
101
+
102
+ $.fn.show_venue = () ->
103
+ @each ->
104
+ map = $.gmap
105
+ if src = $(@).attr("data-url")
106
+ $.getJSON src, (venue) =>
107
+ latlng = new google.maps.LatLng venue.lat, venue.lng
108
+ marker = new google.maps.Marker
109
+ position: latlng
110
+ map: map
111
+ icon: $.set_icon(venue.events.length)
112
+ map.setCenter(latlng)
113
+
114
+
115
+ $.urlParam = (name) ->
116
+ results = new RegExp("[\\?&]" + name + "=([^&#]*)").exec(window.location.href)
117
+ return 0 unless results
118
+ results[1] or 0
119
+
120
+ $.set_icon = (size) ->
121
+ name = if size > 0 then "place_busy" else "place_quiet"
122
+ new google.maps.MarkerImage "/assets/droom/#{name}.png", new google.maps.Size(36, 36), new google.maps.Point(0, 0), new google.maps.Point(18, 18)
123
+
@@ -0,0 +1,126 @@
1
+ jQuery ($) ->
2
+ $.headers = []
3
+
4
+ $.params = (name) ->
5
+ decodeURIComponent $.urlParam(name)
6
+
7
+ class TableSort
8
+ constructor: (element, options) ->
9
+ @order = options.order
10
+ @sort = options.sort
11
+ @table = $(element)
12
+ @body = @table.find('tbody')
13
+ @_original_content = @body.children()
14
+ @sort = $.params("sort")
15
+ @order = $.params("order")
16
+ @headers = @table.find('th a.sorter')
17
+ $.each @headers, (i, header) =>
18
+ header = new SortLink header, @
19
+ $.headers.push header
20
+ @activate()
21
+ @table.bind "refresh", @update
22
+ if Modernizr.history
23
+ $(window).bind 'popstate', @restoreState
24
+
25
+ resort: (sort, order) =>
26
+ sort ?= "name"
27
+ order ?= "ASC"
28
+ @sort = sort
29
+ @order = order
30
+ @get("/documents.js?sort=#{sort}&order=#{order}")
31
+
32
+ update: () =>
33
+ @body.fadeTo('fast', 0.2)
34
+ $.ajax
35
+ url: "/documents.js?sort=#{@sort}&order=#{@order}"
36
+ dataType: "html"
37
+ success: @display
38
+
39
+ get: (url) =>
40
+ @body.fadeTo('fast', 0.2)
41
+ $.ajax
42
+ url: (url)
43
+ dataType: "html"
44
+ success: @refresh
45
+
46
+ refresh: (data, textStatus, jqXHR) =>
47
+ @clear()
48
+ @saveState(data) if Modernizr.history
49
+ @display(data)
50
+
51
+ display: (data) =>
52
+ replacement = $(data)
53
+ @body.after(replacement)
54
+ @body.remove()
55
+ @body = replacement
56
+ @body.fadeTo('fast', 1)
57
+ @body.activate()
58
+ @activate()
59
+
60
+ activate: () =>
61
+ @body.find('a.popup').popup_remote_content()
62
+ @body.find('.pagination').find('a').retable(@)
63
+ @body.refresher()
64
+
65
+ clear: () =>
66
+ @body.children().remove()
67
+
68
+ saveState: (results) =>
69
+ url = window.location.pathname + '?sort=' + encodeURIComponent(@sort) + '&order=' + encodeURIComponent(@order)
70
+ state =
71
+ html: results
72
+ sort: @sort
73
+ order: @order
74
+ history.pushState state, "New page title", url
75
+
76
+ restoreState: (e) =>
77
+ event = e.originalEvent
78
+ if event.state? && event.state.html?
79
+ @clear()
80
+ @order = event.state.order
81
+ @sort = event.state.sort
82
+ @display(event.state.html)
83
+ $.each $.headers, (i, header) =>
84
+ header.check()
85
+
86
+
87
+ class SortLink
88
+ constructor: (element, table) ->
89
+ @table = table
90
+ @_link = $(element)
91
+ @_sort = @_link.attr("data-sort")
92
+ @_link.bind "click", @click
93
+ @check()
94
+ @_order = @_link.attr("data-order")
95
+
96
+ check: () =>
97
+ if @table.sort == @_sort
98
+ @table.headers.removeAttr('data-order')
99
+ @_link.attr("data-order", @table.order)
100
+
101
+ click: (e) =>
102
+ e.preventDefault()
103
+ @table.headers.removeAttr('data-order')
104
+ if @_order == "ASC"
105
+ @_link.attr('data-order', 'DESC')
106
+ @_order = "DESC"
107
+ else
108
+ @_link.attr('data-order', 'ASC')
109
+ @_order = "ASC"
110
+ @table.resort(@_sort, @_order)
111
+
112
+
113
+ $.fn.table_sort = (options) ->
114
+ @each ->
115
+ new TableSort @, options
116
+ @
117
+
118
+ $.fn.retable = (table) ->
119
+ @click (e) ->
120
+ e.preventDefault() if e
121
+ url = $(@).attr('href')
122
+ table.get(url)
123
+ @
124
+
125
+
126
+
@@ -0,0 +1,230 @@
1
+ jQuery ($) ->
2
+
3
+ # The Suggester is a polymorphic suggestion engine able to support any number of object types
4
+ # without really knowing what they are.
5
+ #
6
+ # * calling
7
+ # * data structure
8
+ # * object types
9
+ #
10
+ $.fn.suggestible = (options) ->
11
+ options = $.extend(
12
+ submit_form: true
13
+ threshold: 3
14
+ , options)
15
+ @each ->
16
+ new Suggester(@, options)
17
+ @
18
+
19
+ $.fn.venue_picker = (options) ->
20
+ options = $.extend(
21
+ submit_form: true
22
+ threshold: 1
23
+ type: 'venue'
24
+ , options)
25
+ @each ->
26
+ new Suggester(@, options)
27
+ @
28
+
29
+ $.fn.person_picker = (options) ->
30
+ options = $.extend(
31
+ submit_form: true
32
+ threshold: 1
33
+ type: 'person'
34
+ , options)
35
+ @each ->
36
+ target = $(@).siblings('.person_picker_target')
37
+ $(@).bind "keyup", () =>
38
+ target.val null
39
+ suggester = new Suggester(@, options)
40
+ suggester.options.afterSelect = () ->
41
+ id = JSON.parse(suggester.request.responseText)[0].id
42
+ target.val id
43
+ suggester.form.submit()
44
+ @
45
+
46
+ $.fn.group_picker = (options) ->
47
+ options = $.extend(
48
+ submit_form: true
49
+ threshold: 1
50
+ type: 'group'
51
+ , options)
52
+ @each ->
53
+ target = $(@).siblings('.group_picker_target')
54
+ $(@).bind "keyup", () =>
55
+ target.val null
56
+ suggester = new Suggester(@, options)
57
+ suggester.options.afterSelect = () ->
58
+ id = JSON.parse(suggester.request.responseText)[0].id
59
+ target.val id
60
+ suggester.form.submit()
61
+ @
62
+
63
+
64
+ class Suggester
65
+ constructor: (element, options) ->
66
+ @prompt = $(element)
67
+ @type = @prompt.attr('data-type')
68
+ @form = @prompt.parents("form")
69
+ if options.type
70
+ @url = "/suggestions/#{options.type}"
71
+ else
72
+ @url = "/suggestions"
73
+ @options = $.extend(
74
+ url: @url
75
+ fill_field: true
76
+ empty_field: false
77
+ submit_form: false
78
+ threshold: 2
79
+ afterSuggest: null
80
+ afterSelect: null
81
+ , options)
82
+ @container = $("<ul class=\"suggestions\"></ul>").insertAfter(@prompt)
83
+ @button = @form.find("a.search")
84
+ @previously = null
85
+ @request = null
86
+ @visible = false
87
+ @suggestions = []
88
+ @suggestion = null
89
+ @cache = {}
90
+ @blanks = []
91
+
92
+ @prompt.keyup @key
93
+ @form.submit @hide
94
+ @
95
+
96
+ place: () =>
97
+ @container.css
98
+ top: @prompt.position().top + @prompt.outerHeight() - 2
99
+ left: @prompt.position().left
100
+ width: @prompt.outerWidth() - 2
101
+
102
+ reset: () =>
103
+ @container.empty()
104
+ @suggestions = []
105
+ @suggestion = null
106
+
107
+ pend: () =>
108
+ @place()
109
+ @reset()
110
+ @button.addClass "waiting"
111
+
112
+ get: (e) =>
113
+ @pend()
114
+ query = @prompt.val()
115
+ if query.length >= @options.threshold and query isnt @previously
116
+ if @cache[query]
117
+ @suggest @cache[query]
118
+ else if @previously_blank(query)
119
+ @suggest []
120
+ else
121
+ @request.abort() if @request
122
+ @request = $.getJSON(@options.url, "term=" + encodeURIComponent(query), (suggestions) =>
123
+ @cache[query] = suggestions
124
+ @blanks.push query if suggestions.length is 0
125
+ @suggest suggestions
126
+ )
127
+ else
128
+ @hide()
129
+
130
+ suggest: (suggestions) =>
131
+ @button.removeClass "waiting"
132
+ @show()
133
+ if suggestions.length > 0
134
+ $.each suggestions, (i, suggestion) =>
135
+ link = $("<a href=\"#\">#{suggestion.text}</a>")
136
+ link.hover () =>
137
+ @hover(link)
138
+ link.click (e) =>
139
+ @select(e, link)
140
+ $("<li></li>").addClass(suggestion.type).append(link).appendTo @container
141
+
142
+ @suggestions = @container.find("a")
143
+ else
144
+ @hide()
145
+ @options.afterSuggest.call @, suggestions if @options.afterSuggest
146
+
147
+ select: (e, selection) =>
148
+ e.preventDefault() if e
149
+ selection ?= $(@suggestions.get(@suggestion))
150
+ value = selection.text()
151
+ if @options.fill_field
152
+ @prompt.val value
153
+ else @prompt.val "" if @options.empty_field
154
+ @form.submit() if @options.submit_form
155
+ @options.afterSelect.call @, value if @options.afterSelect
156
+ @hide()
157
+
158
+ show: () =>
159
+ unless @visible
160
+ @container.fadeIn "slow"
161
+ @visible = true
162
+
163
+ hide: () =>
164
+ if @visible
165
+ @container.fadeOut "fast"
166
+ @visible = false
167
+
168
+ key: (e) =>
169
+ key_code = e.which
170
+ if @movement(key_code)
171
+ @show() if @suggestions.length > 0
172
+ if @visible
173
+ @movement(key_code).call @, e
174
+ e.preventDefault()
175
+ e.stopPropagation()
176
+ else
177
+ @get e
178
+
179
+ movement: (key_code) =>
180
+ switch key_code
181
+ when 27 # escape
182
+ @hide
183
+ when 33 # page up
184
+ @first
185
+ when 38 # up
186
+ @previous
187
+ when 40 # down
188
+ @next
189
+ when 33 # page down
190
+ @last
191
+ when 9 # tab
192
+ @next
193
+ when 13 # enter
194
+ @select
195
+
196
+ next: (e) =>
197
+ if @suggestion is null or @suggestion >= @suggestions.length - 1
198
+ @first()
199
+ else
200
+ @highlight @suggestion + 1
201
+
202
+ previous: (e) =>
203
+ if @suggestion <= 0
204
+ @last()
205
+ else
206
+ @highlight @suggestion - 1
207
+
208
+ first: (e) =>
209
+ @highlight 0
210
+
211
+ last: (e) =>
212
+ @highlight @suggestions.length - 1
213
+
214
+ hover: (link) =>
215
+ @highlight @suggestions.index(link) # this will be the hovered link
216
+
217
+ highlight: (i) =>
218
+ @unHighlight @suggestion if @suggestion isnt null
219
+ $(@suggestions.get(i)).addClass "hover"
220
+ @suggestion = i
221
+
222
+ unHighlight: (i) =>
223
+ $(@suggestions.get(i)).removeClass "hover"
224
+ @suggestion = null
225
+
226
+ previously_blank: (query) =>
227
+ if @blanks.length > 0
228
+ blank_re = new RegExp("(" + @blanks.join("|") + ")")
229
+ return blank_re.test(query)
230
+ false