PageSourceSearch

https://cymobase.org/javascripts/controls.js?1413977841

js cymobase.org collected 2026-09-25 08:14:32 UTC 34,995 bytes, 970 lines download raw bytes

1// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
4// Contributors:
5//  Richard Livsey
6//  Rahul Bhargava
7//  Rob Wills
8//
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/
11
12// Autocompleter.Base handles all the autocompletion functionality
13// that's independent of the data source for autocompletion. This
14// includes drawing the autocompletion menu, observing keyboard
15// and mouse events, and similar.
16//
17// Specific autocompleters need to provide, at the very least,
18// a getUpdatedChoices function that will be invoked every time
19// the text inside the monitored textbox changes. This method
20// should get the text for which to provide autocompletion by
21// invoking this.getToken(), NOT by directly accessing
22// this.element.value. This is to allow incremental tokenized
23// autocompletion. Specific auto-completion logic (AJAX, etc)
24// belongs in getUpdatedChoices.
25//
26// Tokenized incremental autocompletion is enabled automatically
27// when an autocompleter is instantiated with the 'tokens' option
28// in the options parameter, e.g.:
29// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
30// will incrementally autocomplete with a comma as the token.
31// Additionally, ',' in the above example can be replaced with
32// a token array, e.g. { tokens: [',', '\n'] } which
33// enables autocompletion on multiple tokens. This is most
34// useful when one of the tokens is \n (a newline), as it
35// allows smart autocompletion after linebreaks.
36
37if(typeof Effect == 'undefined')
38  throw("controls.js requires including script.aculo.us' effects.js library");
39
40var Autocompleter = { };
41Autocompleter.Base = Class.create({
42  baseInitialize: function(element, update, options) {
43    element          = $(element);
44    this.element     = element;
45    this.update      = $(update);
46    this.hasFocus    = false;
47    this.changed     = false;
48    this.active      = false;
49    this.index       = 0;
50    this.entryCount  = 0;
51    this.oldElementValue = this.element.value;
52
53    if(this.setOptions)
54      this.setOptions(options);
55    else
56      this.options = options || { };
57
58    this.options.paramName    = this.options.paramName || this.element.name;
59    this.options.tokens       = this.options.tokens || [];
60    this.options.frequency    = this.options.frequency || 0.4;
61    this.options.minChars     = this.options.minChars || 1;
62    this.options.onShow       = this.options.onShow ||
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      };
73    this.options.onHide = this.options.onHide ||
74      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
75
76    if(typeof(this.options.tokens) == 'string')
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');
81
82    this.observer = null;
83
84    this.element.setAttribute('autocomplete','off');
85
86    Element.hide(this.update);
87
88    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
90  },
91
92  show: function() {
93    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
94    if(!this.iefix &&
95      (Prototype.Browser.IE) &&
96      (Element.getStyle(this.update, 'position')=='absolute')) {
97      new Insertion.After(this.update,
98       '<iframe id="' + this.update.id + '_iefix" '+
99       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
100       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
101      this.iefix = $(this.update.id+'_iefix');
102    }
103    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
104  },
105
106  fixIEOverlapping: function() {
107    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
108    this.iefix.style.zIndex = 1;
109    this.update.style.zIndex = 2;
110    Element.show(this.iefix);
111  },
112
113  hide: function() {
114    this.stopIndicator();
115    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
116    if(this.iefix) Element.hide(this.iefix);
117  },
118
119  startIndicator: function() {
120    if(this.options.indicator) Element.show(this.options.indicator);
121  },
122
123  stopIndicator: function() {
124    if(this.options.indicator) Element.hide(this.options.indicator);
125  },
126
127  onKeyPress: function(event) {
128    if(this.active)
129      switch(event.keyCode) {
130       case Event.KEY_TAB:
131       case Event.KEY_RETURN:
132         this.selectEntry();
133         Event.stop(event);
134       case Event.KEY_ESC:
135         this.hide();
136         this.active = false;
137         Event.stop(event);
138         return;
139       case Event.KEY_LEFT:
140       case Event.KEY_RIGHT:
141         return;
142       case Event.KEY_UP:
143         this.markPrevious();
144         this.render();
145         Event.stop(event);
146         return;
147       case Event.KEY_DOWN:
148         this.markNext();
149         this.render();
150         Event.stop(event);
151         return;
152      }
153     else
154       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
155         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
156
157    this.changed = true;
158    this.hasFocus = true;
159
160    if(this.observer) clearTimeout(this.observer);
161      this.observer =
162        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
163  },
164
165  activate: function() {
166    this.changed = false;
167    this.hasFocus = true;
168    this.getUpdatedChoices();
169  },
170
171  onHover: function(event) {
172    var element = Event.findElement(event, 'li');
173    if(this.index != element.autocompleteIndex)
174    {
175        this.index = element.autocompleteIndex;
176        this.render();
177    }
178    Event.stop(event);
179  },
180
181  onClick: function(event) {
182    var element = Event.findElement(event, 'li');
183    this.index = element.autocompleteIndex;
184    this.selectEntry();
185    this.hide();
186  },
187
188  onBlur: function(event) {
189    // needed to make click events working
190    setTimeout(this.hide.bind(this), 250);
191    this.hasFocus = false;
192    this.active = false;
193  },
194
195  render: function() {
196    if(this.entryCount > 0) {
197      for (var i = 0; i < this.entryCount; i++)
198        this.index==i ?
199          Element.addClassName(this.getEntry(i),"selected") :
200          Element.removeClassName(this.getEntry(i),"selected");
201      if(this.hasFocus) {
202        this.show();
203        this.active = true;
204      }
205    } else {
206      this.active = false;
207      this.hide();
208    }
209  },
210
211  markPrevious: function() {
212    if(this.index > 0) this.index--;
213      else this.index = this.entryCount-1;
214    this.getEntry(this.index).scrollIntoView(true);
215  },
216
217  markNext: function() {
218    if(this.index < this.entryCount-1) this.index++;
219      else this.index = 0;
220    this.getEntry(this.index).scrollIntoView(false);
221  },
222
223  getEntry: function(index) {
224    return this.update.firstChild.childNodes[index];
225  },
226
227  getCurrentEntry: function() {
228    return this.getEntry(this.index);
229  },
230
231  selectEntry: function() {
232    this.active = false;
233    this.updateElement(this.getCurrentEntry());
234  },
235
236  updateElement: function(selectedElement) {
237    if (this.options.updateElement) {
238      this.options.updateElement(selectedElement);
239      return;
240    }
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+/);
252      if (whitespace)
253        newValue += whitespace[0];
254      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
255    } else {
256      this.element.value = value;
257    }
258    this.oldElementValue = this.element.value;
259    this.element.focus();
260    // Manually added: Submit search proposal onclick
261    this.element.up('form').request();
262
263    if (this.options.afterUpdateElement)
264      this.options.afterUpdateElement(this.element, selectedElement);
265
266    // Load search results automatically
267    // TODO: Implement trigger of load_search_results as reaction of AJAX completed
268    setTimeout(function(){ load_search_results() }, 2000);
269
270  },
271
272  updateChoices: function(choices) {
273    if(!this.changed && this.hasFocus) {
274      this.update.innerHTML = choices;
275      Element.cleanWhitespace(this.update);
276      Element.cleanWhitespace(this.update.down());
277
278      if(this.update.firstChild && this.update.down().childNodes) {
279        this.entryCount =
280          this.update.down().childNodes.length;
281        for (var i = 0; i < this.entryCount; i++) {
282          var entry = this.getEntry(i);
283          entry.autocompleteIndex = i;
284          this.addObservers(entry);
285        }
286      } else {
287        this.entryCount = 0;
288      }
289
290      this.stopIndicator();
291      this.index = 0;
292
293      if(this.entryCount==1 && this.options.autoSelect) {
294        this.selectEntry();
295        this.hide();
296      } else {
297        this.render();
298      }
299    }
300  },
301
302  addObservers: function(element) {
303    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
304    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
305  },
306
307  onObserverEvent: function() {
308    this.changed = false;
309    this.tokenBounds = null;
310    if(this.getToken().length>=this.options.minChars) {
311      this.getUpdatedChoices();
312    } else {
313      this.active = false;
314      this.hide();
315    }
316    this.oldElementValue = this.element.value;
317  },
318
319  getToken: function() {
320    var bounds = this.getTokenBounds();
321    return this.element.value.substring(bounds[0], bounds[1]).strip();
322  },
323
324  getTokenBounds: function() {
325    if (null != this.tokenBounds) return this.tokenBounds;
326    var value = this.element.value;
327    if (value.strip().empty()) return [-1, 0];
328    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
329    var offset = (diff == this.oldElementValue.length ? 1 : 0);
330    var prevTokenPos = -1, nextTokenPos = value.length;
331    var tp;
332    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
333      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
334      if (tp > prevTokenPos) prevTokenPos = tp;
335      tp = value.indexOf(this.options.tokens[index], diff + offset);
336      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
337    }
338    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
339  }
340});
341
342Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
343  var boundary = Math.min(newS.length, oldS.length);
344  for (var index = 0; index < boundary; ++index)
345    if (newS[index] != oldS[index])
346      return index;
347  return boundary;
348};
349
350Ajax.Autocompleter = Class.create(Autocompleter.Base, {
351  initialize: function(element, update, url, options) {
352    this.baseInitialize(element, update, options);
353    this.options.asynchronous  = true;
354    this.options.onComplete    = this.onComplete.bind(this);
355    this.options.defaultParams = this.options.parameters || null;
356    this.url                   = url;
357  },
358
359  getUpdatedChoices: function() {
360    this.startIndicator();
361
362    var entry = encodeURIComponent(this.options.paramName) + '=' +
363      encodeURIComponent(this.getToken());
364
365    this.options.parameters = this.options.callback ?
366      this.options.callback(this.element, entry) : entry;
367
368    if(this.options.defaultParams)
369      this.options.parameters += '&' + this.options.defaultParams;
370
371    new Ajax.Request(this.url, this.options);
372  },
373
374  onComplete: function(request) {
375    this.updateChoices(request.responseText);
376  }
377});
378
379// The local array autocompleter. Used when you'd prefer to
380// inject an array of autocompletion options into the page, rather
381// than sending out Ajax queries, which can be quite slow sometimes.
382//
383// The constructor takes four parameters. The first two are, as usual,
384// the id of the monitored textbox, and id of the autocompletion menu.
385// The third is the array you want to autocomplete from, and the fourth
386// is the options block.
387//
388// Extra local autocompletion options:
389// - choices - How many autocompletion choices to offer
390//
391// - partialSearch - If false, the autocompleter will match entered
392//                    text only at the beginning of strings in the
393//                    autocomplete array. Defaults to true, which will
394//                    match text at the beginning of any *word* in the
395//                    strings in the autocomplete array. If you want to
396//                    search anywhere in the string, additionally set
397//                    the option fullSearch to true (default: off).
398//
399// - fullSsearch - Search anywhere in autocomplete array strings.
400//
401// - partialChars - How many characters to enter before triggering
402//                   a partial match (unlike minChars, which defines
403//                   how many characters are required to do any match
404//                   at all). Defaults to 2.
405//
406// - ignoreCase - Whether to ignore case when autocompleting.
407//                 Defaults to true.
408//
409// It's possible to pass in a custom function as the 'selector'
410// option, if you prefer to write your own autocompletion logic.
411// In that case, the other options above will not apply unless
412// you support them.
413
414Autocompleter.Local = Class.create(Autocompleter.Base, {
415  initialize: function(element, update, array, options) {
416    this.baseInitialize(element, update, options);
417    this.options.array = array;
418  },
419
420  getUpdatedChoices: function() {
421    this.updateChoices(this.options.selector(this));
422  },
423
424  setOptions: function(options) {
425    this.options = Object.extend({
426      choices: 10,
427      partialSearch: true,
428      partialChars: 2,
429      ignoreCase: true,
430      fullSearch: false,
431      selector: function(instance) {
432        var ret       = []; // Beginning matches
433        var partial   = []; // Inside matches
434        var entry     = instance.getToken();
435        var count     = 0;
436
437        for (var i = 0; i < instance.options.array.length &&
438          ret.length < instance.options.choices ; i++) {
439
440          var elem = instance.options.array[i];
441          var foundPos = instance.options.ignoreCase ?
442            elem.toLowerCase().indexOf(entry.toLowerCase()) :
443            elem.indexOf(entry);
444
445          while (foundPos != -1) {
446            if (foundPos == 0 && elem.length != entry.length) {
447              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
448                elem.substr(entry.length) + "</li>");
449              break;
450            } else if (entry.length >= instance.options.partialChars &&
451              instance.options.partialSearch && foundPos != -1) {
452              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
453                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
454                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
455                  foundPos + entry.length) + "</li>");
456                break;
457              }
458            }
459
460            foundPos = instance.options.ignoreCase ?
461              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
462              elem.indexOf(entry, foundPos + 1);
463
464          }
465        }
466        if (partial.length)
467          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
468        return "<ul>" + ret.join('') + "</ul>";
469      }
470    }, options || { });
471  }
472});
473
474// AJAX in-place editor and collection editor
475// Full rewrite by Christophe Porteneuve <[email protected]> (April 2007).
476
477// Use this if you notice weird scrolling problems on some browsers,
478// the DOM might be a bit confused when this gets called so do this
479// waits 1 ms (with setTimeout) until it does the activation
480Field.scrollFreeActivate = function(field) {
481  setTimeout(function() {
482    Field.activate(field);
483  }, 1);
484};
485
486Ajax.InPlaceEditor = Class.create({
487  initialize: function(element, url, options) {
488    this.url = url;
489    this.element = element = $(element);
490    this.prepareOptions();
491    this._controls = { };
492    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
493    Object.extend(this.options, options || { });
494    if (!this.options.formId && this.element.id) {
495      this.options.formId = this.element.id + '-inplaceeditor';
496      if ($(this.options.formId))
497        this.options.formId = '';
498    }
499    if (this.options.externalControl)
500      this.options.externalControl = $(this.options.externalControl);
501    if (!this.options.externalControl)
502      this.options.externalControlOnly = false;
503    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
504    this.element.title = this.options.clickToEditText;
505    this._boundCancelHandler = this.handleFormCancellation.bind(this);
506    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
507    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
508    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
509    this._boundWrapperHandler = this.wrapUp.bind(this);
510    this.registerListeners();
511  },
512  checkForEscapeOrReturn: function(e) {
513    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
514    if (Event.KEY_ESC == e.keyCode)
515      this.handleFormCancellation(e);
516    else if (Event.KEY_RETURN == e.keyCode)
517      this.handleFormSubmission(e);
518  },
519  createControl: function(mode, handler, extraClasses) {
520    var control = this.options[mode + 'Control'];
521    var text = this.options[mode + 'Text'];
522    if ('button' == control) {
523      var btn = document.createElement('input');
524      btn.type = 'submit';
525      btn.value = text;
526      btn.className = 'editor_' + mode + '_button';
527      if ('cancel' == mode)
528        btn.onclick = this._boundCancelHandler;
529      this._form.appendChild(btn);
530      this._controls[mode] = btn;
531    } else if ('link' == control) {
532      var link = document.createElement('a');
533      link.href = '#';
534      link.appendChild(document.createTextNode(text));
535      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
536      link.className = 'editor_' + mode + '_link';
537      if (extraClasses)
538        link.className += ' ' + extraClasses;
539      this._form.appendChild(link);
540      this._controls[mode] = link;
541    }
542  },
543  createEditField: function() {
544    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
545    var fld;
546    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
547      fld = document.createElement('input');
548      fld.type = 'text';
549      var size = this.options.size || this.options.cols || 0;
550      if (0 < size) fld.size = size;
551    } else {
552      fld = document.createElement('textarea');
553      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
554      fld.cols = this.options.cols || 40;
555    }
556    fld.name = this.options.paramName;
557    fld.value = text; // No HTML breaks conversion anymore
558    fld.className = 'editor_field';
559    if (this.options.submitOnBlur)
560      fld.onblur = this._boundSubmitHandler;
561    this._controls.editor = fld;
562    if (this.options.loadTextURL)
563      this.loadExternalText();
564    this._form.appendChild(this._controls.editor);
565  },
566  createForm: function() {
567    var ipe = this;
568    function addText(mode, condition) {
569      var text = ipe.options['text' + mode + 'Controls'];
570      if (!text || condition === false) return;
571      ipe._form.appendChild(document.createTextNode(text));
572    };
573    this._form = $(document.createElement('form'));
574    this._form.id = this.options.formId;
575    this._form.addClassName(this.options.formClassName);
576    this._form.onsubmit = this._boundSubmitHandler;
577    this.createEditField();
578    if ('textarea' == this._controls.editor.tagName.toLowerCase())
579      this._form.appendChild(document.createElement('br'));
580    if (this.options.onFormCustomization)
581      this.options.onFormCustomization(this, this._form);
582    addText('Before', this.options.okControl || this.options.cancelControl);
583    this.createControl('ok', this._boundSubmitHandler);
584    addText('Between', this.options.okControl && this.options.cancelControl);
585    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
586    addText('After', this.options.okControl || this.options.cancelControl);
587  },
588  destroy: function() {
589    if (this._oldInnerHTML)
590      this.element.innerHTML = this._oldInnerHTML;
591    this.leaveEditMode();
592    this.unregisterListeners();
593  },
594  enterEditMode: function(e) {
595    if (this._saving || this._editing) return;
596    this._editing = true;
597    this.triggerCallback('onEnterEditMode');
598    if (this.options.externalControl)
599      this.options.externalControl.hide();
600    this.element.hide();
601    this.createForm();
602    this.element.parentNode.insertBefore(this._form, this.element);
603    if (!this.options.loadTextURL)
604      this.postProcessEditField();
605    if (e) Event.stop(e);
606  },
607  enterHover: function(e) {
608    if (this.options.hoverClassName)
609      this.element.addClassName(this.options.hoverClassName);
610    if (this._saving) return;
611    this.triggerCallback('onEnterHover');
612  },
613  getText: function() {
614    return this.element.innerHTML.unescapeHTML();
615  },
616  handleAJAXFailure: function(transport) {
617    this.triggerCallback('onFailure', transport);
618    if (this._oldInnerHTML) {
619      this.element.innerHTML = this._oldInnerHTML;
620      this._oldInnerHTML = null;
621    }
622  },
623  handleFormCancellation: function(e) {
624    this.wrapUp();
625    if (e) Event.stop(e);
626  },
627  handleFormSubmission: function(e) {
628    var form = this._form;
629    var value = $F(this._controls.editor);
630    this.prepareSubmission();
631    var params = this.options.callback(form, value) || '';
632    if (Object.isString(params))
633      params = params.toQueryParams();
634    params.editorId = this.element.id;
635    if (this.options.htmlResponse) {
636      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
637      Object.extend(options, {
638        parameters: params,
639        onComplete: this._boundWrapperHandler,
640        onFailure: this._boundFailureHandler
641      });
642      new Ajax.Updater({ success: this.element }, this.url, options);
643    } else {
644      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
645      Object.extend(options, {
646        parameters: params,
647        onComplete: this._boundWrapperHandler,
648        onFailure: this._boundFailureHandler
649      });
650      new Ajax.Request(this.url, options);
651    }
652    if (e) Event.stop(e);
653  },
654  leaveEditMode: function() {
655    this.element.removeClassName(this.options.savingClassName);
656    this.removeForm();
657    this.leaveHover();
658    this.element.style.backgroundColor = this._originalBackground;
659    this.element.show();
660    if (this.options.externalControl)
661      this.options.externalControl.show();
662    this._saving = false;
663    this._editing = false;
664    this._oldInnerHTML = null;
665    this.triggerCallback('onLeaveEditMode');
666  },
667  leaveHover: function(e) {
668    if (this.options.hoverClassName)
669      this.element.removeClassName(this.options.hoverClassName);
670    if (this._saving) return;
671    this.triggerCallback('onLeaveHover');
672  },
673  loadExternalText: function() {
674    this._form.addClassName(this.options.loadingClassName);
675    this._controls.editor.disabled = true;
676    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
677    Object.extend(options, {
678      parameters: 'editorId=' + encodeURIComponent(this.element.id),
679      onComplete: Prototype.emptyFunction,
680      onSuccess: function(transport) {
681        this._form.removeClassName(this.options.loadingClassName);
682        var text = transport.responseText;
683        if (this.options.stripLoadedTextTags)
684          text = text.stripTags();
685        this._controls.editor.value = text;
686        this._controls.editor.disabled = false;
687        this.postProcessEditField();
688      }.bind(this),
689      onFailure: this._boundFailureHandler
690    });
691    new Ajax.Request(this.options.loadTextURL, options);
692  },
693  postProcessEditField: function() {
694    var fpc = this.options.fieldPostCreation;
695    if (fpc)
696      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
697  },
698  prepareOptions: function() {
699    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
700    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
701    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
702      Object.extend(this.options, defs);
703    }.bind(this));
704  },
705  prepareSubmission: function() {
706    this._saving = true;
707    this.removeForm();
708    this.leaveHover();
709    this.showSaving();
710  },
711  registerListeners: function() {
712    this._listeners = { };
713    var listener;
714    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
715      listener = this[pair.value].bind(this);
716      this._listeners[pair.key] = listener;
717      if (!this.options.externalControlOnly)
718        this.element.observe(pair.key, listener);
719      if (this.options.externalControl)
720        this.options.externalControl.observe(pair.key, listener);
721    }.bind(this));
722  },
723  removeForm: function() {
724    if (!this._form) return;
725    this._form.remove();
726    this._form = null;
727    this._controls = { };
728  },
729  showSaving: function() {
730    this._oldInnerHTML = this.element.innerHTML;
731    this.element.innerHTML = this.options.savingText;
732    this.element.addClassName(this.options.savingClassName);
733    this.element.style.backgroundColor = this._originalBackgroun
733d;
734    this.element.show();
735  },
736  triggerCallback: function(cbName, arg) {
737    if ('function' == typeof this.options[cbName]) {
738      this.options[cbName](this, arg);
739    }
740  },
741  unregisterListeners: function() {
742    $H(this._listeners).each(function(pair) {
743      if (!this.options.externalControlOnly)
744        this.element.stopObserving(pair.key, pair.value);
745      if (this.options.externalControl)
746        this.options.externalControl.stopObserving(pair.key, pair.value);
747    }.bind(this));
748  },
749  wrapUp: function(transport) {
750    this.leaveEditMode();
751    // Can't use triggerCallback due to backward compatibility: requires
752    // binding + direct element
753    this._boundComplete(transport, this.element);
754  }
755});
756
757Object.extend(Ajax.InPlaceEditor.prototype, {
758  dispose: Ajax.InPlaceEditor.prototype.destroy
759});
760
761Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
762  initialize: function($super, element, url, options) {
763    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
764    $super(element, url, options);
765  },
766
767  createEditField: function() {
768    var list = document.createElement('select');
769    list.name = this.options.paramName;
770    list.size = 1;
771    this._controls.editor = list;
772    this._collection = this.options.collection || [];
773    if (this.options.loadCollectionURL)
774      this.loadCollection();
775    else
776      this.checkForExternalText();
777    this._form.appendChild(this._controls.editor);
778  },
779
780  loadCollection: function() {
781    this._form.addClassName(this.options.loadingClassName);
782    this.showLoadingText(this.options.loadingCollectionText);
783    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
784    Object.extend(options, {
785      parameters: 'editorId=' + encodeURIComponent(this.element.id),
786      onComplete: Prototype.emptyFunction,
787      onSuccess: function(transport) {
788        var js = transport.responseText.strip();
789        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
790          throw('Server returned an invalid collection representation.');
791        this._collection = eval(js);
792        this.checkForExternalText();
793      }.bind(this),
794      onFailure: this.onFailure
795    });
796    new Ajax.Request(this.options.loadCollectionURL, options);
797  },
798
799  showLoadingText: function(text) {
800    this._controls.editor.disabled = true;
801    var tempOption = this._controls.editor.firstChild;
802    if (!tempOption) {
803      tempOption = document.createElement('option');
804      tempOption.value = '';
805      this._controls.editor.appendChild(tempOption);
806      tempOption.selected = true;
807    }
808    tempOption.update((text || '').stripScripts().stripTags());
809  },
810
811  checkForExternalText: function() {
812    this._text = this.getText();
813    if (this.options.loadTextURL)
814      this.loadExternalText();
815    else
816      this.buildOptionList();
817  },
818
819  loadExternalText: function() {
820    this.showLoadingText(this.options.loadingText);
821    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
822    Object.extend(options, {
823      parameters: 'editorId=' + encodeURIComponent(this.element.id),
824      onComplete: Prototype.emptyFunction,
825      onSuccess: function(transport) {
826        this._text = transport.responseText.strip();
827        this.buildOptionList();
828      }.bind(this),
829      onFailure: this.onFailure
830    });
831    new Ajax.Request(this.options.loadTextURL, options);
832  },
833
834  buildOptionList: function() {
835    this._form.removeClassName(this.options.loadingClassName);
836    this._collection = this._collection.map(function(entry) {
837      return 2 === entry.length ? entry : [entry, entry].flatten();
838    });
839    var marker = ('value' in this.options) ? this.options.value : this._text;
840    var textFound = this._collection.any(function(entry) {
841      return entry[0] == marker;
842    }.bind(this));
843    this._controls.editor.update('');
844    var option;
845    this._collection.each(function(entry, index) {
846      option = document.createElement('option');
847      option.value = entry[0];
848      option.selected = textFound ? entry[0] == marker : 0 == index;
849      option.appendChild(document.createTextNode(entry[1]));
850      this._controls.editor.appendChild(option);
851    }.bind(this));
852    this._controls.editor.disabled = false;
853    Field.scrollFreeActivate(this._controls.editor);
854  }
855});
856
857//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
858//**** This only  exists for a while,  in order to  let ****
859//**** users adapt to  the new API.  Read up on the new ****
860//**** API and convert your code to it ASAP!            ****
861
862Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
863  if (!options) return;
864  function fallback(name, expr) {
865    if (name in options || expr === undefined) return;
866    options[name] = expr;
867  };
868  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
869    options.cancelLink == options.cancelButton == false ? false : undefined)));
870  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
871    options.okLink == options.okButton == false ? false : undefined)));
872  fallback('highlightColor', options.highlightcolor);
873  fallback('highlightEndColor', options.highlightendcolor);
874};
875
876Object.extend(Ajax.InPlaceEditor, {
877  DefaultOptions: {
878    ajaxOptions: { },
879    autoRows: 3,                                // Use when multi-line w/ rows == 1
880    cancelControl: 'link',                      // 'link'|'button'|false
881    cancelText: 'cancel',
882    clickToEditText: 'Click to edit',
883    externalControl: null,                      // id|elt
884    externalControlOnly: false,
885    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
886    formClassName: 'inplaceeditor-form',
887    formId: null,                               // id|elt
888    highlightColor: '#ffff99',
889    highlightEndColor: '#ffffff',
890    hoverClassName: '',
891    htmlResponse: true,
892    loadingClassName: 'inplaceeditor-loading',
893    loadingText: 'Loading...',
894    okControl: 'button',                        // 'link'|'button'|false
895    okText: 'ok',
896    paramName: 'value',
897    rows: 1,                                    // If 1 and multi-line, uses autoRows
898    savingClassName: 'inplaceeditor-saving',
899    savingText: 'Saving...',
900    size: 0,
901    stripLoadedTextTags: false,
902    submitOnBlur: false,
903    textAfterControls: '',
904    textBeforeControls: '',
905    textBetweenControls: ''
906  },
907  DefaultCallbacks: {
908    callback: function(form) {
909      return Form.serialize(form);
910    },
911    onComplete: function(transport, element) {
912      // For backward compatibility, this one is bound to the IPE, and passes
913      // the element directly.  It was too often customized, so we don't break it.
914      new Effect.Highlight(element, {
915        startcolor: this.options.highlightColor, keepBackgroundImage: true });
916    },
917    onEnterEditMode: null,
918    onEnterHover: function(ipe) {
919      ipe.element.style.backgroundColor = ipe.options.highlightColor;
920      if (ipe._effect)
921        ipe._effect.cancel();
922    },
923    onFailure: function(transport, ipe) {
924      alert('Error communication with the server: ' + transport.responseText.stripTags());
925    },
926    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
927    onLeaveEditMode: null,
928    onLeaveHover: function(ipe) {
929      ipe._effect = new Effect.Highlight(ipe.element, {
930        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
931        restorecolor: ipe._originalBackground, keepBackgroundImage: true
932      });
933    }
934  },
935  Listeners: {
936    click: 'enterEditMode',
937    keydown: 'checkForEscapeOrReturn',
938    mouseover: 'enterHover',
939    mouseout: 'leaveHover'
940  }
941});
942
943Ajax.InPlaceCollectionEditor.DefaultOptions = {
944  loadingCollectionText: 'Loading options...'
945};
946
947// Delayed observer, like Form.Element.Observer,
948// but waits for delay after last key input
949// Ideal for live-search fields
950
951Form.Element.DelayedObserver = Class.create({
952  initialize: function(element, delay, callback) {
953    this.delay     = delay || 0.5;
954    this.element   = $(element);
955    this.callback  = callback;
956    this.timer     = null;
957    this.lastValue = $F(this.element);
958    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
959  },
960  delayedListener: function(event) {
961    if(this.lastValue == $F(this.element)) return;
962    if(this.timer) clearTimeout(this.timer);
963    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
964    this.lastValue = $F(this.element);
965  },
966  onTimerEvent: function() {
967    this.timer = null;
968    this.callback(this.element, $F(this.element));
969  }
970});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.