home *** CD-ROM | disk | FTP | other *** search
/ CrystalVision Software Se… Wiki Wonder - Wikipedia / CrystalVision Software Services 703: The Wiki Wonder - Wikipedia.iso / 0703 / Business / CodeX Apps / CodeX.exe / CodeX / html / lib / Web 2.0 / JavaScripts / controls.js < prev    next >
Encoding:
Text File  |  2006-09-05  |  28.1 KB  |  834 lines

  1. // script.aculo.us controls.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
  2.  
  3. // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  4. //           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  5. //           (c) 2005 Jon Tirsen (http://www.tirsen.com)
  6. // Contributors:
  7. //  Richard Livsey
  8. //  Rahul Bhargava
  9. //  Rob Wills
  10. // 
  11. // See scriptaculous.js for full license.
  12.  
  13. // Autocompleter.Base handles all the autocompletion functionality 
  14. // that's independent of the data source for autocompletion. This
  15. // includes drawing the autocompletion menu, observing keyboard
  16. // and mouse events, and similar.
  17. //
  18. // Specific autocompleters need to provide, at the very least, 
  19. // a getUpdatedChoices function that will be invoked every time
  20. // the text inside the monitored textbox changes. This method 
  21. // should get the text for which to provide autocompletion by
  22. // invoking this.getToken(), NOT by directly accessing
  23. // this.element.value. This is to allow incremental tokenized
  24. // autocompletion. Specific auto-completion logic (AJAX, etc)
  25. // belongs in getUpdatedChoices.
  26. //
  27. // Tokenized incremental autocompletion is enabled automatically
  28. // when an autocompleter is instantiated with the 'tokens' option
  29. // in the options parameter, e.g.:
  30. // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  31. // will incrementally autocomplete with a comma as the token.
  32. // Additionally, ',' in the above example can be replaced with
  33. // a token array, e.g. { tokens: [',', '\n'] } which
  34. // enables autocompletion on multiple tokens. This is most 
  35. // useful when one of the tokens is \n (a newline), as it 
  36. // allows smart autocompletion after linebreaks.
  37.  
  38. if(typeof Effect == 'undefined')
  39.   throw("controls.js requires including script.aculo.us' effects.js library");
  40.  
  41. var Autocompleter = {}
  42. Autocompleter.Base = function() {};
  43. Autocompleter.Base.prototype = {
  44.   baseInitialize: function(element, update, options) {
  45.     this.element     = $(element); 
  46.     this.update      = $(update);  
  47.     this.hasFocus    = false; 
  48.     this.changed     = false; 
  49.     this.active      = false; 
  50.     this.index       = 0;     
  51.     this.entryCount  = 0;
  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.  
  79.     this.observer = null;
  80.     
  81.     this.element.setAttribute('autocomplete','off');
  82.  
  83.     Element.hide(this.update);
  84.  
  85.     Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
  86.     Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  87.   },
  88.  
  89.   show: function() {
  90.     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
  91.     if(!this.iefix && 
  92.       (navigator.appVersion.indexOf('MSIE')>0) &&
  93.       (navigator.userAgent.indexOf('Opera')<0) &&
  94.       (Element.getStyle(this.update, 'position')=='absolute')) {
  95.       new Insertion.After(this.update, 
  96.        '<iframe id="' + this.update.id + '_iefix" '+
  97.        'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  98.        'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  99.       this.iefix = $(this.update.id+'_iefix');
  100.     }
  101.     if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  102.   },
  103.   
  104.   fixIEOverlapping: function() {
  105.     Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
  106.     this.iefix.style.zIndex = 1;
  107.     this.update.style.zIndex = 2;
  108.     Element.show(this.iefix);
  109.   },
  110.  
  111.   hide: function() {
  112.     this.stopIndicator();
  113.     if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
  114.     if(this.iefix) Element.hide(this.iefix);
  115.   },
  116.  
  117.   startIndicator: function() {
  118.     if(this.options.indicator) Element.show(this.options.indicator);
  119.   },
  120.  
  121.   stopIndicator: function() {
  122.     if(this.options.indicator) Element.hide(this.options.indicator);
  123.   },
  124.  
  125.   onKeyPress: function(event) {
  126.     if(this.active)
  127.       switch(event.keyCode) {
  128.        case Event.KEY_TAB:
  129.        case Event.KEY_RETURN:
  130.          this.selectEntry();
  131.          Event.stop(event);
  132.        case Event.KEY_ESC:
  133.          this.hide();
  134.          this.active = false;
  135.          Event.stop(event);
  136.          return;
  137.        case Event.KEY_LEFT:
  138.        case Event.KEY_RIGHT:
  139.          return;
  140.        case Event.KEY_UP:
  141.          this.markPrevious();
  142.          this.render();
  143.          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  144.          return;
  145.        case Event.KEY_DOWN:
  146.          this.markNext();
  147.          this.render();
  148.          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  149.          return;
  150.       }
  151.      else 
  152.        if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
  153.          (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
  154.  
  155.     this.changed = true;
  156.     this.hasFocus = true;
  157.  
  158.     if(this.observer) clearTimeout(this.observer);
  159.       this.observer = 
  160.         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  161.   },
  162.  
  163.   activate: function() {
  164.     this.changed = false;
  165.     this.hasFocus = true;
  166.     this.getUpdatedChoices();
  167.   },
  168.  
  169.   onHover: function(event) {
  170.     var element = Event.findElement(event, 'LI');
  171.     if(this.index != element.autocompleteIndex) 
  172.     {
  173.         this.index = element.autocompleteIndex;
  174.         this.render();
  175.     }
  176.     Event.stop(event);
  177.   },
  178.   
  179.   onClick: function(event) {
  180.     var element = Event.findElement(event, 'LI');
  181.     this.index = element.autocompleteIndex;
  182.     this.selectEntry();
  183.     this.hide();
  184.   },
  185.   
  186.   onBlur: function(event) {
  187.     // needed to make click events working
  188.     setTimeout(this.hide.bind(this), 250);
  189.     this.hasFocus = false;
  190.     this.active = false;     
  191.   }, 
  192.   
  193.   render: function() {
  194.     if(this.entryCount > 0) {
  195.       for (var i = 0; i < this.entryCount; i++)
  196.         this.index==i ? 
  197.           Element.addClassName(this.getEntry(i),"selected") : 
  198.           Element.removeClassName(this.getEntry(i),"selected");
  199.         
  200.       if(this.hasFocus) { 
  201.         this.show();
  202.         this.active = true;
  203.       }
  204.     } else {
  205.       this.active = false;
  206.       this.hide();
  207.     }
  208.   },
  209.   
  210.   markPrevious: function() {
  211.     if(this.index > 0) this.index--
  212.       else this.index = this.entryCount-1;
  213.     this.getEntry(this.index).scrollIntoView(true);
  214.   },
  215.   
  216.   markNext: function() {
  217.     if(this.index < this.entryCount-1) this.index++
  218.       else this.index = 0;
  219.     this.getEntry(this.index).scrollIntoView(false);
  220.   },
  221.   
  222.   getEntry: function(index) {
  223.     return this.update.firstChild.childNodes[index];
  224.   },
  225.   
  226.   getCurrentEntry: function() {
  227.     return this.getEntry(this.index);
  228.   },
  229.   
  230.   selectEntry: function() {
  231.     this.active = false;
  232.     this.updateElement(this.getCurrentEntry());
  233.   },
  234.  
  235.   updateElement: function(selectedElement) {
  236.     if (this.options.updateElement) {
  237.       this.options.updateElement(selectedElement);
  238.       return;
  239.     }
  240.     var value = '';
  241.     if (this.options.select) {
  242.       var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
  243.       if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
  244.     } else
  245.       value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
  246.     
  247.     var lastTokenPos = this.findLastToken();
  248.     if (lastTokenPos != -1) {
  249.       var newValue = this.element.value.substr(0, lastTokenPos + 1);
  250.       var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
  251.       if (whitespace)
  252.         newValue += whitespace[0];
  253.       this.element.value = newValue + value;
  254.     } else {
  255.       this.element.value = value;
  256.     }
  257.     this.element.focus();
  258.     
  259.     if (this.options.afterUpdateElement)
  260.       this.options.afterUpdateElement(this.element, selectedElement);
  261.   },
  262.  
  263.   updateChoices: function(choices) {
  264.     if(!this.changed && this.hasFocus) {
  265.       this.update.innerHTML = choices;
  266.       Element.cleanWhitespace(this.update);
  267.       Element.cleanWhitespace(this.update.firstChild);
  268.  
  269.       if(this.update.firstChild && this.update.firstChild.childNodes) {
  270.         this.entryCount = 
  271.           this.update.firstChild.childNodes.length;
  272.         for (var i = 0; i < this.entryCount; i++) {
  273.           var entry = this.getEntry(i);
  274.           entry.autocompleteIndex = i;
  275.           this.addObservers(entry);
  276.         }
  277.       } else { 
  278.         this.entryCount = 0;
  279.       }
  280.  
  281.       this.stopIndicator();
  282.       this.index = 0;
  283.       
  284.       if(this.entryCount==1 && this.options.autoSelect) {
  285.         this.selectEntry();
  286.         this.hide();
  287.       } else {
  288.         this.render();
  289.       }
  290.     }
  291.   },
  292.  
  293.   addObservers: function(element) {
  294.     Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
  295.     Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  296.   },
  297.  
  298.   onObserverEvent: function() {
  299.     this.changed = false;   
  300.     if(this.getToken().length>=this.options.minChars) {
  301.       this.startIndicator();
  302.       this.getUpdatedChoices();
  303.     } else {
  304.       this.active = false;
  305.       this.hide();
  306.     }
  307.   },
  308.  
  309.   getToken: function() {
  310.     var tokenPos = this.findLastToken();
  311.     if (tokenPos != -1)
  312.       var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
  313.     else
  314.       var ret = this.element.value;
  315.  
  316.     return /\n/.test(ret) ? '' : ret;
  317.   },
  318.  
  319.   findLastToken: function() {
  320.     var lastTokenPos = -1;
  321.  
  322.     for (var i=0; i<this.options.tokens.length; i++) {
  323.       var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
  324.       if (thisTokenPos > lastTokenPos)
  325.         lastTokenPos = thisTokenPos;
  326.     }
  327.     return lastTokenPos;
  328.   }
  329. }
  330.  
  331. Ajax.Autocompleter = Class.create();
  332. Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  333.   initialize: function(element, update, url, options) {
  334.     this.baseInitialize(element, update, options);
  335.     this.options.asynchronous  = true;
  336.     this.options.onComplete    = this.onComplete.bind(this);
  337.     this.options.defaultParams = this.options.parameters || null;
  338.     this.url                   = url;
  339.   },
  340.  
  341.   getUpdatedChoices: function() {
  342.     entry = encodeURIComponent(this.options.paramName) + '=' + 
  343.       encodeURIComponent(this.getToken());
  344.  
  345.     this.options.parameters = this.options.callback ?
  346.       this.options.callback(this.element, entry) : entry;
  347.  
  348.     if(this.options.defaultParams) 
  349.       this.options.parameters += '&' + this.options.defaultParams;
  350.  
  351.     new Ajax.Request(this.url, this.options);
  352.   },
  353.  
  354.   onComplete: function(request) {
  355.     this.updateChoices(request.responseText);
  356.   }
  357.  
  358. });
  359.  
  360. // The local array autocompleter. Used when you'd prefer to
  361. // inject an array of autocompletion options into the page, rather
  362. // than sending out Ajax queries, which can be quite slow sometimes.
  363. //
  364. // The constructor takes four parameters. The first two are, as usual,
  365. // the id of the monitored textbox, and id of the autocompletion menu.
  366. // The third is the array you want to autocomplete from, and the fourth
  367. // is the options block.
  368. //
  369. // Extra local autocompletion options:
  370. // - choices - How many autocompletion choices to offer
  371. //
  372. // - partialSearch - If false, the autocompleter will match entered
  373. //                    text only at the beginning of strings in the 
  374. //                    autocomplete array. Defaults to true, which will
  375. //                    match text at the beginning of any *word* in the
  376. //                    strings in the autocomplete array. If you want to
  377. //                    search anywhere in the string, additionally set
  378. //                    the option fullSearch to true (default: off).
  379. //
  380. // - fullSsearch - Search anywhere in autocomplete array strings.
  381. //
  382. // - partialChars - How many characters to enter before triggering
  383. //                   a partial match (unlike minChars, which defines
  384. //                   how many characters are required to do any match
  385. //                   at all). Defaults to 2.
  386. //
  387. // - ignoreCase - Whether to ignore case when autocompleting.
  388. //                 Defaults to true.
  389. //
  390. // It's possible to pass in a custom function as the 'selector' 
  391. // option, if you prefer to write your own autocompletion logic.
  392. // In that case, the other options above will not apply unless
  393. // you support them.
  394.  
  395. Autocompleter.Local = Class.create();
  396. Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  397.   initialize: function(element, update, array, options) {
  398.     this.baseInitialize(element, update, options);
  399.     this.options.array = array;
  400.   },
  401.  
  402.   getUpdatedChoices: function() {
  403.     this.updateChoices(this.options.selector(this));
  404.   },
  405.  
  406.   setOptions: function(options) {
  407.     this.options = Object.extend({
  408.       choices: 10,
  409.       partialSearch: true,
  410.       partialChars: 2,
  411.       ignoreCase: true,
  412.       fullSearch: false,
  413.       selector: function(instance) {
  414.         var ret       = []; // Beginning matches
  415.         var partial   = []; // Inside matches
  416.         var entry     = instance.getToken();
  417.         var count     = 0;
  418.  
  419.         for (var i = 0; i < instance.options.array.length &&  
  420.           ret.length < instance.options.choices ; i++) { 
  421.  
  422.           var elem = instance.options.array[i];
  423.           var foundPos = instance.options.ignoreCase ? 
  424.             elem.toLowerCase().indexOf(entry.toLowerCase()) : 
  425.             elem.indexOf(entry);
  426.  
  427.           while (foundPos != -1) {
  428.             if (foundPos == 0 && elem.length != entry.length) { 
  429.               ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
  430.                 elem.substr(entry.length) + "</li>");
  431.               break;
  432.             } else if (entry.length >= instance.options.partialChars && 
  433.               instance.options.partialSearch && foundPos != -1) {
  434.               if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
  435.                 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
  436.                   elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
  437.                   foundPos + entry.length) + "</li>");
  438.                 break;
  439.               }
  440.             }
  441.  
  442.             foundPos = instance.options.ignoreCase ? 
  443.               elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
  444.               elem.indexOf(entry, foundPos + 1);
  445.  
  446.           }
  447.         }
  448.         if (partial.length)
  449.           ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
  450.         return "<ul>" + ret.join('') + "</ul>";
  451.       }
  452.     }, options || {});
  453.   }
  454. });
  455.  
  456. // AJAX in-place editor
  457. //
  458. // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
  459.  
  460. // Use this if you notice weird scrolling problems on some browsers,
  461. // the DOM might be a bit confused when this gets called so do this
  462. // waits 1 ms (with setTimeout) until it does the activation
  463. Field.scrollFreeActivate = function(field) {
  464.   setTimeout(function() {
  465.     Field.activate(field);
  466.   }, 1);
  467. }
  468.  
  469. Ajax.InPlaceEditor = Class.create();
  470. Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
  471. Ajax.InPlaceEditor.prototype = {
  472.   initialize: function(element, url, options) {
  473.     this.url = url;
  474.     this.element = $(element);
  475.  
  476.     this.options = Object.extend({
  477.       okButton: true,
  478.       okText: "ok",
  479.       cancelLink: true,
  480.       cancelText: "cancel",
  481.       savingText: "Saving...",
  482.       clickToEditText: "Click to edit",
  483.       okText: "ok",
  484.       rows: 1,
  485.       onComplete: function(transport, element) {
  486.         new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
  487.       },
  488.       onFailure: function(transport) {
  489.         alert("Error communicating with the server: " + transport.responseText.stripTags());
  490.       },
  491.       callback: function(form) {
  492.         return Form.serialize(form);
  493.       },
  494.       handleLineBreaks: true,
  495.       loadingText: 'Loading...',
  496.       savingClassName: 'inplaceeditor-saving',
  497.       loadingClassName: 'inplaceeditor-loading',
  498.       formClassName: 'inplaceeditor-form',
  499.       highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
  500.       highlightendcolor: "#FFFFFF",
  501.       externalControl: null,
  502.       submitOnBlur: false,
  503.       ajaxOptions: {},
  504.       evalScripts: false
  505.     }, options || {});
  506.  
  507.     if(!this.options.formId && this.element.id) {
  508.       this.options.formId = this.element.id + "-inplaceeditor";
  509.       if ($(this.options.formId)) {
  510.         // there's already a form with that name, don't specify an id
  511.         this.options.formId = null;
  512.       }
  513.     }
  514.     
  515.     if (this.options.externalControl) {
  516.       this.options.externalControl = $(this.options.externalControl);
  517.     }
  518.     
  519.     this.originalBackground = Element.getStyle(this.element, 'background-color');
  520.     if (!this.originalBackground) {
  521.       this.originalBackground = "transparent";
  522.     }
  523.     
  524.     this.element.title = this.options.clickToEditText;
  525.     
  526.     this.onclickListener = this.enterEditMode.bindAsEventListener(this);
  527.     this.mouseoverListener = this.enterHover.bindAsEventListener(this);
  528.     this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
  529.     Event.observe(this.element, 'click', this.onclickListener);
  530.     Event.observe(this.element, 'mouseover', this.mouseoverListener);
  531.     Event.observe(this.element, 'mouseout', this.mouseoutListener);
  532.     if (this.options.externalControl) {
  533.       Event.observe(this.options.externalControl, 'click', this.onclickListener);
  534.       Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
  535.       Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
  536.     }
  537.   },
  538.   enterEditMode: function(evt) {
  539.     if (this.saving) return;
  540.     if (this.editing) return;
  541.     this.editing = true;
  542.     this.onEnterEditMode();
  543.     if (this.options.externalControl) {
  544.       Element.hide(this.options.externalControl);
  545.     }
  546.     Element.hide(this.element);
  547.     this.createForm();
  548.     this.element.parentNode.insertBefore(this.form, this.element);
  549.     if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
  550.     // stop the event to avoid a page refresh in Safari
  551.     if (evt) {
  552.       Event.stop(evt);
  553.     }
  554.     return false;
  555.   },
  556.   createForm: function() {
  557.     this.form = document.createElement("form");
  558.     this.form.id = this.options.formId;
  559.     Element.addClassName(this.form, this.options.formClassName)
  560.     this.form.onsubmit = this.onSubmit.bind(this);
  561.  
  562.     this.createEditField();
  563.  
  564.     if (this.options.textarea) {
  565.       var br = document.createElement("br");
  566.       this.form.appendChild(br);
  567.     }
  568.  
  569.     if (this.options.okButton) {
  570.       okButton = document.createElement("input");
  571.       okButton.type = "submit";
  572.       okButton.value = this.options.okText;
  573.       okButton.className = 'editor_ok_button';
  574.       this.form.appendChild(okButton);
  575.     }
  576.  
  577.     if (this.options.cancelLink) {
  578.       cancelLink = document.createElement("a");
  579.       cancelLink.href = "#";
  580.       cancelLink.appendChild(document.createTextNode(this.options.cancelText));
  581.       cancelLink.onclick = this.onclickCancel.bind(this);
  582.       cancelLink.className = 'editor_cancel';      
  583.       this.form.appendChild(cancelLink);
  584.     }
  585.   },
  586.   hasHTMLLineBreaks: function(string) {
  587.     if (!this.options.handleLineBreaks) return false;
  588.     return string.match(/<br/i) || string.match(/<p>/i);
  589.   },
  590.   convertHTMLLineBreaks: function(string) {
  591.     return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  592.   },
  593.   createEditField: function() {
  594.     var text;
  595.     if(this.options.loadTextURL) {
  596.       text = this.options.loadingText;
  597.     } else {
  598.       text = this.getText();
  599.     }
  600.  
  601.     var obj = this;
  602.     
  603.     if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
  604.       this.options.textarea = false;
  605.       var textField = document.createElement("input");
  606.       textField.obj = this;
  607.       textField.type = "text";
  608.       textField.name = "value";
  609.       textField.value = text;
  610.       textField.style.backgroundColor = this.options.highlightcolor;
  611.       textField.className = 'editor_field';
  612.       var size = this.options.size || this.options.cols || 0;
  613.       if (size != 0) textField.size = size;
  614.       if (this.options.submitOnBlur)
  615.         textField.onblur = this.onSubmit.bind(this);
  616.       this.editField = textField;
  617.     } else {
  618.       this.options.textarea = true;
  619.       var textArea = document.createElement("textarea");
  620.       textArea.obj = this;
  621.       textArea.name = "value";
  622.       textArea.value = this.convertHTMLLineBreaks(text);
  623.       textArea.rows = this.options.rows;
  624.       textArea.cols = this.options.cols || 40;
  625.       textArea.className = 'editor_field';      
  626.       if (this.options.submitOnBlur)
  627.         textArea.onblur = this.onSubmit.bind(this);
  628.       this.editField = textArea;
  629.     }
  630.     
  631.     if(this.options.loadTextURL) {
  632.       this.loadExternalText();
  633.     }
  634.     this.form.appendChild(this.editField);
  635.   },
  636.   getText: function() {
  637.     return this.element.innerHTML;
  638.   },
  639.   loadExternalText: function() {
  640.     Element.addClassName(this.form, this.options.loadingClassName);
  641.     this.editField.disabled = true;
  642.     new Ajax.Request(
  643.       this.options.loadTextURL,
  644.       Object.extend({
  645.         asynchronous: true,
  646.         onComplete: this.onLoadedExternalText.bind(this)
  647.       }, this.options.ajaxOptions)
  648.     );
  649.   },
  650.   onLoadedExternalText: function(transport) {
  651.     Element.removeClassName(this.form, this.options.loadingClassName);
  652.     this.editField.disabled = false;
  653.     this.editField.value = transport.responseText.stripTags();
  654.     Field.scrollFreeActivate(this.editField);
  655.   },
  656.   onclickCancel: function() {
  657.     this.onComplete();
  658.     this.leaveEditMode();
  659.     return false;
  660.   },
  661.   onFailure: function(transport) {
  662.     this.options.onFailure(transport);
  663.     if (this.oldInnerHTML) {
  664.       this.element.innerHTML = this.oldInnerHTML;
  665.       this.oldInnerHTML = null;
  666.     }
  667.     return false;
  668.   },
  669.   onSubmit: function() {
  670.     // onLoading resets these so we need to save them away for the Ajax call
  671.     var form = this.form;
  672.     var value = this.editField.value;
  673.     
  674.     // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
  675.     // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
  676.     // to be displayed indefinitely
  677.     this.onLoading();
  678.     
  679.     if (this.options.evalScripts) {
  680.       new Ajax.Request(
  681.         this.url, Object.extend({
  682.           parameters: this.options.callback(form, value),
  683.           onComplete: this.onComplete.bind(this),
  684.           onFailure: this.onFailure.bind(this),
  685.           asynchronous:true, 
  686.           evalScripts:true
  687.         }, this.options.ajaxOptions));
  688.     } else  {
  689.       new Ajax.Updater(
  690.         { success: this.element,
  691.           // don't update on failure (this could be an option)
  692.           failure: null }, 
  693.         this.url, Object.extend({
  694.           parameters: this.options.callback(form, value),
  695.           onComplete: this.onComplete.bind(this),
  696.           onFailure: this.onFailure.bind(this)
  697.         }, this.options.ajaxOptions));
  698.     }
  699.     // stop the event to avoid a page refresh in Safari
  700.     if (arguments.length > 1) {
  701.       Event.stop(arguments[0]);
  702.     }
  703.     return false;
  704.   },
  705.   onLoading: function() {
  706.     this.saving = true;
  707.     this.removeForm();
  708.     this.leaveHover();
  709.     this.showSaving();
  710.   },
  711.   showSaving: function() {
  712.     this.oldInnerHTML = this.element.innerHTML;
  713.     this.element.innerHTML = this.options.savingText;
  714.     Element.addClassName(this.element, this.options.savingClassName);
  715.     this.element.style.backgroundColor = this.originalBackground;
  716.     Element.show(this.element);
  717.   },
  718.   removeForm: function() {
  719.     if(this.form) {
  720.       if (this.form.parentNode) Element.remove(this.form);
  721.       this.form = null;
  722.     }
  723.   },
  724.   enterHover: function() {
  725.     if (this.saving) return;
  726.     this.element.style.backgroundColor = this.options.highlightcolor;
  727.     if (this.effect) {
  728.       this.effect.cancel();
  729.     }
  730.     Element.addClassName(this.element, this.options.hoverClassName)
  731.   },
  732.   leaveHover: function() {
  733.     if (this.options.backgroundColor) {
  734.       this.element.style.backgroundColor = this.oldBackground;
  735.     }
  736.     Element.removeClassName(this.element, this.options.hoverClassName)
  737.     if (this.saving) return;
  738.     this.effect = new Effect.Highlight(this.element, {
  739.       startcolor: this.options.highlightcolor,
  740.       endcolor: this.options.highlightendcolor,
  741.       restorecolor: this.originalBackground
  742.     });
  743.   },
  744.   leaveEditMode: function() {
  745.     Element.removeClassName(this.element, this.options.savingClassName);
  746.     this.removeForm();
  747.     this.leaveHover();
  748.     this.element.style.backgroundColor = this.originalBackground;
  749.     Element.show(this.element);
  750.     if (this.options.externalControl) {
  751.       Element.show(this.options.externalControl);
  752.     }
  753.     this.editing = false;
  754.     this.saving = false;
  755.     this.oldInnerHTML = null;
  756.     this.onLeaveEditMode();
  757.   },
  758.   onComplete: function(transport) {
  759.     this.leaveEditMode();
  760.     this.options.onComplete.bind(this)(transport, this.element);
  761.   },
  762.   onEnterEditMode: function() {},
  763.   onLeaveEditMode: function() {},
  764.   dispose: function() {
  765.     if (this.oldInnerHTML) {
  766.       this.element.innerHTML = this.oldInnerHTML;
  767.     }
  768.     this.leaveEditMode();
  769.     Event.stopObserving(this.element, 'click', this.onclickListener);
  770.     Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
  771.     Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
  772.     if (this.options.externalControl) {
  773.       Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
  774.       Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
  775.       Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
  776.     }
  777.   }
  778. };
  779.  
  780. Ajax.InPlaceCollectionEditor = Class.create();
  781. Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
  782. Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  783.   createEditField: function() {
  784.     if (!this.cached_selectTag) {
  785.       var selectTag = document.createElement("select");
  786.       var collection = this.options.collection || [];
  787.       var optionTag;
  788.       collection.each(function(e,i) {
  789.         optionTag = document.createElement("option");
  790.         optionTag.value = (e instanceof Array) ? e[0] : e;
  791.         if((typeof this.options.value == 'undefined') && 
  792.           ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
  793.         if(this.options.value==optionTag.value) optionTag.selected = true;
  794.         optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
  795.         selectTag.appendChild(optionTag);
  796.       }.bind(this));
  797.       this.cached_selectTag = selectTag;
  798.     }
  799.  
  800.     this.editField = this.cached_selectTag;
  801.     if(this.options.loadTextURL) this.loadExternalText();
  802.     this.form.appendChild(this.editField);
  803.     this.options.callback = function(form, value) {
  804.       return "value=" + encodeURIComponent(value);
  805.     }
  806.   }
  807. });
  808.  
  809. // Delayed observer, like Form.Element.Observer, 
  810. // but waits for delay after last key input
  811. // Ideal for live-search fields
  812.  
  813. Form.Element.DelayedObserver = Class.create();
  814. Form.Element.DelayedObserver.prototype = {
  815.   initialize: function(element, delay, callback) {
  816.     this.delay     = delay || 0.5;
  817.     this.element   = $(element);
  818.     this.callback  = callback;
  819.     this.timer     = null;
  820.     this.lastValue = $F(this.element); 
  821.     Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  822.   },
  823.   delayedListener: function(event) {
  824.     if(this.lastValue == $F(this.element)) return;
  825.     if(this.timer) clearTimeout(this.timer);
  826.     this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
  827.     this.lastValue = $F(this.element);
  828.   },
  829.   onTimerEvent: function() {
  830.     this.timer = null;
  831.     this.callback(this.element, $F(this.element));
  832.   }
  833. };
  834.