Created
November 20, 2008 15:41
-
-
Save subtleGradient/27079 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From d954fbce5fbfc5d6070d796afacf998036c0cd51 Mon Sep 17 00:00:00 2001 | |
| From: subtleGradient <[email protected]> | |
| Date: Thu, 20 Nov 2008 08:51:07 -0700 | |
| Subject: [PATCH] Move observe into a method so that it can be observed | |
| --- | |
| Autocompleter.js | 15 ++++++++++----- | |
| 1 files changed, 10 insertions(+), 5 deletions(-) | |
| diff --git a/Autocompleter.js b/Autocompleter.js | |
| index 98f5814..16a4424 100644 | |
| --- a/Autocompleter.js | |
| +++ b/Autocompleter.js | |
| @@ -61,9 +61,7 @@ var Autocompleter = new Class({ | |
| this.element = $(element); | |
| this.setOptions(options); | |
| this.build(); | |
| - this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({ | |
| - 'delay': this.options.delay | |
| - }, this.options.observerOptions)); | |
| + this.observe(this.element); | |
| this.queryValue = null; | |
| if (this.options.filter) this.filter = this.options.filter.bind(this); | |
| var mode = this.options.selectMode; | |
| @@ -71,7 +69,12 @@ var Autocompleter = new Class({ | |
| this.selectMode = (mode === true) ? 'selection' : mode; | |
| this.cached = []; | |
| }, | |
| - | |
| + observe: function(element){ | |
| + this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({ | |
| + 'delay': this.options.delay | |
| + }, this.options.observerOptions)); | |
| + }, | |
| /** | |
| * build - Initialize DOM | |
| * | |
| @@ -239,7 +244,7 @@ var Autocompleter = new Class({ | |
| }, | |
| prefetch: function() { | |
| - var value = this.element.value, query = value; | |
| + var value = this.element.value||'', query = value; | |
| if (this.options.multiple) { | |
| var split = this.options.separatorSplit; | |
| var values = value.split(split); | |
| 1.6.0.2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Autocompleter | |
| * | |
| * http://digitarald.de/project/autocompleter/ | |
| * | |
| * @version 1.1.2 | |
| * | |
| * @license MIT-style license | |
| * @author Harald Kirschner <mail [at] digitarald.de> | |
| * @copyright Author | |
| */ | |
| var Autocompleter = new Class({ | |
| Implements: [Options, Events], | |
| options: {/* | |
| onOver: $empty, | |
| onSelect: $empty, | |
| onSelection: $empty, | |
| onShow: $empty, | |
| onHide: $empty, | |
| onBlur: $empty, | |
| onFocus: $empty,*/ | |
| minLength: 1, | |
| markQuery: true, | |
| width: 'inherit', | |
| maxChoices: 10, | |
| injectChoice: null, | |
| customChoices: null, | |
| emptyChoices: null, | |
| visibleChoices: true, | |
| className: 'autocompleter-choices', | |
| zIndex: 42, | |
| delay: 400, | |
| observerOptions: {}, | |
| fxOptions: {}, | |
| autoSubmit: false, | |
| overflow: false, | |
| overflowMargin: 25, | |
| selectFirst: false, | |
| filter: null, | |
| filterCase: false, | |
| filterSubset: false, | |
| forceSelect: false, | |
| selectMode: true, | |
| choicesMatch: null, | |
| multiple: false, | |
| separator: ', ', | |
| separatorSplit: /\s*[,;]\s*/, | |
| autoTrim: false, | |
| allowDupes: false, | |
| cache: true, | |
| relative: false | |
| }, | |
| initialize: function(element, options) { | |
| this.element = $(element); | |
| this.setOptions(options); | |
| this.build(); | |
| this.observe(this.element); | |
| this.queryValue = null; | |
| if (this.options.filter) this.filter = this.options.filter.bind(this); | |
| var mode = this.options.selectMode; | |
| this.typeAhead = (mode == 'type-ahead'); | |
| this.selectMode = (mode === true) ? 'selection' : mode; | |
| this.cached = []; | |
| }, | |
| observe: function(element){ | |
| try{console.log( "Autocompleter#observe" );}catch(e){}; | |
| this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({ | |
| 'delay': this.options.delay | |
| }, this.options.observerOptions)); | |
| }, | |
| /** | |
| * build - Initialize DOM | |
| * | |
| * Builds the html structure for choices and appends the events to the element. | |
| * Override this function to modify the html generation. | |
| */ | |
| build: function() { | |
| try{console.log( "build" );}catch(e){}; | |
| if ($(this.options.customChoices)) { | |
| this.choices = this.options.customChoices; | |
| } else { | |
| this.choices = new Element('ul', { | |
| 'class': this.options.className, | |
| 'styles': { | |
| 'zIndex': this.options.zIndex | |
| } | |
| }).inject(document.body); | |
| this.relative = false; | |
| if (this.options.relative) { | |
| this.choices.inject(this.element, 'after'); | |
| this.relative = this.element.getOffsetParent(); | |
| } | |
| this.fix = new OverlayFix(this.choices); | |
| } | |
| if (!this.options.separator.test(this.options.separatorSplit)) { | |
| this.options.separatorSplit = this.options.separator; | |
| } | |
| this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({ | |
| 'property': 'opacity', | |
| 'link': 'cancel', | |
| 'duration': 200 | |
| }, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0); | |
| this.element.setProperty('autocomplete', 'off') | |
| .addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this)) | |
| .addEvent('click', this.onCommand.bind(this, [false])) | |
| .addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100})) | |
| .addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100})); | |
| }, | |
| destroy: function() { | |
| if (this.fix) this.fix.destroy(); | |
| this.choices = this.selected = this.choices.destroy(); | |
| }, | |
| toggleFocus: function(state) { | |
| this.focussed = state; | |
| if (!state) this.hideChoices(true); | |
| this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]); | |
| }, | |
| onCommand: function(e) { | |
| if (!e && this.focussed) return this.prefetch(); | |
| if (e && e.key && !e.shift) { | |
| switch (e.key) { | |
| case 'enter': | |
| if (this.element.value != this.opted) return true; | |
| if (this.selected && this.visible) { | |
| this.choiceSelect(this.selected); | |
| return !!(this.options.autoSubmit); | |
| } | |
| break; | |
| case 'up': case 'down': | |
| if (!this.prefetch() && this.queryValue !== null) { | |
| var up = (e.key == 'up'); | |
| this.choiceOver((this.selected || this.choices)[ | |
| (this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst') | |
| ](this.options.choicesMatch), true); | |
| } | |
| return false; | |
| case 'esc': case 'tab': | |
| this.hideChoices(true); | |
| break; | |
| } | |
| } | |
| return true; | |
| }, | |
| setSelection: function(finish) { | |
| var input = this.selected.inputValue, value = input; | |
| var start = this.queryValue.length, end = input.length; | |
| if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0; | |
| if (this.options.multiple) { | |
| var split = this.options.separatorSplit; | |
| value = this.element.value; | |
| start += this.queryIndex; | |
| end += this.queryIndex; | |
| var old = value.substr(this.queryIndex).split(split, 1)[0]; | |
| value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length); | |
| if (finish) { | |
| var tokens = value.split(this.options.separatorSplit).filter(function(entry) { | |
| return this.test(entry); | |
| }, /[^\s,]+/); | |
| if (!this.options.allowDupes) tokens = [].combine(tokens); | |
| var sep = this.options.separator; | |
| value = tokens.join(sep) + sep; | |
| end = value.length; | |
| } | |
| } | |
| this.observer.setValue(value); | |
| this.opted = value; | |
| if (finish || this.selectMode == 'pick') start = end; | |
| this.element.selectRange(start, end); | |
| this.fireEvent('onSelection', [this.element, this.selected, value, input]); | |
| }, | |
| showChoices: function() { | |
| try{console.log( "showChoices" );}catch(e){}; | |
| var match = this.options.choicesMatch, first = this.choices.getFirst(match); | |
| this.selected = this.selectedValue = null; | |
| if (this.fix) { | |
| var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto'; | |
| this.choices.setStyles({ | |
| 'left': pos.left, | |
| 'top': pos.bottom, | |
| 'width': (width === true || width == 'inherit') ? pos.width : width | |
| }); | |
| } | |
| if (!first) return; | |
| if (!this.visible) { | |
| this.visible = true; | |
| this.choices.setStyle('display', ''); | |
| if (this.fx) this.fx.start(1); | |
| this.fireEvent('onShow', [this.element, this.choices]); | |
| } | |
| if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead); | |
| var items = this.choices.getChildren(match), max = this.options.maxChoices; | |
| var styles = {'overflowY': 'hidden', 'height': ''}; | |
| this.overflown = false; | |
| if (items.length > max) { | |
| var item = items[max - 1]; | |
| styles.overflowY = 'scroll'; | |
| styles.height = item.getCoordinates(this.choices).bottom; | |
| this.overflown = true; | |
| }; | |
| this.choices.setStyles(styles); | |
| this.fix.show(); | |
| if (this.options.visibleChoices) { | |
| var scroll = document.getScroll(), | |
| size = document.getSize(), | |
| coords = this.choices.getCoordinates(); | |
| if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x; | |
| if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y; | |
| window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top)); | |
| } | |
| }, | |
| hideChoices: function(clear) { | |
| if (clear) { | |
| var value = this.element.value; | |
| if (this.options.forceSelect) value = this.opted; | |
| if (this.options.autoTrim) { | |
| value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator); | |
| } | |
| this.observer.setValue(value); | |
| } | |
| if (!this.visible) return; | |
| this.visible = false; | |
| if (this.selected) this.selected.removeClass('autocompleter-selected'); | |
| this.observer.clear(); | |
| var hide = function(){ | |
| this.choices.setStyle('display', 'none'); | |
| this.fix.hide(); | |
| }.bind(this); | |
| if (this.fx) this.fx.start(0).chain(hide); | |
| else hide(); | |
| this.fireEvent('onHide', [this.element, this.choices]); | |
| }, | |
| prefetch: function() { | |
| var value = this.element.value||'', query = value; | |
| if (this.options.multiple) { | |
| var split = this.options.separatorSplit; | |
| var values = value.split(split); | |
| var index = this.element.getSelectedRange().start; | |
| var toIndex = value.substr(0, index).split(split); | |
| var last = toIndex.length - 1; | |
| index -= toIndex[last].length; | |
| query = values[last]; | |
| } | |
| if (query.length < this.options.minLength) { | |
| this.hideChoices(); | |
| } else { | |
| if (query === this.queryValue || (this.visible && query == this.selectedValue)) { | |
| if (this.visible) return false; | |
| this.showChoices(); | |
| } else { | |
| this.queryValue = query; | |
| this.queryIndex = index; | |
| if (!this.fetchCached()) this.query(); | |
| } | |
| } | |
| return true; | |
| }, | |
| fetchCached: function() { | |
| return false; | |
| if (!this.options.cache | |
| || !this.cached | |
| || !this.cached.length | |
| || this.cached.length >= this.options.maxChoices | |
| || this.queryValue) return false; | |
| this.update(this.filter(this.cached)); | |
| return true; | |
| }, | |
| update: function(tokens) { | |
| this.choices.empty(); | |
| this.cached = tokens; | |
| var type = tokens && $type(tokens); | |
| if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) { | |
| (this.options.emptyChoices || this.hideChoices).call(this); | |
| } else { | |
| if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices; | |
| tokens.each(this.options.injectChoice || function(token){ | |
| var choice = new Element('li', {'html': this.markQueryValue(token)}); | |
| choice.inputValue = token; | |
| this.addChoiceEvents(choice).inject(this.choices); | |
| }, this); | |
| this.showChoices(); | |
| } | |
| }, | |
| choiceOver: function(choice, selection) { | |
| if (!choice || choice == this.selected) return; | |
| if (this.selected) this.selected.removeClass('autocompleter-selected'); | |
| this.selected = choice.addClass('autocompleter-selected'); | |
| this.fireEvent('onSelect', [this.element, this.selected, selection]); | |
| if (!this.selectMode) this.opted = this.element.value; | |
| if (!selection) return; | |
| this.selectedValue = this.selected.inputValue; | |
| if (this.overflown) { | |
| var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin, | |
| top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height; | |
| if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0); | |
| else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom); | |
| } | |
| if (this.selectMode) this.setSelection(); | |
| }, | |
| choiceSelect: function(choice) { | |
| if (choice) this.choiceOver(choice); | |
| this.setSelection(true); | |
| this.queryValue = false; | |
| this.hideChoices(); | |
| }, | |
| filter: function(tokens) { | |
| return (tokens || this.tokens).filter(function(token) { | |
| return this.test(token); | |
| }, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i')); | |
| }, | |
| /** | |
| * markQueryValue | |
| * | |
| * Marks the queried word in the given string with <span class="autocompleter-queried">*</span> | |
| * Call this i.e. from your custom parseChoices, same for addChoiceEvents | |
| * | |
| * @param {String} Text | |
| * @return {String} Text | |
| */ | |
| markQueryValue: function(str) { | |
| 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>'); | |
| }, | |
| /** | |
| * addChoiceEvents | |
| * | |
| * Appends the needed event handlers for a choice-entry to the given element. | |
| * | |
| * @param {Element} Choice entry | |
| * @return {Element} Choice entry | |
| */ | |
| addChoiceEvents: function(el) { | |
| return el.addEvents({ | |
| 'mouseover': this.choiceOver.bind(this, [el]), | |
| 'click': this.choiceSelect.bind(this, [el]) | |
| }); | |
| } | |
| }); | |
| var OverlayFix = new Class({ | |
| initialize: function(el) { | |
| if (Browser.Engine.trident) { | |
| this.element = $(el); | |
| this.relative = this.element.getOffsetParent(); | |
| this.fix = new Element('iframe', { | |
| 'frameborder': '0', | |
| 'scrolling': 'no', | |
| 'src': 'javascript:false;', | |
| 'styles': { | |
| 'position': 'absolute', | |
| 'border': 'none', | |
| 'display': 'none', | |
| 'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)' | |
| } | |
| }).inject(this.element, 'after'); | |
| } | |
| }, | |
| show: function() { | |
| if (this.fix) { | |
| var coords = this.element.getCoordinates(this.relative); | |
| delete coords.right; | |
| delete coords.bottom; | |
| this.fix.setStyles($extend(coords, { | |
| 'display': '', | |
| 'zIndex': (this.element.getStyle('zIndex') || 1) - 1 | |
| })); | |
| } | |
| return this; | |
| }, | |
| hide: function() { | |
| if (this.fix) this.fix.setStyle('display', 'none'); | |
| return this; | |
| }, | |
| destroy: function() { | |
| if (this.fix) this.fix = this.fix.destroy(); | |
| } | |
| }); | |
| Element.implement({ | |
| getSelectedRange: function() { | |
| if (!Browser.Engine.trident) return {start: this.selectionStart, end: this.selectionEnd}; | |
| var pos = {start: 0, end: 0}; | |
| var range = this.getDocument().selection.createRange(); | |
| if (!range || range.parentElement() != this) return pos; | |
| var dup = range.duplicate(); | |
| if (this.type == 'text') { | |
| pos.start = 0 - dup.moveStart('character', -100000); | |
| pos.end = pos.start + range.text.length; | |
| } else { | |
| var value = this.value; | |
| var offset = value.length - value.match(/[\n\r]*$/)[0].length; | |
| dup.moveToElementText(this); | |
| dup.setEndPoint('StartToEnd', range); | |
| pos.end = offset - dup.text.length; | |
| dup.setEndPoint('StartToStart', range); | |
| pos.start = offset - dup.text.length; | |
| } | |
| return pos; | |
| }, | |
| selectRange: function(start, end) { | |
| if (Browser.Engine.trident) { | |
| var diff = this.value.substr(start, end - start).replace(/\r/g, '').length; | |
| start = this.value.substr(0, start).replace(/\r/g, '').length; | |
| var range = this.createTextRange(); | |
| range.collapse(true); | |
| range.moveEnd('character', start + diff); | |
| range.moveStart('character', start); | |
| range.select(); | |
| } else { | |
| this.focus(); | |
| this.setSelectionRange(start, end); | |
| } | |
| return this; | |
| } | |
| }); | |
| /* compatibility */ | |
| Autocompleter.Base = Autocompleter; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Autocompleter.Local | |
| * | |
| * http://digitarald.de/project/autocompleter/ | |
| * | |
| * @version 1.1.2 | |
| * | |
| * @license MIT-style license | |
| * @author Harald Kirschner <mail [at] digitarald.de> | |
| * @copyright Author | |
| */ | |
| Autocompleter.Local = new Class({ | |
| Extends: Autocompleter, | |
| options: { | |
| minLength: 0, | |
| delay: 200 | |
| }, | |
| initialize: function(element, tokens, options) { | |
| this.parent(element, options); | |
| this.tokens = tokens; | |
| }, | |
| query: function() { | |
| this.update(this.filter()); | |
| } | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Autocompleter.LocalRelayed = new Class({ | |
| Extends: Autocompleter, | |
| options: { | |
| minLength: 0, | |
| delay: 200 | |
| }, | |
| initialize: function(element, fields_selector, tokens, options) { | |
| var self = this; | |
| element = $(element); | |
| element.relayEvent(fields_selector, 'keydown', function(e){ | |
| self.element = $(e.target); | |
| self.observe(self.element); | |
| }); | |
| this.parent(element, options); | |
| this.tokens = tokens; | |
| }, | |
| observe: function(element){ | |
| try{console.log( "Autocompleter.LocalRelayed#observe" );}catch(e){}; | |
| this.observer = this.element.retrieve('observer'); | |
| if(!this.observer) this.parent(element); | |
| this.element.store('observer', this.observer); | |
| }, | |
| query: function() { | |
| this.update(this.filter()); | |
| } | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Observer - Observe formelements for changes | |
| * | |
| * - Additional code from clientside.cnet.com | |
| * | |
| * @version 1.1 | |
| * | |
| * @license MIT-style license | |
| * @author Harald Kirschner <mail [at] digitarald.de> | |
| * @copyright Author | |
| */ | |
| var Observer = new Class({ | |
| Implements: [Options, Events], | |
| options: { | |
| periodical: false, | |
| delay: 1000 | |
| }, | |
| initialize: function(el, onFired, options){ | |
| this.element = $(el) || $$(el); | |
| this.addEvent('onFired', onFired); | |
| this.setOptions(options); | |
| this.bound = this.changed.bind(this); | |
| this.resume(); | |
| }, | |
| changed: function() { | |
| var value = this.element.get('value'); | |
| if ($equals(this.value, value)) return; | |
| this.clear(); | |
| this.value = value; | |
| this.timeout = this.onFired.delay(this.options.delay, this); | |
| }, | |
| setValue: function(value) { | |
| this.value = value; | |
| this.element.set('value', value); | |
| return this.clear(); | |
| }, | |
| onFired: function() { | |
| this.fireEvent('onFired', [this.value, this.element]); | |
| }, | |
| clear: function() { | |
| $clear(this.timeout || null); | |
| return this; | |
| }, | |
| pause: function(){ | |
| if (this.timer) $clear(this.timer); | |
| else this.element.removeEvent('keyup', this.bound); | |
| return this.clear(); | |
| }, | |
| resume: function(){ | |
| this.value = this.element.get('value'); | |
| if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this); | |
| else this.element.addEvent('keyup', this.bound); | |
| return this; | |
| } | |
| }); | |
| var $equals = function(obj1, obj2) { | |
| return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2)); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment