worthwhile 0.0.1.alpha → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +19 -0
  5. data/Gemfile +7 -11
  6. data/README.md +8 -12
  7. data/Rakefile +17 -2
  8. data/app/actors/concerns/worthwhile/manages_embargoes_actor.rb +79 -0
  9. data/app/actors/curation_concern/base_actor.rb +68 -0
  10. data/app/actors/curation_concern/generic_file_actor.rb +40 -0
  11. data/app/actors/curation_concern/generic_work_actor.rb +92 -0
  12. data/app/actors/curation_concern/linked_resource_actor.rb +4 -0
  13. data/app/assets/images/default.png +0 -0
  14. data/app/assets/images/nope.png +0 -0
  15. data/app/assets/javascripts/.gitkeep +0 -0
  16. data/app/assets/javascripts/modernizr.js +3 -0
  17. data/app/assets/javascripts/worthwhile/.gitkeep +0 -0
  18. data/app/assets/javascripts/worthwhile/accept_contributor_agreement.js +15 -0
  19. data/app/assets/javascripts/worthwhile/application.js +18 -0
  20. data/app/assets/javascripts/worthwhile/browse_everything_implement.js +7 -0
  21. data/app/assets/javascripts/worthwhile/embargoes.js +17 -0
  22. data/app/assets/javascripts/worthwhile/facet_mine.js +22 -0
  23. data/app/assets/javascripts/worthwhile/help_modal.js +36 -0
  24. data/app/assets/javascripts/worthwhile/link_groups.js.coffee +123 -0
  25. data/app/assets/javascripts/worthwhile/link_users.js.coffee +126 -0
  26. data/app/assets/javascripts/worthwhile/manage_repeating_fields.js +74 -0
  27. data/app/assets/javascripts/worthwhile/proxy_rights.js.coffee +95 -0
  28. data/app/assets/javascripts/worthwhile/proxy_submission.js +23 -0
  29. data/app/assets/javascripts/worthwhile/select_works.js.coffee +20 -0
  30. data/app/assets/javascripts/worthwhile/worthwhile.js +45 -0
  31. data/app/assets/stylesheets/worthwhile.css.scss +23 -0
  32. data/app/assets/stylesheets/worthwhile/_global-variables.css.scss +5 -0
  33. data/app/assets/stylesheets/worthwhile/_modules.css.scss +14 -0
  34. data/app/assets/stylesheets/worthwhile/_positioning.css.scss +138 -0
  35. data/app/assets/stylesheets/worthwhile/_theme.css.scss +46 -0
  36. data/app/assets/stylesheets/worthwhile/_typography.css.scss +142 -0
  37. data/app/assets/stylesheets/worthwhile/_variables_bootstrap.css.scss +7 -0
  38. data/app/assets/stylesheets/worthwhile/_worthwhile.css.scss +7 -0
  39. data/app/assets/stylesheets/worthwhile/downloads.css.scss +3 -0
  40. data/app/assets/stylesheets/worthwhile/help_requests.css.scss +3 -0
  41. data/app/assets/stylesheets/worthwhile/modules/accessibility.css.scss +50 -0
  42. data/app/assets/stylesheets/worthwhile/modules/accordion.css.scss +33 -0
  43. data/app/assets/stylesheets/worthwhile/modules/attributes.css.scss +26 -0
  44. data/app/assets/stylesheets/worthwhile/modules/classify_work.css.scss +100 -0
  45. data/app/assets/stylesheets/worthwhile/modules/collections.css.scss +42 -0
  46. data/app/assets/stylesheets/worthwhile/modules/embargoes.css.scss +15 -0
  47. data/app/assets/stylesheets/worthwhile/modules/emphatic_action_area.css.scss +14 -0
  48. data/app/assets/stylesheets/worthwhile/modules/forms.css.scss +116 -0
  49. data/app/assets/stylesheets/worthwhile/modules/multi_value_fields.css.scss +52 -0
  50. data/app/assets/stylesheets/worthwhile/modules/pagination.css.scss +4 -0
  51. data/app/assets/stylesheets/worthwhile/modules/search_results.css.scss +55 -0
  52. data/app/assets/stylesheets/worthwhile/modules/site_actions.css.scss +53 -0
  53. data/app/assets/stylesheets/worthwhile/modules/site_search.css.scss +46 -0
  54. data/app/controllers/catalog_controller.rb +3 -0
  55. data/app/controllers/collections_controller.rb +59 -0
  56. data/app/controllers/concerns/worthwhile/application_controller_behavior.rb +23 -0
  57. data/app/controllers/concerns/worthwhile/catalog_controller.rb +347 -0
  58. data/app/controllers/concerns/worthwhile/curation_concern_controller.rb +131 -0
  59. data/app/controllers/concerns/worthwhile/files_controller.rb +149 -0
  60. data/app/controllers/concerns/worthwhile/manages_embargoes.rb +22 -0
  61. data/app/controllers/concerns/worthwhile/parent_container.rb +31 -0
  62. data/app/controllers/concerns/worthwhile/themed_layout_controller.rb +31 -0
  63. data/app/controllers/concerns/worthwhile/without_namespace.rb +15 -0
  64. data/app/controllers/curation_concern/generic_files_controller.rb +6 -0
  65. data/app/controllers/curation_concern/generic_works_controller.rb +6 -0
  66. data/app/controllers/curation_concern/linked_resources_controller.rb +68 -0
  67. data/app/controllers/curation_concern/permissions_controller.rb +19 -0
  68. data/app/controllers/downloads_controller.rb +4 -0
  69. data/app/controllers/embargoes_controller.rb +44 -0
  70. data/app/controllers/leases_controller.rb +34 -0
  71. data/app/controllers/registrations_controller.rb +20 -0
  72. data/app/controllers/sessions_controller.rb +4 -0
  73. data/app/controllers/worthwhile/application_controller.rb +7 -0
  74. data/app/controllers/worthwhile/classify_concerns_controller.rb +35 -0
  75. data/app/datastreams/generic_work_metadata.rb +3 -0
  76. data/app/datastreams/worthwhile/generic_work_rdf_properties.rb +57 -0
  77. data/app/datastreams/worthwhile/properties_datastream.rb +28 -0
  78. data/app/helpers/curate/collections_helper.rb +133 -0
  79. data/app/helpers/worthwhile/ability_helper.rb +46 -0
  80. data/app/helpers/worthwhile/attribute_helper.rb +67 -0
  81. data/app/helpers/worthwhile/catalog_helper.rb +38 -0
  82. data/app/helpers/worthwhile/collections_helper.rb +19 -0
  83. data/app/helpers/worthwhile/embargo_helper.rb +17 -0
  84. data/app/helpers/worthwhile/generic_file_helper.rb +19 -0
  85. data/app/helpers/worthwhile/lease_helper.rb +18 -0
  86. data/app/helpers/worthwhile/main_app_helpers.rb +14 -0
  87. data/app/helpers/worthwhile/render_constraints_helper.rb +42 -0
  88. data/app/helpers/worthwhile/search_paths_helper.rb +13 -0
  89. data/app/helpers/worthwhile/thumbnail_helper.rb +11 -0
  90. data/app/helpers/worthwhile/title_helper.rb +23 -0
  91. data/app/helpers/worthwhile/url_helper.rb +16 -0
  92. data/app/inputs/multi_value_input.rb +72 -0
  93. data/app/models/collection.rb +15 -0
  94. data/app/models/concerns/curation_concern/collection_model.rb +62 -0
  95. data/app/models/concerns/curation_concern/curatable.rb +77 -0
  96. data/app/models/concerns/curation_concern/has_representative.rb +14 -0
  97. data/app/models/concerns/curation_concern/human_readable_type.rb +23 -0
  98. data/app/models/concerns/curation_concern/with_basic_metadata.rb +49 -0
  99. data/app/models/concerns/curation_concern/with_editors.rb +44 -0
  100. data/app/models/concerns/curation_concern/with_generic_files.rb +23 -0
  101. data/app/models/concerns/curation_concern/with_linked_resources.rb +21 -0
  102. data/app/models/concerns/curation_concern/work.rb +24 -0
  103. data/app/models/concerns/worthwhile/ability.rb +34 -0
  104. data/app/models/concerns/worthwhile/generic_file/versioned_content.rb +18 -0
  105. data/app/models/concerns/worthwhile/generic_file_base.rb +64 -0
  106. data/app/models/concerns/worthwhile/solr_document_behavior.rb +140 -0
  107. data/app/models/generic_work.rb +5 -0
  108. data/app/models/worthwhile/classify_concern.rb +47 -0
  109. data/app/models/worthwhile/content_version.rb +23 -0
  110. data/app/models/worthwhile/contributor_agreement.rb +23 -0
  111. data/app/models/worthwhile/generic_file.rb +5 -0
  112. data/app/models/worthwhile/linked_resource.rb +41 -0
  113. data/app/models/worthwhile/quick_classification_query.rb +31 -0
  114. data/app/services/worthwhile/curation_concern.rb +21 -0
  115. data/app/services/worthwhile/embargo_service.rb +26 -0
  116. data/app/services/worthwhile/lease_service.rb +23 -0
  117. data/app/views/catalog/_action_menu_partials/_collection.html.erb +27 -0
  118. data/app/views/catalog/_action_menu_partials/_default.html.erb +27 -0
  119. data/app/views/catalog/_document.html.erb +3 -0
  120. data/app/views/catalog/_document_list.html.erb +5 -0
  121. data/app/views/catalog/_home_text.html.erb +8 -0
  122. data/app/views/catalog/_index_default.html.erb +17 -0
  123. data/app/views/catalog/_index_header_list_default.html.erb +17 -0
  124. data/app/views/catalog/_navbar.html.erb +12 -0
  125. data/app/views/catalog/_show_partials/_default.html.erb +22 -0
  126. data/app/views/catalog/_show_partials/_default_details.html.erb +15 -0
  127. data/app/views/catalog/_show_partials/_facets.html.erb +52 -0
  128. data/app/views/catalog/index.html.erb +48 -0
  129. data/app/views/collections/_batch_edits_actions.html.erb +3 -0
  130. data/app/views/collections/_button_for_creating_empty_collection.html.erb +3 -0
  131. data/app/views/collections/_collection.html.erb +1 -0
  132. data/app/views/collections/_dashboard_document_list.html.erb +6 -0
  133. data/app/views/collections/_document_list.html.erb +7 -0
  134. data/app/views/collections/_edit_actions.html.erb +5 -0
  135. data/app/views/collections/_edit_descriptions.html.erb +20 -0
  136. data/app/views/collections/_form.html.erb +26 -0
  137. data/app/views/collections/_form_for_select_collection.html.erb +34 -0
  138. data/app/views/collections/_form_permission.html.erb +36 -0
  139. data/app/views/collections/_form_required_information.html.erb +11 -0
  140. data/app/views/collections/_identifier_and_action.html.erb +15 -0
  141. data/app/views/collections/_media_display.html.erb +1 -0
  142. data/app/views/collections/_paginate.html.erb +6 -0
  143. data/app/views/collections/_search_collection_dashboard_form.html.erb +10 -0
  144. data/app/views/collections/_show_actions.html.erb +8 -0
  145. data/app/views/collections/_show_descriptions.html.erb +10 -0
  146. data/app/views/collections/_show_fields.html.erb +0 -0
  147. data/app/views/collections/_single_item_action_fields.html.erb +6 -0
  148. data/app/views/collections/_sort_and_per_page.html.erb +29 -0
  149. data/app/views/collections/_view_type_group.html.erb +13 -0
  150. data/app/views/collections/edit.html.erb +22 -0
  151. data/app/views/collections/new.html.erb +15 -0
  152. data/app/views/collections/show.html.erb +24 -0
  153. data/app/views/curate/collections/_add_to_collection_modal.html.erb +15 -0
  154. data/app/views/curate/collections/_button_remove_from_collection.html.erb +6 -0
  155. data/app/views/curate/collections/_form_to_add_member.html.erb +27 -0
  156. data/app/views/curate/collections/add_member_form.html.erb +6 -0
  157. data/app/views/curation_concern/base/_attributes.html.erb +25 -0
  158. data/app/views/curation_concern/base/_collections.html.erb +26 -0
  159. data/app/views/curation_concern/base/_form.html.erb +27 -0
  160. data/app/views/curation_concern/base/_form_additional_information.html.erb +9 -0
  161. data/app/views/curation_concern/base/_form_content_license.html.erb +16 -0
  162. data/app/views/curation_concern/base/_form_contributor_agreement.html.erb +29 -0
  163. data/app/views/curation_concern/base/_form_descriptive_fields.erb +23 -0
  164. data/app/views/curation_concern/base/_form_files_and_links.html.erb +29 -0
  165. data/app/views/curation_concern/base/_form_permission.html.erb +52 -0
  166. data/app/views/curation_concern/base/_form_permission_embargo.html.erb +6 -0
  167. data/app/views/curation_concern/base/_form_permission_lease.html.erb +6 -0
  168. data/app/views/curation_concern/base/_form_permission_under_embargo.html.erb +16 -0
  169. data/app/views/curation_concern/base/_form_permission_under_lease.html.erb +18 -0
  170. data/app/views/curation_concern/base/_form_representative_image.html.erb +13 -0
  171. data/app/views/curation_concern/base/_form_required_information.html.erb +11 -0
  172. data/app/views/curation_concern/base/_form_supplementary_fields.html.erb +15 -0
  173. data/app/views/curation_concern/base/_legally_binding_text.html.erb +33 -0
  174. data/app/views/curation_concern/base/_related_files.html.erb +24 -0
  175. data/app/views/curation_concern/base/_related_resources.html.erb +40 -0
  176. data/app/views/curation_concern/base/_representative_media.html.erb +8 -0
  177. data/app/views/curation_concern/base/edit.html.erb +15 -0
  178. data/app/views/curation_concern/base/new.html.erb +14 -0
  179. data/app/views/curation_concern/base/show.html.erb +32 -0
  180. data/app/views/curation_concern/base/unauthorized.html.erb +3 -0
  181. data/app/views/curation_concern/generic_files/_form.html.erb +35 -0
  182. data/app/views/curation_concern/generic_files/_media_display.html.erb +20 -0
  183. data/app/views/curation_concern/generic_files/edit.html.erb +5 -0
  184. data/app/views/curation_concern/generic_files/show.html.erb +15 -0
  185. data/app/views/curation_concern/generic_works/_generic_work.html.erb +3 -0
  186. data/app/views/curation_concern/linked_resources/_form.html.erb +11 -0
  187. data/app/views/curation_concern/linked_resources/edit.html.erb +5 -0
  188. data/app/views/curation_concern/linked_resources/new.html.erb +5 -0
  189. data/app/views/curation_concern/permissions/confirm.html.erb +13 -0
  190. data/app/views/curation_concern/worthwhile/generic_files/_actions.html.erb +16 -0
  191. data/app/views/curation_concern/worthwhile/generic_files/_generic_file.html.erb +12 -0
  192. data/app/views/embargoes/_embargo_history.html.erb +7 -0
  193. data/app/views/embargoes/_list_active_embargoes.html.erb +16 -0
  194. data/app/views/embargoes/_list_deactivated_embargoes.html.erb +10 -0
  195. data/app/views/embargoes/_list_expired_active_embargoes.html.erb +44 -0
  196. data/app/views/embargoes/edit.html.erb +43 -0
  197. data/app/views/embargoes/index.html.erb +18 -0
  198. data/app/views/layouts/boilerplate.html.erb +22 -0
  199. data/app/views/layouts/common_objects.html.erb +36 -0
  200. data/app/views/layouts/curate_nd.html.erb +33 -0
  201. data/app/views/layouts/curate_nd/1_column.html.erb +22 -0
  202. data/app/views/layouts/curate_nd/2_column.html.erb +26 -0
  203. data/app/views/layouts/curate_nd/catalog.html.erb +38 -0
  204. data/app/views/layouts/curate_nd/dashboard.html.erb +22 -0
  205. data/app/views/leases/_lease_history.html.erb +7 -0
  206. data/app/views/leases/_list_active_leases.html.erb +16 -0
  207. data/app/views/leases/_list_deactivated_leases.html.erb +4 -0
  208. data/app/views/leases/_list_expired_active_leases.html.erb +38 -0
  209. data/app/views/leases/edit.html.erb +43 -0
  210. data/app/views/leases/index.html.erb +18 -0
  211. data/app/views/shared/_add_content.html.erb +32 -0
  212. data/app/views/shared/_brand_bar.html.erb +10 -0
  213. data/app/views/shared/_flash_message.html.erb +17 -0
  214. data/app/views/shared/_footer.html.erb +10 -0
  215. data/app/views/shared/_ga.html.erb +6 -0
  216. data/app/views/shared/_header.html.erb +12 -0
  217. data/app/views/shared/_my_actions.html.erb +21 -0
  218. data/app/views/shared/_site_actions.html.erb +7 -0
  219. data/app/views/shared/_site_search.html.erb +12 -0
  220. data/app/views/shared/_title_bar.html.erb +16 -0
  221. data/app/views/worthwhile/classify_concerns/new.html.erb +27 -0
  222. data/app/workers/visibility_copy_worker.rb +29 -0
  223. data/config/initializers/simple_form.rb +31 -0
  224. data/config/locales/sufia.en.yml +26 -0
  225. data/config/locales/worthwhile.en.yml +68 -0
  226. data/config/routes.rb +4 -0
  227. data/lib/generators/worthwhile/install_generator.rb +74 -0
  228. data/lib/generators/worthwhile/templates/worthwhile.css.scss +3 -0
  229. data/lib/generators/worthwhile/templates/worthwhile.js +1 -0
  230. data/lib/generators/worthwhile/templates/worthwhile_config.rb +3 -0
  231. data/lib/generators/worthwhile/templates/worthwhile_helper.rb +4 -0
  232. data/lib/worthwhile.rb +3 -1
  233. data/lib/worthwhile/configuration.rb +74 -0
  234. data/lib/worthwhile/controller_resource.rb +10 -0
  235. data/lib/worthwhile/engine.rb +20 -0
  236. data/lib/worthwhile/rails/routes.rb +78 -0
  237. data/lib/worthwhile/spec_support.rb +10 -0
  238. data/lib/worthwhile/version.rb +1 -1
  239. data/spec/abilities/generic_file_abilities_spec.rb +62 -0
  240. data/spec/abilities/generic_work_abilities_spec.rb +58 -0
  241. data/spec/actors/curation_concern/generic_work_actor_spec.rb +215 -0
  242. data/spec/actors/curation_concern/linked_resource_actor_spec.rb +36 -0
  243. data/spec/actors/worthwile/manages_embargoes_actor_spec.rb +95 -0
  244. data/spec/controllers/catalog_controller_spec.rb +114 -0
  245. data/spec/controllers/collections_controller_spec.rb +216 -0
  246. data/spec/controllers/curation_concern/generic_files_controller_spec.rb +258 -0
  247. data/spec/controllers/curation_concern/generic_works_controller_spec.rb +171 -0
  248. data/spec/controllers/curation_concern/linked_resources_controller_spec.rb +123 -0
  249. data/spec/controllers/curation_concern/permissions_controller_spec.rb +29 -0
  250. data/spec/controllers/downloads_controller_spec.rb +53 -0
  251. data/spec/controllers/embargoes_controller_spec.rb +107 -0
  252. data/spec/controllers/leases_controller_spec.rb +95 -0
  253. data/spec/controllers/worthwhile/classify_concerns_controller_spec.rb +35 -0
  254. data/spec/factories/collections_factory.rb +21 -0
  255. data/spec/factories/create_curation_concern.rb +3 -0
  256. data/spec/factories/generic_files.rb +18 -0
  257. data/spec/factories/generic_works.rb +37 -0
  258. data/spec/factories/linked_resources_factory.rb +18 -0
  259. data/spec/factories/users.rb +15 -0
  260. data/spec/features/add_external_link_spec.rb +25 -0
  261. data/spec/features/add_file_spec.rb +28 -0
  262. data/spec/features/collection_spec.rb +238 -0
  263. data/spec/features/embargo_spec.rb +45 -0
  264. data/spec/features/lease_spec.rb +43 -0
  265. data/spec/features/update_file_spec.rb +28 -0
  266. data/spec/fixtures/files/image.png +0 -0
  267. data/spec/helpers/catalog_helper_spec.rb +58 -0
  268. data/spec/helpers/configuration_helper_spec.rb +13 -0
  269. data/spec/helpers/render_constraints_helper_spec.rb +19 -0
  270. data/spec/helpers/thumbnail_helper_spec.rb +21 -0
  271. data/spec/helpers/url_helper_spec.rb +19 -0
  272. data/spec/matchers.rb +24 -0
  273. data/spec/matchers/metadata_field_matchers.rb +28 -0
  274. data/spec/models/collection_spec.rb +136 -0
  275. data/spec/models/curation_concern/collection_model_spec.rb +50 -0
  276. data/spec/models/curation_concern/work_spec.rb +30 -0
  277. data/spec/models/generic_work_spec.rb +24 -0
  278. data/spec/models/worthwhile/content_version_spec.rb +27 -0
  279. data/spec/models/worthwhile/generic_file_spec.rb +77 -0
  280. data/spec/models/worthwhile/linked_resource_spec.rb +76 -0
  281. data/spec/routing/worthwhile/routes_spec.rb +23 -0
  282. data/spec/services/embargo_service_spec.rb +38 -0
  283. data/spec/services/lease_service_spec.rb +37 -0
  284. data/spec/spec_helper.rb +30 -5
  285. data/spec/support/controllers/engine_helpers.rb +7 -0
  286. data/spec/support/curation_concern/factory_helpers.rb +14 -0
  287. data/spec/support/features.rb +19 -0
  288. data/spec/support/features/fixture_file_upload.rb +14 -0
  289. data/spec/support/features/session_helpers.rb +41 -0
  290. data/spec/support/shared/shared_examples_has_dc_metadata.rb +17 -0
  291. data/spec/support/shared/shared_examples_is_a_curation_concern_model.rb +40 -0
  292. data/spec/support/shared/shared_examples_is_embargoable.rb +8 -0
  293. data/spec/support/shared/shared_examples_with_access_rights.rb +63 -0
  294. data/spec/test_app_templates/lib/generators/test_app_generator.rb +8 -29
  295. data/spec/views/curation_concern/base/_attributes.html.erb_spec.rb +25 -0
  296. data/spec/views/curation_concern/base/show.html.erb_spec.rb +25 -0
  297. data/spec/views/shared/_add_content.html.erb_spec.rb +42 -0
  298. data/spec/views/shared/_my_actions.html.erb_spec.rb +23 -0
  299. data/spec/workers/visibility_copy_worker_spec.rb +68 -0
  300. data/vendor/assets/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  301. data/vendor/assets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  302. data/vendor/assets/javascripts/handlebars.js +2278 -0
  303. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.autocomplete.js +602 -0
  304. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.core.js +356 -0
  305. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.effect-highlight.js +50 -0
  306. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.effect.js +1276 -0
  307. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.menu.js +610 -0
  308. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.position.js +498 -0
  309. data/vendor/assets/javascripts/jquery-ui-1.9.2/jquery.ui.widget.js +528 -0
  310. data/vendor/assets/javascripts/jquery.tokeninput.js +1061 -0
  311. data/vendor/assets/stylesheets/jquery-ui-lightness.css +474 -0
  312. data/vendor/assets/stylesheets/token-input-facebook.css +122 -0
  313. data/vendor/assets/stylesheets/token-input-mac.css +204 -0
  314. data/vendor/assets/stylesheets/token-input.css +127 -0
  315. data/worthwhile.gemspec +9 -3
  316. metadata +469 -9
@@ -0,0 +1,1061 @@
1
+ /*
2
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
3
+ * Version 1.6.1
4
+ *
5
+ * Copyright (c) 2009 James Smith (http://loopj.com)
6
+ * Licensed jointly under the GPL and MIT licenses,
7
+ * choose which one suits your project best!
8
+ *
9
+ */
10
+
11
+ (function ($) {
12
+ // Default settings
13
+ var DEFAULT_SETTINGS = {
14
+ // Search settings
15
+ method: "GET",
16
+ queryParam: "q",
17
+ searchDelay: 300,
18
+ minChars: 1,
19
+ propertyToSearch: "name",
20
+ jsonContainer: null,
21
+ contentType: "json",
22
+
23
+ // Prepopulation settings
24
+ prePopulate: null,
25
+ processPrePopulate: false,
26
+
27
+ // Display settings
28
+ hintText: "Type in a search term",
29
+ noResultsText: "No results",
30
+ searchingText: "Searching...",
31
+ deleteText: "×",
32
+ animateDropdown: true,
33
+ placeholder: null,
34
+ theme: null,
35
+ zindex: 999,
36
+ resultsLimit: null,
37
+
38
+ enableHTML: false,
39
+
40
+ resultsFormatter: function(item) {
41
+ var string = item[this.propertyToSearch];
42
+ return "<li>" + (this.enableHTML ? string : _escapeHTML(string)) + "</li>";
43
+ },
44
+
45
+ tokenFormatter: function(item) {
46
+ var string = item[this.propertyToSearch];
47
+ return "<li><p>" + (this.enableHTML ? string : _escapeHTML(string)) + "</p></li>";
48
+ },
49
+
50
+ // Tokenization settings
51
+ tokenLimit: null,
52
+ tokenDelimiter: ",",
53
+ preventDuplicates: false,
54
+ tokenValue: "id",
55
+
56
+ // Behavioral settings
57
+ allowFreeTagging: false,
58
+ allowTabOut: false,
59
+
60
+ // Callbacks
61
+ onResult: null,
62
+ onCachedResult: null,
63
+ onAdd: null,
64
+ onFreeTaggingAdd: null,
65
+ onDelete: null,
66
+ onReady: null,
67
+
68
+ // Other settings
69
+ idPrefix: "token-input-",
70
+
71
+ // Keep track if the input is currently in disabled mode
72
+ disabled: false
73
+ };
74
+
75
+ // Default classes to use when theming
76
+ var DEFAULT_CLASSES = {
77
+ tokenList: "token-input-list",
78
+ token: "token-input-token",
79
+ tokenReadOnly: "token-input-token-readonly",
80
+ tokenDelete: "token-input-delete-token",
81
+ selectedToken: "token-input-selected-token",
82
+ highlightedToken: "token-input-highlighted-token",
83
+ dropdown: "token-input-dropdown",
84
+ dropdownItem: "token-input-dropdown-item",
85
+ dropdownItem2: "token-input-dropdown-item2",
86
+ selectedDropdownItem: "token-input-selected-dropdown-item",
87
+ inputToken: "token-input-input-token",
88
+ focused: "token-input-focused",
89
+ disabled: "token-input-disabled"
90
+ };
91
+
92
+ // Input box position "enum"
93
+ var POSITION = {
94
+ BEFORE: 0,
95
+ AFTER: 1,
96
+ END: 2
97
+ };
98
+
99
+ // Keys "enum"
100
+ var KEY = {
101
+ BACKSPACE: 8,
102
+ TAB: 9,
103
+ ENTER: 13,
104
+ ESCAPE: 27,
105
+ SPACE: 32,
106
+ PAGE_UP: 33,
107
+ PAGE_DOWN: 34,
108
+ END: 35,
109
+ HOME: 36,
110
+ LEFT: 37,
111
+ UP: 38,
112
+ RIGHT: 39,
113
+ DOWN: 40,
114
+ NUMPAD_ENTER: 108,
115
+ COMMA: 188
116
+ };
117
+
118
+ var HTML_ESCAPES = {
119
+ '&': '&amp;',
120
+ '<': '&lt;',
121
+ '>': '&gt;',
122
+ '"': '&quot;',
123
+ "'": '&#x27;',
124
+ '/': '&#x2F;'
125
+ };
126
+
127
+ var HTML_ESCAPE_CHARS = /[&<>"'\/]/g;
128
+
129
+ function coerceToString(val) {
130
+ return String((val === null || val === undefined) ? '' : val);
131
+ }
132
+
133
+ function _escapeHTML(text) {
134
+ return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) {
135
+ return HTML_ESCAPES[match];
136
+ });
137
+ }
138
+
139
+ // Additional public (exposed) methods
140
+ var methods = {
141
+ init: function(url_or_data_or_function, options) {
142
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
143
+
144
+ return this.each(function () {
145
+ $(this).data("settings", settings);
146
+ $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
147
+ });
148
+ },
149
+ clear: function() {
150
+ this.data("tokenInputObject").clear();
151
+ return this;
152
+ },
153
+ add: function(item) {
154
+ this.data("tokenInputObject").add(item);
155
+ return this;
156
+ },
157
+ remove: function(item) {
158
+ this.data("tokenInputObject").remove(item);
159
+ return this;
160
+ },
161
+ get: function() {
162
+ return this.data("tokenInputObject").getTokens();
163
+ },
164
+ toggleDisabled: function(disable) {
165
+ this.data("tokenInputObject").toggleDisabled(disable);
166
+ return this;
167
+ },
168
+ setOptions: function(options){
169
+ $(this).data("settings", $.extend({}, $(this).data("settings"), options || {}));
170
+ return this;
171
+ },
172
+ destroy: function () {
173
+ if(this.data("tokenInputObject")){
174
+ this.data("tokenInputObject").clear();
175
+ var tmpInput = this;
176
+ var closest = this.parent();
177
+ closest.empty();
178
+ tmpInput.show();
179
+ closest.append(tmpInput);
180
+ return tmpInput;
181
+ }
182
+ }
183
+ };
184
+
185
+ // Expose the .tokenInput function to jQuery as a plugin
186
+ $.fn.tokenInput = function (method) {
187
+ // Method calling and initialization logic
188
+ if(methods[method]) {
189
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
190
+ } else {
191
+ return methods.init.apply(this, arguments);
192
+ }
193
+ };
194
+
195
+ // TokenList class for each input
196
+ $.TokenList = function (input, url_or_data, settings) {
197
+ //
198
+ // Initialization
199
+ //
200
+
201
+ // Configure the data source
202
+ if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
203
+ // Set the url to query against
204
+ $(input).data("settings").url = url_or_data;
205
+
206
+ // If the URL is a function, evaluate it here to do our initalization work
207
+ var url = computeURL();
208
+
209
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
210
+ if($(input).data("settings").crossDomain === undefined && typeof url === "string") {
211
+ if(url.indexOf("://") === -1) {
212
+ $(input).data("settings").crossDomain = false;
213
+ } else {
214
+ $(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
215
+ }
216
+ }
217
+ } else if(typeof(url_or_data) === "object") {
218
+ // Set the local data to search through
219
+ $(input).data("settings").local_data = url_or_data;
220
+ }
221
+
222
+ // Build class names
223
+ if($(input).data("settings").classes) {
224
+ // Use custom class names
225
+ $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes);
226
+ } else if($(input).data("settings").theme) {
227
+ // Use theme-suffixed default class names
228
+ $(input).data("settings").classes = {};
229
+ $.each(DEFAULT_CLASSES, function(key, value) {
230
+ $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme;
231
+ });
232
+ } else {
233
+ $(input).data("settings").classes = DEFAULT_CLASSES;
234
+ }
235
+
236
+
237
+ // Save the tokens
238
+ var saved_tokens = [];
239
+
240
+ // Keep track of the number of tokens in the list
241
+ var token_count = 0;
242
+
243
+ // Basic cache to save on db hits
244
+ var cache = new $.TokenList.Cache();
245
+
246
+ // Keep track of the timeout, old vals
247
+ var timeout;
248
+ var input_val;
249
+
250
+ // Create a new text input an attach keyup events
251
+ var input_box = $("<input type=\"text\" autocomplete=\"off\" autocapitalize=\"off\">")
252
+ .css({
253
+ outline: "none"
254
+ })
255
+ .attr("id", $(input).data("settings").idPrefix + input.id)
256
+ .focus(function () {
257
+ if ($(input).data("settings").disabled) {
258
+ return false;
259
+ } else
260
+ if ($(input).data("settings").tokenLimit === null || $(input).data("settings").tokenLimit !== token_count) {
261
+ show_dropdown_hint();
262
+ }
263
+ token_list.addClass($(input).data("settings").classes.focused);
264
+ })
265
+ .blur(function () {
266
+ hide_dropdown();
267
+
268
+ if ($(input).data("settings").allowFreeTagging) {
269
+ add_freetagging_tokens();
270
+ }
271
+
272
+ $(this).val("");
273
+ token_list.removeClass($(input).data("settings").classes.focused);
274
+ })
275
+ .bind("keyup keydown blur update", resize_input)
276
+ .keydown(function (event) {
277
+ var previous_token;
278
+ var next_token;
279
+
280
+ switch(event.keyCode) {
281
+ case KEY.LEFT:
282
+ case KEY.RIGHT:
283
+ case KEY.UP:
284
+ case KEY.DOWN:
285
+ if(!$(this).val()) {
286
+ previous_token = input_token.prev();
287
+ next_token = input_token.next();
288
+
289
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
290
+ // Check if there is a previous/next token and it is selected
291
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
292
+ deselect_token($(selected_token), POSITION.BEFORE);
293
+ } else {
294
+ deselect_token($(selected_token), POSITION.AFTER);
295
+ }
296
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
297
+ // We are moving left, select the previous token if it exists
298
+ select_token($(previous_token.get(0)));
299
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
300
+ // We are moving right, select the next token if it exists
301
+ select_token($(next_token.get(0)));
302
+ }
303
+ } else {
304
+ var dropdown_item = null;
305
+
306
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
307
+ dropdown_item = $(selected_dropdown_item).next();
308
+ } else {
309
+ dropdown_item = $(selected_dropdown_item).prev();
310
+ }
311
+
312
+ if(dropdown_item.length) {
313
+ select_dropdown_item(dropdown_item);
314
+ }
315
+ }
316
+ return false;
317
+ break;
318
+
319
+ case KEY.BACKSPACE:
320
+ previous_token = input_token.prev();
321
+
322
+ if(!$(this).val().length) {
323
+ if(selected_token) {
324
+ delete_token($(selected_token));
325
+ hidden_input.change();
326
+ } else if(previous_token.length) {
327
+ select_token($(previous_token.get(0)));
328
+ }
329
+
330
+ return false;
331
+ } else if($(this).val().length === 1) {
332
+ hide_dropdown();
333
+ } else {
334
+ // set a timeout just long enough to let this function finish.
335
+ setTimeout(function(){do_search();}, 5);
336
+ }
337
+ break;
338
+
339
+ case KEY.TAB:
340
+ case KEY.ENTER:
341
+ case KEY.NUMPAD_ENTER:
342
+ case KEY.COMMA:
343
+ if(selected_dropdown_item) {
344
+ add_token($(selected_dropdown_item).data("tokeninput"));
345
+ hidden_input.change();
346
+ } else {
347
+ if ($(input).data("settings").allowFreeTagging) {
348
+ if($(input).data("settings").allowTabOut && $(this).val() === "") {
349
+ return true;
350
+ } else {
351
+ add_freetagging_tokens();
352
+ }
353
+ } else {
354
+ $(this).val("");
355
+ if($(input).data("settings").allowTabOut) {
356
+ return true;
357
+ }
358
+ }
359
+ event.stopPropagation();
360
+ event.preventDefault();
361
+ }
362
+ return false;
363
+
364
+ case KEY.ESCAPE:
365
+ hide_dropdown();
366
+ return true;
367
+
368
+ default:
369
+ if(String.fromCharCode(event.which)) {
370
+ // set a timeout just long enough to let this function finish.
371
+ setTimeout(function(){do_search();}, 5);
372
+ }
373
+ break;
374
+ }
375
+ });
376
+
377
+ // Keep reference for placeholder
378
+ if (settings.placeholder)
379
+ input_box.attr("placeholder", settings.placeholder)
380
+
381
+ // Keep a reference to the original input box
382
+ var hidden_input = $(input)
383
+ .hide()
384
+ .val("")
385
+ .focus(function () {
386
+ focus_with_timeout(input_box);
387
+ })
388
+ .blur(function () {
389
+ input_box.blur();
390
+ //return the object to this can be referenced in the callback functions.
391
+ return hidden_input;
392
+ });
393
+
394
+ // Keep a reference to the selected token and dropdown item
395
+ var selected_token = null;
396
+ var selected_token_index = 0;
397
+ var selected_dropdown_item = null;
398
+
399
+ // The list to store the token items in
400
+ var token_list = $("<ul />")
401
+ .addClass($(input).data("settings").classes.tokenList)
402
+ .click(function (event) {
403
+ var li = $(event.target).closest("li");
404
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
405
+ toggle_select_token(li);
406
+ } else {
407
+ // Deselect selected token
408
+ if(selected_token) {
409
+ deselect_token($(selected_token), POSITION.END);
410
+ }
411
+
412
+ // Focus input box
413
+ focus_with_timeout(input_box);
414
+ }
415
+ })
416
+ .mouseover(function (event) {
417
+ var li = $(event.target).closest("li");
418
+ if(li && selected_token !== this) {
419
+ li.addClass($(input).data("settings").classes.highlightedToken);
420
+ }
421
+ })
422
+ .mouseout(function (event) {
423
+ var li = $(event.target).closest("li");
424
+ if(li && selected_token !== this) {
425
+ li.removeClass($(input).data("settings").classes.highlightedToken);
426
+ }
427
+ })
428
+ .insertBefore(hidden_input);
429
+
430
+ // The token holding the input box
431
+ var input_token = $("<li />")
432
+ .addClass($(input).data("settings").classes.inputToken)
433
+ .appendTo(token_list)
434
+ .append(input_box);
435
+
436
+ // The list to store the dropdown items in
437
+ var dropdown = $("<div>")
438
+ .addClass($(input).data("settings").classes.dropdown)
439
+ .appendTo("body")
440
+ .hide();
441
+
442
+ // Magic element to help us resize the text input
443
+ var input_resizer = $("<tester/>")
444
+ .insertAfter(input_box)
445
+ .css({
446
+ position: "absolute",
447
+ top: -9999,
448
+ left: -9999,
449
+ width: "auto",
450
+ fontSize: input_box.css("fontSize"),
451
+ fontFamily: input_box.css("fontFamily"),
452
+ fontWeight: input_box.css("fontWeight"),
453
+ letterSpacing: input_box.css("letterSpacing"),
454
+ whiteSpace: "nowrap"
455
+ });
456
+
457
+ // Pre-populate list if items exist
458
+ hidden_input.val("");
459
+ var li_data = $(input).data("settings").prePopulate || hidden_input.data("pre");
460
+ if($(input).data("settings").processPrePopulate && $.isFunction($(input).data("settings").onResult)) {
461
+ li_data = $(input).data("settings").onResult.call(hidden_input, li_data);
462
+ }
463
+ if(li_data && li_data.length) {
464
+ $.each(li_data, function (index, value) {
465
+ insert_token(value);
466
+ checkTokenLimit();
467
+ input_box.attr("placeholder", null)
468
+ });
469
+ }
470
+
471
+ // Check if widget should initialize as disabled
472
+ if ($(input).data("settings").disabled) {
473
+ toggleDisabled(true);
474
+ }
475
+
476
+ // Initialization is done
477
+ if($.isFunction($(input).data("settings").onReady)) {
478
+ $(input).data("settings").onReady.call();
479
+ }
480
+
481
+ //
482
+ // Public functions
483
+ //
484
+
485
+ this.clear = function() {
486
+ token_list.children("li").each(function() {
487
+ if ($(this).children("input").length === 0) {
488
+ delete_token($(this));
489
+ }
490
+ });
491
+ };
492
+
493
+ this.add = function(item) {
494
+ add_token(item);
495
+ };
496
+
497
+ this.remove = function(item) {
498
+ token_list.children("li").each(function() {
499
+ if ($(this).children("input").length === 0) {
500
+ var currToken = $(this).data("tokeninput");
501
+ var match = true;
502
+ for (var prop in item) {
503
+ if (item[prop] !== currToken[prop]) {
504
+ match = false;
505
+ break;
506
+ }
507
+ }
508
+ if (match) {
509
+ delete_token($(this));
510
+ }
511
+ }
512
+ });
513
+ };
514
+
515
+ this.getTokens = function() {
516
+ return saved_tokens;
517
+ };
518
+
519
+ this.toggleDisabled = function(disable) {
520
+ toggleDisabled(disable);
521
+ };
522
+
523
+ // Resize input to maximum width so the placeholder can be seen
524
+ resize_input();
525
+
526
+ //
527
+ // Private functions
528
+ //
529
+
530
+ function escapeHTML(text) {
531
+ return $(input).data("settings").enableHTML ? text : _escapeHTML(text);
532
+ }
533
+
534
+ // Toggles the widget between enabled and disabled state, or according
535
+ // to the [disable] parameter.
536
+ function toggleDisabled(disable) {
537
+ if (typeof disable === 'boolean') {
538
+ $(input).data("settings").disabled = disable
539
+ } else {
540
+ $(input).data("settings").disabled = !$(input).data("settings").disabled;
541
+ }
542
+ input_box.attr('disabled', $(input).data("settings").disabled);
543
+ token_list.toggleClass($(input).data("settings").classes.disabled, $(input).data("settings").disabled);
544
+ // if there is any token selected we deselect it
545
+ if(selected_token) {
546
+ deselect_token($(selected_token), POSITION.END);
547
+ }
548
+ hidden_input.attr('disabled', $(input).data("settings").disabled);
549
+ }
550
+
551
+ function checkTokenLimit() {
552
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
553
+ input_box.hide();
554
+ hide_dropdown();
555
+ return;
556
+ }
557
+ }
558
+
559
+ function resize_input() {
560
+ if(input_val === (input_val = input_box.val())) {return;}
561
+
562
+ // Get width left on the current line
563
+ var width_left = token_list.width() - input_box.offset().left - token_list.offset().left;
564
+ // Enter new content into resizer and resize input accordingly
565
+ input_resizer.html(_escapeHTML(input_val) || _escapeHTML(settings.placeholder));
566
+ // Get maximum width, minimum the size of input and maximum the widget's width
567
+ input_box.width(Math.min(token_list.width(),
568
+ Math.max(width_left, input_resizer.width() + 30)));
569
+ }
570
+
571
+ function is_printable_character(keycode) {
572
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
573
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
574
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
575
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
576
+ }
577
+
578
+ function add_freetagging_tokens() {
579
+ var value = $.trim(input_box.val());
580
+ var tokens = value.split($(input).data("settings").tokenDelimiter);
581
+ $.each(tokens, function(i, token) {
582
+ if (!token) {
583
+ return;
584
+ }
585
+
586
+ if ($.isFunction($(input).data("settings").onFreeTaggingAdd)) {
587
+ token = $(input).data("settings").onFreeTaggingAdd.call(hidden_input, token);
588
+ }
589
+ var object = {};
590
+ object[$(input).data("settings").tokenValue] = object[$(input).data("settings").propertyToSearch] = token;
591
+ add_token(object);
592
+ });
593
+ }
594
+
595
+ // Inner function to a token to the list
596
+ function insert_token(item) {
597
+ var $this_token = $($(input).data("settings").tokenFormatter(item));
598
+ var readonly = item.readonly === true ? true : false;
599
+
600
+ if(readonly) $this_token.addClass($(input).data("settings").classes.tokenReadOnly);
601
+
602
+ $this_token.addClass($(input).data("settings").classes.token).insertBefore(input_token);
603
+
604
+ // The 'delete token' button
605
+ if(!readonly) {
606
+ $("<span>" + $(input).data("settings").deleteText + "</span>")
607
+ .addClass($(input).data("settings").classes.tokenDelete)
608
+ .appendTo($this_token)
609
+ .click(function () {
610
+ if (!$(input).data("settings").disabled) {
611
+ delete_token($(this).parent());
612
+ hidden_input.change();
613
+ return false;
614
+ }
615
+ });
616
+ }
617
+
618
+ // Store data on the token
619
+ var token_data = item;
620
+ $.data($this_token.get(0), "tokeninput", item);
621
+
622
+ // Save this token for duplicate checking
623
+ saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
624
+ selected_token_index++;
625
+
626
+ // Update the hidden input
627
+ update_hidden_input(saved_tokens, hidden_input);
628
+
629
+ token_count += 1;
630
+
631
+ // Check the token limit
632
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
633
+ input_box.hide();
634
+ hide_dropdown();
635
+ }
636
+
637
+ return $this_token;
638
+ }
639
+
640
+ // Add a token to the token list based on user input
641
+ function add_token (item) {
642
+ var callback = $(input).data("settings").onAdd;
643
+
644
+ // See if the token already exists and select it if we don't want duplicates
645
+ if(token_count > 0 && $(input).data("settings").preventDuplicates) {
646
+ var found_existing_token = null;
647
+ token_list.children().each(function () {
648
+ var existing_token = $(this);
649
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
650
+ if(existing_data && existing_data[settings.tokenValue] === item[settings.tokenValue]) {
651
+ found_existing_token = existing_token;
652
+ return false;
653
+ }
654
+ });
655
+
656
+ if(found_existing_token) {
657
+ select_token(found_existing_token);
658
+ input_token.insertAfter(found_existing_token);
659
+ focus_with_timeout(input_box);
660
+ return;
661
+ }
662
+ }
663
+
664
+ // Squeeze input_box so we force no unnecessary line break
665
+ input_box.width(0);
666
+
667
+ // Insert the new tokens
668
+ if($(input).data("settings").tokenLimit == null || token_count < $(input).data("settings").tokenLimit) {
669
+ insert_token(item);
670
+ // Remove the placeholder so it's not seen after you've added a token
671
+ input_box.attr("placeholder", null)
672
+ checkTokenLimit();
673
+ }
674
+
675
+ // Clear input box
676
+ input_box.val("");
677
+
678
+ // Don't show the help dropdown, they've got the idea
679
+ hide_dropdown();
680
+
681
+ // Execute the onAdd callback if defined
682
+ if($.isFunction(callback)) {
683
+ callback.call(hidden_input,item);
684
+ }
685
+ }
686
+
687
+ // Select a token in the token list
688
+ function select_token (token) {
689
+ if (!$(input).data("settings").disabled) {
690
+ token.addClass($(input).data("settings").classes.selectedToken);
691
+ selected_token = token.get(0);
692
+
693
+ // Hide input box
694
+ input_box.val("");
695
+
696
+ // Hide dropdown if it is visible (eg if we clicked to select token)
697
+ hide_dropdown();
698
+ }
699
+ }
700
+
701
+ // Deselect a token in the token list
702
+ function deselect_token (token, position) {
703
+ token.removeClass($(input).data("settings").classes.selectedToken);
704
+ selected_token = null;
705
+
706
+ if(position === POSITION.BEFORE) {
707
+ input_token.insertBefore(token);
708
+ selected_token_index--;
709
+ } else if(position === POSITION.AFTER) {
710
+ input_token.insertAfter(token);
711
+ selected_token_index++;
712
+ } else {
713
+ input_token.appendTo(token_list);
714
+ selected_token_index = token_count;
715
+ }
716
+
717
+ // Show the input box and give it focus again
718
+ focus_with_timeout(input_box);
719
+ }
720
+
721
+ // Toggle selection of a token in the token list
722
+ function toggle_select_token(token) {
723
+ var previous_selected_token = selected_token;
724
+
725
+ if(selected_token) {
726
+ deselect_token($(selected_token), POSITION.END);
727
+ }
728
+
729
+ if(previous_selected_token === token.get(0)) {
730
+ deselect_token(token, POSITION.END);
731
+ } else {
732
+ select_token(token);
733
+ }
734
+ }
735
+
736
+ // Delete a token from the token list
737
+ function delete_token (token) {
738
+ // Remove the id from the saved list
739
+ var token_data = $.data(token.get(0), "tokeninput");
740
+ var callback = $(input).data("settings").onDelete;
741
+
742
+ var index = token.prevAll().length;
743
+ if(index > selected_token_index) index--;
744
+
745
+ // Delete the token
746
+ token.remove();
747
+ selected_token = null;
748
+
749
+ // Show the input box and give it focus again
750
+ focus_with_timeout(input_box);
751
+
752
+ // Remove this token from the saved list
753
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
754
+ if (saved_tokens.length == 0) {
755
+ input_box.attr("placeholder", settings.placeholder)
756
+ }
757
+ if(index < selected_token_index) selected_token_index--;
758
+
759
+ // Update the hidden input
760
+ update_hidden_input(saved_tokens, hidden_input);
761
+
762
+ token_count -= 1;
763
+
764
+ if($(input).data("settings").tokenLimit !== null) {
765
+ input_box
766
+ .show()
767
+ .val("");
768
+ focus_with_timeout(input_box);
769
+ }
770
+
771
+ // Execute the onDelete callback if defined
772
+ if($.isFunction(callback)) {
773
+ callback.call(hidden_input,token_data);
774
+ }
775
+ }
776
+
777
+ // Update the hidden input box value
778
+ function update_hidden_input(saved_tokens, hidden_input) {
779
+ var token_values = $.map(saved_tokens, function (el) {
780
+ if(typeof $(input).data("settings").tokenValue == 'function')
781
+ return $(input).data("settings").tokenValue.call(this, el);
782
+
783
+ return el[$(input).data("settings").tokenValue];
784
+ });
785
+ hidden_input.val(token_values.join($(input).data("settings").tokenDelimiter));
786
+
787
+ }
788
+
789
+ // Hide and clear the results dropdown
790
+ function hide_dropdown () {
791
+ dropdown.hide().empty();
792
+ selected_dropdown_item = null;
793
+ }
794
+
795
+ function show_dropdown() {
796
+ dropdown
797
+ .css({
798
+ position: "absolute",
799
+ top: token_list.offset().top + token_list.outerHeight(),
800
+ left: token_list.offset().left,
801
+ width: token_list.width(),
802
+ 'z-index': $(input).data("settings").zindex
803
+ })
804
+ .show();
805
+ }
806
+
807
+ function show_dropdown_searching () {
808
+ if($(input).data("settings").searchingText) {
809
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").searchingText) + "</p>");
810
+ show_dropdown();
811
+ }
812
+ }
813
+
814
+ function show_dropdown_hint () {
815
+ if($(input).data("settings").hintText) {
816
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").hintText) + "</p>");
817
+ show_dropdown();
818
+ }
819
+ }
820
+
821
+ var regexp_special_chars = new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g');
822
+ function regexp_escape(term) {
823
+ return term.replace(regexp_special_chars, '\\$&');
824
+ }
825
+
826
+ // Highlight the query part of the search term
827
+ function highlight_term(value, term) {
828
+ return value.replace(
829
+ new RegExp(
830
+ "(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(term) + ")(?![^<>]*>)(?![^&;]+;)",
831
+ "gi"
832
+ ), function(match, p1) {
833
+ return "<b>" + escapeHTML(p1) + "</b>";
834
+ }
835
+ );
836
+ }
837
+
838
+ function find_value_and_highlight_term(template, value, term) {
839
+ return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(value) + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
840
+ }
841
+
842
+ // Populate the results dropdown with some results
843
+ function populate_dropdown (query, results) {
844
+ if(results && results.length) {
845
+ dropdown.empty();
846
+ var dropdown_ul = $("<ul>")
847
+ .appendTo(dropdown)
848
+ .mouseover(function (event) {
849
+ select_dropdown_item($(event.target).closest("li"));
850
+ })
851
+ .mousedown(function (event) {
852
+ add_token($(event.target).closest("li").data("tokeninput"));
853
+ hidden_input.change();
854
+ return false;
855
+ })
856
+ .hide();
857
+
858
+ if ($(input).data("settings").resultsLimit && results.length > $(input).data("settings").resultsLimit) {
859
+ results = results.slice(0, $(input).data("settings").resultsLimit);
860
+ }
861
+
862
+ $.each(results, function(index, value) {
863
+ var this_li = $(input).data("settings").resultsFormatter(value);
864
+
865
+ this_li = find_value_and_highlight_term(this_li ,value[$(input).data("settings").propertyToSearch], query);
866
+
867
+ this_li = $(this_li).appendTo(dropdown_ul);
868
+
869
+ if(index % 2) {
870
+ this_li.addClass($(input).data("settings").classes.dropdownItem);
871
+ } else {
872
+ this_li.addClass($(input).data("settings").classes.dropdownItem2);
873
+ }
874
+
875
+ if(index === 0) {
876
+ select_dropdown_item(this_li);
877
+ }
878
+
879
+ $.data(this_li.get(0), "tokeninput", value);
880
+ });
881
+
882
+ show_dropdown();
883
+
884
+ if($(input).data("settings").animateDropdown) {
885
+ dropdown_ul.slideDown("fast");
886
+ } else {
887
+ dropdown_ul.show();
888
+ }
889
+ } else {
890
+ if($(input).data("settings").noResultsText) {
891
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").noResultsText) + "</p>");
892
+ show_dropdown();
893
+ }
894
+ }
895
+ }
896
+
897
+ // Highlight an item in the results dropdown
898
+ function select_dropdown_item (item) {
899
+ if(item) {
900
+ if(selected_dropdown_item) {
901
+ deselect_dropdown_item($(selected_dropdown_item));
902
+ }
903
+
904
+ item.addClass($(input).data("settings").classes.selectedDropdownItem);
905
+ selected_dropdown_item = item.get(0);
906
+ }
907
+ }
908
+
909
+ // Remove highlighting from an item in the results dropdown
910
+ function deselect_dropdown_item (item) {
911
+ item.removeClass($(input).data("settings").classes.selectedDropdownItem);
912
+ selected_dropdown_item = null;
913
+ }
914
+
915
+ // Do a search and show the "searching" dropdown if the input is longer
916
+ // than $(input).data("settings").minChars
917
+ function do_search() {
918
+ var query = input_box.val();
919
+
920
+ if(query && query.length) {
921
+ if(selected_token) {
922
+ deselect_token($(selected_token), POSITION.AFTER);
923
+ }
924
+
925
+ if(query.length >= $(input).data("settings").minChars) {
926
+ show_dropdown_searching();
927
+ clearTimeout(timeout);
928
+
929
+ timeout = setTimeout(function(){
930
+ run_search(query);
931
+ }, $(input).data("settings").searchDelay);
932
+ } else {
933
+ hide_dropdown();
934
+ }
935
+ }
936
+ }
937
+
938
+ // Do the actual search
939
+ function run_search(query) {
940
+ var cache_key = query + computeURL();
941
+ var cached_results = cache.get(cache_key);
942
+ if(cached_results) {
943
+ if ($.isFunction($(input).data("settings").onCachedResult)) {
944
+ cached_results = $(input).data("settings").onCachedResult.call(hidden_input, cached_results);
945
+ }
946
+ populate_dropdown(query, cached_results);
947
+ } else {
948
+ // Are we doing an ajax search or local data search?
949
+ if($(input).data("settings").url) {
950
+ var url = computeURL();
951
+ // Extract exisiting get params
952
+ var ajax_params = {};
953
+ ajax_params.data = {};
954
+ if(url.indexOf("?") > -1) {
955
+ var parts = url.split("?");
956
+ ajax_params.url = parts[0];
957
+
958
+ var param_array = parts[1].split("&");
959
+ $.each(param_array, function (index, value) {
960
+ var kv = value.split("=");
961
+ ajax_params.data[kv[0]] = kv[1];
962
+ });
963
+ } else {
964
+ ajax_params.url = url;
965
+ }
966
+
967
+ // Prepare the request
968
+ ajax_params.data[$(input).data("settings").queryParam] = query;
969
+ ajax_params.type = $(input).data("settings").method;
970
+ ajax_params.dataType = $(input).data("settings").contentType;
971
+ if($(input).data("settings").crossDomain) {
972
+ ajax_params.dataType = "jsonp";
973
+ }
974
+
975
+ // Attach the success callback
976
+ ajax_params.success = function(results) {
977
+ cache.add(cache_key, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
978
+ if($.isFunction($(input).data("settings").onResult)) {
979
+ results = $(input).data("settings").onResult.call(hidden_input, results);
980
+ }
981
+
982
+ // only populate the dropdown if the results are associated with the active search query
983
+ if(input_box.val() === query) {
984
+ populate_dropdown(query, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
985
+ }
986
+ };
987
+
988
+ // Provide a beforeSend callback
989
+ if (settings.onSend) {
990
+ settings.onSend(ajax_params);
991
+ }
992
+
993
+ // Make the request
994
+ $.ajax(ajax_params);
995
+ } else if($(input).data("settings").local_data) {
996
+ // Do the search through local data
997
+ var results = $.grep($(input).data("settings").local_data, function (row) {
998
+ return row[$(input).data("settings").propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
999
+ });
1000
+
1001
+ cache.add(cache_key, results);
1002
+ if($.isFunction($(input).data("settings").onResult)) {
1003
+ results = $(input).data("settings").onResult.call(hidden_input, results);
1004
+ }
1005
+ populate_dropdown(query, results);
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ // compute the dynamic URL
1011
+ function computeURL() {
1012
+ var url = $(input).data("settings").url;
1013
+ if(typeof $(input).data("settings").url == 'function') {
1014
+ url = $(input).data("settings").url.call($(input).data("settings"));
1015
+ }
1016
+ return url;
1017
+ }
1018
+
1019
+ // Bring browser focus to the specified object.
1020
+ // Use of setTimeout is to get around an IE bug.
1021
+ // (See, e.g., http://stackoverflow.com/questions/2600186/focus-doesnt-work-in-ie)
1022
+ //
1023
+ // obj: a jQuery object to focus()
1024
+ function focus_with_timeout(obj) {
1025
+ setTimeout(function() { obj.focus(); }, 50);
1026
+ }
1027
+
1028
+ };
1029
+
1030
+ // Really basic cache for the results
1031
+ $.TokenList.Cache = function (options) {
1032
+ var settings = $.extend({
1033
+ max_size: 500
1034
+ }, options);
1035
+
1036
+ var data = {};
1037
+ var size = 0;
1038
+
1039
+ var flush = function () {
1040
+ data = {};
1041
+ size = 0;
1042
+ };
1043
+
1044
+ this.add = function (query, results) {
1045
+ if(size > settings.max_size) {
1046
+ flush();
1047
+ }
1048
+
1049
+ if(!data[query]) {
1050
+ size += 1;
1051
+ }
1052
+
1053
+ data[query] = results;
1054
+ };
1055
+
1056
+ this.get = function (query) {
1057
+ return data[query];
1058
+ };
1059
+ };
1060
+ }(jQuery));
1061
+