bivouac 0.1.4 → 0.1.5

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