/[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 83 - (show annotations)
Mon Nov 21 17:46:27 2005 UTC (18 years, 6 months ago) by dpavlin
File MIME type: text/cpp
File size: 15800 byte(s)
Import of web font-end for WebPAC v2 called WebPACus based on Catalyst
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
77 if (this.setOptions)
78 this.setOptions(options);
79 else
80 this.options = {}
81
82 this.options.tokens = this.options.tokens || new Array();
83 this.options.frequency = this.options.frequency || 0.4;
84 this.options.min_chars = this.options.min_chars || 1;
85 this.options.onShow = this.options.onShow ||
86 function(element, update){
87 if(!update.style.position || update.style.position=='absolute') {
88 update.style.position = 'absolute';
89 var offsets = Position.cumulativeOffset(element);
90 update.style.left = offsets[0] + 'px';
91 update.style.top = (offsets[1] + element.offsetHeight) + 'px';
92 update.style.width = element.offsetWidth + 'px';
93 }
94 new Effect.Appear(update,{duration:0.15});
95 };
96 this.options.onHide = this.options.onHide ||
97 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
98
99 if(this.options.indicator)
100 this.indicator = $(this.options.indicator);
101
102 if (typeof(this.options.tokens) == 'string')
103 this.options.tokens = new Array(this.options.tokens);
104
105 this.observer = null;
106
107 Element.hide(this.update);
108
109 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
110 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
111 },
112
113 show: function() {
114 if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
115 if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
116 new Insertion.After(this.update,
117 '<iframe id="' + this.update.id + '_iefix" '+
118 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
119 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
120 this.iefix = $(this.update.id+'_iefix');
121 }
122 if(this.iefix) {
123 Position.clone(this.update, this.iefix);
124 this.iefix.style.zIndex = 1;
125 this.update.style.zIndex = 2;
126 Element.show(this.iefix);
127 }
128 },
129
130 hide: function() {
131 if(this.update.style.display=='') this.options.onHide(this.element, this.update);
132 if(this.iefix) Element.hide(this.iefix);
133 },
134
135 startIndicator: function() {
136 if(this.indicator) Element.show(this.indicator);
137 },
138
139 stopIndicator: function() {
140 if(this.indicator) Element.hide(this.indicator);
141 },
142
143 onKeyPress: function(event) {
144 if(this.active)
145 switch(event.keyCode) {
146 case Event.KEY_TAB:
147 case Event.KEY_RETURN:
148 this.select_entry();
149 Event.stop(event);
150 case Event.KEY_ESC:
151 this.hide();
152 this.active = false;
153 return;
154 case Event.KEY_LEFT:
155 case Event.KEY_RIGHT:
156 return;
157 case Event.KEY_UP:
158 this.mark_previous();
159 this.render();
160 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
161 return;
162 case Event.KEY_DOWN:
163 this.mark_next();
164 this.render();
165 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
166 return;
167 }
168 else
169 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
170 return;
171
172 this.changed = true;
173 this.has_focus = true;
174
175 if(this.observer) clearTimeout(this.observer);
176 this.observer =
177 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
178 },
179
180 onHover: function(event) {
181 var element = Event.findElement(event, 'LI');
182 if(this.index != element.autocompleteIndex)
183 {
184 this.index = element.autocompleteIndex;
185 this.render();
186 }
187 Event.stop(event);
188 },
189
190 onClick: function(event) {
191 var element = Event.findElement(event, 'LI');
192 this.index = element.autocompleteIndex;
193 this.select_entry();
194 Event.stop(event);
195 },
196
197 onBlur: function(event) {
198 // needed to make click events working
199 setTimeout(this.hide.bind(this), 250);
200 this.has_focus = false;
201 this.active = false;
202 },
203
204 render: function() {
205 if(this.entry_count > 0) {
206 for (var i = 0; i < this.entry_count; i++)
207 this.index==i ?
208 Element.addClassName(this.get_entry(i),"selected") :
209 Element.removeClassName(this.get_entry(i),"selected");
210
211 if(this.has_focus) {
212 if(this.get_current_entry().scrollIntoView)
213 this.get_current_entry().scrollIntoView(false);
214
215 this.show();
216 this.active = true;
217 }
218 } else this.hide();
219 },
220
221 mark_previous: function() {
222 if(this.index > 0) this.index--
223 else this.index = this.entry_count-1;
224 },
225
226 mark_next: function() {
227 if(this.index < this.entry_count-1) this.index++
228 else this.index = 0;
229 },
230
231 get_entry: function(index) {
232 return this.update.firstChild.childNodes[index];
233 },
234
235 get_current_entry: function() {
236 return this.get_entry(this.index);
237 },
238
239 select_entry: function() {
240 this.active = false;
241 value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
242 this.updateElement(value);
243 this.element.focus();
244 },
245
246 updateElement: function(value) {
247 var last_token_pos = this.findLastToken();
248 if (last_token_pos != -1) {
249 var new_value = this.element.value.substr(0, last_token_pos + 1);
250 var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
251 if (whitespace)
252 new_value += whitespace[0];
253 this.element.value = new_value + value;
254 } else {
255 this.element.value = value;
256 }
257 },
258
259 updateChoices: function(choices) {
260 if(!this.changed && this.has_focus) {
261 this.update.innerHTML = choices;
262 Element.cleanWhitespace(this.update);
263 Element.cleanWhitespace(this.update.firstChild);
264
265 if(this.update.firstChild && this.update.firstChild.childNodes) {
266 this.entry_count =
267 this.update.firstChild.childNodes.length;
268 for (var i = 0; i < this.entry_count; i++) {
269 entry = this.get_entry(i);
270 entry.autocompleteIndex = i;
271 this.addObservers(entry);
272 }
273 } else {
274 this.entry_count = 0;
275 }
276
277 this.stopIndicator();
278
279 this.index = 0;
280 this.render();
281 }
282 },
283
284 addObservers: function(element) {
285 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
286 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
287 },
288
289 onObserverEvent: function() {
290 this.changed = false;
291 if(this.getEntry().length>=this.options.min_chars) {
292 this.startIndicator();
293 this.getUpdatedChoices();
294 } else {
295 this.active = false;
296 this.hide();
297 }
298 },
299
300 getEntry: function() {
301 var token_pos = this.findLastToken();
302 if (token_pos != -1)
303 var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
304 else
305 var ret = this.element.value;
306
307 return /\n/.test(ret) ? '' : ret;
308 },
309
310 findLastToken: function() {
311 var last_token_pos = -1;
312
313 for (var i=0; i<this.options.tokens.length; i++) {
314 var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
315 if (this_token_pos > last_token_pos)
316 last_token_pos = this_token_pos;
317 }
318 return last_token_pos;
319 }
320 }
321
322 Ajax.Autocompleter = Class.create();
323 Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
324 Object.extend(new Ajax.Base(), {
325 initialize: function(element, update, url, options) {
326 this.base_initialize(element, update, options);
327 this.options.asynchronous = true;
328 this.options.onComplete = this.onComplete.bind(this)
329 this.options.method = 'post';
330 this.options.defaultParams = this.options.parameters || null;
331 this.url = url;
332 },
333
334 getUpdatedChoices: function() {
335 entry = encodeURIComponent(this.element.name) + '=' +
336 encodeURIComponent(this.getEntry());
337
338 this.options.parameters = this.options.callback ?
339 this.options.callback(this.element, entry) : entry;
340
341 if(this.options.defaultParams)
342 this.options.parameters += '&' + this.options.defaultParams;
343
344 new Ajax.Request(this.url, this.options);
345 },
346
347 onComplete: function(request) {
348 this.updateChoices(request.responseText);
349 }
350
351 }));
352
353 // The local array autocompleter. Used when you'd prefer to
354 // inject an array of autocompletion options into the page, rather
355 // than sending out Ajax queries, which can be quite slow sometimes.
356 //
357 // The constructor takes four parameters. The first two are, as usual,
358 // the id of the monitored textbox, and id of the autocompletion menu.
359 // The third is the array you want to autocomplete from, and the fourth
360 // is the options block.
361 //
362 // Extra local autocompletion options:
363 // - choices - How many autocompletion choices to offer
364 //
365 // - partial_search - If false, the autocompleter will match entered
366 // text only at the beginning of strings in the
367 // autocomplete array. Defaults to true, which will
368 // match text at the beginning of any *word* in the
369 // strings in the autocomplete array. If you want to
370 // search anywhere in the string, additionally set
371 // the option full_search to true (default: off).
372 //
373 // - full_search - Search anywhere in autocomplete array strings.
374 //
375 // - partial_chars - How many characters to enter before triggering
376 // a partial match (unlike min_chars, which defines
377 // how many characters are required to do any match
378 // at all). Defaults to 2.
379 //
380 // - ignore_case - Whether to ignore case when autocompleting.
381 // Defaults to true.
382 //
383 // It's possible to pass in a custom function as the 'selector'
384 // option, if you prefer to write your own autocompletion logic.
385 // In that case, the other options above will not apply unless
386 // you support them.
387
388 Autocompleter.Local = Class.create();
389 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
390 initialize: function(element, update, array, options) {
391 this.base_initialize(element, update, options);
392 this.options.array = array;
393 },
394
395 getUpdatedChoices: function() {
396 this.updateChoices(this.options.selector(this));
397 },
398
399 setOptions: function(options) {
400 this.options = Object.extend({
401 choices: 10,
402 partial_search: true,
403 partial_chars: 2,
404 ignore_case: true,
405 full_search: false,
406 selector: function(instance) {
407 var ret = new Array(); // Beginning matches
408 var partial = new Array(); // Inside matches
409 var entry = instance.getEntry();
410 var count = 0;
411
412 for (var i = 0; i < instance.options.array.length &&
413 ret.length < instance.options.choices ; i++) {
414 var elem = instance.options.array[i];
415 var found_pos = instance.options.ignore_case ?
416 elem.toLowerCase().indexOf(entry.toLowerCase()) :
417 elem.indexOf(entry);
418
419 while (found_pos != -1) {
420 if (found_pos == 0 && elem.length != entry.length) {
421 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
422 elem.substr(entry.length) + "</li>");
423 break;
424 } else if (entry.length >= instance.options.partial_chars &&
425 instance.options.partial_search && found_pos != -1) {
426 if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
427 partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
428 elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
429 found_pos + entry.length) + "</li>");
430 break;
431 }
432 }
433
434 found_pos = instance.options.ignore_case ?
435 elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :
436 elem.indexOf(entry, found_pos + 1);
437
438 }
439 }
440 if (partial.length)
441 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
442 return "<ul>" + ret.join('') + "</ul>";
443 }
444 }, options || {});
445 }
446 });

Properties

Name Value
svn:mime-type text/cpp

  ViewVC Help
Powered by ViewVC 1.1.26