/** * Show suggestions for query of an input. * * The `.remotesearchUiSuggest()` is only applicable to input elements from type text. * * This widget displays suggests for the current value of the input field while typing. * * ### Examples * Apply .remotesearchUiSuggest() to input field with id 'ProductSearchField'. * * JavaScript: * * de_epages('#ProductSearchField') * .remotesearchUiSuggest({ * suggestUrl: '?ViewAction=ViewRemoteSearchSuggests&ObjectID=12345', * searchFilter: 'input[name=filter]', * suggestMax: 8, * showImages: false * }); * * HTML: * * <form action="?ViewAction=ViewRemoteSearchResults&ObjectID=12345" method="post"> * <input type="hidden" name="filter"/> * <input type="text" name="SearchString" id="ProductSearchField" value=""/> * <button type="submit" name="SearchButton">{Search}</button> * </form> * * * @class jQuery.ui.remotesearchUiSuggest * @extends jQuery.widget * * @uses jQuery.json * @uses jQuery.tmpl * @uses jQuery.ui.widget * @uses ep * @uses jQuery.dict * @uses ep.fn.contextOrientation * @uses ep.ui.validate * @uses de_epages * @since 6.14.0 */ /** * @cfg {String} [searchFilter] A string containing a selector expression, a DOM element, an existing jQuery object for the filter input. */ /** * @cfg {String} suggestUrl A url to get suggestions for the query, the server needs to support JSONP via the callback argument. */ /** * @cfg {Integer} [suggestChars] An integer value of minimum required characters to start the suggest search. */ /** * @cfg {Integer} [suggestMax] An integer value of maximum displayed suggestions. */ /** * @cfg {Boolean} [showImages] A boolean indication whether to display images in the suggest list. */ /** * See `jQuery.ui.remotesearchUiSuggest` for details. * * @param {Object} [options] A map of additional options pass to the method. * @param {String} [searchFilter] A string containing a selector expression, a DOM element, an existing jQuery object for the filter input. * @param {String} suggestUrl A url to get suggestions for the query, the server needs to support JSONP via the callback argument. * @param {Integer} [suggestChars] An integer value of minimum required characters to start the suggest search. * @param {Integer} [suggestMax] An integer value of maximum displayed suggestions. * @param {Boolean} [showImages] A boolean indication whether to display images in the suggest list. * * @method remotesearchUiSuggest * @member jQuery * * @since 6.14.0 */ /* * @copyright © Copyright 2006-2011, epages GmbH, All Rights Reserved. * * @module de_epages.remotesearch.ui.suggest */ define("de_epages/remotesearch/ui/suggest", [ "jquery", "ep", "de_epages", "util/string", "$dict!../dictionary", "$tmpl!./suggest", "jquery/ui/widget", "ep/fn/contextorientation", "ep/ui/validate" ], function ($ , ep, de_epages, str, remotesearchDict, tmplSuggest) { /* * @dictionary de_epages.remotesearch.dictionary * * @translation {Products} * {Categories} * {Manufacturers} * {SearchFor} */ var tmplDict = { products : remotesearchDict.translate("Products"), categories : remotesearchDict.translate("Categories"), manufacturers : remotesearchDict.translate("Manufacturers"), searchfor : remotesearchDict.translate("SearchFor") }; $.widget( "ui.remotesearchUiSuggest", $.ui.uiValidate, { options: { // searchFilter : "selector input", // suggestUrl : "?", suggestChars : 1, suggestMax : 10, showImages : true }, _create: function(){ this._superApply(arguments); var self = this, o = self.options; // hidden input for filter options self.filter = o.filter ? $(o.searchFilter) : $('<input type="hidden"/>').insertAfter(self.elem); self.elem .attr("autocomplete","off") .on("keydown", $.proxy(self, "_keydown")) .on("keyup", $.proxy(self, "_keyup")) .on("focus", $.proxy(self, "_show")) .on("blur", $.proxy(self, "_hide")); // list container self.list = $('<div class="de_epages-remotesearchUiSuggest-box"/>') .on('mouseenter','li',function(){ $(this).addClass('ui-hover'); }) .on('mouseleave','li',function(){ $(this).removeClass('ui-hover'); }) .on('mousedown','li:not(.Separator)',function( event ){ self._select( $(this).data('index') ); self._action( event ); // move action in other tread setTimeout(function(){ self.elem.trigger('focus'); },1); }); // caching for fast suggests self.cache = {}; self.dataModels = {}; // default selected index of suggest items self.selectedIndex = -1; // setup an object like jqXHR to provide an abort method self.xhr = {abort: $.noop}; // basic shr settings self.xhrSettings = { type : "get", dataType: "jsonp", data : { // datasource : ep.config.storeName, // shopGUID : ep.config.siteGuid, // lang : ep.config.language } }; if( o.suggestUrl ){ self.xhrSettings.url = o.suggestUrl; } }, _keydown: function( event ){ var keyCode = event.keyCode || event.which; if( keyCode == 38 || keyCode == 40 ){ event.preventDefault(); } else if( keyCode == 13 ){ this._action( event ); } }, _keyup: function( event ){ var self = this, keyCode = event.keyCode || event.which, query = self.elem.val(), o = self.options; if( keyCode == 9 || keyCode == 13 || keyCode == 37 || keyCode == 39 || keyCode == 32 ){ return; } else if( keyCode == 38 ){ self._select( 'prev' ); } else if( keyCode == 40 ){ self._select( 'next' ); } else if( query.length < self.options.suggestChars ){ self._clear(); } else if( self.cache[ query ] ){ self._build(); } else{ if (self.lastAjaxFail) { if ((new Date().getTime() - self.lastAjaxFail) < 60000) { // in case a request failed less than 1 minute ago // we will not bother the server this time. return; } else { // let us try talking to the server again now. self.lastAjaxFail = null; } } $.ajax( $.extend(true,{ data:{ q: query } }, self.xhrSettings) ) .done(function( response ){ $.extend( self.cache[ query ] || (self.cache[ query ] = {}), response ); self._build(); }) .fail(function() { self.lastAjaxFail = new Date().getTime(); }); } }, _hide: function(){ this.list.hide(); }, _show: function(){ if( this.list.find('li').length ){ this.list.show(); } }, _clear: function(){ this.list.empty(); this._hide(); }, _dataModel: function( query ){ var self = this, dataModel = self.dataModels[ query ], data; if (!dataModel) { data = self.cache[ query ]; dataModel = { query : query, list : [], dict : tmplDict }; if (data) { $.each(["products", "manufacturers", "categories"], function (i, type) { data[type] = $.each(data[type] || [], function () { this.type = type; }); dataModel.list = dataModel.list.concat(data[type]); }); // Cache model self.dataModels[ query ] = dataModel; } } // Use current options dataModel.o = self.options; return dataModel; }, _build: function(){ var self = this, query = self.elem.val(), dataModel = self.dataModel = self._dataModel(query), rHighlight = new RegExp( "("+str.escExpStr(query)+")", "ig"); self._clear(); self.list .appendTo("body") .contextOrientation(self.elem,"bottom") .append( tmplSuggest( [dataModel], { highlight: function( text ){ return text.replace(rHighlight,"<strong>$1</strong>"); }, separatorLast: '', separator: function( type ){ return this.separatorLast == type ? false : !!(this.separatorLast = type); } } ) ); //check and correct min-width of suggestbox var listWidth = self.list.width(), boxWidth = self.elem.parent().innerWidth(); if(boxWidth > listWidth){ self.list.width(boxWidth); } if(self.options.navbar){ self.list.addClass(self.options.navbar); } self._select(-1); self._show(); // trigger event if suggest was built $(document).trigger('suggestsearch:build', {list: self.list, elem: self.elem}); }, _select: function( i ){ var self = this, set = self.selectedIndex, items = self.list.find('li:not(.Separator)'); // reset filter data self.filterData = {}; if( i == 'next' ){ set++; } else if( i == 'prev' ){ set--; } else{ set = i; } if( set >= items.length ){ set = -1 } else if( set < -1 ){ set = items.length - 1; } items.removeClass('ui-select ui-active'); if( set != -1 ){ items.eq(set) .addClass('ui-select ui-active'); self.filterData = (self.dataModel.list[ set ] || {}).filter || {}; } if( $.type(self.filterData) === "object" ){ self.filter .attr("name", self.filterData.fieldName || "" ) .val(self.filterData.attributeValue || "" ); } self.selectedIndex = set; }, _action: function( event ){ var self = this; event.preventDefault(); // Special: go to page by object id if( $.type(self.filterData) === "number" ){ location.href = ep.config.baseUrl + "?ObjectID=" + self.filterData; } // Default: submit form else{ self.elem .closest('form') .trigger('submit'); } } }); return de_epages; });