// SpryAutoSuggest.js - version 0.7 - Spry Pre-Release 1.6 // // Copyright (c) 2006. Adobe Systems Incorporated. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Adobe Systems Incorporated nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. var Spry; if (!Spry) Spry = {}; if (!Spry.Widget) Spry.Widget = {}; Spry.Widget.BrowserSniff = function() { var b = navigator.appName.toString(); var up = navigator.platform.toString(); var ua = navigator.userAgent.toString(); this.mozilla = this.ie = this.opera = r = false; var re_opera = /Opera.([0-9\.]*)/i; var re_msie = /MSIE.([0-9\.]*)/i; var re_gecko = /gecko/i; var re_safari = /safari\/([\d\.]*)/i; if (ua.match(re_opera)) { r = ua.match(re_opera); this.opera = true; this.version = parseFloat(r[1]); } else if (ua.match(re_msie)) { r = ua.match(re_msie); this.ie = true; this.version = parseFloat(r[1]); } else if (ua.match(re_safari)) { this.safari = true; this.version = 1.4; } else if (ua.match(re_gecko)) { var re_gecko_version = /rv:\s*([0-9\.]+)/i; r = ua.match(re_gecko_version); this.mozilla = true; this.version = parseFloat(r[1]); } this.windows = this.mac = this.linux = false; this.Platform = ua.match(/windows/i) ? "windows" : (ua.match(/linux/i) ? "linux" : (ua.match(/mac/i) ? "mac" : ua.match(/unix/i)? "unix" : "unknown")); this[this.Platform] = true; this.v = this.version; if (this.safari && this.mac && this.mozilla) { this.mozilla = false; } }; Spry.is = new Spry.Widget.BrowserSniff(); Spry.Widget.AutoSuggest = function(region, sRegion, dataset, field, options) { if (!this.isBrowserSupported()) return; options = options || {}; this.init(region, sRegion, dataset, field); Spry.Widget.Utils.setOptions(this, options); if (Spry.Widget.AutoSuggest.onloadDidFire) this.attachBehaviors(); else Spry.Widget.AutoSuggest.loadQueue.push(this); // when data is changing we will decide if we will have to show the suggestions this.dataset.addObserver(this); // Set up an observer so we can attach our click behaviors whenever // the region is regenerated. var regionID = Spry.Widget.Utils.getElementID(sRegion); var self = this; this._notifyDataset = { onPostUpdate: function() { self.attachClickBehaviors(); }, onPreUpdate: function(){ self.removeClickBehaviours(); }}; Spry.Data.Region.addObserver(regionID,this._notifyDataset); // clean up the widget when on page unload Spry.Widget.Utils.addEventListener(window, 'unload', function(){self.destroy()}, false); // make the first computation in case the textfield is not empty this.attachClickBehaviors(); this.handleKeyUp(null); this.showSuggestions(false); }; Spry.Widget.AutoSuggest.prototype.init = function(region, sRegion, dataset, field) { this.region = Spry.Widget.Utils.getElement(region); if (!this.region) return; this.minCharsType = false; this.containsString = false; this.loadFromServer = false; this.urlParam = ''; this.suggestionIsVisible = false; this.stopFocus = false; this.hasFocus = false; this.showSuggestClass = 'showSuggestClass'; this.hideSuggestClass = 'hideSuggestClass'; this.hoverSuggestClass = 'hoverSuggestClass'; this.movePrevKeyCode = Spry.Widget.AutoSuggest.KEY_UP; this.moveNextKeyCode = Spry.Widget.AutoSuggest.KEY_DOWN; this.textElement = Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel(this.region, "INPUT"); this.textElement.setAttribute('AutoComplete', 'off'); this.suggestRegion = Spry.Widget.Utils.getElement(sRegion); // prepare the suggest region Spry.Widget.Utils.makePositioned(this.suggestRegion); Spry.Widget.Utils.addClassName(this.suggestRegion, this.hideSuggestClass); this.timerID = null; if (typeof dataset == "string"){ this.dataset = window[dataset]; }else{ this.dataset = dataset; } this.field = field; if (typeof field == 'string' && field.indexOf(',') != -1) { field = field.replace(/\s*,\s*/ig, ','); this.field = field.split(','); } }; Spry.Widget.AutoSuggest.prototype.isBrowserSupported = function() { return Spry.is.ie && Spry.is.v >= 5 && Spry.is.windows || Spry.is.mozilla && Spry.is.v >= 1.4 || Spry.is.safari || Spry.is.opera && Spry.is.v >= 9; }; Spry.Widget.AutoSuggest.prototype.getValue = function() { if (!this.textElement) return ''; return this.textElement.value; }; Spry.Widget.AutoSuggest.prototype.setValue = function(str) { if (!this.textElement) return; this.textElement.value = str; this.showSuggestions(false); }; Spry.Widget.AutoSuggest.prototype.focus = function() { if (!this.textElement) return; this.textElement.focus(); }; Spry.Widget.AutoSuggest.prototype.showSuggestions = function(doShow) { if (this.region && this.isVisibleSuggestion() != doShow) { if (doShow && this.hasFocus) { Spry.Widget.Utils.addClassName(this.region, this.showSuggestClass); if (Spry.is.ie && Spry.is.version < 7) this.createIframeLayer(this.suggestRegion); } else { if (Spry.is.ie && Spry.is.version < 7) this.removeIframeLayer(); Spry.Widget.Utils.removeClassName(this.region, this.showSuggestClass); } } this.suggestionIsVisible = Spry.Widget.Utils.hasClassName(this.region, this.showSuggestClass); }; Spry.Widget.AutoSuggest.prototype.isVisibleSuggestion = function() { return this.suggestionIsVisible; }; Spry.Widget.AutoSuggest.prototype.onDataChanged = function(el) { var data = el.getData(true); var val = this.getValue(); this.showSuggestions(data && (!this.minCharsType || val.length >= this.minCharsType) && (data.length > 1 || (data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem("spry:suggest").value != this.getValue()))); }; Spry.Widget.AutoSuggest.prototype.nodeMouseOver = function(e, node) { var l = this.childs.length; for (var i=0; i this.scrolParent.scrollTop + h) { // the 5 pixels make the latest option more visible. this.scrolParent.scrollTop = el.offsetTop + el.offsetHeight - h + 5; if (this.scrolParent.scrollTop < 0) this.scrolParent.scrollTop = 0; } } }; Spry.Widget.AutoSuggest.KEY_UP = 38; Spry.Widget.AutoSuggest.KEY_DOWN = 40; Spry.Widget.AutoSuggest.prototype.handleSpecialKeys = function(e){ switch (e.keyCode) { case this.moveNextKeyCode: // Down key case this.movePrevKeyCode: // Up Key if (!(this.childs.length > 0) || !this.getValue()) return; var prev = this.childs.length-1; var next = false; var found = false; var data = this.dataset.getData(); if (this.childs.length > 1 || (data && data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem('spry:suggest').value != this.getValue())) { this.showSuggestions(true); } else return; var utils = Spry.Widget.Utils; for (var k=0; k < this.childs.length; k++) { if (next) { utils.addClassName(this.childs[k], this.hoverSuggestClass); this.scrollVisible(this.childs[k]); break; } if (utils.hasClassName(this.childs[k], this.hoverSuggestClass)) { utils.removeClassName(this.childs[k], this.hoverSuggestClass); found = true; if (e.keyCode == this.moveNextKeyCode) { next = true; continue; } else { utils.addClassName(this.childs[prev], this.hoverSuggestClass); this.scrollVisible(this.childs[prev]); break; } } prev = k; } if (!found || (next && k == this.childs.length)) { utils.addClassName(this.childs[0], this.hoverSuggestClass); this.scrollVisible(this.childs[0]); } utils.stopEvent(e); break; case 27: // ESC key this.showSuggestions(false); break; case 13: //Enter Key if (!this.isVisibleSuggestion()) return; for (var k=0; k < this.childs.length; k++) if (Spry.Widget.Utils.hasClassName(this.childs[k], this.hoverSuggestClass)) { var attr = this.childs[k].attributes.getNamedItem('spry:suggest'); if (attr){ this.setValue(attr.value); this.handleKeyUp(null); } // stop form submission Spry.Widget.Utils.stopEvent(e); return false; } break; case 9: //Tab Key this.showSuggestions(false); } return; }; Spry.Widget.AutoSuggest.prototype.filterDataSet = function() { var contains = this.containsString; var columnName = this.field; var val = this.getValue(); if (this.previousString && this.previousString == val) return; this.previousString = val; if (!val || (this.minCharsType && this.minCharsType > val.length)) { this.dataset.filter(function(ds, row, rowNumber) {return null;}); this.showSuggestions(false); return; } var regExpStr = Spry.Widget.Utils.escapeRegExp(val); if (!contains) regExpStr = "^" + regExpStr; var regExp = new RegExp(regExpStr, "ig"); if (this.maxListItems > 0) this.dataset.maxItems = this.maxListItems; var filterFunc = function(ds, row, rowNumber) { if (ds.maxItems >0 && ds.maxItems <= ds.data.length) return null; if (typeof columnName == 'object') { var l = columnName.length; for (var i=0; i < l; i++) { var str = row[columnName[i]]; if (str && str.search(regExp) != -1) return row; } } else { var str = row[columnName]; if (str && str.search(regExp) != -1) return row; } return null; }; this.dataset.filter(filterFunc); var data = this.dataset.getData(); this.showSuggestions(data && (!this.minCharsType || val.length >= this.minCharsType) && (data.length > 1 || (data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem('spry:suggest').value != val ))); }; Spry.Widget.AutoSuggest.prototype.loadDataSet = function() { var val = this.getValue(); var ds = this.dataset; ds.cancelLoadData(); ds.useCache = false; if (!val || (this.minCharsType && this.minCharsType > val.length)) { this.showSuggestions(false); return; } if (this.previousString && this.previousString == val) { var data = ds.getData(); this.showSuggestions(data && (data.length > 1 || (data.length == 1 && this.childs[0].attributes.getNamedItem("spry:suggest").value != val))); return; } this.previousString = val; var url = Spry.Widget.Utils.addReplaceParam(ds.url, this.urlParam, val); ds.setURL(url); ds.loadData(); }; Spry.Widget.AutoSuggest.prototype.addMouseListener = function(node, value) { var self = this; var addListener = Spry.Widget.Utils.addEventListener; addListener(node, "click", function(e){ return self.nodeClick(e, value); self.handleKeyUp(null);}, false); addListener(node, "mouseover", function(e){ Spry.Widget.Utils.addClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); addListener(node, "mouseout", function(e){ Spry.Widget.Utils.removeClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); }; Spry.Widget.AutoSuggest.prototype.removeMouseListener = function(node, value) { var self = this; var removeListener = Spry.Widget.Utils.removeEventListener; removeListener(node, "click", function (e){ self.nodeClick(e, value); self.handleKeyUp(null);}, false); removeListener(node, "mouseover", function(e){ Spry.Widget.Utils.addClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); removeListener(node, "mouseout", function(e){ Spry.Widget.Utils.removeClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); }; Spry.Widget.AutoSuggest.prototype.attachClickBehaviors = function() { var self = this; var valNodes = Spry.Utils.getNodesByFunc(this.region, function(node) { if (node.nodeType == 1) /* Node.ELEMENT_NODE */ { var attr = node.attributes.getNamedItem("spry:suggest"); if (attr){ self.addMouseListener(node, attr.value); return true; } } return false; }); this.childs = valNodes; }; Spry.Widget.AutoSuggest.prototype.removeClickBehaviours = function() { var self = this; var valNodes = Spry.Utils.getNodesByFunc(this.region, function(node) { if (node.nodeType == 1) /* Node.ELEMENT_NODE */ { var attr = node.attributes.getNamedItem("spry:suggest"); if (attr){ self.removeMouseListener(node, attr.value); return true; } } return false; }); }; Spry.Widget.AutoSuggest.prototype.destroy = function(){ this.removeClickBehaviours(); Spry.Data.Region.removeObserver(Spry.Widget.Utils.getElementID(this.suggestRegion),this._notifyDataset); if (this.event_handlers) for (var i=0; i0) { if(isFirstEntry) { camelizedString = oStringList[i]; isFirstEntry = false; } else { var s = oStringList[i]; camelizedString += s.charAt(0).toUpperCase() + s.substring(1); } } } return camelizedString; }; Spry.Widget.Utils.getStyleProp = function(element, prop) { var value; var camel = Spry.Widget.Utils.camelize(prop); try { value = element.style[camel]; if (!value) { if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css.getPropertyValue(prop) : null; } else if (element.currentStyle) value = element.currentStyle[camel]; } } catch (e) {} return value == 'auto' ? null : value; }; Spry.Widget.Utils.makePositioned = function(element) { var pos = Spry.Widget.Utils.getStyleProp(element, 'position'); if (!pos || pos == 'static') { element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } }; Spry.Widget.Utils.escapeRegExp = function(rexp) { return rexp.replace(/([\.\/\]\[\{\}\(\)\\\$\^\?\*\|\!\=\+\-])/g, '\\$1'); }; Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel = function(node, nodeName) { var elements = node.getElementsByTagName(nodeName); if (elements) return elements[0]; return null; };