/[webpac2]/Webpacus/root/js/controls.js
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /Webpacus/root/js/controls.js

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 202 by dpavlin, Tue Nov 22 19:34:27 2005 UTC revision 203 by dpavlin, Fri Dec 2 23:01:25 2005 UTC
# Line 1  Line 1 
1  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2  //           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)  //           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3  //  //           (c) 2005 Jon Tirsen (http://www.tirsen.com)
4  // Permission is hereby granted, free of charge, to any person obtaining  // Contributors:
5  // a copy of this software and associated documentation files (the  //  Richard Livsey
6  // "Software"), to deal in the Software without restriction, including  //  Rahul Bhargava
7  // without limitation the rights to use, copy, modify, merge, publish,  //  Rob Wills
8  // distribute, sublicense, and/or sell copies of the Software, and to  //
9  // permit persons to whom the Software is furnished to do so, subject to  // See scriptaculous.js for full license.
 // the following conditions:  
 //  
 // The above copyright notice and this permission notice shall be  
 // included in all copies or substantial portions of the Software.  
 //  
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,  
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF  
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  
 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE  
 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION  
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION  
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
   
 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {  
   var children = $(element).childNodes;  
   var text     = "";  
   var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");  
     
   for (var i = 0; i < children.length; i++) {  
     if(children[i].nodeType==3) {  
       text+=children[i].nodeValue;  
     } else {  
       if((!children[i].className.match(classtest)) && children[i].hasChildNodes())  
         text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);  
     }  
   }  
     
   return text;  
 }  
10    
11  // Autocompleter.Base handles all the autocompletion functionality  // Autocompleter.Base handles all the autocompletion functionality
12  // that's independent of the data source for autocompletion. This  // that's independent of the data source for autocompletion. This
13  // includes drawing the autocompletion menu, observing keyboard  // includes drawing the autocompletion menu, observing keyboard
14  // and mouse events, and similar.  // and mouse events, and similar.
15  //  //
16  // Specific autocompleters need to provide, at the very least,  // Specific autocompleters need to provide, at the very least,
17  // a getUpdatedChoices function that will be invoked every time  // a getUpdatedChoices function that will be invoked every time
18  // the text inside the monitored textbox changes. This method  // the text inside the monitored textbox changes. This method
19  // should get the text for which to provide autocompletion by  // should get the text for which to provide autocompletion by
20  // invoking this.getEntry(), NOT by directly accessing  // invoking this.getToken(), NOT by directly accessing
21  // this.element.value. This is to allow incremental tokenized  // this.element.value. This is to allow incremental tokenized
22  // autocompletion. Specific auto-completion logic (AJAX, etc)  // autocompletion. Specific auto-completion logic (AJAX, etc)
23  // belongs in getUpdatedChoices.  // belongs in getUpdatedChoices.
# Line 57  Element.collectTextNodesIgnoreClass = fu Line 28  Element.collectTextNodesIgnoreClass = fu
28  // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });  // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29  // will incrementally autocomplete with a comma as the token.  // will incrementally autocomplete with a comma as the token.
30  // Additionally, ',' in the above example can be replaced with  // Additionally, ',' in the above example can be replaced with
31  // a token array, e.g. { tokens: new Array (',', '\n') } which  // a token array, e.g. { tokens: [',', '\n'] } which
32  // enables autocompletion on multiple tokens. This is most  // enables autocompletion on multiple tokens. This is most
33  // useful when one of the tokens is \n (a newline), as it  // useful when one of the tokens is \n (a newline), as it
34  // allows smart autocompletion after linebreaks.  // allows smart autocompletion after linebreaks.
35    
36  var Autocompleter = {}  var Autocompleter = {}
37  Autocompleter.Base = function() {};  Autocompleter.Base = function() {};
38  Autocompleter.Base.prototype = {  Autocompleter.Base.prototype = {
39    base_initialize: function(element, update, options) {    baseInitialize: function(element, update, options) {
40      this.element     = $(element);      this.element     = $(element);
41      this.update      = $(update);        this.update      = $(update);
42      this.has_focus   = false;      this.hasFocus    = false;
43      this.changed     = false;      this.changed     = false;
44      this.active      = false;      this.active      = false;
45      this.index       = 0;          this.index       = 0;
46      this.entry_count = 0;      this.entryCount  = 0;
     this.in_dropdown = false;  
47    
48      if (this.setOptions)      if (this.setOptions)
49        this.setOptions(options);        this.setOptions(options);
50      else      else
51        this.options = {}        this.options = options || {};
52        
53      this.options.tokens       = this.options.tokens || new Array();      this.options.paramName    = this.options.paramName || this.element.name;
54        this.options.tokens       = this.options.tokens || [];
55      this.options.frequency    = this.options.frequency || 0.4;      this.options.frequency    = this.options.frequency || 0.4;
56      this.options.min_chars    = this.options.min_chars || 1;      this.options.minChars     = this.options.minChars || 1;
57      this.options.onShow       = this.options.onShow ||      this.options.onShow       = this.options.onShow ||
58      function(element, update){      function(element, update){
59        if(!update.style.position || update.style.position=='absolute') {        if(!update.style.position || update.style.position=='absolute') {
60          update.style.position = 'absolute';          update.style.position = 'absolute';
61            var offsets = Position.cumulativeOffset(element);          Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
           update.style.left = offsets[0] + 'px';  
           update.style.top  = (offsets[1] + element.offsetHeight) + 'px';  
           update.style.width = element.offsetWidth + 'px';  
62        }        }
63        new Effect.Appear(update,{duration:0.15});        Effect.Appear(update,{duration:0.15});
64      };      };
65      this.options.onHide = this.options.onHide ||      this.options.onHide = this.options.onHide ||
66      function(element, update){ new Effect.Fade(update,{duration:0.15}) };      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
       
     if(this.options.indicator)  
       this.indicator = $(this.options.indicator);  
67    
68      if (typeof(this.options.tokens) == 'string')      if (typeof(this.options.tokens) == 'string')
69        this.options.tokens = new Array(this.options.tokens);        this.options.tokens = new Array(this.options.tokens);
70          
71      this.observer = null;      this.observer = null;
72        
73        this.element.setAttribute('autocomplete','off');
74    
75      Element.hide(this.update);      Element.hide(this.update);
76        
77      Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));      Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78      Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));      Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79    },    },
80    
81    show: function() {    show: function() {
82      if(this.update.style.display=='none') this.options.onShow(this.element, this.update);      if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83      if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {      if(!this.iefix &&
84        new Insertion.After(this.update,        (navigator.appVersion.indexOf('MSIE')>0) &&
85          (navigator.userAgent.indexOf('Opera')<0) &&
86          (Element.getStyle(this.update, 'position')=='absolute')) {
87          new Insertion.After(this.update,
88         '<iframe id="' + this.update.id + '_iefix" '+         '<iframe id="' + this.update.id + '_iefix" '+
89         'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +         'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90         'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');         'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91        this.iefix = $(this.update.id+'_iefix');        this.iefix = $(this.update.id+'_iefix');
92      }      }
93      if(this.iefix) {      if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94        Position.clone(this.update, this.iefix);    },
95        this.iefix.style.zIndex = 1;  
96        this.update.style.zIndex = 2;    fixIEOverlapping: function() {
97        Element.show(this.iefix);      Position.clone(this.update, this.iefix);
98      }      this.iefix.style.zIndex = 1;
99        this.update.style.zIndex = 2;
100        Element.show(this.iefix);
101    },    },
102      
103    hide: function() {    hide: function() {
104      if(this.update.style.display=='') this.options.onHide(this.element, this.update);      this.stopIndicator();
105        if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106      if(this.iefix) Element.hide(this.iefix);      if(this.iefix) Element.hide(this.iefix);
107    },    },
108      
109    startIndicator: function() {    startIndicator: function() {
110      if(this.indicator) Element.show(this.indicator);      if(this.options.indicator) Element.show(this.options.indicator);
111    },    },
112      
113    stopIndicator: function() {    stopIndicator: function() {
114      if(this.indicator) Element.hide(this.indicator);      if(this.options.indicator) Element.hide(this.options.indicator);
115    },    },
116    
117    onKeyPress: function(event) {    onKeyPress: function(event) {
118      if(this.active)      if(this.active)
119        switch(event.keyCode) {        switch(event.keyCode) {
120         case Event.KEY_TAB:         case Event.KEY_TAB:
121         case Event.KEY_RETURN:           this.selectEntry();
          if (this.in_dropdown) this.select_entry();  
122           Event.stop(event);           Event.stop(event);
123           case Event.KEY_RETURN:
124             if (this.ignoreReturn) {
125               this.hide();
126               this.active = false;
127               return;
128             } else {
129               this.selectEntry();
130               Event.stop(event);
131             }
132         case Event.KEY_ESC:         case Event.KEY_ESC:
133           this.hide();           this.hide();
134           this.active = false;           this.active = false;
135             Event.stop(event);
136           return;           return;
137         case Event.KEY_LEFT:         case Event.KEY_LEFT:
138         case Event.KEY_RIGHT:         case Event.KEY_RIGHT:
139           return;           return;
140         case Event.KEY_UP:         case Event.KEY_UP:
141           this.mark_previous();           this.markPrevious();
142           this.render();           this.render();
143           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
144           return;           return;
145         case Event.KEY_DOWN:         case Event.KEY_DOWN:
146           this.mark_next();           this.markNext();
147           this.render();           this.render();
148           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
149           return;           return;
        default:  
          this.in_dropdown = false;  
150        }        }
151       else       else
152        if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)        if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
153          return;          return;
154        
155      this.changed = true;      this.changed = true;
156      this.has_focus = true;      this.hasFocus = true;
157        
158      if(this.observer) clearTimeout(this.observer);      if(this.observer) clearTimeout(this.observer);
159        this.observer =        this.observer =
160          setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);          setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
161    },    },
162      
163    onHover: function(event) {    onHover: function(event) {
164      var element = Event.findElement(event, 'LI');      var element = Event.findElement(event, 'LI');
165      if(this.index != element.autocompleteIndex)      if(this.index != element.autocompleteIndex)
166      {      {
167          this.index = element.autocompleteIndex;          this.index = element.autocompleteIndex;
168          this.render();          this.render();
169      }      }
170      Event.stop(event);      Event.stop(event);
171    },    },
172      
173    onClick: function(event) {    onClick: function(event) {
174      var element = Event.findElement(event, 'LI');      var element = Event.findElement(event, 'LI');
175      this.index = element.autocompleteIndex;      this.index = element.autocompleteIndex;
176      this.select_entry();      this.selectEntry();
177      Event.stop(event);      this.hide();
178    },    },
179      
180    onBlur: function(event) {    onBlur: function(event) {
181      // needed to make click events working      // needed to make click events working
182      setTimeout(this.hide.bind(this), 250);      setTimeout(this.hide.bind(this), 250);
183      this.has_focus = false;      this.hasFocus = false;
184      this.active = false;          this.active = false;
185    },    },
186      
187    render: function() {    render: function() {
188      if(this.entry_count > 0) {      if(this.entryCount > 0) {
189        for (var i = 0; i < this.entry_count; i++)        for (var i = 0; i < this.entryCount; i++)
190          this.index==i && this.in_dropdown ?          this.index==i ?
191            Element.addClassName(this.get_entry(i),"selected") :            Element.addClassName(this.getEntry(i),"selected") :
192            Element.removeClassName(this.get_entry(i),"selected");            Element.removeClassName(this.getEntry(i),"selected");
193            
194        if(this.has_focus) {        if(this.hasFocus) {
         if(this.get_current_entry().scrollIntoView)  
           this.get_current_entry().scrollIntoView(false);  
           
195          this.show();          this.show();
196          this.active = true;          this.active = true;
197        }        }
198      } else this.hide();      } else {
199          this.active = false;
200          this.hide();
201        }
202    },    },
203      
204    mark_previous: function() {    markPrevious: function() {
205      if(this.index > 0) this.index--      if(this.index > 0) this.index--
206        else this.index = this.entry_count-1;        else this.index = this.entryCount-1;
     this.in_dropdown = true;  
207    },    },
208      
209    mark_next: function() {    markNext: function() {
210      if(this.index < this.entry_count-1) this.index++      if(this.index < this.entryCount-1) this.index++
211        else this.index = 0;        else this.index = 0;
     if (! this.in_dropdown) this.index = 0;  
     this.in_dropdown = true;  
212    },    },
213      
214    get_entry: function(index) {    getEntry: function(index) {
215      return this.update.firstChild.childNodes[index];      return this.update.firstChild.childNodes[index];
216    },    },
217      
218    get_current_entry: function() {    getCurrentEntry: function() {
219      return this.get_entry(this.index);      return this.getEntry(this.index);
220    },    },
221      
222    select_entry: function() {    selectEntry: function() {
223      this.active = false;      this.active = false;
224      value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();      this.updateElement(this.getCurrentEntry());
     this.updateElement(value);  
     this.element.focus();  
225    },    },
226    
227    updateElement: function(value) {    updateElement: function(selectedElement) {
228      var last_token_pos = this.findLastToken();      if (this.options.updateElement) {
229      if (last_token_pos != -1) {        this.options.updateElement(selectedElement);
230        var new_value = this.element.value.substr(0, last_token_pos + 1);        return;
231        var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);      }
232    
233        var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
234        var lastTokenPos = this.findLastToken();
235        if (lastTokenPos != -1) {
236          var newValue = this.element.value.substr(0, lastTokenPos + 1);
237          var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
238        if (whitespace)        if (whitespace)
239          new_value += whitespace[0];          newValue += whitespace[0];
240        this.element.value = new_value + value;        this.element.value = newValue + value;
241      } else {      } else {
242        this.element.value = value;        this.element.value = value;
243      }      }
244        this.element.focus();
245    
246        if (this.options.afterUpdateElement)
247          this.options.afterUpdateElement(this.element, selectedElement);
248    },    },
249      
250    updateChoices: function(choices) {    updateChoices: function(choices) {
251      if(!this.changed && this.has_focus) {      if(!this.changed && this.hasFocus) {
252        this.update.innerHTML = choices;        this.update.innerHTML = choices;
253        Element.cleanWhitespace(this.update);        Element.cleanWhitespace(this.update);
254        Element.cleanWhitespace(this.update.firstChild);        Element.cleanWhitespace(this.update.firstChild);
255    
256        if(this.update.firstChild && this.update.firstChild.childNodes) {        if(this.update.firstChild && this.update.firstChild.childNodes) {
257          this.entry_count =          this.entryCount =
258            this.update.firstChild.childNodes.length;            this.update.firstChild.childNodes.length;
259          for (var i = 0; i < this.entry_count; i++) {          for (var i = 0; i < this.entryCount; i++) {
260            entry = this.get_entry(i);            var entry = this.getEntry(i);
261            entry.autocompleteIndex = i;            entry.autocompleteIndex = i;
262            this.addObservers(entry);            this.addObservers(entry);
263          }          }
264        } else {        } else {
265          this.entry_count = 0;          this.entryCount = 0;
266        }        }
267          
268        this.stopIndicator();        this.stopIndicator();
269          
270        this.index = 0;        this.index = 0;
271        this.render();        this.render();
272      }      }
# Line 293  Autocompleter.Base.prototype = { Line 278  Autocompleter.Base.prototype = {
278    },    },
279    
280    onObserverEvent: function() {    onObserverEvent: function() {
281      this.changed = false;        this.changed = false;
282      if(this.getEntry().length>=this.options.min_chars) {      if(this.getToken().length>=this.options.minChars) {
283        this.startIndicator();        this.startIndicator();
284        this.getUpdatedChoices();        this.getUpdatedChoices();
285      } else {      } else {
# Line 303  Autocompleter.Base.prototype = { Line 288  Autocompleter.Base.prototype = {
288      }      }
289    },    },
290    
291    getEntry: function() {    getToken: function() {
292      var token_pos = this.findLastToken();      var tokenPos = this.findLastToken();
293      if (token_pos != -1)      if (tokenPos != -1)
294        var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');        var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
295      else      else
296        var ret = this.element.value;        var ret = this.element.value;
297        
298      return /\n/.test(ret) ? '' : ret;      return /\n/.test(ret) ? '' : ret;
299    },    },
300    
301    findLastToken: function() {    findLastToken: function() {
302      var last_token_pos = -1;      var lastTokenPos = -1;
303    
304      for (var i=0; i<this.options.tokens.length; i++) {      for (var i=0; i<this.options.tokens.length; i++) {
305        var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);        var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
306        if (this_token_pos > last_token_pos)        if (thisTokenPos > lastTokenPos)
307          last_token_pos = this_token_pos;          lastTokenPos = thisTokenPos;
308      }      }
309      return last_token_pos;      return lastTokenPos;
310    }    }
311  }  }
312    
313  Ajax.Autocompleter = Class.create();  Ajax.Autocompleter = Class.create();
314  Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),  Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
 Object.extend(new Ajax.Base(), {  
315    initialize: function(element, update, url, options) {    initialize: function(element, update, url, options) {
316            this.base_initialize(element, update, options);            this.baseInitialize(element, update, options);
317      this.options.asynchronous  = true;      this.options.asynchronous  = true;
318      this.options.onComplete    = this.onComplete.bind(this)      this.options.onComplete    = this.onComplete.bind(this);
     this.options.method        = 'post';  
319      this.options.defaultParams = this.options.parameters || null;      this.options.defaultParams = this.options.parameters || null;
320      this.url                   = url;      this.url                   = url;
321    },    },
322      
323    getUpdatedChoices: function() {    getUpdatedChoices: function() {
324      entry = encodeURIComponent(this.element.name) + '=' +      entry = encodeURIComponent(this.options.paramName) + '=' +
325        encodeURIComponent(this.getEntry());        encodeURIComponent(this.getToken());
326          
327      this.options.parameters = this.options.callback ?      this.options.parameters = this.options.callback ?
328        this.options.callback(this.element, entry) : entry;        this.options.callback(this.element, entry) : entry;
329            
330      if(this.options.defaultParams)      if(this.options.defaultParams)
331        this.options.parameters += '&' + this.options.defaultParams;        this.options.parameters += '&' + this.options.defaultParams;
332        
333      new Ajax.Request(this.url, this.options);      new Ajax.Request(this.url, this.options);
334    },    },
335      
336    onComplete: function(request) {    onComplete: function(request) {
337      this.updateChoices(request.responseText);      this.updateChoices(request.responseText);
338    }    }
339    
340  }));  });
341    
342  // The local array autocompleter. Used when you'd prefer to  // The local array autocompleter. Used when you'd prefer to
343  // inject an array of autocompletion options into the page, rather  // inject an array of autocompletion options into the page, rather
# Line 368  Object.extend(new Ajax.Base(), { Line 351  Object.extend(new Ajax.Base(), {
351  // Extra local autocompletion options:  // Extra local autocompletion options:
352  // - choices - How many autocompletion choices to offer  // - choices - How many autocompletion choices to offer
353  //  //
354  // - partial_search - If false, the autocompleter will match entered  // - partialSearch - If false, the autocompleter will match entered
355  //                    text only at the beginning of strings in the  //                    text only at the beginning of strings in the
356  //                    autocomplete array. Defaults to true, which will  //                    autocomplete array. Defaults to true, which will
357  //                    match text at the beginning of any *word* in the  //                    match text at the beginning of any *word* in the
358  //                    strings in the autocomplete array. If you want to  //                    strings in the autocomplete array. If you want to
359  //                    search anywhere in the string, additionally set  //                    search anywhere in the string, additionally set
360  //                    the option full_search to true (default: off).  //                    the option fullSearch to true (default: off).
361  //  //
362  // - full_search - Search anywhere in autocomplete array strings.  // - fullSsearch - Search anywhere in autocomplete array strings.
363  //  //
364  // - partial_chars - How many characters to enter before triggering  // - partialChars - How many characters to enter before triggering
365  //                   a partial match (unlike min_chars, which defines  //                   a partial match (unlike minChars, which defines
366  //                   how many characters are required to do any match  //                   how many characters are required to do any match
367  //                   at all). Defaults to 2.  //                   at all). Defaults to 2.
368  //  //
369  // - ignore_case - Whether to ignore case when autocompleting.  // - ignoreCase - Whether to ignore case when autocompleting.
370  //                 Defaults to true.  //                 Defaults to true.
371  //  //
372  // It's possible to pass in a custom function as the 'selector'  // It's possible to pass in a custom function as the 'selector'
373  // option, if you prefer to write your own autocompletion logic.  // option, if you prefer to write your own autocompletion logic.
374  // In that case, the other options above will not apply unless  // In that case, the other options above will not apply unless
375  // you support them.  // you support them.
# Line 394  Object.extend(new Ajax.Base(), { Line 377  Object.extend(new Ajax.Base(), {
377  Autocompleter.Local = Class.create();  Autocompleter.Local = Class.create();
378  Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {  Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
379    initialize: function(element, update, array, options) {    initialize: function(element, update, array, options) {
380      this.base_initialize(element, update, options);      this.baseInitialize(element, update, options);
381      this.options.array = array;      this.options.array = array;
382    },    },
383    
# Line 405  Autocompleter.Local.prototype = Object.e Line 388  Autocompleter.Local.prototype = Object.e
388    setOptions: function(options) {    setOptions: function(options) {
389      this.options = Object.extend({      this.options = Object.extend({
390        choices: 10,        choices: 10,
391        partial_search: true,        partialSearch: true,
392        partial_chars: 2,        partialChars: 2,
393        ignore_case: true,        ignoreCase: true,
394        full_search: false,        fullSearch: false,
395        selector: function(instance) {        selector: function(instance) {
396          var ret       = new Array(); // Beginning matches          var ret       = []; // Beginning matches
397          var partial   = new Array(); // Inside matches          var partial   = []; // Inside matches
398          var entry     = instance.getEntry();          var entry     = instance.getToken();
399          var count     = 0;          var count     = 0;
400            
401          for (var i = 0; i < instance.options.array.length &&            for (var i = 0; i < instance.options.array.length &&
402              ret.length < instance.options.choices ; i++) {            ret.length < instance.options.choices ; i++) {
403    
404            var elem = instance.options.array[i];            var elem = instance.options.array[i];
405            var found_pos = instance.options.ignore_case ?            var foundPos = instance.options.ignoreCase ?
406              elem.toLowerCase().indexOf(entry.toLowerCase()) :              elem.toLowerCase().indexOf(entry.toLowerCase()) :
407              elem.indexOf(entry);              elem.indexOf(entry);
408    
409            while (found_pos != -1) {            while (foundPos != -1) {
410              if (found_pos == 0 && elem.length != entry.length) {              if (foundPos == 0 && elem.length != entry.length) {
411                ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +                ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
412                  elem.substr(entry.length) + "</li>");                  elem.substr(entry.length) + "</li>");
413                break;                break;
414              } else if (entry.length >= instance.options.partial_chars &&              } else if (entry.length >= instance.options.partialChars &&
415                instance.options.partial_search && found_pos != -1) {                instance.options.partialSearch && foundPos != -1) {
416                if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {                if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
417                  partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +                  partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
418                    elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(                    elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
419                    found_pos + entry.length) + "</li>");                    foundPos + entry.length) + "</li>");
420                  break;                  break;
421                }                }
422              }              }
423    
424              found_pos = instance.options.ignore_case ?              foundPos = instance.options.ignoreCase ?
425                elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :                elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
426                elem.indexOf(entry, found_pos + 1);                elem.indexOf(entry, foundPos + 1);
427    
428            }            }
429          }          }
# Line 450  Autocompleter.Local.prototype = Object.e Line 434  Autocompleter.Local.prototype = Object.e
434      }, options || {});      }, options || {});
435    }    }
436  });  });
437    
438    // AJAX in-place editor
439    //
440    // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
441    
442    // Use this if you notice weird scrolling problems on some browsers,
443    // the DOM might be a bit confused when this gets called so do this
444    // waits 1 ms (with setTimeout) until it does the activation
445    Field.scrollFreeActivate = function(field) {
446      setTimeout(function() {
447        Field.activate(field);
448      }, 1);
449    }
450    
451    Ajax.InPlaceEditor = Class.create();
452    Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
453    Ajax.InPlaceEditor.prototype = {
454      initialize: function(element, url, options) {
455        this.url = url;
456        this.element = $(element);
457    
458        this.options = Object.extend({
459          okText: "ok",
460          cancelText: "cancel",
461          savingText: "Saving...",
462          clickToEditText: "Click to edit",
463          okText: "ok",
464          rows: 1,
465          onComplete: function(transport, element) {
466            new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
467          },
468          onFailure: function(transport) {
469            alert("Error communicating with the server: " + transport.responseText.stripTags());
470          },
471          callback: function(form) {
472            return Form.serialize(form);
473          },
474          handleLineBreaks: true,
475          loadingText: 'Loading...',
476          savingClassName: 'inplaceeditor-saving',
477          loadingClassName: 'inplaceeditor-loading',
478          formClassName: 'inplaceeditor-form',
479          highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
480          highlightendcolor: "#FFFFFF",
481          externalControl:  null,
482          ajaxOptions: {}
483        }, options || {});
484    
485        if(!this.options.formId && this.element.id) {
486          this.options.formId = this.element.id + "-inplaceeditor";
487          if ($(this.options.formId)) {
488            // there's already a form with that name, don't specify an id
489            this.options.formId = null;
490          }
491        }
492    
493        if (this.options.externalControl) {
494          this.options.externalControl = $(this.options.externalControl);
495        }
496    
497        this.originalBackground = Element.getStyle(this.element, 'background-color');
498        if (!this.originalBackground) {
499          this.originalBackground = "transparent";
500        }
501    
502        this.element.title = this.options.clickToEditText;
503    
504        this.onclickListener = this.enterEditMode.bindAsEventListener(this);
505        this.mouseoverListener = this.enterHover.bindAsEventListener(this);
506        this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
507        Event.observe(this.element, 'click', this.onclickListener);
508        Event.observe(this.element, 'mouseover', this.mouseoverListener);
509        Event.observe(this.element, 'mouseout', this.mouseoutListener);
510        if (this.options.externalControl) {
511          Event.observe(this.options.externalControl, 'click', this.onclickListener);
512          Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
513          Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
514        }
515      },
516      enterEditMode: function(evt) {
517        if (this.saving) return;
518        if (this.editing) return;
519        this.editing = true;
520        this.onEnterEditMode();
521        if (this.options.externalControl) {
522          Element.hide(this.options.externalControl);
523        }
524        Element.hide(this.element);
525        this.createForm();
526        this.element.parentNode.insertBefore(this.form, this.element);
527        Field.scrollFreeActivate(this.editField);
528        // stop the event to avoid a page refresh in Safari
529        if (evt) {
530          Event.stop(evt);
531        }
532        return false;
533      },
534      createForm: function() {
535        this.form = document.createElement("form");
536        this.form.id = this.options.formId;
537        Element.addClassName(this.form, this.options.formClassName)
538        this.form.onsubmit = this.onSubmit.bind(this);
539    
540        this.createEditField();
541    
542        if (this.options.textarea) {
543          var br = document.createElement("br");
544          this.form.appendChild(br);
545        }
546    
547        okButton = document.createElement("input");
548        okButton.type = "submit";
549        okButton.value = this.options.okText;
550        this.form.appendChild(okButton);
551    
552        cancelLink = document.createElement("a");
553        cancelLink.href = "#";
554        cancelLink.appendChild(document.createTextNode(this.options.cancelText));
555        cancelLink.onclick = this.onclickCancel.bind(this);
556        this.form.appendChild(cancelLink);
557      },
558      hasHTMLLineBreaks: function(string) {
559        if (!this.options.handleLineBreaks) return false;
560        return string.match(/<br/i) || string.match(/<p>/i);
561      },
562      convertHTMLLineBreaks: function(string) {
563        return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
564      },
565      createEditField: function() {
566        var text;
567        if(this.options.loadTextURL) {
568          text = this.options.loadingText;
569        } else {
570          text = this.getText();
571        }
572    
573        if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
574          this.options.textarea = false;
575          var textField = document.createElement("input");
576          textField.type = "text";
577          textField.name = "value";
578          textField.value = text;
579          textField.style.backgroundColor = this.options.highlightcolor;
580          var size = this.options.size || this.options.cols || 0;
581          if (size != 0) textField.size = size;
582          this.editField = textField;
583        } else {
584          this.options.textarea = true;
585          var textArea = document.createElement("textarea");
586          textArea.name = "value";
587          textArea.value = this.convertHTMLLineBreaks(text);
588          textArea.rows = this.options.rows;
589          textArea.cols = this.options.cols || 40;
590          this.editField = textArea;
591        }
592    
593        if(this.options.loadTextURL) {
594          this.loadExternalText();
595        }
596        this.form.appendChild(this.editField);
597      },
598      getText: function() {
599        return this.element.innerHTML;
600      },
601      loadExternalText: function() {
602        Element.addClassName(this.form, this.options.loadingClassName);
603        this.editField.disabled = true;
604        new Ajax.Request(
605          this.options.loadTextURL,
606          Object.extend({
607            asynchronous: true,
608            onComplete: this.onLoadedExternalText.bind(this)
609          }, this.options.ajaxOptions)
610        );
611      },
612      onLoadedExternalText: function(transport) {
613        Element.removeClassName(this.form, this.options.loadingClassName);
614        this.editField.disabled = false;
615        this.editField.value = transport.responseText.stripTags();
616      },
617      onclickCancel: function() {
618        this.onComplete();
619        this.leaveEditMode();
620        return false;
621      },
622      onFailure: function(transport) {
623        this.options.onFailure(transport);
624        if (this.oldInnerHTML) {
625          this.element.innerHTML = this.oldInnerHTML;
626          this.oldInnerHTML = null;
627        }
628        return false;
629      },
630      onSubmit: function() {
631        // onLoading resets these so we need to save them away for the Ajax call
632        var form = this.form;
633        var value = this.editField.value;
634    
635        // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
636        // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
637        // to be displayed indefinitely
638        this.onLoading();
639    
640        new Ajax.Updater(
641          {
642            success: this.element,
643             // don't update on failure (this could be an option)
644            failure: null
645          },
646          this.url,
647          Object.extend({
648            parameters: this.options.callback(form, value),
649            onComplete: this.onComplete.bind(this),
650            onFailure: this.onFailure.bind(this)
651          }, this.options.ajaxOptions)
652        );
653        // stop the event to avoid a page refresh in Safari
654        if (arguments.length > 1) {
655          Event.stop(arguments[0]);
656        }
657        return false;
658      },
659      onLoading: function() {
660        this.saving = true;
661        this.removeForm();
662        this.leaveHover();
663        this.showSaving();
664      },
665      showSaving: function() {
666        this.oldInnerHTML = this.element.innerHTML;
667        this.element.innerHTML = this.options.savingText;
668        Element.addClassName(this.element, this.options.savingClassName);
669        this.element.style.backgroundColor = this.originalBackground;
670        Element.show(this.element);
671      },
672      removeForm: function() {
673        if(this.form) {
674          if (this.form.parentNode) Element.remove(this.form);
675          this.form = null;
676        }
677      },
678      enterHover: function() {
679        if (this.saving) return;
680        this.element.style.backgroundColor = this.options.highlightcolor;
681        if (this.effect) {
682          this.effect.cancel();
683        }
684        Element.addClassName(this.element, this.options.hoverClassName)
685      },
686      leaveHover: function() {
687        if (this.options.backgroundColor) {
688          this.element.style.backgroundColor = this.oldBackground;
689        }
690        Element.removeClassName(this.element, this.options.hoverClassName)
691        if (this.saving) return;
692        this.effect = new Effect.Highlight(this.element, {
693          startcolor: this.options.highlightcolor,
694          endcolor: this.options.highlightendcolor,
695          restorecolor: this.originalBackground
696        });
697      },
698      leaveEditMode: function() {
699        Element.removeClassName(this.element, this.options.savingClassName);
700        this.removeForm();
701        this.leaveHover();
702        this.element.style.backgroundColor = this.originalBackground;
703        Element.show(this.element);
704        if (this.options.externalControl) {
705          Element.show(this.options.externalControl);
706        }
707        this.editing = false;
708        this.saving = false;
709        this.oldInnerHTML = null;
710        this.onLeaveEditMode();
711      },
712      onComplete: function(transport) {
713        this.leaveEditMode();
714        this.options.onComplete.bind(this)(transport, this.element);
715      },
716      onEnterEditMode: function() {},
717      onLeaveEditMode: function() {},
718      dispose: function() {
719        if (this.oldInnerHTML) {
720          this.element.innerHTML = this.oldInnerHTML;
721        }
722        this.leaveEditMode();
723        Event.stopObserving(this.element, 'click', this.onclickListener);
724        Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
725        Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
726        if (this.options.externalControl) {
727          Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
728          Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
729          Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
730        }
731      }
732    };
733    
734    // Delayed observer, like Form.Element.Observer,
735    // but waits for delay after last key input
736    // Ideal for live-search fields
737    
738    Form.Element.DelayedObserver = Class.create();
739    Form.Element.DelayedObserver.prototype = {
740      initialize: function(element, delay, callback) {
741        this.delay     = delay || 0.5;
742        this.element   = $(element);
743        this.callback  = callback;
744        this.timer     = null;
745        this.lastValue = $F(this.element);
746        Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
747      },
748      delayedListener: function(event) {
749        if(this.lastValue == $F(this.element)) return;
750        if(this.timer) clearTimeout(this.timer);
751        this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
752        this.lastValue = $F(this.element);
753      },
754      onTimerEvent: function() {
755        this.timer = null;
756        this.callback(this.element, $F(this.element));
757      }
758    };

Legend:
Removed from v.202  
changed lines
  Added in v.203

  ViewVC Help
Powered by ViewVC 1.1.26