droom 0.0.1

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