rails 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rails might be problematic. Click here for more details.

Files changed (177) hide show
  1. data/CHANGELOG +1020 -10
  2. data/MIT-LICENSE +1 -1
  3. data/README +110 -60
  4. data/Rakefile +74 -139
  5. data/bin/about +1 -1
  6. data/bin/console +1 -1
  7. data/bin/destroy +1 -1
  8. data/bin/generate +1 -1
  9. data/bin/performance/request +3 -0
  10. data/bin/plugin +1 -1
  11. data/bin/process/{spinner → inspector} +1 -1
  12. data/bin/rails +10 -12
  13. data/bin/runner +1 -1
  14. data/bin/server +1 -1
  15. data/{lib/rails_info.rb → builtin/rails_info/rails/info.rb} +33 -14
  16. data/builtin/rails_info/rails/info_controller.rb +9 -0
  17. data/builtin/rails_info/rails/info_helper.rb +2 -0
  18. data/builtin/rails_info/rails_info_controller.rb +2 -0
  19. data/configs/apache.conf +1 -1
  20. data/configs/databases/frontbase.yml +28 -0
  21. data/configs/databases/mysql.yml +54 -0
  22. data/configs/databases/oracle.yml +39 -0
  23. data/configs/databases/postgresql.yml +48 -0
  24. data/configs/databases/sqlite2.yml +16 -0
  25. data/configs/databases/sqlite3.yml +19 -0
  26. data/configs/initializers/inflections.rb +10 -0
  27. data/configs/initializers/mime_types.rb +5 -0
  28. data/configs/lighttpd.conf +29 -15
  29. data/configs/routes.rb +27 -11
  30. data/doc/README_FOR_APP +1 -1
  31. data/environments/boot.rb +103 -14
  32. data/environments/development.rb +5 -6
  33. data/environments/environment.rb +36 -30
  34. data/environments/production.rb +2 -3
  35. data/environments/test.rb +5 -2
  36. data/fresh_rakefile +2 -2
  37. data/helpers/application.rb +8 -2
  38. data/helpers/test_helper.rb +10 -0
  39. data/html/404.html +27 -5
  40. data/html/422.html +30 -0
  41. data/html/500.html +27 -5
  42. data/html/index.html +6 -6
  43. data/html/javascripts/application.js +2 -0
  44. data/html/javascripts/controls.js +532 -319
  45. data/html/javascripts/dragdrop.js +521 -133
  46. data/html/javascripts/effects.js +708 -442
  47. data/html/javascripts/prototype.js +3393 -953
  48. data/html/robots.txt +5 -1
  49. data/lib/code_statistics.rb +2 -2
  50. data/lib/commands/console.rb +18 -9
  51. data/lib/commands/performance/profiler.rb +25 -9
  52. data/lib/commands/performance/request.rb +6 -0
  53. data/lib/commands/plugin.rb +196 -96
  54. data/lib/commands/process/inspector.rb +68 -0
  55. data/lib/commands/process/reaper.rb +90 -71
  56. data/lib/commands/process/spawner.rb +188 -21
  57. data/lib/commands/process/spinner.rb +3 -3
  58. data/lib/commands/runner.rb +28 -7
  59. data/lib/commands/server.rb +20 -9
  60. data/lib/commands/servers/base.rb +31 -0
  61. data/lib/commands/servers/lighttpd.rb +60 -26
  62. data/lib/commands/servers/mongrel.rb +69 -0
  63. data/lib/commands/servers/webrick.rb +18 -11
  64. data/lib/console_app.rb +30 -0
  65. data/lib/console_sandbox.rb +2 -2
  66. data/lib/console_with_helpers.rb +26 -0
  67. data/lib/dispatcher.rb +3 -78
  68. data/lib/fcgi_handler.rb +98 -64
  69. data/lib/initializer.rb +323 -194
  70. data/lib/rails/plugin/loader.rb +150 -0
  71. data/lib/rails/plugin/locator.rb +78 -0
  72. data/lib/rails/plugin.rb +84 -0
  73. data/lib/{rails_version.rb → rails/version.rb} +1 -1
  74. data/lib/rails_generator/base.rb +85 -25
  75. data/lib/rails_generator/commands.rb +122 -40
  76. data/lib/rails_generator/generated_attribute.rb +42 -0
  77. data/lib/rails_generator/generators/applications/app/USAGE +0 -7
  78. data/lib/rails_generator/generators/applications/app/app_generator.rb +67 -28
  79. data/lib/rails_generator/generators/components/controller/USAGE +11 -12
  80. data/lib/rails_generator/generators/components/controller/controller_generator.rb +2 -3
  81. data/lib/rails_generator/generators/components/controller/templates/functional_test.rb +1 -11
  82. data/lib/rails_generator/generators/components/controller/templates/{view.rhtml → view.html.erb} +0 -0
  83. data/lib/rails_generator/generators/components/integration_test/USAGE +8 -0
  84. data/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb +16 -0
  85. data/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb +10 -0
  86. data/lib/rails_generator/generators/components/mailer/USAGE +9 -11
  87. data/lib/rails_generator/generators/components/mailer/mailer_generator.rb +10 -8
  88. data/lib/rails_generator/generators/components/mailer/templates/fixture.erb +3 -0
  89. data/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml +0 -3
  90. data/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +9 -25
  91. data/lib/rails_generator/generators/components/mailer/templates/view.erb +3 -0
  92. data/lib/rails_generator/generators/components/mailer/templates/view.rhtml +0 -3
  93. data/lib/rails_generator/generators/components/migration/USAGE +23 -8
  94. data/lib/rails_generator/generators/components/migration/migration_generator.rb +15 -2
  95. data/lib/rails_generator/generators/components/migration/templates/migration.rb +7 -3
  96. data/lib/rails_generator/generators/components/model/USAGE +21 -11
  97. data/lib/rails_generator/generators/components/model/model_generator.rb +28 -1
  98. data/lib/rails_generator/generators/components/model/templates/fixtures.yml +18 -4
  99. data/lib/rails_generator/generators/components/model/templates/migration.rb +16 -0
  100. data/lib/rails_generator/generators/components/model/templates/unit_test.rb +2 -4
  101. data/lib/rails_generator/generators/components/observer/USAGE +13 -0
  102. data/lib/rails_generator/generators/components/observer/observer_generator.rb +16 -0
  103. data/lib/rails_generator/generators/components/observer/templates/observer.rb +2 -0
  104. data/lib/rails_generator/generators/components/observer/templates/unit_test.rb +8 -0
  105. data/lib/rails_generator/generators/components/plugin/USAGE +10 -18
  106. data/lib/rails_generator/generators/components/plugin/plugin_generator.rb +6 -0
  107. data/lib/rails_generator/generators/components/plugin/templates/MIT-LICENSE +20 -0
  108. data/lib/rails_generator/generators/components/plugin/templates/README +10 -1
  109. data/lib/rails_generator/generators/components/plugin/templates/Rakefile +1 -1
  110. data/lib/rails_generator/generators/components/plugin/templates/USAGE +1 -1
  111. data/lib/rails_generator/generators/components/plugin/templates/init.rb +1 -1
  112. data/lib/rails_generator/generators/components/plugin/templates/install.rb +1 -0
  113. data/lib/rails_generator/generators/components/plugin/templates/plugin.rb +1 -1
  114. data/lib/rails_generator/generators/components/plugin/templates/tasks.rake +1 -1
  115. data/lib/rails_generator/generators/components/plugin/templates/uninstall.rb +1 -0
  116. data/lib/rails_generator/generators/components/resource/USAGE +23 -0
  117. data/lib/rails_generator/generators/components/resource/resource_generator.rb +74 -0
  118. data/lib/rails_generator/generators/components/resource/templates/controller.rb +2 -0
  119. data/lib/rails_generator/generators/components/resource/templates/functional_test.rb +8 -0
  120. data/lib/rails_generator/generators/components/resource/templates/helper.rb +2 -0
  121. data/lib/rails_generator/generators/components/scaffold/USAGE +24 -31
  122. data/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +45 -137
  123. data/lib/rails_generator/generators/components/scaffold/templates/controller.rb +65 -34
  124. data/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +23 -76
  125. data/lib/rails_generator/generators/components/scaffold/templates/layout.html.erb +17 -0
  126. data/lib/rails_generator/generators/components/scaffold/templates/style.css +5 -5
  127. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.html.erb +19 -0
  128. data/lib/rails_generator/generators/components/scaffold/templates/view_index.html.erb +24 -0
  129. data/lib/rails_generator/generators/components/scaffold/templates/view_new.html.erb +18 -0
  130. data/lib/rails_generator/generators/components/scaffold/templates/view_show.html.erb +10 -0
  131. data/lib/rails_generator/generators/components/session_migration/USAGE +6 -11
  132. data/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb +7 -1
  133. data/lib/rails_generator/generators/components/session_migration/templates/migration.rb +8 -7
  134. data/lib/rails_generator/lookup.rb +46 -12
  135. data/lib/rails_generator/options.rb +11 -8
  136. data/lib/rails_generator/scripts/destroy.rb +23 -0
  137. data/lib/rails_generator/scripts.rb +7 -4
  138. data/lib/rails_generator/secret_key_generator.rb +160 -0
  139. data/lib/rails_generator/spec.rb +1 -1
  140. data/lib/rails_generator.rb +1 -1
  141. data/lib/railties_path.rb +1 -1
  142. data/lib/ruby_version_check.rb +17 -0
  143. data/lib/source_annotation_extractor.rb +62 -0
  144. data/lib/tasks/annotations.rake +23 -0
  145. data/lib/tasks/databases.rake +328 -133
  146. data/lib/tasks/documentation.rake +72 -68
  147. data/lib/tasks/framework.rake +99 -58
  148. data/lib/tasks/log.rake +9 -0
  149. data/lib/tasks/misc.rake +2 -17
  150. data/lib/tasks/rails.rb +2 -2
  151. data/lib/tasks/routes.rake +17 -0
  152. data/lib/tasks/statistics.rake +10 -8
  153. data/lib/tasks/testing.rake +99 -31
  154. data/lib/tasks/tmp.rake +37 -0
  155. data/lib/test_help.rb +8 -5
  156. data/lib/webrick_server.rb +11 -16
  157. metadata +312 -272
  158. data/bin/breakpointer +0 -3
  159. data/builtin/controllers/rails_info_controller.rb +0 -11
  160. data/configs/database.yml +0 -85
  161. data/lib/binding_of_caller.rb +0 -85
  162. data/lib/breakpoint.rb +0 -523
  163. data/lib/breakpoint_client.rb +0 -196
  164. data/lib/commands/breakpointer.rb +0 -1
  165. data/lib/rails_generator/generators/components/scaffold/templates/form.rhtml +0 -3
  166. data/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml +0 -1
  167. data/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml +0 -13
  168. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml +0 -9
  169. data/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml +0 -27
  170. data/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml +0 -8
  171. data/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml +0 -8
  172. data/lib/rails_generator/generators/components/web_service/USAGE +0 -28
  173. data/lib/rails_generator/generators/components/web_service/templates/api_definition.rb +0 -5
  174. data/lib/rails_generator/generators/components/web_service/templates/controller.rb +0 -8
  175. data/lib/rails_generator/generators/components/web_service/templates/functional_test.rb +0 -19
  176. data/lib/rails_generator/generators/components/web_service/web_service_generator.rb +0 -29
  177. data/lib/tasks/javascripts.rake +0 -6
@@ -1,12 +1,13 @@
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)
1
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
+ // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
4
4
  // Contributors:
5
5
  // Richard Livsey
6
6
  // Rahul Bhargava
7
7
  // Rob Wills
8
8
  //
9
- // See scriptaculous.js for full license.
9
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
10
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
10
11
 
11
12
  // Autocompleter.Base handles all the autocompletion functionality
12
13
  // that's independent of the data source for autocompletion. This
@@ -33,40 +34,50 @@
33
34
  // useful when one of the tokens is \n (a newline), as it
34
35
  // allows smart autocompletion after linebreaks.
35
36
 
36
- var Autocompleter = {}
37
- Autocompleter.Base = function() {};
38
- Autocompleter.Base.prototype = {
37
+ if(typeof Effect == 'undefined')
38
+ throw("controls.js requires including script.aculo.us' effects.js library");
39
+
40
+ var Autocompleter = { }
41
+ Autocompleter.Base = Class.create({
39
42
  baseInitialize: function(element, update, options) {
40
- this.element = $(element);
43
+ element = $(element)
44
+ this.element = element;
41
45
  this.update = $(update);
42
46
  this.hasFocus = false;
43
47
  this.changed = false;
44
48
  this.active = false;
45
49
  this.index = 0;
46
50
  this.entryCount = 0;
51
+ this.oldElementValue = this.element.value;
47
52
 
48
- if (this.setOptions)
53
+ if(this.setOptions)
49
54
  this.setOptions(options);
50
55
  else
51
- this.options = options || {};
56
+ this.options = options || { };
52
57
 
53
58
  this.options.paramName = this.options.paramName || this.element.name;
54
59
  this.options.tokens = this.options.tokens || [];
55
60
  this.options.frequency = this.options.frequency || 0.4;
56
61
  this.options.minChars = this.options.minChars || 1;
57
62
  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
- };
63
+ function(element, update){
64
+ if(!update.style.position || update.style.position=='absolute') {
65
+ update.style.position = 'absolute';
66
+ Position.clone(element, update, {
67
+ setHeight: false,
68
+ offsetTop: element.offsetHeight
69
+ });
70
+ }
71
+ Effect.Appear(update,{duration:0.15});
72
+ };
65
73
  this.options.onHide = this.options.onHide ||
66
- function(element, update){ new Effect.Fade(update,{duration:0.15}) };
74
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67
75
 
68
- if (typeof(this.options.tokens) == 'string')
76
+ if(typeof(this.options.tokens) == 'string')
69
77
  this.options.tokens = new Array(this.options.tokens);
78
+ // Force carriage returns as token delimiters anyway
79
+ if (!this.options.tokens.include('\n'))
80
+ this.options.tokens.push('\n');
70
81
 
71
82
  this.observer = null;
72
83
 
@@ -74,15 +85,14 @@ Autocompleter.Base.prototype = {
74
85
 
75
86
  Element.hide(this.update);
76
87
 
77
- Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78
- Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
88
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
79
90
  },
80
91
 
81
92
  show: function() {
82
93
  if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83
94
  if(!this.iefix &&
84
- (navigator.appVersion.indexOf('MSIE')>0) &&
85
- (navigator.userAgent.indexOf('Opera')<0) &&
95
+ (Prototype.Browser.IE) &&
86
96
  (Element.getStyle(this.update, 'position')=='absolute')) {
87
97
  new Insertion.After(this.update,
88
98
  '<iframe id="' + this.update.id + '_iefix" '+
@@ -94,7 +104,7 @@ Autocompleter.Base.prototype = {
94
104
  },
95
105
 
96
106
  fixIEOverlapping: function() {
97
- Position.clone(this.update, this.iefix);
107
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
98
108
  this.iefix.style.zIndex = 1;
99
109
  this.update.style.zIndex = 2;
100
110
  Element.show(this.iefix);
@@ -132,17 +142,17 @@ Autocompleter.Base.prototype = {
132
142
  case Event.KEY_UP:
133
143
  this.markPrevious();
134
144
  this.render();
135
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
145
+ Event.stop(event);
136
146
  return;
137
147
  case Event.KEY_DOWN:
138
148
  this.markNext();
139
149
  this.render();
140
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
150
+ Event.stop(event);
141
151
  return;
142
152
  }
143
153
  else
144
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145
- return;
154
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
155
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
146
156
 
147
157
  this.changed = true;
148
158
  this.hasFocus = true;
@@ -152,6 +162,12 @@ Autocompleter.Base.prototype = {
152
162
  setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
163
  },
154
164
 
165
+ activate: function() {
166
+ this.changed = false;
167
+ this.hasFocus = true;
168
+ this.getUpdatedChoices();
169
+ },
170
+
155
171
  onHover: function(event) {
156
172
  var element = Event.findElement(event, 'LI');
157
173
  if(this.index != element.autocompleteIndex)
@@ -182,7 +198,6 @@ Autocompleter.Base.prototype = {
182
198
  this.index==i ?
183
199
  Element.addClassName(this.getEntry(i),"selected") :
184
200
  Element.removeClassName(this.getEntry(i),"selected");
185
-
186
201
  if(this.hasFocus) {
187
202
  this.show();
188
203
  this.active = true;
@@ -196,11 +211,13 @@ Autocompleter.Base.prototype = {
196
211
  markPrevious: function() {
197
212
  if(this.index > 0) this.index--
198
213
  else this.index = this.entryCount-1;
214
+ this.getEntry(this.index).scrollIntoView(true);
199
215
  },
200
216
 
201
217
  markNext: function() {
202
218
  if(this.index < this.entryCount-1) this.index++
203
219
  else this.index = 0;
220
+ this.getEntry(this.index).scrollIntoView(false);
204
221
  },
205
222
 
206
223
  getEntry: function(index) {
@@ -221,18 +238,24 @@ Autocompleter.Base.prototype = {
221
238
  this.options.updateElement(selectedElement);
222
239
  return;
223
240
  }
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+/);
241
+ var value = '';
242
+ if (this.options.select) {
243
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
244
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
245
+ } else
246
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
247
+
248
+ var bounds = this.getTokenBounds();
249
+ if (bounds[0] != -1) {
250
+ var newValue = this.element.value.substr(0, bounds[0]);
251
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
230
252
  if (whitespace)
231
253
  newValue += whitespace[0];
232
- this.element.value = newValue + value;
254
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
233
255
  } else {
234
256
  this.element.value = value;
235
257
  }
258
+ this.oldElementValue = this.element.value;
236
259
  this.element.focus();
237
260
 
238
261
  if (this.options.afterUpdateElement)
@@ -243,11 +266,11 @@ Autocompleter.Base.prototype = {
243
266
  if(!this.changed && this.hasFocus) {
244
267
  this.update.innerHTML = choices;
245
268
  Element.cleanWhitespace(this.update);
246
- Element.cleanWhitespace(this.update.firstChild);
269
+ Element.cleanWhitespace(this.update.down());
247
270
 
248
- if(this.update.firstChild && this.update.firstChild.childNodes) {
271
+ if(this.update.firstChild && this.update.down().childNodes) {
249
272
  this.entryCount =
250
- this.update.firstChild.childNodes.length;
273
+ this.update.down().childNodes.length;
251
274
  for (var i = 0; i < this.entryCount; i++) {
252
275
  var entry = this.getEntry(i);
253
276
  entry.autocompleteIndex = i;
@@ -258,9 +281,14 @@ Autocompleter.Base.prototype = {
258
281
  }
259
282
 
260
283
  this.stopIndicator();
261
-
262
284
  this.index = 0;
263
- this.render();
285
+
286
+ if(this.entryCount==1 && this.options.autoSelect) {
287
+ this.selectEntry();
288
+ this.hide();
289
+ } else {
290
+ this.render();
291
+ }
264
292
  }
265
293
  },
266
294
 
@@ -271,41 +299,50 @@ Autocompleter.Base.prototype = {
271
299
 
272
300
  onObserverEvent: function() {
273
301
  this.changed = false;
302
+ this.tokenBounds = null;
274
303
  if(this.getToken().length>=this.options.minChars) {
275
- this.startIndicator();
276
304
  this.getUpdatedChoices();
277
305
  } else {
278
306
  this.active = false;
279
307
  this.hide();
280
308
  }
309
+ this.oldElementValue = this.element.value;
281
310
  },
282
311
 
283
312
  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;
313
+ var bounds = this.getTokenBounds();
314
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
315
+ },
316
+
317
+ getTokenBounds: function() {
318
+ if (null != this.tokenBounds) return this.tokenBounds;
319
+ var value = this.element.value;
320
+ if (value.strip().empty()) return [-1, 0];
321
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
322
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
323
+ var prevTokenPos = -1, nextTokenPos = value.length;
324
+ var tp;
325
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
326
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
327
+ if (tp > prevTokenPos) prevTokenPos = tp;
328
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
329
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
300
330
  }
301
- return lastTokenPos;
331
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
302
332
  }
303
- }
333
+ });
334
+
335
+ Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336
+ var boundary = Math.min(newS.length, oldS.length);
337
+ for (var index = 0; index < boundary; ++index)
338
+ if (newS[index] != oldS[index])
339
+ return index;
340
+ return boundary;
341
+ };
304
342
 
305
- Ajax.Autocompleter = Class.create();
306
- Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
343
+ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
307
344
  initialize: function(element, update, url, options) {
308
- this.baseInitialize(element, update, options);
345
+ this.baseInitialize(element, update, options);
309
346
  this.options.asynchronous = true;
310
347
  this.options.onComplete = this.onComplete.bind(this);
311
348
  this.options.defaultParams = this.options.parameters || null;
@@ -313,7 +350,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
313
350
  },
314
351
 
315
352
  getUpdatedChoices: function() {
316
- entry = encodeURIComponent(this.options.paramName) + '=' +
353
+ this.startIndicator();
354
+
355
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
317
356
  encodeURIComponent(this.getToken());
318
357
 
319
358
  this.options.parameters = this.options.callback ?
@@ -321,14 +360,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
321
360
 
322
361
  if(this.options.defaultParams)
323
362
  this.options.parameters += '&' + this.options.defaultParams;
324
-
363
+
325
364
  new Ajax.Request(this.url, this.options);
326
365
  },
327
366
 
328
367
  onComplete: function(request) {
329
368
  this.updateChoices(request.responseText);
330
369
  }
331
-
332
370
  });
333
371
 
334
372
  // The local array autocompleter. Used when you'd prefer to
@@ -366,8 +404,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
366
404
  // In that case, the other options above will not apply unless
367
405
  // you support them.
368
406
 
369
- Autocompleter.Local = Class.create();
370
- Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
407
+ Autocompleter.Local = Class.create(Autocompleter.Base, {
371
408
  initialize: function(element, update, array, options) {
372
409
  this.baseInitialize(element, update, options);
373
410
  this.options.array = array;
@@ -423,13 +460,12 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
423
460
  ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
424
461
  return "<ul>" + ret.join('') + "</ul>";
425
462
  }
426
- }, options || {});
463
+ }, options || { });
427
464
  }
428
465
  });
429
466
 
430
- // AJAX in-place editor
431
- //
432
- // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
467
+ // AJAX in-place editor and collection editor
468
+ // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
433
469
 
434
470
  // Use this if you notice weird scrolling problems on some browsers,
435
471
  // the DOM might be a bit confused when this gets called so do this
@@ -440,295 +476,472 @@ Field.scrollFreeActivate = function(field) {
440
476
  }, 1);
441
477
  }
442
478
 
443
- Ajax.InPlaceEditor = Class.create();
444
- Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
445
- Ajax.InPlaceEditor.prototype = {
479
+ Ajax.InPlaceEditor = Class.create({
446
480
  initialize: function(element, url, options) {
447
481
  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
- }
482
+ this.element = element = $(element);
483
+ this.prepareOptions();
484
+ this._controls = { };
485
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
486
+ Object.extend(this.options, options || { });
487
+ if (!this.options.formId && this.element.id) {
488
+ this.options.formId = this.element.id + '-inplaceeditor';
489
+ if ($(this.options.formId))
490
+ this.options.formId = '';
483
491
  }
484
-
485
- if (this.options.externalControl) {
492
+ if (this.options.externalControl)
486
493
  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
+ if (!this.options.externalControl)
495
+ this.options.externalControlOnly = false;
496
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
494
497
  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);
498
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
499
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
500
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
501
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
502
+ this._boundWrapperHandler = this.wrapUp.bind(this);
503
+ this.registerListeners();
504
+ },
505
+ checkForEscapeOrReturn: function(e) {
506
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
507
+ if (Event.KEY_ESC == e.keyCode)
508
+ this.handleFormCancellation(e);
509
+ else if (Event.KEY_RETURN == e.keyCode)
510
+ this.handleFormSubmission(e);
511
+ },
512
+ createControl: function(mode, handler, extraClasses) {
513
+ var control = this.options[mode + 'Control'];
514
+ var text = this.options[mode + 'Text'];
515
+ if ('button' == control) {
516
+ var btn = document.createElement('input');
517
+ btn.type = 'submit';
518
+ btn.value = text;
519
+ btn.className = 'editor_' + mode + '_button';
520
+ if ('cancel' == mode)
521
+ btn.onclick = this._boundCancelHandler;
522
+ this._form.appendChild(btn);
523
+ this._controls[mode] = btn;
524
+ } else if ('link' == control) {
525
+ var link = document.createElement('a');
526
+ link.href = '#';
527
+ link.appendChild(document.createTextNode(text));
528
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
529
+ link.className = 'editor_' + mode + '_link';
530
+ if (extraClasses)
531
+ link.className += ' ' + extraClasses;
532
+ this._form.appendChild(link);
533
+ this._controls[mode] = link;
506
534
  }
507
535
  },
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
536
  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;
537
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
538
+ var fld;
539
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
540
+ fld = document.createElement('input');
541
+ fld.type = 'text';
572
542
  var size = this.options.size || this.options.cols || 0;
573
- if (size != 0) textField.size = size;
574
- this.editField = textField;
543
+ if (0 < size) fld.size = size;
575
544
  } 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;
545
+ fld = document.createElement('textarea');
546
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
547
+ fld.cols = this.options.cols || 40;
583
548
  }
584
-
585
- if(this.options.loadTextURL) {
549
+ fld.name = this.options.paramName;
550
+ fld.value = text; // No HTML breaks conversion anymore
551
+ fld.className = 'editor_field';
552
+ if (this.options.submitOnBlur)
553
+ fld.onblur = this._boundSubmitHandler;
554
+ this._controls.editor = fld;
555
+ if (this.options.loadTextURL)
586
556
  this.loadExternalText();
587
- }
588
- this.form.appendChild(this.editField);
557
+ this._form.appendChild(this._controls.editor);
558
+ },
559
+ createForm: function() {
560
+ var ipe = this;
561
+ function addText(mode, condition) {
562
+ var text = ipe.options['text' + mode + 'Controls'];
563
+ if (!text || condition === false) return;
564
+ ipe._form.appendChild(document.createTextNode(text));
565
+ };
566
+ this._form = $(document.createElement('form'));
567
+ this._form.id = this.options.formId;
568
+ this._form.addClassName(this.options.formClassName);
569
+ this._form.onsubmit = this._boundSubmitHandler;
570
+ this.createEditField();
571
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
572
+ this._form.appendChild(document.createElement('br'));
573
+ if (this.options.onFormCustomization)
574
+ this.options.onFormCustomization(this, this._form);
575
+ addText('Before', this.options.okControl || this.options.cancelControl);
576
+ this.createControl('ok', this._boundSubmitHandler);
577
+ addText('Between', this.options.okControl && this.options.cancelControl);
578
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579
+ addText('After', this.options.okControl || this.options.cancelControl);
580
+ },
581
+ destroy: function() {
582
+ if (this._oldInnerHTML)
583
+ this.element.innerHTML = this._oldInnerHTML;
584
+ this.leaveEditMode();
585
+ this.unregisterListeners();
586
+ },
587
+ enterEditMode: function(e) {
588
+ if (this._saving || this._editing) return;
589
+ this._editing = true;
590
+ this.triggerCallback('onEnterEditMode');
591
+ if (this.options.externalControl)
592
+ this.options.externalControl.hide();
593
+ this.element.hide();
594
+ this.createForm();
595
+ this.element.parentNode.insertBefore(this._form, this.element);
596
+ if (!this.options.loadTextURL)
597
+ this.postProcessEditField();
598
+ if (e) Event.stop(e);
599
+ },
600
+ enterHover: function(e) {
601
+ if (this.options.hoverClassName)
602
+ this.element.addClassName(this.options.hoverClassName);
603
+ if (this._saving) return;
604
+ this.triggerCallback('onEnterHover');
589
605
  },
590
606
  getText: function() {
591
607
  return this.element.innerHTML;
592
608
  },
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;
609
+ handleAJAXFailure: function(transport) {
610
+ this.triggerCallback('onFailure', transport);
611
+ if (this._oldInnerHTML) {
612
+ this.element.innerHTML = this._oldInnerHTML;
613
+ this._oldInnerHTML = null;
619
614
  }
620
- return false;
621
615
  },
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]);
616
+ handleFormCancellation: function(e) {
617
+ this.wrapUp();
618
+ if (e) Event.stop(e);
619
+ },
620
+ handleFormSubmission: function(e) {
621
+ var form = this._form;
622
+ var value = $F(this._controls.editor);
623
+ this.prepareSubmission();
624
+ var params = this.options.callback(form, value) || '';
625
+ if (Object.isString(params))
626
+ params = params.toQueryParams();
627
+ params.editorId = this.element.id;
628
+ if (this.options.htmlResponse) {
629
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
630
+ Object.extend(options, {
631
+ parameters: params,
632
+ onComplete: this._boundWrapperHandler,
633
+ onFailure: this._boundFailureHandler
634
+ });
635
+ new Ajax.Updater({ success: this.element }, this.url, options);
636
+ } else {
637
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
638
+ Object.extend(options, {
639
+ parameters: params,
640
+ onComplete: this._boundWrapperHandler,
641
+ onFailure: this._boundFailureHandler
642
+ });
643
+ new Ajax.Request(this.url, options);
648
644
  }
649
- return false;
645
+ if (e) Event.stop(e);
650
646
  },
651
- onLoading: function() {
652
- this.saving = true;
647
+ leaveEditMode: function() {
648
+ this.element.removeClassName(this.options.savingClassName);
649
+ this.removeForm();
650
+ this.leaveHover();
651
+ this.element.style.backgroundColor = this._originalBackground;
652
+ this.element.show();
653
+ if (this.options.externalControl)
654
+ this.options.externalControl.show();
655
+ this._saving = false;
656
+ this._editing = false;
657
+ this._oldInnerHTML = null;
658
+ this.triggerCallback('onLeaveEditMode');
659
+ },
660
+ leaveHover: function(e) {
661
+ if (this.options.hoverClassName)
662
+ this.element.removeClassName(this.options.hoverClassName);
663
+ if (this._saving) return;
664
+ this.triggerCallback('onLeaveHover');
665
+ },
666
+ loadExternalText: function() {
667
+ this._form.addClassName(this.options.loadingClassName);
668
+ this._controls.editor.disabled = true;
669
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670
+ Object.extend(options, {
671
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
672
+ onComplete: Prototype.emptyFunction,
673
+ onSuccess: function(transport) {
674
+ this._form.removeClassName(this.options.loadingClassName);
675
+ var text = transport.responseText;
676
+ if (this.options.stripLoadedTextTags)
677
+ text = text.stripTags();
678
+ this._controls.editor.value = text;
679
+ this._controls.editor.disabled = false;
680
+ this.postProcessEditField();
681
+ }.bind(this),
682
+ onFailure: this._boundFailureHandler
683
+ });
684
+ new Ajax.Request(this.options.loadTextURL, options);
685
+ },
686
+ postProcessEditField: function() {
687
+ var fpc = this.options.fieldPostCreation;
688
+ if (fpc)
689
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690
+ },
691
+ prepareOptions: function() {
692
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695
+ Object.extend(this.options, defs);
696
+ }.bind(this));
697
+ },
698
+ prepareSubmission: function() {
699
+ this._saving = true;
653
700
  this.removeForm();
654
701
  this.leaveHover();
655
702
  this.showSaving();
656
703
  },
704
+ registerListeners: function() {
705
+ this._listeners = { };
706
+ var listener;
707
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708
+ listener = this[pair.value].bind(this);
709
+ this._listeners[pair.key] = listener;
710
+ if (!this.options.externalControlOnly)
711
+ this.element.observe(pair.key, listener);
712
+ if (this.options.externalControl)
713
+ this.options.externalControl.observe(pair.key, listener);
714
+ }.bind(this));
715
+ },
716
+ removeForm: function() {
717
+ if (!this._form) return;
718
+ this._form.remove();
719
+ this._form = null;
720
+ this._controls = { };
721
+ },
657
722
  showSaving: function() {
658
- this.oldInnerHTML = this.element.innerHTML;
723
+ this._oldInnerHTML = this.element.innerHTML;
659
724
  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);
725
+ this.element.addClassName(this.options.savingClassName);
726
+ this.element.style.backgroundColor = this._originalBackground;
727
+ this.element.show();
663
728
  },
664
- removeForm: function() {
665
- if(this.form) {
666
- if (this.form.parentNode) Element.remove(this.form);
667
- this.form = null;
729
+ triggerCallback: function(cbName, arg) {
730
+ if ('function' == typeof this.options[cbName]) {
731
+ this.options[cbName](this, arg);
668
732
  }
669
733
  },
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)
734
+ unregisterListeners: function() {
735
+ $H(this._listeners).each(function(pair) {
736
+ if (!this.options.externalControlOnly)
737
+ this.element.stopObserving(pair.key, pair.value);
738
+ if (this.options.externalControl)
739
+ this.options.externalControl.stopObserving(pair.key, pair.value);
740
+ }.bind(this));
677
741
  },
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
742
+ wrapUp: function(transport) {
743
+ this.leaveEditMode();
744
+ // Can't use triggerCallback due to backward compatibility: requires
745
+ // binding + direct element
746
+ this._boundComplete(transport, this.element);
747
+ }
748
+ });
749
+
750
+ Object.extend(Ajax.InPlaceEditor.prototype, {
751
+ dispose: Ajax.InPlaceEditor.prototype.destroy
752
+ });
753
+
754
+ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755
+ initialize: function($super, element, url, options) {
756
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757
+ $super(element, url, options);
758
+ },
759
+
760
+ createEditField: function() {
761
+ var list = document.createElement('select');
762
+ list.name = this.options.paramName;
763
+ list.size = 1;
764
+ this._controls.editor = list;
765
+ this._collection = this.options.collection || [];
766
+ if (this.options.loadCollectionURL)
767
+ this.loadCollection();
768
+ else
769
+ this.checkForExternalText();
770
+ this._form.appendChild(this._controls.editor);
771
+ },
772
+
773
+ loadCollection: function() {
774
+ this._form.addClassName(this.options.loadingClassName);
775
+ this.showLoadingText(this.options.loadingCollectionText);
776
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777
+ Object.extend(options, {
778
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
779
+ onComplete: Prototype.emptyFunction,
780
+ onSuccess: function(transport) {
781
+ var js = transport.responseText.strip();
782
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783
+ throw 'Server returned an invalid collection representation.';
784
+ this._collection = eval(js);
785
+ this.checkForExternalText();
786
+ }.bind(this),
787
+ onFailure: this.onFailure
688
788
  });
789
+ new Ajax.Request(this.options.loadCollectionURL, options);
689
790
  },
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);
791
+
792
+ showLoadingText: function(text) {
793
+ this._controls.editor.disabled = true;
794
+ var tempOption = this._controls.editor.firstChild;
795
+ if (!tempOption) {
796
+ tempOption = document.createElement('option');
797
+ tempOption.value = '';
798
+ this._controls.editor.appendChild(tempOption);
799
+ tempOption.selected = true;
698
800
  }
699
- this.editing = false;
700
- this.saving = false;
701
- this.oldInnerHTML = null;
702
- this.onLeaveEditMode();
801
+ tempOption.update((text || '').stripScripts().stripTags());
703
802
  },
704
- onComplete: function(transport) {
705
- this.leaveEditMode();
706
- this.options.onComplete.bind(this)(transport, this.element);
803
+
804
+ checkForExternalText: function() {
805
+ this._text = this.getText();
806
+ if (this.options.loadTextURL)
807
+ this.loadExternalText();
808
+ else
809
+ this.buildOptionList();
707
810
  },
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);
811
+
812
+ loadExternalText: function() {
813
+ this.showLoadingText(this.options.loadingText);
814
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815
+ Object.extend(options, {
816
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
817
+ onComplete: Prototype.emptyFunction,
818
+ onSuccess: function(transport) {
819
+ this._text = transport.responseText.strip();
820
+ this.buildOptionList();
821
+ }.bind(this),
822
+ onFailure: this.onFailure
823
+ });
824
+ new Ajax.Request(this.options.loadTextURL, options);
825
+ },
826
+
827
+ buildOptionList: function() {
828
+ this._form.removeClassName(this.options.loadingClassName);
829
+ this._collection = this._collection.map(function(entry) {
830
+ return 2 === entry.length ? entry : [entry, entry].flatten();
831
+ });
832
+ var marker = ('value' in this.options) ? this.options.value : this._text;
833
+ var textFound = this._collection.any(function(entry) {
834
+ return entry[0] == marker;
835
+ }.bind(this));
836
+ this._controls.editor.update('');
837
+ var option;
838
+ this._collection.each(function(entry, index) {
839
+ option = document.createElement('option');
840
+ option.value = entry[0];
841
+ option.selected = textFound ? entry[0] == marker : 0 == index;
842
+ option.appendChild(document.createTextNode(entry[1]));
843
+ this._controls.editor.appendChild(option);
844
+ }.bind(this));
845
+ this._controls.editor.disabled = false;
846
+ Field.scrollFreeActivate(this._controls.editor);
847
+ }
848
+ });
849
+
850
+ //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851
+ //**** This only exists for a while, in order to let ****
852
+ //**** users adapt to the new API. Read up on the new ****
853
+ //**** API and convert your code to it ASAP! ****
854
+
855
+ Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856
+ if (!options) return;
857
+ function fallback(name, expr) {
858
+ if (name in options || expr === undefined) return;
859
+ options[name] = expr;
860
+ };
861
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
863
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864
+ options.okLink == options.okButton == false ? false : undefined)));
865
+ fallback('highlightColor', options.highlightcolor);
866
+ fallback('highlightEndColor', options.highlightendcolor);
867
+ };
868
+
869
+ Object.extend(Ajax.InPlaceEditor, {
870
+ DefaultOptions: {
871
+ ajaxOptions: { },
872
+ autoRows: 3, // Use when multi-line w/ rows == 1
873
+ cancelControl: 'link', // 'link'|'button'|false
874
+ cancelText: 'cancel',
875
+ clickToEditText: 'Click to edit',
876
+ externalControl: null, // id|elt
877
+ externalControlOnly: false,
878
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
879
+ formClassName: 'inplaceeditor-form',
880
+ formId: null, // id|elt
881
+ highlightColor: '#ffff99',
882
+ highlightEndColor: '#ffffff',
883
+ hoverClassName: '',
884
+ htmlResponse: true,
885
+ loadingClassName: 'inplaceeditor-loading',
886
+ loadingText: 'Loading...',
887
+ okControl: 'button', // 'link'|'button'|false
888
+ okText: 'ok',
889
+ paramName: 'value',
890
+ rows: 1, // If 1 and multi-line, uses autoRows
891
+ savingClassName: 'inplaceeditor-saving',
892
+ savingText: 'Saving...',
893
+ size: 0,
894
+ stripLoadedTextTags: false,
895
+ submitOnBlur: false,
896
+ textAfterControls: '',
897
+ textBeforeControls: '',
898
+ textBetweenControls: ''
899
+ },
900
+ DefaultCallbacks: {
901
+ callback: function(form) {
902
+ return Form.serialize(form);
903
+ },
904
+ onComplete: function(transport, element) {
905
+ // For backward compatibility, this one is bound to the IPE, and passes
906
+ // the element directly. It was too often customized, so we don't break it.
907
+ new Effect.Highlight(element, {
908
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
909
+ },
910
+ onEnterEditMode: null,
911
+ onEnterHover: function(ipe) {
912
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
913
+ if (ipe._effect)
914
+ ipe._effect.cancel();
915
+ },
916
+ onFailure: function(transport, ipe) {
917
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
918
+ },
919
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920
+ onLeaveEditMode: null,
921
+ onLeaveHover: function(ipe) {
922
+ ipe._effect = new Effect.Highlight(ipe.element, {
923
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
925
+ });
722
926
  }
927
+ },
928
+ Listeners: {
929
+ click: 'enterEditMode',
930
+ keydown: 'checkForEscapeOrReturn',
931
+ mouseover: 'enterHover',
932
+ mouseout: 'leaveHover'
723
933
  }
934
+ });
935
+
936
+ Ajax.InPlaceCollectionEditor.DefaultOptions = {
937
+ loadingCollectionText: 'Loading options...'
724
938
  };
725
939
 
726
940
  // Delayed observer, like Form.Element.Observer,
727
941
  // but waits for delay after last key input
728
942
  // Ideal for live-search fields
729
943
 
730
- Form.Element.DelayedObserver = Class.create();
731
- Form.Element.DelayedObserver.prototype = {
944
+ Form.Element.DelayedObserver = Class.create({
732
945
  initialize: function(element, delay, callback) {
733
946
  this.delay = delay || 0.5;
734
947
  this.element = $(element);
@@ -747,4 +960,4 @@ Form.Element.DelayedObserver.prototype = {
747
960
  this.timer = null;
748
961
  this.callback(this.element, $F(this.element));
749
962
  }
750
- };
963
+ });