motiro 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. data/LICENSE +280 -0
  2. data/README +28 -0
  3. data/README.en +175 -0
  4. data/README.pt-br +175 -0
  5. data/Rakefile +10 -0
  6. data/app/controllers/account_controller.rb +62 -0
  7. data/app/controllers/application.rb +72 -0
  8. data/app/controllers/edition_controller.rb +13 -0
  9. data/app/controllers/javascript_controller.rb +21 -0
  10. data/app/controllers/report_controller.rb +79 -0
  11. data/app/controllers/root_controller.rb +7 -0
  12. data/app/controllers/wiki_controller.rb +102 -0
  13. data/app/core/cache_reporter.rb +53 -0
  14. data/app/core/cache_reporter_fetcher.rb +33 -0
  15. data/app/core/chief_editor.rb +69 -0
  16. data/app/core/reporter.rb +105 -0
  17. data/app/core/reporter_driver.rb +36 -0
  18. data/app/core/reporter_fetcher.rb +39 -0
  19. data/app/core/settings.rb +43 -0
  20. data/app/core/version.rb +1 -0
  21. data/app/core/wiki_page_not_found.rb +33 -0
  22. data/app/core/wiki_reporter.rb +83 -0
  23. data/app/helpers/account_helper.rb +2 -0
  24. data/app/helpers/application_helper.rb +68 -0
  25. data/app/helpers/default_page_provider.rb +28 -0
  26. data/app/helpers/report_helper.rb +2 -0
  27. data/app/helpers/root_helper.rb +2 -0
  28. data/app/helpers/wiki_helper.rb +3 -0
  29. data/app/models/change.rb +96 -0
  30. data/app/models/diff_table_builder.rb +285 -0
  31. data/app/models/event.rb +18 -0
  32. data/app/models/headline.rb +109 -0
  33. data/app/models/page.rb +114 -0
  34. data/app/models/page_sweeper.rb +26 -0
  35. data/app/models/user.rb +85 -0
  36. data/app/ports/chdir_runner.rb +36 -0
  37. data/app/ports/reporter_loader.rb +9 -0
  38. data/app/ports/runner.rb +64 -0
  39. data/app/reporters/darcs_connection.rb +54 -0
  40. data/app/reporters/darcs_reporter.rb +104 -0
  41. data/app/reporters/darcs_settings.rb +9 -0
  42. data/app/reporters/darcs_temp_repo.rb +40 -0
  43. data/app/reporters/events_reporter.rb +28 -0
  44. data/app/reporters/features_reporter.rb +24 -0
  45. data/app/reporters/subversion_reporter.rb +203 -0
  46. data/app/reporters/svn_connection.rb +62 -0
  47. data/app/reporters/svn_settings.rb +40 -0
  48. data/app/views/account/_authorization.rhtml +54 -0
  49. data/app/views/account/_availability.rhtml +5 -0
  50. data/app/views/account/availability.rhtml +1 -0
  51. data/app/views/account/login.rhtml +22 -0
  52. data/app/views/account/logout.rhtml +10 -0
  53. data/app/views/javascript/motiro.rjs +132 -0
  54. data/app/views/javascript/niftycube.rjs +300 -0
  55. data/app/views/layouts/_bottom.rhtml +5 -0
  56. data/app/views/layouts/_header.rhtml +7 -0
  57. data/app/views/layouts/_top.rhtml +25 -0
  58. data/app/views/layouts/application.rhtml +3 -0
  59. data/app/views/layouts/scaffold.rhtml +13 -0
  60. data/app/views/layouts/wiki_edit.rhtml +26 -0
  61. data/app/views/report/list.rhtml +32 -0
  62. data/app/views/report/older.rhtml +26 -0
  63. data/app/views/report/rss.rxml +29 -0
  64. data/app/views/report/show.rhtml +20 -0
  65. data/app/views/root/index.rhtml +33 -0
  66. data/app/views/wiki/_editbar.rhtml +26 -0
  67. data/app/views/wiki/_properties_edit.rhtml +5 -0
  68. data/app/views/wiki/_properties_show.rhtml +5 -0
  69. data/app/views/wiki/edit.rhtml +56 -0
  70. data/app/views/wiki/properties_edit.rhtml +1 -0
  71. data/app/views/wiki/show.rhtml +9 -0
  72. data/bin/motiro +44 -0
  73. data/config/boot.rb +19 -0
  74. data/config/database.yml +14 -0
  75. data/config/environment.rb +64 -0
  76. data/config/environments/development.rb +20 -0
  77. data/config/environments/production.rb +19 -0
  78. data/config/environments/test.rb +19 -0
  79. data/config/motiro.yml +43 -0
  80. data/config/routes.rb +79 -0
  81. data/db/migrate/005_globalize_migration.rb +11363 -0
  82. data/db/migrate/006_remove_headline_title.rb +14 -0
  83. data/db/migrate/007_stretch_rid.rb +11 -0
  84. data/db/migrate/008_add_page_editors.rb +12 -0
  85. data/db/migrate/009_add_page_original_author.rb +12 -0
  86. data/db/migrate/010_remove_empty_string_defaults_from_pages.rb +17 -0
  87. data/db/migrate/011_add_title_and_kind_to_pages.rb +13 -0
  88. data/db/migrate/012_page_modification_info.rb +13 -0
  89. data/db/migrate/013_nullify_initial_page_attributes.rb +21 -0
  90. data/db/migrate/014_events_are_wiki_pages.rb +13 -0
  91. data/db/migrate/015_migrate_previous_event_data.rb +23 -0
  92. data/db/migrate/1_initial_structure.rb +36 -0
  93. data/db/migrate/2_add_authentication.rb +12 -0
  94. data/db/migrate/3_drop_articles.rb +26 -0
  95. data/db/migrate/4_add_page_editing.rb +14 -0
  96. data/db/motirodb.sqlite.initial +0 -0
  97. data/db/schema_version +1 -0
  98. data/db/translation/pt-BR.rb +76 -0
  99. data/doc/README_FOR_APP +2 -0
  100. data/installer/rails_installer_defaults.yml +5 -0
  101. data/lib/import_translations.rb +154 -0
  102. data/lib/login_system.rb +89 -0
  103. data/lib/relative_time.rb +48 -0
  104. data/lib/string_extensions.rb +10 -0
  105. data/lib/stub_hash.rb +22 -0
  106. data/lib/tasks/packaging.rake +93 -0
  107. data/lib/tasks/testing.rake +32 -0
  108. data/lib/tick_daemon.rb +41 -0
  109. data/lib/translator.rb +67 -0
  110. data/lib/wiki_renderer.rb +42 -0
  111. data/lib/wiki_url_generator.rb +29 -0
  112. data/log/.keepdir +0 -0
  113. data/public/404.html +8 -0
  114. data/public/500.html +8 -0
  115. data/public/dispatch.cgi +10 -0
  116. data/public/dispatch.fcgi +24 -0
  117. data/public/dispatch.rb +10 -0
  118. data/public/favicon.ico +0 -0
  119. data/public/images/calendar.png +0 -0
  120. data/public/images/rss.png +0 -0
  121. data/public/javascripts/controls.js +750 -0
  122. data/public/javascripts/dragdrop.js +584 -0
  123. data/public/javascripts/effects.js +854 -0
  124. data/public/javascripts/prototype.js +1785 -0
  125. data/public/robots.txt +1 -0
  126. data/public/stylesheets/motiro.css +269 -0
  127. data/public/stylesheets/niftyCorners.css +35 -0
  128. data/public/stylesheets/scaffold.css +74 -0
  129. data/script/about +3 -0
  130. data/script/breakpointer +3 -0
  131. data/script/console +3 -0
  132. data/script/destroy +3 -0
  133. data/script/generate +3 -0
  134. data/script/performance/benchmarker +3 -0
  135. data/script/performance/profiler +3 -0
  136. data/script/plugin +3 -0
  137. data/script/process/reaper +3 -0
  138. data/script/process/spawner +3 -0
  139. data/script/process/spinner +3 -0
  140. data/script/runner +3 -0
  141. data/script/server +3 -0
  142. data/script/ticker +29 -0
  143. data/test/acceptance/account_test.rb +186 -0
  144. data/test/acceptance/darcs_test.rb +62 -0
  145. data/test/acceptance/events_test.rb +47 -0
  146. data/test/acceptance/main_page_test.rb +92 -0
  147. data/test/acceptance/subversion_test.rb +319 -0
  148. data/test/acceptance/ts_all_suites.rb +27 -0
  149. data/test/acceptance/wiki_test.rb +202 -0
  150. data/test/contract/darcs_test.rb +51 -0
  151. data/test/contract/remote_darcs_test.rb +61 -0
  152. data/test/contract/svn_test.rb +53 -0
  153. data/test/fixtures/changes.yml +25 -0
  154. data/test/fixtures/headlines.yml +45 -0
  155. data/test/fixtures/pages.yml +98 -0
  156. data/test/fixtures/users.yml +31 -0
  157. data/test/functional/account_controller_test.rb +96 -0
  158. data/test/functional/report_controller_test.rb +113 -0
  159. data/test/functional/report_features_test.rb +38 -0
  160. data/test/functional/report_subversion_test.rb +79 -0
  161. data/test/functional/root_controller_test.rb +127 -0
  162. data/test/functional/wiki_controller_test.rb +280 -0
  163. data/test/lib/acceptance_test_case.rb +43 -0
  164. data/test/lib/configuration.rb +53 -0
  165. data/test/lib/darcs_excerpts.rb +181 -0
  166. data/test/lib/darcs_repo.rb +77 -0
  167. data/test/lib/live_mode_test.rb +51 -0
  168. data/test/lib/local_svn.rb +157 -0
  169. data/test/lib/netutils.rb +42 -0
  170. data/test/lib/platform_thread.rb +62 -0
  171. data/test/lib/repoutils.rb +23 -0
  172. data/test/lib/selenium_extensions.rb +32 -0
  173. data/test/lib/stubio.rb +37 -0
  174. data/test/lib/svn_excerpts.rb +288 -0
  175. data/test/lib/test_configuration.rb +14 -0
  176. data/test/lib/webserver.rb +71 -0
  177. data/test/meta/configuration_test.rb +72 -0
  178. data/test/meta/darcs_repo_test.rb +118 -0
  179. data/test/meta/local_svn_test.rb +125 -0
  180. data/test/meta/platform_thread_test.rb +46 -0
  181. data/test/meta/stubio_test.rb +44 -0
  182. data/test/mocks/headline.rb +34 -0
  183. data/test/mocks/svn_reporter.rb +29 -0
  184. data/test/stubs/svn_settings.rb +19 -0
  185. data/test/stubs/url_generator.rb +24 -0
  186. data/test/test_helper.rb +36 -0
  187. data/test/unit/cache_reporter_fetcher_test.rb +46 -0
  188. data/test/unit/cache_reporter_test.rb +97 -0
  189. data/test/unit/caching_test.rb +78 -0
  190. data/test/unit/change_test.rb +152 -0
  191. data/test/unit/chdir_runner_test.rb +77 -0
  192. data/test/unit/chief_editor_test.rb +234 -0
  193. data/test/unit/darcs_connection_test.rb +109 -0
  194. data/test/unit/darcs_reporter_test.rb +146 -0
  195. data/test/unit/darcs_settings_test.rb +37 -0
  196. data/test/unit/darcs_temp_repo_test.rb +51 -0
  197. data/test/unit/default_page_provider_test.rb +46 -0
  198. data/test/unit/diff_table_builder_test.rb +602 -0
  199. data/test/unit/headline_test.rb +259 -0
  200. data/test/unit/page_test.rb +145 -0
  201. data/test/unit/relative_time_test.rb +56 -0
  202. data/test/unit/reporter_driver_test.rb +85 -0
  203. data/test/unit/reporter_fetcher_test.rb +31 -0
  204. data/test/unit/reporter_test.rb +81 -0
  205. data/test/unit/runner_test.rb +93 -0
  206. data/test/unit/settings_test.rb +55 -0
  207. data/test/unit/string_extensions_test.rb +10 -0
  208. data/test/unit/svn_connection_test.rb +183 -0
  209. data/test/unit/svn_reporter_interaction_test.rb +38 -0
  210. data/test/unit/svn_reporter_test.rb +286 -0
  211. data/test/unit/svn_settings_test.rb +86 -0
  212. data/test/unit/translator_test.rb +96 -0
  213. data/test/unit/user_test.rb +125 -0
  214. data/test/unit/wiki_renderer_test.rb +87 -0
  215. data/test/unit/wiki_reporter_test.rb +94 -0
  216. data/test/unit/wiki_url_generator_test.rb +31 -0
  217. data/vendor/motiro-installer.rb +159 -0
  218. data/vendor/plugins/globalize/LICENSE +9 -0
  219. data/vendor/plugins/globalize/README +49 -0
  220. data/vendor/plugins/globalize/data/country_data.csv +240 -0
  221. data/vendor/plugins/globalize/data/language_data.csv +188 -0
  222. data/vendor/plugins/globalize/data/translation_data.csv +3421 -0
  223. data/vendor/plugins/globalize/generators/globalize/USAGE +10 -0
  224. data/vendor/plugins/globalize/generators/globalize/globalize_generator.rb +42 -0
  225. data/vendor/plugins/globalize/generators/globalize/templates/migration.rb.gz +0 -0
  226. data/vendor/plugins/globalize/generators/globalize/templates/tiny_migration.rb.gz +0 -0
  227. data/vendor/plugins/globalize/init.rb +30 -0
  228. data/vendor/plugins/globalize/lib/globalize/localization/core_ext.rb +170 -0
  229. data/vendor/plugins/globalize/lib/globalize/localization/core_ext_hooks.rb +33 -0
  230. data/vendor/plugins/globalize/lib/globalize/localization/db_translate.rb +494 -0
  231. data/vendor/plugins/globalize/lib/globalize/localization/db_view_translator.rb +152 -0
  232. data/vendor/plugins/globalize/lib/globalize/localization/locale.rb +173 -0
  233. data/vendor/plugins/globalize/lib/globalize/localization/rfc_3066.rb +40 -0
  234. data/vendor/plugins/globalize/lib/globalize/models/country.rb +24 -0
  235. data/vendor/plugins/globalize/lib/globalize/models/currency.rb +188 -0
  236. data/vendor/plugins/globalize/lib/globalize/models/language.rb +84 -0
  237. data/vendor/plugins/globalize/lib/globalize/models/model_translation.rb +4 -0
  238. data/vendor/plugins/globalize/lib/globalize/models/translation.rb +9 -0
  239. data/vendor/plugins/globalize/lib/globalize/models/view_translation.rb +14 -0
  240. data/vendor/plugins/globalize/lib/globalize/rails/action_mailer.rb +125 -0
  241. data/vendor/plugins/globalize/lib/globalize/rails/action_view.rb +79 -0
  242. data/vendor/plugins/globalize/lib/globalize/rails/active_record.rb +129 -0
  243. data/vendor/plugins/globalize/lib/globalize/rails/active_record_helper.rb +33 -0
  244. data/vendor/plugins/globalize/populators/pop_dates.rb +81 -0
  245. data/vendor/plugins/globalize/populators/pop_migration.rb +18 -0
  246. data/vendor/plugins/globalize/populators/pop_pluralization.rb +26 -0
  247. data/vendor/plugins/globalize/populators/pop_seps.rb +32 -0
  248. data/vendor/plugins/globalize/tasks/data.rake +130 -0
  249. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en-US.plain.text.rhtml +1 -0
  250. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en.plain.text.rhtml +1 -0
  251. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.he.plain.text.rhtml +1 -0
  252. data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.plain.text.rhtml +1 -0
  253. data/vendor/plugins/globalize/test/action_mailer_test.rb +54 -0
  254. data/vendor/plugins/globalize/test/config/database.yml.default +16 -0
  255. data/vendor/plugins/globalize/test/config/database.yml.example +22 -0
  256. data/vendor/plugins/globalize/test/core_ext_test.rb +61 -0
  257. data/vendor/plugins/globalize/test/currency_test.rb +141 -0
  258. data/vendor/plugins/globalize/test/date_helper_test.rb +634 -0
  259. data/vendor/plugins/globalize/test/db/schema.rb +90 -0
  260. data/vendor/plugins/globalize/test/db_translation_test.rb +374 -0
  261. data/vendor/plugins/globalize/test/fixtures/globalize_categories.yml +7 -0
  262. data/vendor/plugins/globalize/test/fixtures/globalize_categories_products.yml +7 -0
  263. data/vendor/plugins/globalize/test/fixtures/globalize_countries.yml +41 -0
  264. data/vendor/plugins/globalize/test/fixtures/globalize_languages.yml +64 -0
  265. data/vendor/plugins/globalize/test/fixtures/globalize_manufacturers.yml +5 -0
  266. data/vendor/plugins/globalize/test/fixtures/globalize_products.yml +29 -0
  267. data/vendor/plugins/globalize/test/fixtures/globalize_simples.yml +5 -0
  268. data/vendor/plugins/globalize/test/fixtures/globalize_translations.yml +354 -0
  269. data/vendor/plugins/globalize/test/locale_test.rb +27 -0
  270. data/vendor/plugins/globalize/test/mime_responds_test.rb +358 -0
  271. data/vendor/plugins/globalize/test/model_test.rb +17 -0
  272. data/vendor/plugins/globalize/test/standard_data_test_helper.rb +33 -0
  273. data/vendor/plugins/globalize/test/test_helper.rb +19 -0
  274. data/vendor/plugins/globalize/test/test_standard_data.rb +54 -0
  275. data/vendor/plugins/globalize/test/validation_test.rb +29 -0
  276. data/vendor/plugins/globalize/test/view_picking_test.rb +49 -0
  277. data/vendor/plugins/globalize/test/view_translation_test.rb +237 -0
  278. data/vendor/plugins/globalize/test/views/layouts/standard.rhtml +1 -0
  279. data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rhtml +1 -0
  280. data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rjs +1 -0
  281. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rhtml +1 -0
  282. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rjs +1 -0
  283. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rxml +1 -0
  284. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rhtml +1 -0
  285. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rjs +1 -0
  286. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rxml +1 -0
  287. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rhtml +1 -0
  288. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rjs +1 -0
  289. data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rxml +1 -0
  290. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rhtml +1 -0
  291. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rjs +1 -0
  292. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rxml +1 -0
  293. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rhtml +1 -0
  294. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rjs +1 -0
  295. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rxml +1 -0
  296. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rhtml +1 -0
  297. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rjs +1 -0
  298. data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rxml +1 -0
  299. data/vendor/plugins/globalize/test/views/test.he-IL.rhtml +1 -0
  300. data/vendor/plugins/globalize/test/views/test.rhtml +1 -0
  301. data/vendor/plugins/globalize/test/views/test2.he.rhtml +1 -0
  302. data/vendor/plugins/globalize/test/views/test2.rhtml +1 -0
  303. data/vendor/plugins/test_xml/MIT-LICENSE +20 -0
  304. data/vendor/plugins/test_xml/README +20 -0
  305. data/vendor/plugins/test_xml/Rakefile +22 -0
  306. data/vendor/plugins/test_xml/init.rb +7 -0
  307. data/vendor/plugins/test_xml/lib/xml_assertions.rb +22 -0
  308. data/vendor/plugins/test_xml/test/test_xml_test.rb +40 -0
  309. metadata +505 -0
@@ -0,0 +1,750 @@
1
+ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
+ // (c) 2005 Jon Tirsen (http://www.tirsen.com)
4
+ // Contributors:
5
+ // Richard Livsey
6
+ // Rahul Bhargava
7
+ // Rob Wills
8
+ //
9
+ // See scriptaculous.js for full license.
10
+
11
+ // Autocompleter.Base handles all the autocompletion functionality
12
+ // that's independent of the data source for autocompletion. This
13
+ // includes drawing the autocompletion menu, observing keyboard
14
+ // and mouse events, and similar.
15
+ //
16
+ // Specific autocompleters need to provide, at the very least,
17
+ // a getUpdatedChoices function that will be invoked every time
18
+ // the text inside the monitored textbox changes. This method
19
+ // should get the text for which to provide autocompletion by
20
+ // invoking this.getToken(), NOT by directly accessing
21
+ // this.element.value. This is to allow incremental tokenized
22
+ // autocompletion. Specific auto-completion logic (AJAX, etc)
23
+ // belongs in getUpdatedChoices.
24
+ //
25
+ // Tokenized incremental autocompletion is enabled automatically
26
+ // when an autocompleter is instantiated with the 'tokens' option
27
+ // in the options parameter, e.g.:
28
+ // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29
+ // will incrementally autocomplete with a comma as the token.
30
+ // Additionally, ',' in the above example can be replaced with
31
+ // a token array, e.g. { tokens: [',', '\n'] } which
32
+ // enables autocompletion on multiple tokens. This is most
33
+ // useful when one of the tokens is \n (a newline), as it
34
+ // allows smart autocompletion after linebreaks.
35
+
36
+ var Autocompleter = {}
37
+ Autocompleter.Base = function() {};
38
+ Autocompleter.Base.prototype = {
39
+ baseInitialize: function(element, update, options) {
40
+ this.element = $(element);
41
+ this.update = $(update);
42
+ this.hasFocus = false;
43
+ this.changed = false;
44
+ this.active = false;
45
+ this.index = 0;
46
+ this.entryCount = 0;
47
+
48
+ if (this.setOptions)
49
+ this.setOptions(options);
50
+ else
51
+ this.options = options || {};
52
+
53
+ this.options.paramName = this.options.paramName || this.element.name;
54
+ this.options.tokens = this.options.tokens || [];
55
+ this.options.frequency = this.options.frequency || 0.4;
56
+ this.options.minChars = this.options.minChars || 1;
57
+ this.options.onShow = this.options.onShow ||
58
+ function(element, update){
59
+ if(!update.style.position || update.style.position=='absolute') {
60
+ update.style.position = 'absolute';
61
+ Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
62
+ }
63
+ Effect.Appear(update,{duration:0.15});
64
+ };
65
+ this.options.onHide = this.options.onHide ||
66
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67
+
68
+ if (typeof(this.options.tokens) == 'string')
69
+ this.options.tokens = new Array(this.options.tokens);
70
+
71
+ this.observer = null;
72
+
73
+ this.element.setAttribute('autocomplete','off');
74
+
75
+ Element.hide(this.update);
76
+
77
+ Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78
+ Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79
+ },
80
+
81
+ show: function() {
82
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83
+ if(!this.iefix &&
84
+ (navigator.appVersion.indexOf('MSIE')>0) &&
85
+ (navigator.userAgent.indexOf('Opera')<0) &&
86
+ (Element.getStyle(this.update, 'position')=='absolute')) {
87
+ new Insertion.After(this.update,
88
+ '<iframe id="' + this.update.id + '_iefix" '+
89
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91
+ this.iefix = $(this.update.id+'_iefix');
92
+ }
93
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94
+ },
95
+
96
+ fixIEOverlapping: function() {
97
+ Position.clone(this.update, this.iefix);
98
+ this.iefix.style.zIndex = 1;
99
+ this.update.style.zIndex = 2;
100
+ Element.show(this.iefix);
101
+ },
102
+
103
+ hide: function() {
104
+ this.stopIndicator();
105
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106
+ if(this.iefix) Element.hide(this.iefix);
107
+ },
108
+
109
+ startIndicator: function() {
110
+ if(this.options.indicator) Element.show(this.options.indicator);
111
+ },
112
+
113
+ stopIndicator: function() {
114
+ if(this.options.indicator) Element.hide(this.options.indicator);
115
+ },
116
+
117
+ onKeyPress: function(event) {
118
+ if(this.active)
119
+ switch(event.keyCode) {
120
+ case Event.KEY_TAB:
121
+ case Event.KEY_RETURN:
122
+ this.selectEntry();
123
+ Event.stop(event);
124
+ case Event.KEY_ESC:
125
+ this.hide();
126
+ this.active = false;
127
+ Event.stop(event);
128
+ return;
129
+ case Event.KEY_LEFT:
130
+ case Event.KEY_RIGHT:
131
+ return;
132
+ case Event.KEY_UP:
133
+ this.markPrevious();
134
+ this.render();
135
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
136
+ return;
137
+ case Event.KEY_DOWN:
138
+ this.markNext();
139
+ this.render();
140
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
141
+ return;
142
+ }
143
+ else
144
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145
+ return;
146
+
147
+ this.changed = true;
148
+ this.hasFocus = true;
149
+
150
+ if(this.observer) clearTimeout(this.observer);
151
+ this.observer =
152
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
+ },
154
+
155
+ onHover: function(event) {
156
+ var element = Event.findElement(event, 'LI');
157
+ if(this.index != element.autocompleteIndex)
158
+ {
159
+ this.index = element.autocompleteIndex;
160
+ this.render();
161
+ }
162
+ Event.stop(event);
163
+ },
164
+
165
+ onClick: function(event) {
166
+ var element = Event.findElement(event, 'LI');
167
+ this.index = element.autocompleteIndex;
168
+ this.selectEntry();
169
+ this.hide();
170
+ },
171
+
172
+ onBlur: function(event) {
173
+ // needed to make click events working
174
+ setTimeout(this.hide.bind(this), 250);
175
+ this.hasFocus = false;
176
+ this.active = false;
177
+ },
178
+
179
+ render: function() {
180
+ if(this.entryCount > 0) {
181
+ for (var i = 0; i < this.entryCount; i++)
182
+ this.index==i ?
183
+ Element.addClassName(this.getEntry(i),"selected") :
184
+ Element.removeClassName(this.getEntry(i),"selected");
185
+
186
+ if(this.hasFocus) {
187
+ this.show();
188
+ this.active = true;
189
+ }
190
+ } else {
191
+ this.active = false;
192
+ this.hide();
193
+ }
194
+ },
195
+
196
+ markPrevious: function() {
197
+ if(this.index > 0) this.index--
198
+ else this.index = this.entryCount-1;
199
+ },
200
+
201
+ markNext: function() {
202
+ if(this.index < this.entryCount-1) this.index++
203
+ else this.index = 0;
204
+ },
205
+
206
+ getEntry: function(index) {
207
+ return this.update.firstChild.childNodes[index];
208
+ },
209
+
210
+ getCurrentEntry: function() {
211
+ return this.getEntry(this.index);
212
+ },
213
+
214
+ selectEntry: function() {
215
+ this.active = false;
216
+ this.updateElement(this.getCurrentEntry());
217
+ },
218
+
219
+ updateElement: function(selectedElement) {
220
+ if (this.options.updateElement) {
221
+ this.options.updateElement(selectedElement);
222
+ return;
223
+ }
224
+
225
+ var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
226
+ var lastTokenPos = this.findLastToken();
227
+ if (lastTokenPos != -1) {
228
+ var newValue = this.element.value.substr(0, lastTokenPos + 1);
229
+ var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
230
+ if (whitespace)
231
+ newValue += whitespace[0];
232
+ this.element.value = newValue + value;
233
+ } else {
234
+ this.element.value = value;
235
+ }
236
+ this.element.focus();
237
+
238
+ if (this.options.afterUpdateElement)
239
+ this.options.afterUpdateElement(this.element, selectedElement);
240
+ },
241
+
242
+ updateChoices: function(choices) {
243
+ if(!this.changed && this.hasFocus) {
244
+ this.update.innerHTML = choices;
245
+ Element.cleanWhitespace(this.update);
246
+ Element.cleanWhitespace(this.update.firstChild);
247
+
248
+ if(this.update.firstChild && this.update.firstChild.childNodes) {
249
+ this.entryCount =
250
+ this.update.firstChild.childNodes.length;
251
+ for (var i = 0; i < this.entryCount; i++) {
252
+ var entry = this.getEntry(i);
253
+ entry.autocompleteIndex = i;
254
+ this.addObservers(entry);
255
+ }
256
+ } else {
257
+ this.entryCount = 0;
258
+ }
259
+
260
+ this.stopIndicator();
261
+
262
+ this.index = 0;
263
+ this.render();
264
+ }
265
+ },
266
+
267
+ addObservers: function(element) {
268
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
269
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
270
+ },
271
+
272
+ onObserverEvent: function() {
273
+ this.changed = false;
274
+ if(this.getToken().length>=this.options.minChars) {
275
+ this.startIndicator();
276
+ this.getUpdatedChoices();
277
+ } else {
278
+ this.active = false;
279
+ this.hide();
280
+ }
281
+ },
282
+
283
+ getToken: function() {
284
+ var tokenPos = this.findLastToken();
285
+ if (tokenPos != -1)
286
+ var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
287
+ else
288
+ var ret = this.element.value;
289
+
290
+ return /\n/.test(ret) ? '' : ret;
291
+ },
292
+
293
+ findLastToken: function() {
294
+ var lastTokenPos = -1;
295
+
296
+ for (var i=0; i<this.options.tokens.length; i++) {
297
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
298
+ if (thisTokenPos > lastTokenPos)
299
+ lastTokenPos = thisTokenPos;
300
+ }
301
+ return lastTokenPos;
302
+ }
303
+ }
304
+
305
+ Ajax.Autocompleter = Class.create();
306
+ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
307
+ initialize: function(element, update, url, options) {
308
+ this.baseInitialize(element, update, options);
309
+ this.options.asynchronous = true;
310
+ this.options.onComplete = this.onComplete.bind(this);
311
+ this.options.defaultParams = this.options.parameters || null;
312
+ this.url = url;
313
+ },
314
+
315
+ getUpdatedChoices: function() {
316
+ entry = encodeURIComponent(this.options.paramName) + '=' +
317
+ encodeURIComponent(this.getToken());
318
+
319
+ this.options.parameters = this.options.callback ?
320
+ this.options.callback(this.element, entry) : entry;
321
+
322
+ if(this.options.defaultParams)
323
+ this.options.parameters += '&' + this.options.defaultParams;
324
+
325
+ new Ajax.Request(this.url, this.options);
326
+ },
327
+
328
+ onComplete: function(request) {
329
+ this.updateChoices(request.responseText);
330
+ }
331
+
332
+ });
333
+
334
+ // The local array autocompleter. Used when you'd prefer to
335
+ // inject an array of autocompletion options into the page, rather
336
+ // than sending out Ajax queries, which can be quite slow sometimes.
337
+ //
338
+ // The constructor takes four parameters. The first two are, as usual,
339
+ // the id of the monitored textbox, and id of the autocompletion menu.
340
+ // The third is the array you want to autocomplete from, and the fourth
341
+ // is the options block.
342
+ //
343
+ // Extra local autocompletion options:
344
+ // - choices - How many autocompletion choices to offer
345
+ //
346
+ // - partialSearch - If false, the autocompleter will match entered
347
+ // text only at the beginning of strings in the
348
+ // autocomplete array. Defaults to true, which will
349
+ // match text at the beginning of any *word* in the
350
+ // strings in the autocomplete array. If you want to
351
+ // search anywhere in the string, additionally set
352
+ // the option fullSearch to true (default: off).
353
+ //
354
+ // - fullSsearch - Search anywhere in autocomplete array strings.
355
+ //
356
+ // - partialChars - How many characters to enter before triggering
357
+ // a partial match (unlike minChars, which defines
358
+ // how many characters are required to do any match
359
+ // at all). Defaults to 2.
360
+ //
361
+ // - ignoreCase - Whether to ignore case when autocompleting.
362
+ // Defaults to true.
363
+ //
364
+ // It's possible to pass in a custom function as the 'selector'
365
+ // option, if you prefer to write your own autocompletion logic.
366
+ // In that case, the other options above will not apply unless
367
+ // you support them.
368
+
369
+ Autocompleter.Local = Class.create();
370
+ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
371
+ initialize: function(element, update, array, options) {
372
+ this.baseInitialize(element, update, options);
373
+ this.options.array = array;
374
+ },
375
+
376
+ getUpdatedChoices: function() {
377
+ this.updateChoices(this.options.selector(this));
378
+ },
379
+
380
+ setOptions: function(options) {
381
+ this.options = Object.extend({
382
+ choices: 10,
383
+ partialSearch: true,
384
+ partialChars: 2,
385
+ ignoreCase: true,
386
+ fullSearch: false,
387
+ selector: function(instance) {
388
+ var ret = []; // Beginning matches
389
+ var partial = []; // Inside matches
390
+ var entry = instance.getToken();
391
+ var count = 0;
392
+
393
+ for (var i = 0; i < instance.options.array.length &&
394
+ ret.length < instance.options.choices ; i++) {
395
+
396
+ var elem = instance.options.array[i];
397
+ var foundPos = instance.options.ignoreCase ?
398
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
399
+ elem.indexOf(entry);
400
+
401
+ while (foundPos != -1) {
402
+ if (foundPos == 0 && elem.length != entry.length) {
403
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
404
+ elem.substr(entry.length) + "</li>");
405
+ break;
406
+ } else if (entry.length >= instance.options.partialChars &&
407
+ instance.options.partialSearch && foundPos != -1) {
408
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
409
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
410
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
411
+ foundPos + entry.length) + "</li>");
412
+ break;
413
+ }
414
+ }
415
+
416
+ foundPos = instance.options.ignoreCase ?
417
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
418
+ elem.indexOf(entry, foundPos + 1);
419
+
420
+ }
421
+ }
422
+ if (partial.length)
423
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
424
+ return "<ul>" + ret.join('') + "</ul>";
425
+ }
426
+ }, options || {});
427
+ }
428
+ });
429
+
430
+ // AJAX in-place editor
431
+ //
432
+ // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
433
+
434
+ // Use this if you notice weird scrolling problems on some browsers,
435
+ // the DOM might be a bit confused when this gets called so do this
436
+ // waits 1 ms (with setTimeout) until it does the activation
437
+ Field.scrollFreeActivate = function(field) {
438
+ setTimeout(function() {
439
+ Field.activate(field);
440
+ }, 1);
441
+ }
442
+
443
+ Ajax.InPlaceEditor = Class.create();
444
+ Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
445
+ Ajax.InPlaceEditor.prototype = {
446
+ initialize: function(element, url, options) {
447
+ this.url = url;
448
+ this.element = $(element);
449
+
450
+ this.options = Object.extend({
451
+ okText: "ok",
452
+ cancelText: "cancel",
453
+ savingText: "Saving...",
454
+ clickToEditText: "Click to edit",
455
+ okText: "ok",
456
+ rows: 1,
457
+ onComplete: function(transport, element) {
458
+ new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
459
+ },
460
+ onFailure: function(transport) {
461
+ alert("Error communicating with the server: " + transport.responseText.stripTags());
462
+ },
463
+ callback: function(form) {
464
+ return Form.serialize(form);
465
+ },
466
+ handleLineBreaks: true,
467
+ loadingText: 'Loading...',
468
+ savingClassName: 'inplaceeditor-saving',
469
+ loadingClassName: 'inplaceeditor-loading',
470
+ formClassName: 'inplaceeditor-form',
471
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
472
+ highlightendcolor: "#FFFFFF",
473
+ externalControl: null,
474
+ ajaxOptions: {}
475
+ }, options || {});
476
+
477
+ if(!this.options.formId && this.element.id) {
478
+ this.options.formId = this.element.id + "-inplaceeditor";
479
+ if ($(this.options.formId)) {
480
+ // there's already a form with that name, don't specify an id
481
+ this.options.formId = null;
482
+ }
483
+ }
484
+
485
+ if (this.options.externalControl) {
486
+ this.options.externalControl = $(this.options.externalControl);
487
+ }
488
+
489
+ this.originalBackground = Element.getStyle(this.element, 'background-color');
490
+ if (!this.originalBackground) {
491
+ this.originalBackground = "transparent";
492
+ }
493
+
494
+ this.element.title = this.options.clickToEditText;
495
+
496
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
497
+ this.mouseoverListener = this.enterHover.bindAsEventListener(this);
498
+ this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
499
+ Event.observe(this.element, 'click', this.onclickListener);
500
+ Event.observe(this.element, 'mouseover', this.mouseoverListener);
501
+ Event.observe(this.element, 'mouseout', this.mouseoutListener);
502
+ if (this.options.externalControl) {
503
+ Event.observe(this.options.externalControl, 'click', this.onclickListener);
504
+ Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
505
+ Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
506
+ }
507
+ },
508
+ enterEditMode: function(evt) {
509
+ if (this.saving) return;
510
+ if (this.editing) return;
511
+ this.editing = true;
512
+ this.onEnterEditMode();
513
+ if (this.options.externalControl) {
514
+ Element.hide(this.options.externalControl);
515
+ }
516
+ Element.hide(this.element);
517
+ this.createForm();
518
+ this.element.parentNode.insertBefore(this.form, this.element);
519
+ Field.scrollFreeActivate(this.editField);
520
+ // stop the event to avoid a page refresh in Safari
521
+ if (evt) {
522
+ Event.stop(evt);
523
+ }
524
+ return false;
525
+ },
526
+ createForm: function() {
527
+ this.form = document.createElement("form");
528
+ this.form.id = this.options.formId;
529
+ Element.addClassName(this.form, this.options.formClassName)
530
+ this.form.onsubmit = this.onSubmit.bind(this);
531
+
532
+ this.createEditField();
533
+
534
+ if (this.options.textarea) {
535
+ var br = document.createElement("br");
536
+ this.form.appendChild(br);
537
+ }
538
+
539
+ okButton = document.createElement("input");
540
+ okButton.type = "submit";
541
+ okButton.value = this.options.okText;
542
+ this.form.appendChild(okButton);
543
+
544
+ cancelLink = document.createElement("a");
545
+ cancelLink.href = "#";
546
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
547
+ cancelLink.onclick = this.onclickCancel.bind(this);
548
+ this.form.appendChild(cancelLink);
549
+ },
550
+ hasHTMLLineBreaks: function(string) {
551
+ if (!this.options.handleLineBreaks) return false;
552
+ return string.match(/<br/i) || string.match(/<p>/i);
553
+ },
554
+ convertHTMLLineBreaks: function(string) {
555
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
556
+ },
557
+ createEditField: function() {
558
+ var text;
559
+ if(this.options.loadTextURL) {
560
+ text = this.options.loadingText;
561
+ } else {
562
+ text = this.getText();
563
+ }
564
+
565
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
566
+ this.options.textarea = false;
567
+ var textField = document.createElement("input");
568
+ textField.type = "text";
569
+ textField.name = "value";
570
+ textField.value = text;
571
+ textField.style.backgroundColor = this.options.highlightcolor;
572
+ var size = this.options.size || this.options.cols || 0;
573
+ if (size != 0) textField.size = size;
574
+ this.editField = textField;
575
+ } else {
576
+ this.options.textarea = true;
577
+ var textArea = document.createElement("textarea");
578
+ textArea.name = "value";
579
+ textArea.value = this.convertHTMLLineBreaks(text);
580
+ textArea.rows = this.options.rows;
581
+ textArea.cols = this.options.cols || 40;
582
+ this.editField = textArea;
583
+ }
584
+
585
+ if(this.options.loadTextURL) {
586
+ this.loadExternalText();
587
+ }
588
+ this.form.appendChild(this.editField);
589
+ },
590
+ getText: function() {
591
+ return this.element.innerHTML;
592
+ },
593
+ loadExternalText: function() {
594
+ Element.addClassName(this.form, this.options.loadingClassName);
595
+ this.editField.disabled = true;
596
+ new Ajax.Request(
597
+ this.options.loadTextURL,
598
+ Object.extend({
599
+ asynchronous: true,
600
+ onComplete: this.onLoadedExternalText.bind(this)
601
+ }, this.options.ajaxOptions)
602
+ );
603
+ },
604
+ onLoadedExternalText: function(transport) {
605
+ Element.removeClassName(this.form, this.options.loadingClassName);
606
+ this.editField.disabled = false;
607
+ this.editField.value = transport.responseText.stripTags();
608
+ },
609
+ onclickCancel: function() {
610
+ this.onComplete();
611
+ this.leaveEditMode();
612
+ return false;
613
+ },
614
+ onFailure: function(transport) {
615
+ this.options.onFailure(transport);
616
+ if (this.oldInnerHTML) {
617
+ this.element.innerHTML = this.oldInnerHTML;
618
+ this.oldInnerHTML = null;
619
+ }
620
+ return false;
621
+ },
622
+ onSubmit: function() {
623
+ // onLoading resets these so we need to save them away for the Ajax call
624
+ var form = this.form;
625
+ var value = this.editField.value;
626
+
627
+ // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
628
+ // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
629
+ // to be displayed indefinitely
630
+ this.onLoading();
631
+
632
+ new Ajax.Updater(
633
+ {
634
+ success: this.element,
635
+ // don't update on failure (this could be an option)
636
+ failure: null
637
+ },
638
+ this.url,
639
+ Object.extend({
640
+ parameters: this.options.callback(form, value),
641
+ onComplete: this.onComplete.bind(this),
642
+ onFailure: this.onFailure.bind(this)
643
+ }, this.options.ajaxOptions)
644
+ );
645
+ // stop the event to avoid a page refresh in Safari
646
+ if (arguments.length > 1) {
647
+ Event.stop(arguments[0]);
648
+ }
649
+ return false;
650
+ },
651
+ onLoading: function() {
652
+ this.saving = true;
653
+ this.removeForm();
654
+ this.leaveHover();
655
+ this.showSaving();
656
+ },
657
+ showSaving: function() {
658
+ this.oldInnerHTML = this.element.innerHTML;
659
+ this.element.innerHTML = this.options.savingText;
660
+ Element.addClassName(this.element, this.options.savingClassName);
661
+ this.element.style.backgroundColor = this.originalBackground;
662
+ Element.show(this.element);
663
+ },
664
+ removeForm: function() {
665
+ if(this.form) {
666
+ if (this.form.parentNode) Element.remove(this.form);
667
+ this.form = null;
668
+ }
669
+ },
670
+ enterHover: function() {
671
+ if (this.saving) return;
672
+ this.element.style.backgroundColor = this.options.highlightcolor;
673
+ if (this.effect) {
674
+ this.effect.cancel();
675
+ }
676
+ Element.addClassName(this.element, this.options.hoverClassName)
677
+ },
678
+ leaveHover: function() {
679
+ if (this.options.backgroundColor) {
680
+ this.element.style.backgroundColor = this.oldBackground;
681
+ }
682
+ Element.removeClassName(this.element, this.options.hoverClassName)
683
+ if (this.saving) return;
684
+ this.effect = new Effect.Highlight(this.element, {
685
+ startcolor: this.options.highlightcolor,
686
+ endcolor: this.options.highlightendcolor,
687
+ restorecolor: this.originalBackground
688
+ });
689
+ },
690
+ leaveEditMode: function() {
691
+ Element.removeClassName(this.element, this.options.savingClassName);
692
+ this.removeForm();
693
+ this.leaveHover();
694
+ this.element.style.backgroundColor = this.originalBackground;
695
+ Element.show(this.element);
696
+ if (this.options.externalControl) {
697
+ Element.show(this.options.externalControl);
698
+ }
699
+ this.editing = false;
700
+ this.saving = false;
701
+ this.oldInnerHTML = null;
702
+ this.onLeaveEditMode();
703
+ },
704
+ onComplete: function(transport) {
705
+ this.leaveEditMode();
706
+ this.options.onComplete.bind(this)(transport, this.element);
707
+ },
708
+ onEnterEditMode: function() {},
709
+ onLeaveEditMode: function() {},
710
+ dispose: function() {
711
+ if (this.oldInnerHTML) {
712
+ this.element.innerHTML = this.oldInnerHTML;
713
+ }
714
+ this.leaveEditMode();
715
+ Event.stopObserving(this.element, 'click', this.onclickListener);
716
+ Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
717
+ Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
718
+ if (this.options.externalControl) {
719
+ Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
720
+ Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
721
+ Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
722
+ }
723
+ }
724
+ };
725
+
726
+ // Delayed observer, like Form.Element.Observer,
727
+ // but waits for delay after last key input
728
+ // Ideal for live-search fields
729
+
730
+ Form.Element.DelayedObserver = Class.create();
731
+ Form.Element.DelayedObserver.prototype = {
732
+ initialize: function(element, delay, callback) {
733
+ this.delay = delay || 0.5;
734
+ this.element = $(element);
735
+ this.callback = callback;
736
+ this.timer = null;
737
+ this.lastValue = $F(this.element);
738
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
739
+ },
740
+ delayedListener: function(event) {
741
+ if(this.lastValue == $F(this.element)) return;
742
+ if(this.timer) clearTimeout(this.timer);
743
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
744
+ this.lastValue = $F(this.element);
745
+ },
746
+ onTimerEvent: function() {
747
+ this.timer = null;
748
+ this.callback(this.element, $F(this.element));
749
+ }
750
+ };