bivouac 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/README +6 -7
  2. data/bin/bivouac +1 -1
  3. data/doc/rdoc/classes/BivouacHelpers/BaseView.html +178 -0
  4. data/doc/rdoc/classes/BivouacHelpers/FormView.html +398 -0
  5. data/doc/rdoc/classes/BivouacHelpers/HtmlView.html +305 -0
  6. data/doc/rdoc/classes/BivouacHelpers/JavaScriptView.html +573 -0
  7. data/doc/rdoc/classes/BivouacHelpers/ScriptAculoUsView.html +258 -0
  8. data/doc/rdoc/classes/BivouacHelpers/TooltipView.html +158 -0
  9. data/doc/rdoc/classes/BivouacHelpers.html +117 -0
  10. data/doc/rdoc/classes/JavaScriptGenerator.html +564 -0
  11. data/doc/rdoc/created.rid +1 -0
  12. data/doc/rdoc/files/AUTHORS.html +109 -0
  13. data/doc/rdoc/files/COPYING.html +533 -0
  14. data/doc/rdoc/files/README.html +427 -0
  15. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/base_rb.html +109 -0
  16. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/form_rb.html +109 -0
  17. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/html_rb.html +109 -0
  18. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/javascript_rb.html +113 -0
  19. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/scriptaculous_rb.html +113 -0
  20. data/doc/rdoc/files/lib/bivouac/helpers/view/goh/tooltip_rb.html +109 -0
  21. data/doc/rdoc/index.html +10 -0
  22. data/doc/rdoc/permalink.gif +0 -0
  23. data/doc/rdoc/rdoc-style.css +106 -0
  24. data/doc/rdoc/rubyfr.png +0 -0
  25. data/examples/bivouac_sample/Rakefile +48 -0
  26. data/examples/bivouac_sample/app/bivouac_sample.rb +15 -7
  27. data/examples/bivouac_sample/app/controllers/index.rb +2 -2
  28. data/examples/bivouac_sample/app/controllers/sound.rb +10 -0
  29. data/examples/bivouac_sample/app/helpers/_helpers.rb +6 -3
  30. data/examples/bivouac_sample/app/views/sound.rb +16 -0
  31. data/examples/bivouac_sample/config/environment.rb +5 -2
  32. data/examples/bivouac_sample/config/postamble.rb +89 -18
  33. data/examples/bivouac_sample/public/javascripts/builder.js +12 -7
  34. data/examples/bivouac_sample/public/javascripts/controls.js +485 -355
  35. data/examples/bivouac_sample/public/javascripts/dragdrop.js +82 -52
  36. data/examples/bivouac_sample/public/javascripts/effects.js +361 -329
  37. data/examples/bivouac_sample/public/javascripts/prototype.js +2826 -1120
  38. data/examples/bivouac_sample/public/javascripts/scriptaculous.js +15 -8
  39. data/examples/bivouac_sample/public/javascripts/slider.js +40 -43
  40. data/examples/bivouac_sample/public/javascripts/sound.js +55 -0
  41. data/examples/bivouac_sample/public/javascripts/unittest.js +16 -12
  42. data/examples/bivouac_sample/public/sound/sword.mp3 +0 -0
  43. data/examples/bivouac_sample/script/console +6 -0
  44. data/examples/bivouac_sample/script/plugin +3 -0
  45. data/examples/bivouac_sample/script/server +2 -1
  46. data/examples/bivouac_sample/test/test_sound.rb +15 -0
  47. data/lib/bivouac/helpers/view/goh/sound.rb +38 -0
  48. data/lib/bivouac/template/application/helpers_goh.rb +2 -0
  49. data/lib/bivouac/template/static/builder.js +12 -7
  50. data/lib/bivouac/template/static/controls.js +485 -355
  51. data/lib/bivouac/template/static/dragdrop.js +82 -52
  52. data/lib/bivouac/template/static/effects.js +361 -329
  53. data/lib/bivouac/template/static/prototype.js +2826 -1120
  54. data/lib/bivouac/template/static/scriptaculous.js +15 -8
  55. data/lib/bivouac/template/static/slider.js +40 -43
  56. data/lib/bivouac/template/static/sound.js +55 -0
  57. data/lib/bivouac/template/static/unittest.js +16 -12
  58. metadata +45 -2
@@ -1,8 +1,8 @@
1
- // script.aculo.us controls.js v1.7.0, Fri Jan 19 19:16:36 CET 2007
1
+ // script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
2
 
3
- // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
- // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5
- // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
3
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+ // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5
+ // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
6
6
  // Contributors:
7
7
  // Richard Livsey
8
8
  // Rahul Bhargava
@@ -39,22 +39,23 @@
39
39
  if(typeof Effect == 'undefined')
40
40
  throw("controls.js requires including script.aculo.us' effects.js library");
41
41
 
42
- var Autocompleter = {}
43
- Autocompleter.Base = function() {};
44
- Autocompleter.Base.prototype = {
42
+ var Autocompleter = { }
43
+ Autocompleter.Base = Class.create({
45
44
  baseInitialize: function(element, update, options) {
46
- this.element = $(element);
45
+ element = $(element)
46
+ this.element = element;
47
47
  this.update = $(update);
48
48
  this.hasFocus = false;
49
49
  this.changed = false;
50
50
  this.active = false;
51
51
  this.index = 0;
52
52
  this.entryCount = 0;
53
+ this.oldElementValue = this.element.value;
53
54
 
54
55
  if(this.setOptions)
55
56
  this.setOptions(options);
56
57
  else
57
- this.options = options || {};
58
+ this.options = options || { };
58
59
 
59
60
  this.options.paramName = this.options.paramName || this.element.name;
60
61
  this.options.tokens = this.options.tokens || [];
@@ -76,6 +77,9 @@ Autocompleter.Base.prototype = {
76
77
 
77
78
  if(typeof(this.options.tokens) == 'string')
78
79
  this.options.tokens = new Array(this.options.tokens);
80
+ // Force carriage returns as token delimiters anyway
81
+ if (!this.options.tokens.include('\n'))
82
+ this.options.tokens.push('\n');
79
83
 
80
84
  this.observer = null;
81
85
 
@@ -83,15 +87,14 @@ Autocompleter.Base.prototype = {
83
87
 
84
88
  Element.hide(this.update);
85
89
 
86
- Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
87
- Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
90
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
91
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
88
92
  },
89
93
 
90
94
  show: function() {
91
95
  if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
92
96
  if(!this.iefix &&
93
- (navigator.appVersion.indexOf('MSIE')>0) &&
94
- (navigator.userAgent.indexOf('Opera')<0) &&
97
+ (Prototype.Browser.IE) &&
95
98
  (Element.getStyle(this.update, 'position')=='absolute')) {
96
99
  new Insertion.After(this.update,
97
100
  '<iframe id="' + this.update.id + '_iefix" '+
@@ -141,17 +144,17 @@ Autocompleter.Base.prototype = {
141
144
  case Event.KEY_UP:
142
145
  this.markPrevious();
143
146
  this.render();
144
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
147
+ Event.stop(event);
145
148
  return;
146
149
  case Event.KEY_DOWN:
147
150
  this.markNext();
148
151
  this.render();
149
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
152
+ Event.stop(event);
150
153
  return;
151
154
  }
152
155
  else
153
156
  if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
154
- (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
157
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
155
158
 
156
159
  this.changed = true;
157
160
  this.hasFocus = true;
@@ -197,7 +200,6 @@ Autocompleter.Base.prototype = {
197
200
  this.index==i ?
198
201
  Element.addClassName(this.getEntry(i),"selected") :
199
202
  Element.removeClassName(this.getEntry(i),"selected");
200
-
201
203
  if(this.hasFocus) {
202
204
  this.show();
203
205
  this.active = true;
@@ -240,21 +242,22 @@ Autocompleter.Base.prototype = {
240
242
  }
241
243
  var value = '';
242
244
  if (this.options.select) {
243
- var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
245
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
244
246
  if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
245
247
  } else
246
248
  value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
247
249
 
248
- var lastTokenPos = this.findLastToken();
249
- if (lastTokenPos != -1) {
250
- var newValue = this.element.value.substr(0, lastTokenPos + 1);
251
- var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
250
+ var bounds = this.getTokenBounds();
251
+ if (bounds[0] != -1) {
252
+ var newValue = this.element.value.substr(0, bounds[0]);
253
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
252
254
  if (whitespace)
253
255
  newValue += whitespace[0];
254
- this.element.value = newValue + value;
256
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
255
257
  } else {
256
258
  this.element.value = value;
257
259
  }
260
+ this.oldElementValue = this.element.value;
258
261
  this.element.focus();
259
262
 
260
263
  if (this.options.afterUpdateElement)
@@ -298,39 +301,48 @@ Autocompleter.Base.prototype = {
298
301
 
299
302
  onObserverEvent: function() {
300
303
  this.changed = false;
304
+ this.tokenBounds = null;
301
305
  if(this.getToken().length>=this.options.minChars) {
302
- this.startIndicator();
303
306
  this.getUpdatedChoices();
304
307
  } else {
305
308
  this.active = false;
306
309
  this.hide();
307
310
  }
311
+ this.oldElementValue = this.element.value;
308
312
  },
309
313
 
310
314
  getToken: function() {
311
- var tokenPos = this.findLastToken();
312
- if (tokenPos != -1)
313
- var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
314
- else
315
- var ret = this.element.value;
316
-
317
- return /\n/.test(ret) ? '' : ret;
318
- },
319
-
320
- findLastToken: function() {
321
- var lastTokenPos = -1;
322
-
323
- for (var i=0; i<this.options.tokens.length; i++) {
324
- var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
325
- if (thisTokenPos > lastTokenPos)
326
- lastTokenPos = thisTokenPos;
315
+ var bounds = this.getTokenBounds();
316
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
317
+ },
318
+
319
+ getTokenBounds: function() {
320
+ if (null != this.tokenBounds) return this.tokenBounds;
321
+ var value = this.element.value;
322
+ if (value.strip().empty()) return [-1, 0];
323
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
324
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
325
+ var prevTokenPos = -1, nextTokenPos = value.length;
326
+ var tp;
327
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
328
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
329
+ if (tp > prevTokenPos) prevTokenPos = tp;
330
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
331
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
327
332
  }
328
- return lastTokenPos;
333
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
329
334
  }
330
- }
335
+ });
336
+
337
+ Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
338
+ var boundary = Math.min(newS.length, oldS.length);
339
+ for (var index = 0; index < boundary; ++index)
340
+ if (newS[index] != oldS[index])
341
+ return index;
342
+ return boundary;
343
+ };
331
344
 
332
- Ajax.Autocompleter = Class.create();
333
- Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
345
+ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
334
346
  initialize: function(element, update, url, options) {
335
347
  this.baseInitialize(element, update, options);
336
348
  this.options.asynchronous = true;
@@ -340,7 +352,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
340
352
  },
341
353
 
342
354
  getUpdatedChoices: function() {
343
- entry = encodeURIComponent(this.options.paramName) + '=' +
355
+ this.startIndicator();
356
+
357
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
344
358
  encodeURIComponent(this.getToken());
345
359
 
346
360
  this.options.parameters = this.options.callback ?
@@ -348,14 +362,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
348
362
 
349
363
  if(this.options.defaultParams)
350
364
  this.options.parameters += '&' + this.options.defaultParams;
351
-
365
+
352
366
  new Ajax.Request(this.url, this.options);
353
367
  },
354
368
 
355
369
  onComplete: function(request) {
356
370
  this.updateChoices(request.responseText);
357
371
  }
358
-
359
372
  });
360
373
 
361
374
  // The local array autocompleter. Used when you'd prefer to
@@ -393,8 +406,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
393
406
  // In that case, the other options above will not apply unless
394
407
  // you support them.
395
408
 
396
- Autocompleter.Local = Class.create();
397
- Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
409
+ Autocompleter.Local = Class.create(Autocompleter.Base, {
398
410
  initialize: function(element, update, array, options) {
399
411
  this.baseInitialize(element, update, options);
400
412
  this.options.array = array;
@@ -450,13 +462,12 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
450
462
  ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
451
463
  return "<ul>" + ret.join('') + "</ul>";
452
464
  }
453
- }, options || {});
465
+ }, options || { });
454
466
  }
455
467
  });
456
468
 
457
- // AJAX in-place editor
458
- //
459
- // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
469
+ // AJAX in-place editor and collection editor
470
+ // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
460
471
 
461
472
  // Use this if you notice weird scrolling problems on some browsers,
462
473
  // the DOM might be a bit confused when this gets called so do this
@@ -467,353 +478,472 @@ Field.scrollFreeActivate = function(field) {
467
478
  }, 1);
468
479
  }
469
480
 
470
- Ajax.InPlaceEditor = Class.create();
471
- Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
472
- Ajax.InPlaceEditor.prototype = {
481
+ Ajax.InPlaceEditor = Class.create({
473
482
  initialize: function(element, url, options) {
474
483
  this.url = url;
475
- this.element = $(element);
476
-
477
- this.options = Object.extend({
478
- paramName: "value",
479
- okButton: true,
480
- okText: "ok",
481
- cancelLink: true,
482
- cancelText: "cancel",
483
- savingText: "Saving...",
484
- clickToEditText: "Click to edit",
485
- okText: "ok",
486
- rows: 1,
487
- onComplete: function(transport, element) {
488
- new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
489
- },
490
- onFailure: function(transport) {
491
- alert("Error communicating with the server: " + transport.responseText.stripTags());
492
- },
493
- callback: function(form) {
494
- return Form.serialize(form);
495
- },
496
- handleLineBreaks: true,
497
- loadingText: 'Loading...',
498
- savingClassName: 'inplaceeditor-saving',
499
- loadingClassName: 'inplaceeditor-loading',
500
- formClassName: 'inplaceeditor-form',
501
- highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
502
- highlightendcolor: "#FFFFFF",
503
- externalControl: null,
504
- submitOnBlur: false,
505
- ajaxOptions: {},
506
- evalScripts: false
507
- }, options || {});
508
-
509
- if(!this.options.formId && this.element.id) {
510
- this.options.formId = this.element.id + "-inplaceeditor";
511
- if ($(this.options.formId)) {
512
- // there's already a form with that name, don't specify an id
513
- this.options.formId = null;
514
- }
484
+ this.element = element = $(element);
485
+ this.prepareOptions();
486
+ this._controls = { };
487
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
488
+ Object.extend(this.options, options || { });
489
+ if (!this.options.formId && this.element.id) {
490
+ this.options.formId = this.element.id + '-inplaceeditor';
491
+ if ($(this.options.formId))
492
+ this.options.formId = '';
515
493
  }
516
-
517
- if (this.options.externalControl) {
494
+ if (this.options.externalControl)
518
495
  this.options.externalControl = $(this.options.externalControl);
519
- }
520
-
521
- this.originalBackground = Element.getStyle(this.element, 'background-color');
522
- if (!this.originalBackground) {
523
- this.originalBackground = "transparent";
524
- }
525
-
496
+ if (!this.options.externalControl)
497
+ this.options.externalControlOnly = false;
498
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
526
499
  this.element.title = this.options.clickToEditText;
527
-
528
- this.onclickListener = this.enterEditMode.bindAsEventListener(this);
529
- this.mouseoverListener = this.enterHover.bindAsEventListener(this);
530
- this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
531
- Event.observe(this.element, 'click', this.onclickListener);
532
- Event.observe(this.element, 'mouseover', this.mouseoverListener);
533
- Event.observe(this.element, 'mouseout', this.mouseoutListener);
534
- if (this.options.externalControl) {
535
- Event.observe(this.options.externalControl, 'click', this.onclickListener);
536
- Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
537
- Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
500
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
501
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
502
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
503
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
504
+ this._boundWrapperHandler = this.wrapUp.bind(this);
505
+ this.registerListeners();
506
+ },
507
+ checkForEscapeOrReturn: function(e) {
508
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
509
+ if (Event.KEY_ESC == e.keyCode)
510
+ this.handleFormCancellation(e);
511
+ else if (Event.KEY_RETURN == e.keyCode)
512
+ this.handleFormSubmission(e);
513
+ },
514
+ createControl: function(mode, handler, extraClasses) {
515
+ var control = this.options[mode + 'Control'];
516
+ var text = this.options[mode + 'Text'];
517
+ if ('button' == control) {
518
+ var btn = document.createElement('input');
519
+ btn.type = 'submit';
520
+ btn.value = text;
521
+ btn.className = 'editor_' + mode + '_button';
522
+ if ('cancel' == mode)
523
+ btn.onclick = this._boundCancelHandler;
524
+ this._form.appendChild(btn);
525
+ this._controls[mode] = btn;
526
+ } else if ('link' == control) {
527
+ var link = document.createElement('a');
528
+ link.href = '#';
529
+ link.appendChild(document.createTextNode(text));
530
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
531
+ link.className = 'editor_' + mode + '_link';
532
+ if (extraClasses)
533
+ link.className += ' ' + extraClasses;
534
+ this._form.appendChild(link);
535
+ this._controls[mode] = link;
538
536
  }
539
537
  },
540
- enterEditMode: function(evt) {
541
- if (this.saving) return;
542
- if (this.editing) return;
543
- this.editing = true;
544
- this.onEnterEditMode();
545
- if (this.options.externalControl) {
546
- Element.hide(this.options.externalControl);
547
- }
548
- Element.hide(this.element);
549
- this.createForm();
550
- this.element.parentNode.insertBefore(this.form, this.element);
551
- if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
552
- // stop the event to avoid a page refresh in Safari
553
- if (evt) {
554
- Event.stop(evt);
555
- }
556
- return false;
557
- },
558
- createForm: function() {
559
- this.form = document.createElement("form");
560
- this.form.id = this.options.formId;
561
- Element.addClassName(this.form, this.options.formClassName)
562
- this.form.onsubmit = this.onSubmit.bind(this);
563
-
564
- this.createEditField();
565
-
566
- if (this.options.textarea) {
567
- var br = document.createElement("br");
568
- this.form.appendChild(br);
569
- }
570
-
571
- if (this.options.okButton) {
572
- okButton = document.createElement("input");
573
- okButton.type = "submit";
574
- okButton.value = this.options.okText;
575
- okButton.className = 'editor_ok_button';
576
- this.form.appendChild(okButton);
577
- }
578
-
579
- if (this.options.cancelLink) {
580
- cancelLink = document.createElement("a");
581
- cancelLink.href = "#";
582
- cancelLink.appendChild(document.createTextNode(this.options.cancelText));
583
- cancelLink.onclick = this.onclickCancel.bind(this);
584
- cancelLink.className = 'editor_cancel';
585
- this.form.appendChild(cancelLink);
586
- }
587
- },
588
- hasHTMLLineBreaks: function(string) {
589
- if (!this.options.handleLineBreaks) return false;
590
- return string.match(/<br/i) || string.match(/<p>/i);
591
- },
592
- convertHTMLLineBreaks: function(string) {
593
- return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
594
- },
595
538
  createEditField: function() {
596
- var text;
597
- if(this.options.loadTextURL) {
598
- text = this.options.loadingText;
599
- } else {
600
- text = this.getText();
601
- }
602
-
603
- var obj = this;
604
-
605
- if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
606
- this.options.textarea = false;
607
- var textField = document.createElement("input");
608
- textField.obj = this;
609
- textField.type = "text";
610
- textField.name = this.options.paramName;
611
- textField.value = text;
612
- textField.style.backgroundColor = this.options.highlightcolor;
613
- textField.className = 'editor_field';
539
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
540
+ var fld;
541
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
542
+ fld = document.createElement('input');
543
+ fld.type = 'text';
614
544
  var size = this.options.size || this.options.cols || 0;
615
- if (size != 0) textField.size = size;
616
- if (this.options.submitOnBlur)
617
- textField.onblur = this.onSubmit.bind(this);
618
- this.editField = textField;
545
+ if (0 < size) fld.size = size;
619
546
  } else {
620
- this.options.textarea = true;
621
- var textArea = document.createElement("textarea");
622
- textArea.obj = this;
623
- textArea.name = this.options.paramName;
624
- textArea.value = this.convertHTMLLineBreaks(text);
625
- textArea.rows = this.options.rows;
626
- textArea.cols = this.options.cols || 40;
627
- textArea.className = 'editor_field';
628
- if (this.options.submitOnBlur)
629
- textArea.onblur = this.onSubmit.bind(this);
630
- this.editField = textArea;
547
+ fld = document.createElement('textarea');
548
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
549
+ fld.cols = this.options.cols || 40;
631
550
  }
632
-
633
- if(this.options.loadTextURL) {
551
+ fld.name = this.options.paramName;
552
+ fld.value = text; // No HTML breaks conversion anymore
553
+ fld.className = 'editor_field';
554
+ if (this.options.submitOnBlur)
555
+ fld.onblur = this._boundSubmitHandler;
556
+ this._controls.editor = fld;
557
+ if (this.options.loadTextURL)
634
558
  this.loadExternalText();
635
- }
636
- this.form.appendChild(this.editField);
559
+ this._form.appendChild(this._controls.editor);
560
+ },
561
+ createForm: function() {
562
+ var ipe = this;
563
+ function addText(mode, condition) {
564
+ var text = ipe.options['text' + mode + 'Controls'];
565
+ if (!text || condition === false) return;
566
+ ipe._form.appendChild(document.createTextNode(text));
567
+ };
568
+ this._form = $(document.createElement('form'));
569
+ this._form.id = this.options.formId;
570
+ this._form.addClassName(this.options.formClassName);
571
+ this._form.onsubmit = this._boundSubmitHandler;
572
+ this.createEditField();
573
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
574
+ this._form.appendChild(document.createElement('br'));
575
+ if (this.options.onFormCustomization)
576
+ this.options.onFormCustomization(this, this._form);
577
+ addText('Before', this.options.okControl || this.options.cancelControl);
578
+ this.createControl('ok', this._boundSubmitHandler);
579
+ addText('Between', this.options.okControl && this.options.cancelControl);
580
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
581
+ addText('After', this.options.okControl || this.options.cancelControl);
582
+ },
583
+ destroy: function() {
584
+ if (this._oldInnerHTML)
585
+ this.element.innerHTML = this._oldInnerHTML;
586
+ this.leaveEditMode();
587
+ this.unregisterListeners();
588
+ },
589
+ enterEditMode: function(e) {
590
+ if (this._saving || this._editing) return;
591
+ this._editing = true;
592
+ this.triggerCallback('onEnterEditMode');
593
+ if (this.options.externalControl)
594
+ this.options.externalControl.hide();
595
+ this.element.hide();
596
+ this.createForm();
597
+ this.element.parentNode.insertBefore(this._form, this.element);
598
+ if (!this.options.loadTextURL)
599
+ this.postProcessEditField();
600
+ if (e) Event.stop(e);
601
+ },
602
+ enterHover: function(e) {
603
+ if (this.options.hoverClassName)
604
+ this.element.addClassName(this.options.hoverClassName);
605
+ if (this._saving) return;
606
+ this.triggerCallback('onEnterHover');
637
607
  },
638
608
  getText: function() {
639
609
  return this.element.innerHTML;
640
610
  },
641
- loadExternalText: function() {
642
- Element.addClassName(this.form, this.options.loadingClassName);
643
- this.editField.disabled = true;
644
- new Ajax.Request(
645
- this.options.loadTextURL,
646
- Object.extend({
647
- asynchronous: true,
648
- onComplete: this.onLoadedExternalText.bind(this)
649
- }, this.options.ajaxOptions)
650
- );
651
- },
652
- onLoadedExternalText: function(transport) {
653
- Element.removeClassName(this.form, this.options.loadingClassName);
654
- this.editField.disabled = false;
655
- this.editField.value = transport.responseText.stripTags();
656
- Field.scrollFreeActivate(this.editField);
657
- },
658
- onclickCancel: function() {
659
- this.onComplete();
660
- this.leaveEditMode();
661
- return false;
662
- },
663
- onFailure: function(transport) {
664
- this.options.onFailure(transport);
665
- if (this.oldInnerHTML) {
666
- this.element.innerHTML = this.oldInnerHTML;
667
- this.oldInnerHTML = null;
611
+ handleAJAXFailure: function(transport) {
612
+ this.triggerCallback('onFailure', transport);
613
+ if (this._oldInnerHTML) {
614
+ this.element.innerHTML = this._oldInnerHTML;
615
+ this._oldInnerHTML = null;
668
616
  }
669
- return false;
670
617
  },
671
- onSubmit: function() {
672
- // onLoading resets these so we need to save them away for the Ajax call
673
- var form = this.form;
674
- var value = this.editField.value;
675
-
676
- // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
677
- // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
678
- // to be displayed indefinitely
679
- this.onLoading();
680
-
681
- if (this.options.evalScripts) {
682
- new Ajax.Request(
683
- this.url, Object.extend({
684
- parameters: this.options.callback(form, value),
685
- onComplete: this.onComplete.bind(this),
686
- onFailure: this.onFailure.bind(this),
687
- asynchronous:true,
688
- evalScripts:true
689
- }, this.options.ajaxOptions));
690
- } else {
691
- new Ajax.Updater(
692
- { success: this.element,
693
- // don't update on failure (this could be an option)
694
- failure: null },
695
- this.url, Object.extend({
696
- parameters: this.options.callback(form, value),
697
- onComplete: this.onComplete.bind(this),
698
- onFailure: this.onFailure.bind(this)
699
- }, this.options.ajaxOptions));
700
- }
701
- // stop the event to avoid a page refresh in Safari
702
- if (arguments.length > 1) {
703
- Event.stop(arguments[0]);
618
+ handleFormCancellation: function(e) {
619
+ this.wrapUp();
620
+ if (e) Event.stop(e);
621
+ },
622
+ handleFormSubmission: function(e) {
623
+ var form = this._form;
624
+ var value = $F(this._controls.editor);
625
+ this.prepareSubmission();
626
+ var params = this.options.callback(form, value) || '';
627
+ if (Object.isString(params))
628
+ params = params.toQueryParams();
629
+ params.editorId = this.element.id;
630
+ if (this.options.htmlResponse) {
631
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
632
+ Object.extend(options, {
633
+ parameters: params,
634
+ onComplete: this._boundWrapperHandler,
635
+ onFailure: this._boundFailureHandler
636
+ });
637
+ new Ajax.Updater({ success: this.element }, this.url, options);
638
+ } else {
639
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
640
+ Object.extend(options, {
641
+ parameters: params,
642
+ onComplete: this._boundWrapperHandler,
643
+ onFailure: this._boundFailureHandler
644
+ });
645
+ new Ajax.Request(this.url, options);
704
646
  }
705
- return false;
647
+ if (e) Event.stop(e);
648
+ },
649
+ leaveEditMode: function() {
650
+ this.element.removeClassName(this.options.savingClassName);
651
+ this.removeForm();
652
+ this.leaveHover();
653
+ this.element.style.backgroundColor = this._originalBackground;
654
+ this.element.show();
655
+ if (this.options.externalControl)
656
+ this.options.externalControl.show();
657
+ this._saving = false;
658
+ this._editing = false;
659
+ this._oldInnerHTML = null;
660
+ this.triggerCallback('onLeaveEditMode');
661
+ },
662
+ leaveHover: function(e) {
663
+ if (this.options.hoverClassName)
664
+ this.element.removeClassName(this.options.hoverClassName);
665
+ if (this._saving) return;
666
+ this.triggerCallback('onLeaveHover');
706
667
  },
707
- onLoading: function() {
708
- this.saving = true;
668
+ loadExternalText: function() {
669
+ this._form.addClassName(this.options.loadingClassName);
670
+ this._controls.editor.disabled = true;
671
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
672
+ Object.extend(options, {
673
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
674
+ onComplete: Prototype.emptyFunction,
675
+ onSuccess: function(transport) {
676
+ this._form.removeClassName(this.options.loadingClassName);
677
+ var text = transport.responseText;
678
+ if (this.options.stripLoadedTextTags)
679
+ text = text.stripTags();
680
+ this._controls.editor.value = text;
681
+ this._controls.editor.disabled = false;
682
+ this.postProcessEditField();
683
+ }.bind(this),
684
+ onFailure: this._boundFailureHandler
685
+ });
686
+ new Ajax.Request(this.options.loadTextURL, options);
687
+ },
688
+ postProcessEditField: function() {
689
+ var fpc = this.options.fieldPostCreation;
690
+ if (fpc)
691
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
692
+ },
693
+ prepareOptions: function() {
694
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
695
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
696
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
697
+ Object.extend(this.options, defs);
698
+ }.bind(this));
699
+ },
700
+ prepareSubmission: function() {
701
+ this._saving = true;
709
702
  this.removeForm();
710
703
  this.leaveHover();
711
704
  this.showSaving();
712
705
  },
706
+ registerListeners: function() {
707
+ this._listeners = { };
708
+ var listener;
709
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
710
+ listener = this[pair.value].bind(this);
711
+ this._listeners[pair.key] = listener;
712
+ if (!this.options.externalControlOnly)
713
+ this.element.observe(pair.key, listener);
714
+ if (this.options.externalControl)
715
+ this.options.externalControl.observe(pair.key, listener);
716
+ }.bind(this));
717
+ },
718
+ removeForm: function() {
719
+ if (!this._form) return;
720
+ this._form.remove();
721
+ this._form = null;
722
+ this._controls = { };
723
+ },
713
724
  showSaving: function() {
714
- this.oldInnerHTML = this.element.innerHTML;
725
+ this._oldInnerHTML = this.element.innerHTML;
715
726
  this.element.innerHTML = this.options.savingText;
716
- Element.addClassName(this.element, this.options.savingClassName);
717
- this.element.style.backgroundColor = this.originalBackground;
718
- Element.show(this.element);
727
+ this.element.addClassName(this.options.savingClassName);
728
+ this.element.style.backgroundColor = this._originalBackground;
729
+ this.element.show();
719
730
  },
720
- removeForm: function() {
721
- if(this.form) {
722
- if (this.form.parentNode) Element.remove(this.form);
723
- this.form = null;
731
+ triggerCallback: function(cbName, arg) {
732
+ if ('function' == typeof this.options[cbName]) {
733
+ this.options[cbName](this, arg);
724
734
  }
725
735
  },
726
- enterHover: function() {
727
- if (this.saving) return;
728
- this.element.style.backgroundColor = this.options.highlightcolor;
729
- if (this.effect) {
730
- this.effect.cancel();
731
- }
732
- Element.addClassName(this.element, this.options.hoverClassName)
736
+ unregisterListeners: function() {
737
+ $H(this._listeners).each(function(pair) {
738
+ if (!this.options.externalControlOnly)
739
+ this.element.stopObserving(pair.key, pair.value);
740
+ if (this.options.externalControl)
741
+ this.options.externalControl.stopObserving(pair.key, pair.value);
742
+ }.bind(this));
733
743
  },
734
- leaveHover: function() {
735
- if (this.options.backgroundColor) {
736
- this.element.style.backgroundColor = this.oldBackground;
737
- }
738
- Element.removeClassName(this.element, this.options.hoverClassName)
739
- if (this.saving) return;
740
- this.effect = new Effect.Highlight(this.element, {
741
- startcolor: this.options.highlightcolor,
742
- endcolor: this.options.highlightendcolor,
743
- restorecolor: this.originalBackground
744
+ wrapUp: function(transport) {
745
+ this.leaveEditMode();
746
+ // Can't use triggerCallback due to backward compatibility: requires
747
+ // binding + direct element
748
+ this._boundComplete(transport, this.element);
749
+ }
750
+ });
751
+
752
+ Object.extend(Ajax.InPlaceEditor.prototype, {
753
+ dispose: Ajax.InPlaceEditor.prototype.destroy
754
+ });
755
+
756
+ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
757
+ initialize: function($super, element, url, options) {
758
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
759
+ $super(element, url, options);
760
+ },
761
+
762
+ createEditField: function() {
763
+ var list = document.createElement('select');
764
+ list.name = this.options.paramName;
765
+ list.size = 1;
766
+ this._controls.editor = list;
767
+ this._collection = this.options.collection || [];
768
+ if (this.options.loadCollectionURL)
769
+ this.loadCollection();
770
+ else
771
+ this.checkForExternalText();
772
+ this._form.appendChild(this._controls.editor);
773
+ },
774
+
775
+ loadCollection: function() {
776
+ this._form.addClassName(this.options.loadingClassName);
777
+ this.showLoadingText(this.options.loadingCollectionText);
778
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
779
+ Object.extend(options, {
780
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
781
+ onComplete: Prototype.emptyFunction,
782
+ onSuccess: function(transport) {
783
+ var js = transport.responseText.strip();
784
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
785
+ throw 'Server returned an invalid collection representation.';
786
+ this._collection = eval(js);
787
+ this.checkForExternalText();
788
+ }.bind(this),
789
+ onFailure: this.onFailure
744
790
  });
791
+ new Ajax.Request(this.options.loadCollectionURL, options);
745
792
  },
746
- leaveEditMode: function() {
747
- Element.removeClassName(this.element, this.options.savingClassName);
748
- this.removeForm();
749
- this.leaveHover();
750
- this.element.style.backgroundColor = this.originalBackground;
751
- Element.show(this.element);
752
- if (this.options.externalControl) {
753
- Element.show(this.options.externalControl);
793
+
794
+ showLoadingText: function(text) {
795
+ this._controls.editor.disabled = true;
796
+ var tempOption = this._controls.editor.firstChild;
797
+ if (!tempOption) {
798
+ tempOption = document.createElement('option');
799
+ tempOption.value = '';
800
+ this._controls.editor.appendChild(tempOption);
801
+ tempOption.selected = true;
754
802
  }
755
- this.editing = false;
756
- this.saving = false;
757
- this.oldInnerHTML = null;
758
- this.onLeaveEditMode();
803
+ tempOption.update((text || '').stripScripts().stripTags());
759
804
  },
760
- onComplete: function(transport) {
761
- this.leaveEditMode();
762
- this.options.onComplete.bind(this)(transport, this.element);
805
+
806
+ checkForExternalText: function() {
807
+ this._text = this.getText();
808
+ if (this.options.loadTextURL)
809
+ this.loadExternalText();
810
+ else
811
+ this.buildOptionList();
763
812
  },
764
- onEnterEditMode: function() {},
765
- onLeaveEditMode: function() {},
766
- dispose: function() {
767
- if (this.oldInnerHTML) {
768
- this.element.innerHTML = this.oldInnerHTML;
769
- }
770
- this.leaveEditMode();
771
- Event.stopObserving(this.element, 'click', this.onclickListener);
772
- Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
773
- Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
774
- if (this.options.externalControl) {
775
- Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
776
- Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
777
- Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
778
- }
813
+
814
+ loadExternalText: function() {
815
+ this.showLoadingText(this.options.loadingText);
816
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
817
+ Object.extend(options, {
818
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
819
+ onComplete: Prototype.emptyFunction,
820
+ onSuccess: function(transport) {
821
+ this._text = transport.responseText.strip();
822
+ this.buildOptionList();
823
+ }.bind(this),
824
+ onFailure: this.onFailure
825
+ });
826
+ new Ajax.Request(this.options.loadTextURL, options);
827
+ },
828
+
829
+ buildOptionList: function() {
830
+ this._form.removeClassName(this.options.loadingClassName);
831
+ this._collection = this._collection.map(function(entry) {
832
+ return 2 === entry.length ? entry : [entry, entry].flatten();
833
+ });
834
+ var marker = ('value' in this.options) ? this.options.value : this._text;
835
+ var textFound = this._collection.any(function(entry) {
836
+ return entry[0] == marker;
837
+ }.bind(this));
838
+ this._controls.editor.update('');
839
+ var option;
840
+ this._collection.each(function(entry, index) {
841
+ option = document.createElement('option');
842
+ option.value = entry[0];
843
+ option.selected = textFound ? entry[0] == marker : 0 == index;
844
+ option.appendChild(document.createTextNode(entry[1]));
845
+ this._controls.editor.appendChild(option);
846
+ }.bind(this));
847
+ this._controls.editor.disabled = false;
848
+ Field.scrollFreeActivate(this._controls.editor);
779
849
  }
780
- };
850
+ });
781
851
 
782
- Ajax.InPlaceCollectionEditor = Class.create();
783
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
784
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
785
- createEditField: function() {
786
- if (!this.cached_selectTag) {
787
- var selectTag = document.createElement("select");
788
- var collection = this.options.collection || [];
789
- var optionTag;
790
- collection.each(function(e,i) {
791
- optionTag = document.createElement("option");
792
- optionTag.value = (e instanceof Array) ? e[0] : e;
793
- if((typeof this.options.value == 'undefined') &&
794
- ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
795
- if(this.options.value==optionTag.value) optionTag.selected = true;
796
- optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
797
- selectTag.appendChild(optionTag);
798
- }.bind(this));
799
- this.cached_selectTag = selectTag;
800
- }
852
+ //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
853
+ //**** This only exists for a while, in order to let ****
854
+ //**** users adapt to the new API. Read up on the new ****
855
+ //**** API and convert your code to it ASAP! ****
856
+
857
+ Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
858
+ if (!options) return;
859
+ function fallback(name, expr) {
860
+ if (name in options || expr === undefined) return;
861
+ options[name] = expr;
862
+ };
863
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
864
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
865
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
866
+ options.okLink == options.okButton == false ? false : undefined)));
867
+ fallback('highlightColor', options.highlightcolor);
868
+ fallback('highlightEndColor', options.highlightendcolor);
869
+ };
801
870
 
802
- this.editField = this.cached_selectTag;
803
- if(this.options.loadTextURL) this.loadExternalText();
804
- this.form.appendChild(this.editField);
805
- this.options.callback = function(form, value) {
806
- return "value=" + encodeURIComponent(value);
871
+ Object.extend(Ajax.InPlaceEditor, {
872
+ DefaultOptions: {
873
+ ajaxOptions: { },
874
+ autoRows: 3, // Use when multi-line w/ rows == 1
875
+ cancelControl: 'link', // 'link'|'button'|false
876
+ cancelText: 'cancel',
877
+ clickToEditText: 'Click to edit',
878
+ externalControl: null, // id|elt
879
+ externalControlOnly: false,
880
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
881
+ formClassName: 'inplaceeditor-form',
882
+ formId: null, // id|elt
883
+ highlightColor: '#ffff99',
884
+ highlightEndColor: '#ffffff',
885
+ hoverClassName: '',
886
+ htmlResponse: true,
887
+ loadingClassName: 'inplaceeditor-loading',
888
+ loadingText: 'Loading...',
889
+ okControl: 'button', // 'link'|'button'|false
890
+ okText: 'ok',
891
+ paramName: 'value',
892
+ rows: 1, // If 1 and multi-line, uses autoRows
893
+ savingClassName: 'inplaceeditor-saving',
894
+ savingText: 'Saving...',
895
+ size: 0,
896
+ stripLoadedTextTags: false,
897
+ submitOnBlur: false,
898
+ textAfterControls: '',
899
+ textBeforeControls: '',
900
+ textBetweenControls: ''
901
+ },
902
+ DefaultCallbacks: {
903
+ callback: function(form) {
904
+ return Form.serialize(form);
905
+ },
906
+ onComplete: function(transport, element) {
907
+ // For backward compatibility, this one is bound to the IPE, and passes
908
+ // the element directly. It was too often customized, so we don't break it.
909
+ new Effect.Highlight(element, {
910
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
911
+ },
912
+ onEnterEditMode: null,
913
+ onEnterHover: function(ipe) {
914
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
915
+ if (ipe._effect)
916
+ ipe._effect.cancel();
917
+ },
918
+ onFailure: function(transport, ipe) {
919
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
920
+ },
921
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
922
+ onLeaveEditMode: null,
923
+ onLeaveHover: function(ipe) {
924
+ ipe._effect = new Effect.Highlight(ipe.element, {
925
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
926
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
927
+ });
807
928
  }
929
+ },
930
+ Listeners: {
931
+ click: 'enterEditMode',
932
+ keydown: 'checkForEscapeOrReturn',
933
+ mouseover: 'enterHover',
934
+ mouseout: 'leaveHover'
808
935
  }
809
936
  });
810
937
 
938
+ Ajax.InPlaceCollectionEditor.DefaultOptions = {
939
+ loadingCollectionText: 'Loading options...'
940
+ };
941
+
811
942
  // Delayed observer, like Form.Element.Observer,
812
943
  // but waits for delay after last key input
813
944
  // Ideal for live-search fields
814
945
 
815
- Form.Element.DelayedObserver = Class.create();
816
- Form.Element.DelayedObserver.prototype = {
946
+ Form.Element.DelayedObserver = Class.create({
817
947
  initialize: function(element, delay, callback) {
818
948
  this.delay = delay || 0.5;
819
949
  this.element = $(element);
@@ -832,4 +962,4 @@ Form.Element.DelayedObserver.prototype = {
832
962
  this.timer = null;
833
963
  this.callback(this.element, $F(this.element));
834
964
  }
835
- };
965
+ });