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

Contents of /Webpacus/root/js/controls.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 105 - (show annotations)
Tue Nov 22 19:34:27 2005 UTC (18 years, 5 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 // 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 this.in_dropdown = false;
77
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 if (this.in_dropdown) this.select_entry();
150 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 default:
169 this.in_dropdown = false;
170 }
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 this.index==i && this.in_dropdown ?
211 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 this.in_dropdown = true;
228 },
229
230 mark_next: function() {
231 if(this.index < this.entry_count-1) this.index++
232 else this.index = 0;
233 if (! this.in_dropdown) this.index = 0;
234 this.in_dropdown = true;
235 },
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