PageSourceSearch

https://borer.org.il/media/com_finder/js/autocompleter.js

js borer.org.il collected 2026-10-03 18:46:22 UTC 16,271 bytes, 506 lines download raw bytes

1//@deprecated  3.4
2var Observer = new Class({
3	Implements: [Options, Events],
4	options: {
5		periodical: false,
6		delay: 1000
7	},
8	initialize: function (el, onFired, options) {
9		this.element = document.id(el) || $document.id(el);
10		this.addEvent('onFired', onFired);
11		this.setOptions(options);
12		this.bound = this.changed.bind(this);
13		this.resume();
14	},
15	changed: function () {
16		var value = this.element.get('value');
17		if ($equals(this.value, value)) return;
18		this.clear();
19		this.value = value;
20		this.timeout = this.onFired.delay(this.options.delay, this);
21	},
22	setValue: function (value) {
23		this.value = value;
24		this.element.set('value', value);
25		return this.clear();
26	},
27	onFired: function () {
28		this.fireEvent('onFired', [this.value, this.element]);
29	},
30	clear: function () {
31		clearTimeout(this.timeout || null);
32		return this;
33	},
34	pause: function () {
35		if (this.timer) clearTimeout(this.timer);
36		else this.element.removeEvent('keyup', this.bound);
37		return this.clear();
38	},
39	resume: function () {
40		this.value = this.element.get('value');
41		if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this);
42		else this.element.addEvent('keyup', this.bound);
43		return this;
44	}
45});
46var $equals = function (obj1, obj2) {
47		return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2));
48	};
49var Autocompleter = new Class({
50	Implements: [Options, Events],
51	options: {
52		minLength: 1,
53		markQuery: true,
54		width: 'inherit',
55		maxChoices: 10,
56		injectChoice: null,
57		customChoices: null,
58		emptyChoices: null,
59		visibleChoices: true,
60		className: 'autocompleter-choices',
61		zIndex: 1000,
62		delay: 400,
63		observerOptions: {},
64		fxOptions: {},
65		autoSubmit: false,
66		overflow: false,
67		overflowMargin: 25,
68		selectFirst: false,
69		filter: null,
70		filterCase: false,
71		filterSubset: false,
72		forceSelect: false,
73		selectMode: true,
74		choicesMatch: null,
75		multiple: false,
76		separator: ', ',
77		separatorSplit: /\s*[,;]\s*/,
78		autoTrim: false,
79		allowDupes: false,
80		cache: true,
81		relative: false
82	},
83	initialize: function (element, options) {
84		this.element = document.id(element);
85		this.setOptions(options);
86		this.build();
87		this.observer = new Observer(this.element, this.prefetch.bind(this), Object.merge({}, {
88			'delay': this.options.delay
89		}, this.options.observerOptions));
90		this.queryValue = null;
91		if (this.options.filter) this.filter = this.options.filter.bind(this);
92		var mode = this.options.selectMode;
93		this.typeAhead = (mode == 'type-ahead');
94		this.selectMode = (mode === true) ? 'selection' : mode;
95		this.cached = [];
96	},
97	build: function () {
98		if (document.id(this.options.customChoices)) {
99			this.choices = this.options.customChoices;
100		} else {
101			this.choices = new Element('ul', {
102				'class': this.options.className,
103				'styles': {
104					'zIndex': this.options.zIndex
105				}
106			}).inject(document.body);
107			this.relative = false;
108			if (this.options.relative) {
109				this.choices.inject(this.element, 'after');
110				this.relative = this.element.getOffsetParent();
111			}
112			this.fix = new OverlayFix(this.choices);
113		}
114		if (!this.options.separator.test(this.options.separatorSplit)) {
115			this.options.separatorSplit = this.options.separator;
116		}
117		this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, Ob
117ject.merge({}, {
118			'property': 'opacity',
119			'link': 'cancel',
120			'duration': 200
121		}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);
122		this.element.setProperty('autocomplete', 'off').addEvent((Browser.ie || Browser.safari || Browser.chrome) ? 'keydown' : 'keypress', this.onCommand.bind(this)).addEvent('click', this.onCommand.bind(this, [false])).addEvent('focus', this.toggleFocus.pass({
123			bind: this,
124			arguments: true,
125			delay: 100
126		})).addEvent('blur', this.toggleFocus.pass({
127			bind: this,
128			arguments: false,
129			delay: 100
130		}));
131	},
132	destroy: function () {
133		if (this.fix) this.fix.destroy();
134		this.choices = this.selected = this.choices.destroy();
135	},
136	toggleFocus: function (state) {
137		this.focussed = state;
138		if (!state) this.hideChoices(true);
139		this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);
140	},
141	onCommand: function (e) {
142		if (!e && this.focussed) return this.prefetch();
143		if (e && e.key && !e.shift) {
144			switch (e.key) {
145			case 'enter':
146				if (this.element.value != this.opted) return true;
147				if (this.selected && this.visible) {
148					this.choiceSelect(this.selected);
149					return !!(this.options.autoSubmit);
150				}
151				break;
152			case 'up':
153			case 'down':
154				if (!this.prefetch() && this.queryValue !== null) {
155					var up = (e.key == 'up');
156					this.choiceOver((this.selected || this.choices)[(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')](this.options.choicesMatch), true);
157				}
158				return false;
159			case 'esc':
160			case 'tab':
161				this.hideChoices(true);
162				break;
163			}
164		}
165		return true;
166	},
167	setSelection: function (finish) {
168		var input = this.selected.inputValue,
169			value = input;
170		var start = this.queryValue.length,
171			end = input.length;
172		if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;
173		if (this.options.multiple) {
174			var split = this.options.separatorSplit;
175			value = this.element.value;
176			start += this.queryIndex;
177			end += this.queryIndex;
178			var old = value.substr(this.queryIndex).split(split, 1)[0];
179			value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);
180			if (finish) {
181				var tokens = value.split(this.options.separatorSplit).filter(function (entry) {
182					return this.test(entry);
183				}, /[^\s,]+/);
184				if (!this.options.allowDupes) tokens = [].combine(tokens);
185				var sep = this.options.separator;
186				value = tokens.join(sep) + sep;
187				end = value.length;
188			}
189		}
190		this.observer.setValue(value);
191		this.opted = value;
192		if (finish || this.selectMode == 'pick') start = end;
193		this.element.selectRange(start, end);
194		this.fireEvent('onSelection', [this.element, this.selected, value, input]);
195	},
196	showChoices: function () {
197		var match = this.options.choicesMatch,
198			first = this.choices.getFirst(match);
199		this.selected = this.selectedValue = null;
200		if (this.fix) {
201			var pos = this.element.getCoordinates(this.relative),
202				width = this.options.width || 'auto';
203			this.choices.setStyles({
204				'left': pos.left,
205				'top': pos.bottom,
206				'width': (width === true || width == 'inherit') ? pos.width : width
207			});
208		}
209		if (!first) return;
210		if (!this.visible) {
211			this.visible = true;
212			this.choices.setStyle('display', '');
213			if (this.fx) this.fx.start(1);
214			this.fireEvent('onShow', [this.element, this.choices]);
215		}
216		if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);
217		var items = this.choices.getChildren(match),
218			max = this.options.maxChoices;
219		var styles = {
220			'overflowY': 'hidden',
221			'height': ''
222		};
223		this.overflown = false;
224		if (items.length > max) {
225			var item = items[max - 1];
226			styles.overflowY = 'scroll';
227			styles.height = item.getCoordinates(this.choices).bottom;
228			this.overflown = true;
229		};
230		this.choices.setStyles(styles);
231		this.fix.show();
232		if (this.options.visibleChoices) {
233			var scroll = document.getScroll(),
234				size = document.getSize(),
235				coords = this.choices.getCoordinates();
236			if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x;
237			if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y;
238			window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top));
239		}
240	},
241	// TODO: No $arguments in MT 1.3
242	hideChoices: function (clear) {
243		if (clear) {
244			var value = this.element.value;
245			if (this.options.forceSelect) value = this.opted;
246			if (this.options.autoTrim) {
247				value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);
248			}
249			this.observer.setValue(value);
250		}
251		if (!this.visible) return;
252		this.visible = false;
253		if (this.selected) this.selected.removeClass('autocompleter-selected');
254		this.observer.clear();
255		var hide = function () {
256				this.choices.setStyle('display', 'none');
257				this.fix.hide();
258			}.bind(this);
259		if (this.fx) this.fx.start(0).chain(hide);
260		else hide();
261		this.fireEvent('onHide', [this.element, this.choices]);
262	},
263	prefetch: function () {
264		var value = this.element.value,
265			query = value;
266		if (this.options.multiple) {
267			var split = this.options.separatorSplit;
268			var values = value.split(split);
269			var index = this.element.getSelectedRange().start;
270			var toIndex = value.substr(0, index).split(split);
271			var last = toIndex.length - 1;
272			index -= toIndex[last].length;
273			query = values[last];
274		}
275		if (query.length < this.options.minLength) {
276			this.hideChoices();
277		} else {
278			if (query === this.queryValue || (this.visible && query == this.selectedValue)) {
279				if (this.visible) return false;
280				this.showChoices();
281			} else {
282				this.queryValue = query;
283				this.queryIndex = index;
284				if (!this.fetchCached()) this.query();
285			}
286		}
287		return true;
288	},
289	fetchCached: function () {
290		return false;
291		if (!this.options.cache || !this.cached || !this.cached.length || this.cached.length >= this.options.maxChoices || this.queryValue) return false;
292		this.update(this.filter(this.cached));
293		return true;
294	},
295	update: function (tokens) {
296		this.choices.empty();
297		this.cached = tokens;
298		var type = tokens && typeOf(tokens);
299		if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) {
300			(this.options.emptyChoices || this.hideChoices).call(this);
301		} else {
302			if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;
303			tokens.each(this.options.injectChoice ||
304			function (token) {
305				var choice = new Element('li', {
306					'html': this.markQueryValue(token)
307				});
308				choice.inputValue = token;
309				this.addChoiceEvents(choice).inject(this.choices);
310			}, this);
311			this.showChoices();
312		}
313	},
314	choiceOver: function (choice, selection) {
315		if (!choice || choice == this.selected) return;
316		if (this.selected) this.selected.removeClass('autocompleter-selected');
317		this.selected = choice.addClass('autocompleter-selected');
318		this.fireEvent('onSelect', [this.element, this.selected, selection]);
319		if (!this.selectMode) this.opted = this.element.value;
320		if (!selection) return;
321		this.selectedValue = this.selected.inputValue;
322		if (this.overflown) {
323			var coords = this.selected.getCoordinates(this.choices),
324				margin = this.options.overflowMargin,
325				top = this.choices.scrollTop,
326				height = this.choices.offsetHeight,
327				bottom = top + height;
328			if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);
329			else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);
330		}
331		if (this.selectMode) this.setSelection();
332	},
333	choiceSelect: function (choice) {
334		if (choice) this.choiceOver(choice);
335		this.setSelection(true);
336		this.queryValue = false;
337		this.hideChoices();
338	},
339	filter: function (tokens) {
340		return (tokens || this.tokens).filter(function (token) {
341			return this.test(token);
342		}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i'));
343	},
344	markQueryValue: function (str) {
345		return (!this.options.markQuery || !this.queryValue) ? str : str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');
346	},
347	addChoiceEvents: function (el) {
348		return el.addEvents({
349			'mouseover': this.choiceOver.bind(this, el),
350			'click': this.choiceSelect.bind(this, el)
351		});
352	}
353});
354var OverlayFix = new Class({
355	initialize: function (el) {
356		if (Browser.ie) {
357			this.element = document.id(el);
358			this.relative = this.element.getOffsetParent();
359			this.fix = new Element('iframe', {
360				'frameborder': '0',
361				'scrolling': 'no',
362				'src': 'javascript:false;',
363				'styles': {
364					'position': 'absolute',
365					'border': 'none',
366					'display': 'none',
367					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
368				}
369			}).inject(this.element, 'after');
370		}
371	},
372	show: function () {
373		if (this.fix) {
374			var coords = this.element.getCoordinates(this.relative);
375			delete coords.right;
376			delete coords.bottom;
377			this.fix.setStyles(Object.append(coords, {
378				'display': '',
379				'zIndex': (this.element.getStyle('zIndex') || 1) - 1
380			}));
381		}
382		return this;
383	},
384	hide: function () {
385		if (this.fix) this.fix.setStyle('display', 'none');
386		return this;
387	},
388	destroy: function () {
389		if (this.fix) this.fix = this.fix.destroy();
390	}
391});
392Element.implement({
393	getSelectedRange: function () {
394		if (!Browser.ie) return {
395			start: this.selectionStart,
396			end: this.selectionEnd
397		};
398		var pos = {
399			start: 0,
400			end: 0
401		};
402		var range = this.getDocument().selection.createRange();
403		if (!range || range.parentElement() != this) return pos;
404		var dup = range.duplicate();
405		if (this.type == 'text') {
406			pos.start = 0 - dup.moveStart('character', -100000);
407			pos.end = pos.start + range.text.length;
408		} else {
409			var value = this.value;
410			var offset = value.length - value.match(/[\n\r]*$/)[0].length;
411			dup.moveToElementText(this);
412			dup.setEndPoint('StartToEnd', range);
413			pos.end = offset - dup.text.length;
414			dup.setEndPoint('StartToStart', range);
415			pos.start = offset - dup.text.length;
416		}
417		return pos;
418	},
419	selectRange: function (start, end) {
420		if (Browser.ie) {
421			var diff = this.value.substr(start, end - start).replace(/\r/g, '').length;
422			start = this.value.substr(0, start).replace(/\r/g, '').length;
423			var range = this.createTextRange();
424			range.collapse(true);
425			range.moveEnd('character', start + diff);
426			range.moveStart('character', start);
427			range.select();
428		} else {
429			this.focus();
430			this.setSelectionRange(start, end);
431		}
432		return this;
433	}
434});
435Autocompleter.Base = Autocompleter;
436Autocompleter.Request = new Class({
437	Extends: Autocompleter,
438	options: {
439		postData: {},
440		ajaxOptions: {},
441		postVar: 'value'
442	},
443	query: function () {
444		var data = this.options.postData.unlink || {};
445		data[this.options.postVar] = this.queryValue;
446		var indicator = document.id(this.options.indicator);
447		if (indicator) indicator.setStyle('display', '');
448		var cls = this.options.indicatorClass;
449		if (cls) this.element.addClass(cls);
450		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
451		this.request.send({
452			'data': data
453		});
454	},
455	queryResponse: function () {
456		var indicator = document.id(this.options.indicator);
457		if (indicator) indicator.setStyle('display', 'none');
458		var cls = this.options.indicatorClass;
459		if (cls) this.element.removeClass(cls);
460		return this.fireEvent('onComplete', [this.element, this.request]);
461	}
462});
463Autocompleter.Request.JSON = new Class({
464	Extends: Autocompleter.Request,
465	initialize: function (el, url, options) {
466		this.parent(el, options);
467		this.request = new Request.JSON(Object.merge({}, {
468			'url': url,
469			'link': 'cancel'
470		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
471	},
472	queryResponse: function (response) {
473		this.parent();
474		this.update(response);
475	}
476});
477Autocompleter.Request.HTML = new Class({
478	Extends: Autocompleter.Request,
479	initialize: function (el, url, options) {
480		this.parent(el, options);
481		this.request = new Request.HTML(Object.merge({}, {
482			'url': url,
483			'link': 'cancel',
484			'update': this.choices
485		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
486	},
487	queryResponse: function (tree, elements) {
488		this.parent();
489		if (!elements || !elements.length) {
490			this.hideChoices();
491		} else {
492			this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice ||
493			function (choice) {
494				var value = choice.innerHTML;
495				choice.inputValue = value;
496				this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
497			}, this);
498			this.showChoices();
499		}
500	}
501});
502Autocompleter.Ajax = {
503	Base: Autocompleter.Request,
504	Json: Autocompleter.Request.JSON,
505	Xhtml: Autocompleter.Request.HTML
506};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.