/[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

Annotation of /Webpacus/root/js/controls.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 105 - (hide annotations)
Tue Nov 22 19:34:27 2005 UTC (18 years, 6 months ago) by dpavlin
File MIME type: text/cpp
File size: 16025 byte(s)
 r9048@llin:  dpavlin | 2005-11-22 19:09:14 +0100
 IMPROVEMENT: added in_dropdown and a little logic, so that it behaves more
 like comobbox: you have to press down to get into it, or start typing to
 get out.

1 dpavlin 83 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2     // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3     //
4     // Permission is hereby granted, free of charge, to any person obtaining
5     // a copy of this software and associated documentation files (the
6     // "Software"), to deal in the Software without restriction, including
7     // without limitation the rights to use, copy, modify, merge, publish,
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
10     // the following conditions:
11     //
12     // The above copyright notice and this permission notice shall be
13     // included in all copies or substantial portions of the Software.
14     //
15     // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16     // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17     // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18     // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19     // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20     // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21     // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22    
23     Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
24     var children = $(element).childNodes;
25     var text = "";
26     var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
27    
28     for (var i = 0; i < children.length; i++) {
29     if(children[i].nodeType==3) {
30     text+=children[i].nodeValue;
31     } else {
32     if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
33     text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
34     }
35     }
36    
37     return text;
38     }
39    
40     // Autocompleter.Base handles all the autocompletion functionality
41     // that's independent of the data source for autocompletion. This
42     // includes drawing the autocompletion menu, observing keyboard
43     // and mouse events, and similar.
44     //
45     // Specific autocompleters need to provide, at the very least,
46     // a getUpdatedChoices function that will be invoked every time
47     // the text inside the monitored textbox changes. This method
48     // should get the text for which to provide autocompletion by
49     // invoking this.getEntry(), NOT by directly accessing
50     // this.element.value. This is to allow incremental tokenized
51     // autocompletion. Specific auto-completion logic (AJAX, etc)
52     // belongs in getUpdatedChoices.
53     //
54     // Tokenized incremental autocompletion is enabled automatically
55     // when an autocompleter is instantiated with the 'tokens' option
56     // in the options parameter, e.g.:
57     // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
58     // will incrementally autocomplete with a comma as the token.
59     // Additionally, ',' in the above example can be replaced with
60     // a token array, e.g. { tokens: new Array (',', '\n') } which
61     // enables autocompletion on multiple tokens. This is most
62     // useful when one of the tokens is \n (a newline), as it
63     // allows smart autocompletion after linebreaks.
64    
65     var Autocompleter = {}
66     Autocompleter.Base = function() {};
67     Autocompleter.Base.prototype = {
68     base_initialize: function(element, update, options) {
69     this.element = $(element);
70     this.update = $(update);
71     this.has_focus = false;
72     this.changed = false;
73     this.active = false;
74     this.index = 0;
75     this.entry_count = 0;
76 dpavlin 105 this.in_dropdown = false;
77 dpavlin 83
78     if (this.setOptions)
79     this.setOptions(options);
80     else
81     this.options = {}
82    
83     this.options.tokens = this.options.tokens || new Array();
84     this.options.frequency = this.options.frequency || 0.4;
85     this.options.min_chars = this.options.min_chars || 1;
86     this.options.onShow = this.options.onShow ||
87     function(element, update){
88     if(!update.style.position || update.style.position=='absolute') {
89     update.style.position = 'absolute';
90     var offsets = Position.cumulativeOffset(element);
91     update.style.left = offsets[0] + 'px';
92     update.style.top = (offsets[1] + element.offsetHeight) + 'px';
93     update.style.width = element.offsetWidth + 'px';
94     }
95     new Effect.Appear(update,{duration:0.15});
96     };
97     this.options.onHide = this.options.onHide ||
98     function(element, update){ new Effect.Fade(update,{duration:0.15}) };
99    
100     if(this.options.indicator)
101     this.indicator = $(this.options.indicator);
102    
103     if (typeof(this.options.tokens) == 'string')
104     this.options.tokens = new Array(this.options.tokens);
105    
106     this.observer = null;
107    
108     Element.hide(this.update);
109    
110     Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
111     Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
112     },
113    
114     show: function() {
115     if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
116     if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
117     new Insertion.After(this.update,
118     '<iframe id="' + this.update.id + '_iefix" '+
119     'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
120     'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
121     this.iefix = $(this.update.id+'_iefix');
122     }
123     if(this.iefix) {
124     Position.clone(this.update, this.iefix);
125     this.iefix.style.zIndex = 1;
126     this.update.style.zIndex = 2;
127     Element.show(this.iefix);
128     }
129     },
130    
131     hide: function() {
132     if(this.update.style.display=='') this.options.onHide(this.element, this.update);
133     if(this.iefix) Element.hide(this.iefix);
134     },
135    
136     startIndicator: function() {
137     if(this.indicator) Element.show(this.indicator);
138     },
139    
140     stopIndicator: function() {
141     if(this.indicator) Element.hide(this.indicator);
142     },
143    
144     onKeyPress: function(event) {
145     if(this.active)
146     switch(event.keyCode) {
147     case Event.KEY_TAB:
148     case Event.KEY_RETURN:
149 dpavlin 105 if (this.in_dropdown) this.select_entry();
150 dpavlin 83 Event.stop(event);
151     case Event.KEY_ESC:
152     this.hide();
153     this.active = false;
154     return;
155     case Event.KEY_LEFT:
156     case Event.KEY_RIGHT:
157     return;
158     case Event.KEY_UP:
159     this.mark_previous();
160     this.render();
161     if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
162     return;
163     case Event.KEY_DOWN:
164     this.mark_next();
165     this.render();
166     if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
167     return;
168 dpavlin 105 default:
169     this.in_dropdown = false;
170 dpavlin 83 }
171     else
172     if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
173     return;
174    
175     this.changed = true;
176     this.has_focus = true;
177    
178     if(this.observer) clearTimeout(this.observer);
179     this.observer =
180     setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
181     },
182    
183     onHover: function(event) {
184     var element = Event.findElement(event, 'LI');
185     if(this.index != element.autocompleteIndex)
186     {
187     this.index = element.autocompleteIndex;
188     this.render();
189     }
190     Event.stop(event);
191     },
192    
193     onClick: function(event) {
194     var element = Event.findElement(event, 'LI');
195     this.index = element.autocompleteIndex;
196     this.select_entry();
197     Event.stop(event);
198     },
199    
200     onBlur: function(event) {
201     // needed to make click events working
202     setTimeout(this.hide.bind(this), 250);
203     this.has_focus = false;
204     this.active = false;
205     },
206    
207     render: function() {
208     if(this.entry_count > 0) {
209     for (var i = 0; i < this.entry_count; i++)
210 dpavlin 105 this.index==i && this.in_dropdown ?
211 dpavlin 83 Element.addClassName(this.get_entry(i),"selected") :
212     Element.removeClassName(this.get_entry(i),"selected");
213    
214     if(this.has_focus) {
215     if(this.get_current_entry().scrollIntoView)
216     this.get_current_entry().scrollIntoView(false);
217    
218     this.show();
219     this.active = true;
220     }
221     } else this.hide();
222     },
223    
224     mark_previous: function() {
225     if(this.index > 0) this.index--
226     else this.index = this.entry_count-1;
227 dpavlin 105 this.in_dropdown = true;
228 dpavlin 83 },
229    
230     mark_next: function() {
231     if(this.index < this.entry_count-1) this.index++
232     else this.index = 0;
233 dpavlin 105 if (! this.in_dropdown) this.index = 0;
234     this.in_dropdown = true;
235 dpavlin 83 },
236    
237     get_entry: function(index) {
238     return this.update.firstChild.childNodes[index];
239     },
240    
241     get_current_entry: function() {
242     return this.get_entry(this.index);
243     },
244    
245     select_entry: function() {
246     this.active = false;
247     value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
248     this.updateElement(value);
249     this.element.focus();
250     },
251    
252     updateElement: function(value) {
253     var last_token_pos = this.findLastToken();
254     if (last_token_pos != -1) {
255     var new_value = this.element.value.substr(0, last_token_pos + 1);
256     var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
257     if (whitespace)
258     new_value += whitespace[0];
259     this.element.value = new_value + value;
260     } else {
261     this.element.value = value;
262     }
263     },
264    
265     updateChoices: function(choices) {
266     if(!this.changed && this.has_focus) {
267     this.update.innerHTML = choices;
268     Element.cleanWhitespace(this.update);
269     Element.cleanWhitespace(this.update.firstChild);
270    
271     if(this.update.firstChild && this.update.firstChild.childNodes) {
272     this.entry_count =
273     this.update.firstChild.childNodes.length;
274     for (var i = 0; i < this.entry_count; i++) {
275     entry = this.get_entry(i);
276     entry.autocompleteIndex = i;
277     this.addObservers(entry);
278     }
279     } else {
280     this.entry_count = 0;
281     }
282    
283     this.stopIndicator();
284    
285     this.index = 0;
286     this.render();
287     }
288     },
289    
290     addObservers: function(element) {
291     Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
292     Event.observe(element, "click", this.onClick.bindAsEventListener(this));
293     },
294    
295     onObserverEvent: function() {
296     this.changed = false;
297     if(this.getEntry().length>=this.options.min_chars) {
298     this.startIndicator();
299     this.getUpdatedChoices();
300     } else {
301     this.active = false;
302     this.hide();
303     }
304     },
305    
306     getEntry: function() {
307     var token_pos = this.findLastToken();
308     if (token_pos != -1)
309     var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
310     else
311     var ret = this.element.value;
312    
313     return /\n/.test(ret) ? '' : ret;
314     },
315    
316     findLastToken: function() {
317     var last_token_pos = -1;
318    
319     for (var i=0; i<this.options.tokens.length; i++) {
320     var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
321     if (this_token_pos > last_token_pos)
322     last_token_pos = this_token_pos;
323     }
324     return last_token_pos;
325     }
326     }
327    
328     Ajax.Autocompleter = Class.create();
329     Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
330     Object.extend(new Ajax.Base(), {
331     initialize: function(element, update, url, options) {
332     this.base_initialize(element, update, options);
333     this.options.asynchronous = true;
334     this.options.onComplete = this.onComplete.bind(this)
335     this.options.method = 'post';
336     this.options.defaultParams = this.options.parameters || null;
337     this.url = url;
338     },
339    
340     getUpdatedChoices: function() {
341     entry = encodeURIComponent(this.element.name) + '=' +
342     encodeURIComponent(this.getEntry());
343    
344     this.options.parameters = this.options.callback ?
345     this.options.callback(this.element, entry) : entry;
346    
347     if(this.options.defaultParams)
348     this.options.parameters += '&' + this.options.defaultParams;
349    
350     new Ajax.Request(this.url, this.options);
351     },
352    
353     onComplete: function(request) {
354     this.updateChoices(request.responseText);
355     }
356    
357     }));
358    
359     // The local array autocompleter. Used when you'd prefer to
360     // inject an array of autocompletion options into the page, rather
361     // than sending out Ajax queries, which can be quite slow sometimes.
362     //
363     // The constructor takes four parameters. The first two are, as usual,
364     // the id of the monitored textbox, and id of the autocompletion menu.
365     // The third is the array you want to autocomplete from, and the fourth
366     // is the options block.
367     //
368     // Extra local autocompletion options:
369     // - choices - How many autocompletion choices to offer
370     //
371     // - partial_search - If false, the autocompleter will match entered
372     // text only at the beginning of strings in the
373     // autocomplete array. Defaults to true, which will
374     // match text at the beginning of any *word* in the
375     // strings in the autocomplete array. If you want to
376     // search anywhere in the string, additionally set
377     // the option full_search to true (default: off).
378     //
379     // - full_search - Search anywhere in autocomplete array strings.
380     //
381     // - partial_chars - How many characters to enter before triggering
382     // a partial match (unlike min_chars, which defines
383     // how many characters are required to do any match
384     // at all). Defaults to 2.
385     //
386     // - ignore_case - Whether to ignore case when autocompleting.
387     // Defaults to true.
388     //
389     // It's possible to pass in a custom function as the 'selector'
390     // option, if you prefer to write your own autocompletion logic.
391     // In that case, the other options above will not apply unless
392     // you support them.
393    
394     Autocompleter.Local = Class.create();
395     Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
396     initialize: function(element, update, array, options) {
397     this.base_initialize(element, update, options);
398     this.options.array = array;
399     },
400    
401     getUpdatedChoices: function() {
402     this.updateChoices(this.options.selector(this));
403     },
404    
405     setOptions: function(options) {
406     this.options = Object.extend({
407     choices: 10,
408     partial_search: true,
409     partial_chars: 2,
410     ignore_case: true,
411     full_search: false,
412     selector: function(instance) {
413     var ret = new Array(); // Beginning matches
414     var partial = new Array(); // Inside matches
415     var entry = instance.getEntry();
416     var count = 0;
417    
418     for (var i = 0; i < instance.options.array.length &&
419     ret.length < instance.options.choices ; i++) {
420     var elem = instance.options.array[i];
421     var found_pos = instance.options.ignore_case ?
422     elem.toLowerCase().indexOf(entry.toLowerCase()) :
423     elem.indexOf(entry);
424    
425     while (found_pos != -1) {
426     if (found_pos == 0 && elem.length != entry.length) {
427     ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
428     elem.substr(entry.length) + "</li>");
429     break;
430     } else if (entry.length >= instance.options.partial_chars &&
431     instance.options.partial_search && found_pos != -1) {
432     if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
433     partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
434     elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
435     found_pos + entry.length) + "</li>");
436     break;
437     }
438     }
439    
440     found_pos = instance.options.ignore_case ?
441     elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :
442     elem.indexOf(entry, found_pos + 1);
443    
444     }
445     }
446     if (partial.length)
447     ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
448     return "<ul>" + ret.join('') + "</ul>";
449     }
450     }, options || {});
451     }
452     });

Properties

Name Value
svn:mime-type text/cpp

  ViewVC Help
Powered by ViewVC 1.1.26