web/wp-includes/js/scriptaculous/controls.js
changeset 194 32102edaa81b
parent 136 bde1974c263b
equal deleted inserted replaced
193:2f6f6f7551ca 194:32102edaa81b
     1 // script.aculo.us controls.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
     1 // script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
     2 
     2 
     3 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
     3 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
     4 //           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
     4 //           (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
     5 //           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
     5 //           (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
     6 // Contributors:
     6 // Contributors:
     7 //  Richard Livsey
     7 //  Richard Livsey
     8 //  Rahul Bhargava
     8 //  Rahul Bhargava
     9 //  Rob Wills
     9 //  Rob Wills
    10 // 
    10 //
    11 // script.aculo.us is freely distributable under the terms of an MIT-style license.
    11 // script.aculo.us is freely distributable under the terms of an MIT-style license.
    12 // For details, see the script.aculo.us web site: http://script.aculo.us/
    12 // For details, see the script.aculo.us web site: http://script.aculo.us/
    13 
    13 
    14 // Autocompleter.Base handles all the autocompletion functionality 
    14 // Autocompleter.Base handles all the autocompletion functionality
    15 // that's independent of the data source for autocompletion. This
    15 // that's independent of the data source for autocompletion. This
    16 // includes drawing the autocompletion menu, observing keyboard
    16 // includes drawing the autocompletion menu, observing keyboard
    17 // and mouse events, and similar.
    17 // and mouse events, and similar.
    18 //
    18 //
    19 // Specific autocompleters need to provide, at the very least, 
    19 // Specific autocompleters need to provide, at the very least,
    20 // a getUpdatedChoices function that will be invoked every time
    20 // a getUpdatedChoices function that will be invoked every time
    21 // the text inside the monitored textbox changes. This method 
    21 // the text inside the monitored textbox changes. This method
    22 // should get the text for which to provide autocompletion by
    22 // should get the text for which to provide autocompletion by
    23 // invoking this.getToken(), NOT by directly accessing
    23 // invoking this.getToken(), NOT by directly accessing
    24 // this.element.value. This is to allow incremental tokenized
    24 // this.element.value. This is to allow incremental tokenized
    25 // autocompletion. Specific auto-completion logic (AJAX, etc)
    25 // autocompletion. Specific auto-completion logic (AJAX, etc)
    26 // belongs in getUpdatedChoices.
    26 // belongs in getUpdatedChoices.
    30 // in the options parameter, e.g.:
    30 // in the options parameter, e.g.:
    31 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
    31 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
    32 // will incrementally autocomplete with a comma as the token.
    32 // will incrementally autocomplete with a comma as the token.
    33 // Additionally, ',' in the above example can be replaced with
    33 // Additionally, ',' in the above example can be replaced with
    34 // a token array, e.g. { tokens: [',', '\n'] } which
    34 // a token array, e.g. { tokens: [',', '\n'] } which
    35 // enables autocompletion on multiple tokens. This is most 
    35 // enables autocompletion on multiple tokens. This is most
    36 // useful when one of the tokens is \n (a newline), as it 
    36 // useful when one of the tokens is \n (a newline), as it
    37 // allows smart autocompletion after linebreaks.
    37 // allows smart autocompletion after linebreaks.
    38 
    38 
    39 if(typeof Effect == 'undefined')
    39 if(typeof Effect == 'undefined')
    40   throw("controls.js requires including script.aculo.us' effects.js library");
    40   throw("controls.js requires including script.aculo.us' effects.js library");
    41 
    41 
    42 var Autocompleter = { }
    42 var Autocompleter = { };
    43 Autocompleter.Base = Class.create({
    43 Autocompleter.Base = Class.create({
    44   baseInitialize: function(element, update, options) {
    44   baseInitialize: function(element, update, options) {
    45     element          = $(element)
    45     element          = $(element);
    46     this.element     = element; 
    46     this.element     = element;
    47     this.update      = $(update);  
    47     this.update      = $(update);
    48     this.hasFocus    = false; 
    48     this.hasFocus    = false;
    49     this.changed     = false; 
    49     this.changed     = false;
    50     this.active      = false; 
    50     this.active      = false;
    51     this.index       = 0;     
    51     this.index       = 0;
    52     this.entryCount  = 0;
    52     this.entryCount  = 0;
    53     this.oldElementValue = this.element.value;
    53     this.oldElementValue = this.element.value;
    54 
    54 
    55     if(this.setOptions)
    55     if(this.setOptions)
    56       this.setOptions(options);
    56       this.setOptions(options);
    59 
    59 
    60     this.options.paramName    = this.options.paramName || this.element.name;
    60     this.options.paramName    = this.options.paramName || this.element.name;
    61     this.options.tokens       = this.options.tokens || [];
    61     this.options.tokens       = this.options.tokens || [];
    62     this.options.frequency    = this.options.frequency || 0.4;
    62     this.options.frequency    = this.options.frequency || 0.4;
    63     this.options.minChars     = this.options.minChars || 1;
    63     this.options.minChars     = this.options.minChars || 1;
    64     this.options.onShow       = this.options.onShow || 
    64     this.options.onShow       = this.options.onShow ||
    65       function(element, update){ 
    65       function(element, update){
    66         if(!update.style.position || update.style.position=='absolute') {
    66         if(!update.style.position || update.style.position=='absolute') {
    67           update.style.position = 'absolute';
    67           update.style.position = 'absolute';
    68           Position.clone(element, update, {
    68           Position.clone(element, update, {
    69             setHeight: false, 
    69             setHeight: false,
    70             offsetTop: element.offsetHeight
    70             offsetTop: element.offsetHeight
    71           });
    71           });
    72         }
    72         }
    73         Effect.Appear(update,{duration:0.15});
    73         Effect.Appear(update,{duration:0.15});
    74       };
    74       };
    75     this.options.onHide = this.options.onHide || 
    75     this.options.onHide = this.options.onHide ||
    76       function(element, update){ new Effect.Fade(update,{duration:0.15}) };
    76       function(element, update){ new Effect.Fade(update,{duration:0.15}) };
    77 
    77 
    78     if(typeof(this.options.tokens) == 'string') 
    78     if(typeof(this.options.tokens) == 'string')
    79       this.options.tokens = new Array(this.options.tokens);
    79       this.options.tokens = new Array(this.options.tokens);
    80     // Force carriage returns as token delimiters anyway
    80     // Force carriage returns as token delimiters anyway
    81     if (!this.options.tokens.include('\n'))
    81     if (!this.options.tokens.include('\n'))
    82       this.options.tokens.push('\n');
    82       this.options.tokens.push('\n');
    83 
    83 
    84     this.observer = null;
    84     this.observer = null;
    85     
    85 
    86     this.element.setAttribute('autocomplete','off');
    86     this.element.setAttribute('autocomplete','off');
    87 
    87 
    88     Element.hide(this.update);
    88     Element.hide(this.update);
    89 
    89 
    90     Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    90     Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    91     Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
    91     Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
    92   },
    92   },
    93 
    93 
    94   show: function() {
    94   show: function() {
    95     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    95     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    96     if(!this.iefix && 
    96     if(!this.iefix &&
    97       (Prototype.Browser.IE) &&
    97       (Prototype.Browser.IE) &&
    98       (Element.getStyle(this.update, 'position')=='absolute')) {
    98       (Element.getStyle(this.update, 'position')=='absolute')) {
    99       new Insertion.After(this.update, 
    99       new Insertion.After(this.update,
   100        '<iframe id="' + this.update.id + '_iefix" '+
   100        '<iframe id="' + this.update.id + '_iefix" '+
   101        'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
   101        'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
   102        'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
   102        'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
   103       this.iefix = $(this.update.id+'_iefix');
   103       this.iefix = $(this.update.id+'_iefix');
   104     }
   104     }
   105     if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
   105     if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
   106   },
   106   },
   107   
   107 
   108   fixIEOverlapping: function() {
   108   fixIEOverlapping: function() {
   109     Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
   109     Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
   110     this.iefix.style.zIndex = 1;
   110     this.iefix.style.zIndex = 1;
   111     this.update.style.zIndex = 2;
   111     this.update.style.zIndex = 2;
   112     Element.show(this.iefix);
   112     Element.show(this.iefix);
   142        case Event.KEY_RIGHT:
   142        case Event.KEY_RIGHT:
   143          return;
   143          return;
   144        case Event.KEY_UP:
   144        case Event.KEY_UP:
   145          this.markPrevious();
   145          this.markPrevious();
   146          this.render();
   146          this.render();
   147          if(Prototype.Browser.WebKit) Event.stop(event);
   147          Event.stop(event);
   148          return;
   148          return;
   149        case Event.KEY_DOWN:
   149        case Event.KEY_DOWN:
   150          this.markNext();
   150          this.markNext();
   151          this.render();
   151          this.render();
   152          if(Prototype.Browser.WebKit) Event.stop(event);
   152          Event.stop(event);
   153          return;
   153          return;
   154       }
   154       }
   155      else 
   155      else
   156        if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
   156        if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
   157          (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
   157          (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
   158 
   158 
   159     this.changed = true;
   159     this.changed = true;
   160     this.hasFocus = true;
   160     this.hasFocus = true;
   161 
   161 
   162     if(this.observer) clearTimeout(this.observer);
   162     if(this.observer) clearTimeout(this.observer);
   163       this.observer = 
   163       this.observer =
   164         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
   164         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
   165   },
   165   },
   166 
   166 
   167   activate: function() {
   167   activate: function() {
   168     this.changed = false;
   168     this.changed = false;
   170     this.getUpdatedChoices();
   170     this.getUpdatedChoices();
   171   },
   171   },
   172 
   172 
   173   onHover: function(event) {
   173   onHover: function(event) {
   174     var element = Event.findElement(event, 'LI');
   174     var element = Event.findElement(event, 'LI');
   175     if(this.index != element.autocompleteIndex) 
   175     if(this.index != element.autocompleteIndex)
   176     {
   176     {
   177         this.index = element.autocompleteIndex;
   177         this.index = element.autocompleteIndex;
   178         this.render();
   178         this.render();
   179     }
   179     }
   180     Event.stop(event);
   180     Event.stop(event);
   181   },
   181   },
   182   
   182 
   183   onClick: function(event) {
   183   onClick: function(event) {
   184     var element = Event.findElement(event, 'LI');
   184     var element = Event.findElement(event, 'LI');
   185     this.index = element.autocompleteIndex;
   185     this.index = element.autocompleteIndex;
   186     this.selectEntry();
   186     this.selectEntry();
   187     this.hide();
   187     this.hide();
   188   },
   188   },
   189   
   189 
   190   onBlur: function(event) {
   190   onBlur: function(event) {
   191     // needed to make click events working
   191     // needed to make click events working
   192     setTimeout(this.hide.bind(this), 250);
   192     setTimeout(this.hide.bind(this), 250);
   193     this.hasFocus = false;
   193     this.hasFocus = false;
   194     this.active = false;     
   194     this.active = false;
   195   }, 
   195   },
   196   
   196 
   197   render: function() {
   197   render: function() {
   198     if(this.entryCount > 0) {
   198     if(this.entryCount > 0) {
   199       for (var i = 0; i < this.entryCount; i++)
   199       for (var i = 0; i < this.entryCount; i++)
   200         this.index==i ? 
   200         this.index==i ?
   201           Element.addClassName(this.getEntry(i),"selected") : 
   201           Element.addClassName(this.getEntry(i),"selected") :
   202           Element.removeClassName(this.getEntry(i),"selected");
   202           Element.removeClassName(this.getEntry(i),"selected");
   203       if(this.hasFocus) { 
   203       if(this.hasFocus) {
   204         this.show();
   204         this.show();
   205         this.active = true;
   205         this.active = true;
   206       }
   206       }
   207     } else {
   207     } else {
   208       this.active = false;
   208       this.active = false;
   209       this.hide();
   209       this.hide();
   210     }
   210     }
   211   },
   211   },
   212   
   212 
   213   markPrevious: function() {
   213   markPrevious: function() {
   214     if(this.index > 0) this.index--
   214     if(this.index > 0) this.index--;
   215       else this.index = this.entryCount-1;
   215       else this.index = this.entryCount-1;
   216     this.getEntry(this.index).scrollIntoView(true);
   216     this.getEntry(this.index).scrollIntoView(true);
   217   },
   217   },
   218   
   218 
   219   markNext: function() {
   219   markNext: function() {
   220     if(this.index < this.entryCount-1) this.index++
   220     if(this.index < this.entryCount-1) this.index++;
   221       else this.index = 0;
   221       else this.index = 0;
   222     this.getEntry(this.index).scrollIntoView(false);
   222     this.getEntry(this.index).scrollIntoView(false);
   223   },
   223   },
   224   
   224 
   225   getEntry: function(index) {
   225   getEntry: function(index) {
   226     return this.update.firstChild.childNodes[index];
   226     return this.update.firstChild.childNodes[index];
   227   },
   227   },
   228   
   228 
   229   getCurrentEntry: function() {
   229   getCurrentEntry: function() {
   230     return this.getEntry(this.index);
   230     return this.getEntry(this.index);
   231   },
   231   },
   232   
   232 
   233   selectEntry: function() {
   233   selectEntry: function() {
   234     this.active = false;
   234     this.active = false;
   235     this.updateElement(this.getCurrentEntry());
   235     this.updateElement(this.getCurrentEntry());
   236   },
   236   },
   237 
   237 
   244     if (this.options.select) {
   244     if (this.options.select) {
   245       var nodes = $(selectedElement).select('.' + this.options.select) || [];
   245       var nodes = $(selectedElement).select('.' + this.options.select) || [];
   246       if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
   246       if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
   247     } else
   247     } else
   248       value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
   248       value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
   249     
   249 
   250     var bounds = this.getTokenBounds();
   250     var bounds = this.getTokenBounds();
   251     if (bounds[0] != -1) {
   251     if (bounds[0] != -1) {
   252       var newValue = this.element.value.substr(0, bounds[0]);
   252       var newValue = this.element.value.substr(0, bounds[0]);
   253       var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
   253       var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
   254       if (whitespace)
   254       if (whitespace)
   257     } else {
   257     } else {
   258       this.element.value = value;
   258       this.element.value = value;
   259     }
   259     }
   260     this.oldElementValue = this.element.value;
   260     this.oldElementValue = this.element.value;
   261     this.element.focus();
   261     this.element.focus();
   262     
   262 
   263     if (this.options.afterUpdateElement)
   263     if (this.options.afterUpdateElement)
   264       this.options.afterUpdateElement(this.element, selectedElement);
   264       this.options.afterUpdateElement(this.element, selectedElement);
   265   },
   265   },
   266 
   266 
   267   updateChoices: function(choices) {
   267   updateChoices: function(choices) {
   269       this.update.innerHTML = choices;
   269       this.update.innerHTML = choices;
   270       Element.cleanWhitespace(this.update);
   270       Element.cleanWhitespace(this.update);
   271       Element.cleanWhitespace(this.update.down());
   271       Element.cleanWhitespace(this.update.down());
   272 
   272 
   273       if(this.update.firstChild && this.update.down().childNodes) {
   273       if(this.update.firstChild && this.update.down().childNodes) {
   274         this.entryCount = 
   274         this.entryCount =
   275           this.update.down().childNodes.length;
   275           this.update.down().childNodes.length;
   276         for (var i = 0; i < this.entryCount; i++) {
   276         for (var i = 0; i < this.entryCount; i++) {
   277           var entry = this.getEntry(i);
   277           var entry = this.getEntry(i);
   278           entry.autocompleteIndex = i;
   278           entry.autocompleteIndex = i;
   279           this.addObservers(entry);
   279           this.addObservers(entry);
   280         }
   280         }
   281       } else { 
   281       } else {
   282         this.entryCount = 0;
   282         this.entryCount = 0;
   283       }
   283       }
   284 
   284 
   285       this.stopIndicator();
   285       this.stopIndicator();
   286       this.index = 0;
   286       this.index = 0;
   287       
   287 
   288       if(this.entryCount==1 && this.options.autoSelect) {
   288       if(this.entryCount==1 && this.options.autoSelect) {
   289         this.selectEntry();
   289         this.selectEntry();
   290         this.hide();
   290         this.hide();
   291       } else {
   291       } else {
   292         this.render();
   292         this.render();
   298     Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
   298     Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
   299     Event.observe(element, "click", this.onClick.bindAsEventListener(this));
   299     Event.observe(element, "click", this.onClick.bindAsEventListener(this));
   300   },
   300   },
   301 
   301 
   302   onObserverEvent: function() {
   302   onObserverEvent: function() {
   303     this.changed = false;   
   303     this.changed = false;
   304     this.tokenBounds = null;
   304     this.tokenBounds = null;
   305     if(this.getToken().length>=this.options.minChars) {
   305     if(this.getToken().length>=this.options.minChars) {
   306       this.getUpdatedChoices();
   306       this.getUpdatedChoices();
   307     } else {
   307     } else {
   308       this.active = false;
   308       this.active = false;
   351     this.url                   = url;
   351     this.url                   = url;
   352   },
   352   },
   353 
   353 
   354   getUpdatedChoices: function() {
   354   getUpdatedChoices: function() {
   355     this.startIndicator();
   355     this.startIndicator();
   356     
   356 
   357     var entry = encodeURIComponent(this.options.paramName) + '=' + 
   357     var entry = encodeURIComponent(this.options.paramName) + '=' +
   358       encodeURIComponent(this.getToken());
   358       encodeURIComponent(this.getToken());
   359 
   359 
   360     this.options.parameters = this.options.callback ?
   360     this.options.parameters = this.options.callback ?
   361       this.options.callback(this.element, entry) : entry;
   361       this.options.callback(this.element, entry) : entry;
   362 
   362 
   363     if(this.options.defaultParams) 
   363     if(this.options.defaultParams)
   364       this.options.parameters += '&' + this.options.defaultParams;
   364       this.options.parameters += '&' + this.options.defaultParams;
   365     
   365 
   366     new Ajax.Request(this.url, this.options);
   366     new Ajax.Request(this.url, this.options);
   367   },
   367   },
   368 
   368 
   369   onComplete: function(request) {
   369   onComplete: function(request) {
   370     this.updateChoices(request.responseText);
   370     this.updateChoices(request.responseText);
   382 //
   382 //
   383 // Extra local autocompletion options:
   383 // Extra local autocompletion options:
   384 // - choices - How many autocompletion choices to offer
   384 // - choices - How many autocompletion choices to offer
   385 //
   385 //
   386 // - partialSearch - If false, the autocompleter will match entered
   386 // - partialSearch - If false, the autocompleter will match entered
   387 //                    text only at the beginning of strings in the 
   387 //                    text only at the beginning of strings in the
   388 //                    autocomplete array. Defaults to true, which will
   388 //                    autocomplete array. Defaults to true, which will
   389 //                    match text at the beginning of any *word* in the
   389 //                    match text at the beginning of any *word* in the
   390 //                    strings in the autocomplete array. If you want to
   390 //                    strings in the autocomplete array. If you want to
   391 //                    search anywhere in the string, additionally set
   391 //                    search anywhere in the string, additionally set
   392 //                    the option fullSearch to true (default: off).
   392 //                    the option fullSearch to true (default: off).
   399 //                   at all). Defaults to 2.
   399 //                   at all). Defaults to 2.
   400 //
   400 //
   401 // - ignoreCase - Whether to ignore case when autocompleting.
   401 // - ignoreCase - Whether to ignore case when autocompleting.
   402 //                 Defaults to true.
   402 //                 Defaults to true.
   403 //
   403 //
   404 // It's possible to pass in a custom function as the 'selector' 
   404 // It's possible to pass in a custom function as the 'selector'
   405 // option, if you prefer to write your own autocompletion logic.
   405 // option, if you prefer to write your own autocompletion logic.
   406 // In that case, the other options above will not apply unless
   406 // In that case, the other options above will not apply unless
   407 // you support them.
   407 // you support them.
   408 
   408 
   409 Autocompleter.Local = Class.create(Autocompleter.Base, {
   409 Autocompleter.Local = Class.create(Autocompleter.Base, {
   427         var ret       = []; // Beginning matches
   427         var ret       = []; // Beginning matches
   428         var partial   = []; // Inside matches
   428         var partial   = []; // Inside matches
   429         var entry     = instance.getToken();
   429         var entry     = instance.getToken();
   430         var count     = 0;
   430         var count     = 0;
   431 
   431 
   432         for (var i = 0; i < instance.options.array.length &&  
   432         for (var i = 0; i < instance.options.array.length &&
   433           ret.length < instance.options.choices ; i++) { 
   433           ret.length < instance.options.choices ; i++) {
   434 
   434 
   435           var elem = instance.options.array[i];
   435           var elem = instance.options.array[i];
   436           var foundPos = instance.options.ignoreCase ? 
   436           var foundPos = instance.options.ignoreCase ?
   437             elem.toLowerCase().indexOf(entry.toLowerCase()) : 
   437             elem.toLowerCase().indexOf(entry.toLowerCase()) :
   438             elem.indexOf(entry);
   438             elem.indexOf(entry);
   439 
   439 
   440           while (foundPos != -1) {
   440           while (foundPos != -1) {
   441             if (foundPos == 0 && elem.length != entry.length) { 
   441             if (foundPos == 0 && elem.length != entry.length) {
   442               ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
   442               ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
   443                 elem.substr(entry.length) + "</li>");
   443                 elem.substr(entry.length) + "</li>");
   444               break;
   444               break;
   445             } else if (entry.length >= instance.options.partialChars && 
   445             } else if (entry.length >= instance.options.partialChars &&
   446               instance.options.partialSearch && foundPos != -1) {
   446               instance.options.partialSearch && foundPos != -1) {
   447               if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
   447               if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
   448                 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
   448                 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
   449                   elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
   449                   elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
   450                   foundPos + entry.length) + "</li>");
   450                   foundPos + entry.length) + "</li>");
   451                 break;
   451                 break;
   452               }
   452               }
   453             }
   453             }
   454 
   454 
   455             foundPos = instance.options.ignoreCase ? 
   455             foundPos = instance.options.ignoreCase ?
   456               elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
   456               elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
   457               elem.indexOf(entry, foundPos + 1);
   457               elem.indexOf(entry, foundPos + 1);
   458 
   458 
   459           }
   459           }
   460         }
   460         }
   461         if (partial.length)
   461         if (partial.length)
   462           ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
   462           ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
   463         return "<ul>" + ret.join('') + "</ul>";
   463         return "<ul>" + ret.join('') + "</ul>";
   464       }
   464       }
   465     }, options || { });
   465     }, options || { });
   466   }
   466   }
   467 });
   467 });
   474 // waits 1 ms (with setTimeout) until it does the activation
   474 // waits 1 ms (with setTimeout) until it does the activation
   475 Field.scrollFreeActivate = function(field) {
   475 Field.scrollFreeActivate = function(field) {
   476   setTimeout(function() {
   476   setTimeout(function() {
   477     Field.activate(field);
   477     Field.activate(field);
   478   }, 1);
   478   }, 1);
   479 }
   479 };
   480 
   480 
   481 Ajax.InPlaceEditor = Class.create({
   481 Ajax.InPlaceEditor = Class.create({
   482   initialize: function(element, url, options) {
   482   initialize: function(element, url, options) {
   483     this.url = url;
   483     this.url = url;
   484     this.element = element = $(element);
   484     this.element = element = $(element);
   604       this.element.addClassName(this.options.hoverClassName);
   604       this.element.addClassName(this.options.hoverClassName);
   605     if (this._saving) return;
   605     if (this._saving) return;
   606     this.triggerCallback('onEnterHover');
   606     this.triggerCallback('onEnterHover');
   607   },
   607   },
   608   getText: function() {
   608   getText: function() {
   609     return this.element.innerHTML;
   609     return this.element.innerHTML.unescapeHTML();
   610   },
   610   },
   611   handleAJAXFailure: function(transport) {
   611   handleAJAXFailure: function(transport) {
   612     this.triggerCallback('onFailure', transport);
   612     this.triggerCallback('onFailure', transport);
   613     if (this._oldInnerHTML) {
   613     if (this._oldInnerHTML) {
   614       this.element.innerHTML = this._oldInnerHTML;
   614       this.element.innerHTML = this._oldInnerHTML;
   780       parameters: 'editorId=' + encodeURIComponent(this.element.id),
   780       parameters: 'editorId=' + encodeURIComponent(this.element.id),
   781       onComplete: Prototype.emptyFunction,
   781       onComplete: Prototype.emptyFunction,
   782       onSuccess: function(transport) {
   782       onSuccess: function(transport) {
   783         var js = transport.responseText.strip();
   783         var js = transport.responseText.strip();
   784         if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
   784         if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
   785           throw 'Server returned an invalid collection representation.';
   785           throw('Server returned an invalid collection representation.');
   786         this._collection = eval(js);
   786         this._collection = eval(js);
   787         this.checkForExternalText();
   787         this.checkForExternalText();
   788       }.bind(this),
   788       }.bind(this),
   789       onFailure: this.onFailure
   789       onFailure: this.onFailure
   790     });
   790     });
   937 
   937 
   938 Ajax.InPlaceCollectionEditor.DefaultOptions = {
   938 Ajax.InPlaceCollectionEditor.DefaultOptions = {
   939   loadingCollectionText: 'Loading options...'
   939   loadingCollectionText: 'Loading options...'
   940 };
   940 };
   941 
   941 
   942 // Delayed observer, like Form.Element.Observer, 
   942 // Delayed observer, like Form.Element.Observer,
   943 // but waits for delay after last key input
   943 // but waits for delay after last key input
   944 // Ideal for live-search fields
   944 // Ideal for live-search fields
   945 
   945 
   946 Form.Element.DelayedObserver = Class.create({
   946 Form.Element.DelayedObserver = Class.create({
   947   initialize: function(element, delay, callback) {
   947   initialize: function(element, delay, callback) {
   948     this.delay     = delay || 0.5;
   948     this.delay     = delay || 0.5;
   949     this.element   = $(element);
   949     this.element   = $(element);
   950     this.callback  = callback;
   950     this.callback  = callback;
   951     this.timer     = null;
   951     this.timer     = null;
   952     this.lastValue = $F(this.element); 
   952     this.lastValue = $F(this.element);
   953     Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
   953     Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
   954   },
   954   },
   955   delayedListener: function(event) {
   955   delayedListener: function(event) {
   956     if(this.lastValue == $F(this.element)) return;
   956     if(this.lastValue == $F(this.element)) return;
   957     if(this.timer) clearTimeout(this.timer);
   957     if(this.timer) clearTimeout(this.timer);