/*-------------------------------------------------------- * Copyright © 2009 – 2010* France Telecom * This software is distributed under the "Simplified BSD license", * the text of which is available at http://www.winktoolkit.org/licence.txt * or see the "license.txt" file for more details. *--------------------------------------------------------*/ /** * The wink namespace and main object * * @methods: * --> byId: returns a DOM element * --> translate: returns the translated value of a key * --> isUndefined: returns true if the given parameter is undefined, false otherwise * --> isNull: returns true if the given parameter is null, false otherwise. * --> isSet: returns true if the given parameter is set, false otherwise. * --> isCallback: return true if the given callback object is valid (contains at least a method) * --> isString: returns true if the given parameter is a string, false otherwise. * --> isInteger: returns true if the given parameter is an integer * --> isNumber: returns true if the given parameter is a number * --> isArray: returns true if the given parameter is an array * --> isBoolean: returns true if the given parameter is a boolean * --> trim: returns the given string parameter trimed * --> bind: binds a method to a given context * --> call: invokes the given callback * --> setTimeout: calls a deferred method * --> setInterval: calls a deferred method * --> getUId: generates a unique identifier * * @compatibility: * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Jerome GIRAUD */ if(typeof wink == 'undefined') { wink = {}; } wink.version = 'wo_1.2.1', wink.api = {}; wink.fx = {}; wink.math = {}; wink.mm = {}; wink.net = {}; wink.ui = { form: {}, layout: {}, xy: {}, xyz: {} }; wink.ux = {}; /** * Returns a DOM element * * @parameters: * --> id: the identifier of the DOM element to return */ wink.byId = function(id) { if (this.isString(id)) { return document.getElementById(id); } else { return id; } }, /** * Returns the translated value of a key * * @parameters: * --> key: the key identifying a ressource * --> object: the component that holds the resource list (i18n) */ wink.translate = function(key, object) { try { if (this.isSet(object)) { var resourceList = object.i18n; } else { var resourceList = i18n; } var str = resourceList[key]; if (this.isUndefined(str)) { str = key; } return str; }catch(e) { return key; } }; /** * Returns true if the given parameter is undefined, false otherwise. * * @parameters: * --> object: the object to test */ wink.isUndefined = function(object) { return (object === undefined); }; /** * Returns true if the given parameter is null, false otherwise. * * @parameters: * --> object: the object to test */ wink.isNull = function(object) { return (object === null); }; /** * Returns true if the given parameter is set, false otherwise. * * @parameters: * --> object: the object to test */ wink.isSet = function(object) { return ((!this.isUndefined(object)) && (!this.isNull(object))); }; /** * @deprecated must use wink.isSet */ wink.isset = function(object) { wink.log('wink.isset is deprecated : must use wink.isSet'); return this.isSet(object); }; /** * Returns true if the given callback object is valid (contains at least a method. It can also contain a context) * * @parmameters: * --> callback: the object to test */ wink.isCallback = function(callback) { return !!(callback && callback.method); }; /** * Returns true if the given parameter is a string, false otherwise. * * @parameters: * --> object: the object to test */ wink.isString = function(object) { return (typeof object == "string" || object instanceof String); }; /** * Returns true if the given parameter is an integer * * @parameters: * --> object: the object to test */ wink.isInteger = function(object) { return (parseInt(object)===object); }; /** * Returns true if the given parameter is a number * * @parameters: * --> object: the object to test */ wink.isNumber = function(object) { return (typeof object == "number" || object instanceof Number); }; /** * Returns true if the given parameter is an array * * @parameters: * --> object: the object to test */ wink.isArray = function(object) { return (typeof object == "array" || object instanceof Array); }; /** * Returns true if the given parameter is a boolean * * @parameters: * --> object: the object to test */ wink.isBoolean = function(object) { return (typeof object == "boolean" || object instanceof Boolean); }; /** * Returns the given string parameter trimed. * * @parameters: * --> str: the string to trim */ wink.trim = function(str) { return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }; /** * Binds a method to a given context * * @parameters: * --> method: the method to bind * --> context: the scope with which the method will be executed */ wink.bind = function(method, context) { return function() { return method.apply(context, arguments); }; }; /** * Invokes the given callback * * @parameters: * --> callback: the callback to invoke. The callback must be an object containing a 'method' and a 'context'. * --> parameters: parameters to pass to the callback */ wink.call = function(callback, parameters) { var context = window; var method = callback.method; var args = []; if (this.isSet(callback.context)) { context = callback.context; } if (arguments.length == 2) { args = [parameters]; } if (wink.isSet(callback.arguments)) { var additional = callback.arguments; if (!wink.isArray(additional)) { additional = [callback.arguments]; } args = args.concat(additional); } context[method].apply(context, args); }; /** * Calls a deferred method. * * @parameters: * --> context: the execution context of the method to call * --> method: the method to call * --> delay: time to wait before calling method * --> arg1, arg2 ...: a list of caller arguments */ wink.setTimeout = function(context, method, delay /*[, arg1 [, arg2 ... ] ]*/) { var args = Array.prototype.slice.call(arguments, 3); var toExecute = function() { context[method].apply(context, args); }; return setTimeout(toExecute, delay); }; /** * Calls a deferred method. * * @parameters: * --> context: the execution context of the method to call * --> method: the method to call * --> delay: time to wait before calling method * --> arg1, arg2 ...: a list of caller arguments */ wink.setInterval = function(context, method, delay /*[, arg1 [, arg2 ... ] ]*/) { var args = Array.prototype.slice.call(arguments, 3); var toExecute = function() { context[method].apply(context, args); }; return setInterval(toExecute, delay); }; /** * Generates a unique identifier */ wink.getUId = function() { return (new Date().getTime() - Math.ceil(Math.random()*1000)); }; //Bindings if (wink.isUndefined(window._)){_ = wink.bind(wink.translate, wink);} if (wink.isUndefined(window.$)){$ = wink.bind(wink.byId, wink);}/*-------------------------------------------------------- * Copyright © 2009 – 2010* France Telecom * This software is distributed under the "Simplified BSD license", * the text of which is available at http://www.winktoolkit.org/licence.txt * or see the "license.txt" file for more details. *--------------------------------------------------------*/ /** * Implements an event management system based on a publish/subscribe mechanism * * @methods: * --> subscribe: attach to the given topic, pass a context and a callback method. If the context is null, it is considered as global * --> unsubscribe: detach from the given topic * --> publish: publish a topic to all the subscribers, pass the given parameters to the subscribers * * @compatibility: * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Jerome GIRAUD */ wink.topics = { _subscribed_topics: [], /** * Attach to the given topic * * @parameters: * --> topic: the name of the topic we want to be notified from * --> callback: the callback method called when the event related to the topic is triggered. It should contain a 'method' and a 'context'. */ subscribe: function(topic, callback) { if (!wink.isCallback(callback)) { wink.log('[topics] invalid callback argument'); return; } var subscription = [topic.toLowerCase(), callback]; this._subscribed_topics.push(subscription); }, /** * Detach from the given topic * * @parameters: * --> topic: the name of the topic we don't want to be notified from anymore. * --> callback: This parameter should be the same as the one passed through the subscribe method */ unsubscribe: function(topic, callback) { var st = this._subscribed_topics; for (var i = 0; i < st.length; i++) { if (st[i][0] == topic.toLowerCase() && st[i][1].method == callback.method && st[i][1].context == callback.context) { st.splice(i, 1); } } this._subscribed_topics = st; }, /** * Publish an event to all the subscribers * * @parameters: * --> topic: the name of the topic we are triggering * --> value: the value to pass to the subscribers' callback methods */ publish: function(topic, value) { this._dispatch(topic.toLowerCase(), value); }, /** * Triggers all the events which are in the queue * * @parameters: * --> topic: the name of the topic we are triggering * --> parameters: the value to pass to the subscribers' callback methods */ _dispatch: function(topic, parameters) { var st = this._subscribed_topics; for (var i = 0; i < st.length; i++) { if (st[i] && st[i][0] == topic) { if ( wink.isSet(st[i][1])) { wink.call(st[i][1], parameters); } } } } }; // Bindings wink.publish = wink.bind(wink.topics.publish, wink.topics); wink.subscribe = wink.bind(wink.topics.subscribe, wink.topics); wink.unsubscribe = wink.bind(wink.topics.unsubscribe, wink.topics);/*-------------------------------------------------------- * Copyright © 2009 – 2010* France Telecom * This software is distributed under the "Simplified BSD license", * the text of which is available at http://www.winktoolkit.org/licence.txt * or see the "license.txt" file for more details. *--------------------------------------------------------*/ /** * XHR utility * * @methods: * --> sendData: make an XHR call to the given URL * * @attributes: * --> uId: unique identifier of the component * --> request: an object containing the actual XMLHttpRequest and optionally parameters * * @properties: * --> properties: parameters that will be stored within the request object and can be used in the callbacks methods * * @compatibility: * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Jerome GIRAUD */ wink.net.Xhr = function(properties) { this.uId = wink.getUId(); this.request = { xhrObject: null, params: properties }; /** * Initiate the component */ this._create(); }; wink.net.Xhr.prototype = { /** * Send the datas * * @parameters: * --> url: the URL to call * --> parameters: the parameters to add to the request URL * --> method: either GET or POST * --> successCallback: the method to call in case of success. The 'callback' is an object that must contain a 'method' and a 'scope' * --> failureCallback: the method to call in case of success. The 'callback' is an object that must contain a 'method' and a 'scope' * --> headers: the HTTP headers to add to the request */ sendData: function(url, parameters, method, successCallback, failureCallback, headers) { var r = this.request, xo = r.xhrObject, enc = encodeURIComponent; method = method.toUpperCase(); if ( wink.isSet(parameters) && !wink.isArray(parameters) ) { wink.log('[Xhr] parameters must be in an array of objects containing the parameter name and value'); return; } if (xo) { var p = null; if ( wink.isSet(parameters) ) { var l = parameters.length; for (var i=0; i= 200 && xo.status < 400) || xo.status == 0)) { if ( wink.isSet(failureCallback) ) { wink.call(failureCallback, r); } } if (xo.readyState == 4 && ((xo.status >= 200 && xo.status < 400) || xo.status == 0)) { if ( wink.isSet(successCallback) ) { wink.call(successCallback, r); } } }; } else { return false; } return true; }, /** * Instantiate a new XMLHttpRequest */ _create: function() { if (window.XMLHttpRequest) { var xo; try { xo = new window.XMLHttpRequest(); } catch (e) { xo = false; } this.request.xhrObject = xo; } else { wink.log('[Xhr] XHR not supported'); } } }; //Bindings wink.Xhr = wink.net.Xhr; /*-------------------------------------------------------- * Copyright © 2009 – 2010* France Telecom * This software is distributed under the "Simplified BSD license", * the text of which is available at http://www.winktoolkit.org/licence.txt * or see the "license.txt" file for more details. *--------------------------------------------------------*/ /** * JSON utility * * @methods: * --> parse: Test the validity of a JSON structure and evaluate it * --> concat: Concatenate the given JSON structures * * @compatibility: * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Jerome GIRAUD */ wink.json = { /** * Test the validity of a JSON structure and evaluate it * * @parameters * --> str: the string to evaluate */ parse: function(str) { if (wink.isSet(window.JSON) && wink.isSet(window.JSON.parse)) { return window.JSON.parse(str); } else { var JSON = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(str.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + str + ')'); return JSON; } }, /** * Concatenate the given JSON structures * * @parameters * --> obj1: the object that will be updated with the second * --> obj2: the object that will be concatenated to the first */ concat: function(obj1, obj2) { for (var key in obj2) { obj1[key] = obj2[key]; } } }; //Bindings wink.parseJSON = wink.bind(wink.json.parse, wink.json);/*-------------------------------------------------------- * Copyright © 2009 – 2010* France Telecom * This software is distributed under the "Simplified BSD license", * the text of which is available at http://www.winktoolkit.org/licence.txt * or see the "license.txt" file for more details. *--------------------------------------------------------*/ /** * JSON stringification - a wink.json extension. * * @methods: * --> stringify: Return the JSON representation of a given object * * @compatibility * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Mathieu HELIOT */ wink.json.concat(wink.json, { _stack: new Array(), /** * Return the JSON representation of a given value * Value can be an JS object or an array * Values as "undefined" and functions hasn't string representation : * - in arrays these values are represented as the String null, * - in objects these values causes the property to be excluded from stringification. * Named properties are excluded from the stringification. * See also ECMAScript 5 specifications * for more informations about the JSON structure. * * @parameters * --> value: the object or array to transform */ stringify: function(value) { var str; /** * verify if value exists */ if ( value ) { if ( wink.isSet(window.JSON) && wink.isSet(window.JSON.stringify) ) { str = window.JSON.stringify(value); } else { str = wink.json._str(value); } } return str; }, /** * Transform an object to a validate JSON structure * according to ECMAScript 5 specifications. * ReturnS 'undefined' for 'undefined' and function values * * @parameters * --> value: the value to transform */ _str: function(value) { var str; var indent = ''; var wrapper = new Object(); /** * if exists a native toJSON() method for this object, * let priority to this one */ if ( value && wink.isSet(value.toJSON) ) { str = value.toJSON(); if ( wink.isString(str) ) str = this._quote(str); } /** * else do manually the transformation */ else { /** * if value is of type String * safety stringification of this one. */ if ( wink.isString(value) ) str = this._quote(value); /** * if value is a numeric value, * serializes it */ else if ( wink.isNumber(value) ) str = ( isFinite(value) ) ? value : 'null'; /** * if value is null */ else if ( wink.isNull(value) ) str = 'null'; /** * if value is of type Boolean */ else if ( wink.isBoolean(value) ) str = ( value ) ? 'true' : 'false'; /** * if value is an array, * serializes it by calling _JA() private method ; * else is an JavaScript Object, * tipicaly those which are delimited by '{' and '}', * and serializes it by calling _JO() private method */ else if ( !wink.isUndefined(value) && typeof(value) != 'function' ) str = ( wink.isArray(value) ) ? this._JA(value) : this._JO(value); } return str; }, /** * Wraps a String value in double quotes and escapes characters within it. * * @parameters * --> str: string to process */ _quote: function(str) { /** * Character substitution function for common escapes and special characters */ function _char(c) { var chars = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }; if ( !chars[c] ) chars[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4); return chars[c]; } var product = '"', txt = str, specialChars = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; str = txt.replace(specialChars, _char); /** * add double quotes */ str = product + str + product; return str; }, /** * Serializes an object. * * @parameters: * --> object: object to serialize */ _JO: function(object) { var propertyList = new Array(), partial = new Array(); /** * Ensure that the structure isn't cyclical */ for ( var i=0 ; i array: array to serialize */ _JA: function(array) { var partial = new Array(), separator = ',', str; /** * Ensure that the structure isn't cyclical */ for ( var i=0 ; i log: display a log message if the log level has been set to 1 * * @attributes: * --> logLevel: If set to 0, no message will be displayed. If set to 1, the log messages will be displayed. The default value is 0. * * @compatibility: * --> Iphone OS2, Iphone OS3, Iphone OS4, Android 1.1, Android 1.5, Android 2.1, BlackBerry 6 * * @author: * --> Jerome GIRAUD */ wink.error = { logLevel: 0, /** * Display a log message if the log level has been set to 1 * if the console is defined, use the console, otherwise, alert the user * * @parameters: * --> value: content of the log */ log: function(value) { if ( this.logLevel == 1) { if ( console != undefined ) { console.log(value); } else { alert(value); } } } }; //Bindings wink.log = wink.bind(wink.error.log, wink.error); /** * Completion Component Orange V4 * * Exemple d'initialisation: * * var blockProperties = * [ * { * "label" : "suggestions", * "max" : 7, * "baseId" : -1 * }, * { * "label" : "acces direct", * "max" : 1, * "baseId" : 10, * "type" : "auto", * "options" : {"submitCallback" : "testSubmit"} * } * ]; * * // properties for completion * var properties = * { * "divCompletion" : $("completionBox"), * "field" : $("fieldCompletion"), * "onSubmitFunction" : "testSubmit", * "url" : "http://completion.qualif.ke.voila.fr/fr/cmpliphone/xml/fullxml", * "queryParamName" : "kw", * "additionalParams" : "version=2&dtd=2.0&cbf=cmpl.cmpl", * "cssPrefix" : "orangesearch_", * "clearAccents" : false, * "blocks" : blockProperties * "plugins" : ["DirectLink", "LocalHistory"] * }; * * // instance for completion * var cmpl = new orangesearch.CompletionComponent(properties); * cmpl.start(); // il faut demarrer le composant */ if(typeof orangesearch == 'undefined') { /** * @namespace Holds functionality related to orange search project. */ orangesearch = {}; } if(typeof orangesearch.completion == 'undefined') { /** * @namespace Holds functionality related to completion project. */ orangesearch.completion = {}; } if(typeof orangesearch.completion.plugin == 'undefined') { /** * @namespace Holds functionality related to plugin completion project. */ orangesearch.completion.plugin = {}; } /** * Define the completion component object * with default values * * @class Component class * * @param properties an array containing all completion properties * * @returns Component the completion object */ var Component = function(properties) { // Update the completion component count and initialize the number Component.count += 1; /** * Indicate if the submit process must be stopped * @type boolean */ this._abortSubmit = false; /** * Indicate if we have to display the completion * @type boolean */ this._abortDisplay = false; /** * Additionnal parameters sent to the completion service. * @type string */ this.additionalParams = null; /** * Indicate if we have to apply a delay when managing * the mouse over event on a completion result. * @type integer in ms */ this.applyDelayOnMouseOver = 0; /** * The block configuration. * @type array */ this.blocks = []; /** * The current blocks used for the display process * @type array */ this._currentBlocks = []; /** * Indicate if the cache is activated. * @type boolean */ this._cacheActivated = true; /** * Contains the list of callbacks * * @type array */ this._callbacks = []; /** * Indicate if we have to change the input field value * when navigating on the completion with the mouse. * @type boolean */ this.changeOnMouseOver = false; /** * Indicate if we have to check the input during the process * @type boolean */ this.checkInput = true; /** * Indicate if we have to parse completion result to remove accent. * @type boolean */ this.clearAccents = false; /** * Indicate if we have to clear user query and results from all default separator chars. * @type boolean */ this.clearSeparatorChars = false; /** * Indicate if we have to process the onBlur action * on the field when onBlur event occured. * @type boolean */ this.closeCompletionOnBlur = true; /** * The current suggestion box. * @type HTMLElement */ this._completionDiv = null; /** * The default identifier of the completion box. * @type string */ this._completionDivId = null; /** * Indicate if we have to add a close link * at the end of the suggestion list. * @type boolean */ this._completionCloseLink = false; /** * The parent of the completion box used to retrieve * global size like width, height, top and left. * @type HTMLElement */ this._completionDivContainer = null; /** * Indicate the delay of the completion div unroll (in ms). * @type integer */ this.completionDivUnrollDelay = 0; /** * CSS prefix added to all css styles. * @type string */ this.cssPrefix = ''; /** * The current input field value. * @type string */ this._currentFieldValue = ''; /** * Indicate the debug mode. * @type boolean */ this.debugMode = false; /** * Default value for block label. * @type string */ this.defaultBlockLabel = "Suggestions"; /** * Default value for block base id. * @type integer */ this.defaultBlockBaseId = -1; /** * Disable the onBlur action * @type boolean */ this._disableOnBlur = true; /** * The enable status * @type boolean */ this._enabled = false; /** * The input field of the auto completer. * @type HTMLInputElement */ this.field = null; /** * The field form. * @type HTMLFormElement */ this._fieldForm = null; /** * The current highlighted suggestion index. * @type integer */ this._highlightedSuggestionIndex = 0; /** * Indicate if we have to finally submit the form. * @type boolean */ this.isFormSubmit = true; /** * The maximum number of characters for each suggestion. * Set property to "false" if no maximum is needed * @type integer || false */ this.maxNbChar = false; /** * The number of maximum suggestions. * @type integer */ this.maxNbSuggestions = 10; /** * The minimum query length to call the completion service. * @type integer */ this.minQueryLength = 1; /** * This attribute match the name of completer instance to callback after a completion request. * It uses JsonP with a script eval. * Disable the normal XHR request. * * @type string * @deprecated can be used to allow cross-domain restriction access but it present a security breach */ this.nameOfInstanceForJsonP = false; /** * The instance unique identifier of the autocompleter. * @type integer */ this._uid = Component.count; /** * The old input field value for internal use. * @type string */ this._oldInputFieldValue = ""; /** * Set of associated completion plugins. * @type array */ this.plugins = []; /** * The position reference that will be saved during init DOM. * @type HTMLElement */ this._positionReference = null; /** * Internal properties of the auto completer. * @type object */ this._properties = {}; /** * The parameter name corresponding to the user query. * @type string */ this.queryParamName = "kw"; /** * The result cache. * @type array */ this._resultCache = null; /** * The service status from JSON completion response. * @type string */ this._serviceStatus = ""; /** * The current selected suggestion index. * @type integer */ this._selectedSuggestionIndex = 0; /** * Indicate if the auto completer is started or not. * @type boolean */ this._started = false; /** * Define the final method to call before submitting form. * This method is called after all block callbacks. * @type object callback */ this.submitCallback = null; /** * List of suggestions for current response. * @type SuggestionList */ this._suggestionsList = new orangesearch.completion.SuggestionList(); /** * The default suggestion prefix for result row identification. * @type string */ this._suggestionPrefixId = "completionResultLine_"; /** * Indicate if we have to bold the word in completion. * @type boolean */ this.useBoldInCompletion = true; /** * The URL to call to retrieve suggestion result. * @type string */ this.url = null; /** * Indicate if we have to run the completion on input click and input is empty * @type boolean */ this.onClickCompletion = false; /** * The keyword that will be used with the completion on click * @type string */ this.onClickCompletionKeyword = 'basic'; /** * The completion base id used with the completion on click * @type string */ this.onClickCompletionId = '0'; /** * The URL for completion call on Click * @type string */ this._onClickCompletionUrl = ''; /** * The XHR manager to call ajax request * @type Xhr */ this._xhrManager = new wink.Xhr(); // Save properties if available if (properties) this.setProperties(properties); // Return the new completion component return this; }; /** * Initialize completion component static elements * @type integer * @static */ Component.count = 0; Component.prototype = { /** * Define the main key map * used by key listeners. * * @static */ keyCode: { 'keyBad' : 0, 'keyOtherEnter' : 3, 'keyTab' : 9, 'keyEnter' : 13, 'keyShift' : 16, 'keyAlt' : 18, 'keyCapsLock' : 20, 'keyEscape' : 27, 'keyLeft' : 37, 'keyUp' : 38, 'keyRight' : 39, 'keyDown' : 40 }, /** * Set the new properties of the auto completer. * * @param array properties * @returns boolean true if correctly set */ setProperties: function(properties) { // Check that auto completer is not enabled if (this._started) { wink.log("The autocompleter is started, you can not set properties"); return false; } if (wink.isUndefined(properties) || wink.isNull(properties)) { wink.log("You try to set invalid properties"); return false; } // Load the new properties into internal properties array orangesearch.tools.mixin(this._properties, properties); return true; }, /** * Start the auto completer by initializing * properties, plugins, default callbacks, and DOM. * Should be run only one time. * */ start: function() { if (this._started) { wink.log("Error : Completion already started"); return false; } if (!orangesearch.tools.checkEventCompatibility()) { wink.log("Error : Completion need attachEvent or addEventListener navigator support"); return false; } // Initialize properties if (!this._initProperties()) { wink.log('Completion properties has not been set correctly'); return false; } // Initialize the urls this._initUrl(); // Initialize the DOM this._initDom(); // Initialize plugins this._initPlugins(); // Initialize plugins properties, after creation of plugin instances this._initPluginProperties(); // Initialize subscriptions this._initSubscriptions(); // Enable the completion by initializing if (!this.enable()) return false; // Set as started this._started = true; return true; }, /** * Disable the completion * by removing listeners on leyboard and mouse * and disabling the display of results * */ disable: function() { if (!this._started || !this._enabled) { wink.log("Error : Could not disable a non started or disabled completion"); return false; } // Clean the completion box this.hideCompletionDiv(); this.disableCompletionDisplay(); this.emptyCompletionResults(); // Disable all listeners this._disableListeners(); this._disableOnBlur = true; this._enabled = false; return true; }, /** * Enable or Re enable the completion * By adding listeners on keyboard and mouse * and enabling the display of results * */ enable: function() { if (this._enabled) { wink.log("Error : Could not enable an already enabled completion"); return false; } // Enable all listeners this._enableListeners(); // Re-enable the onBlur action this._disableOnBlur = false; // Allow the display of completion box this.enableCompletionDisplay(); this._enabled = true; return true; }, /** * Return the enable status * * @return boolean */ isEnabled: function() { return this._enabled; }, /** * Update the new properties * and restart the auto completer. * */ restart: function(properties) { if (!properties) return false; // Disable the completion if (!this.disable()) return false; // Stop the completer before restart this._started = false; // Remove subscriptions this._removeSubscriptions(); // Remove plugins this._removePlugins(); // Remove the DOM container this._removeDom(); // Set the new properties if (!this.setProperties(properties)) return false; // Restart the completion return this.start(); }, /** * Return true if the auto completer * is correctly started * * @return boolean */ isStarted: function() { return this._started; }, /** * This function enable the completion * and set the listeners. * */ _enableListeners: function() { // Initialize the form this._initFormListener(); // Initialize listeners this._initFieldListeners(); // Initialize plugin listeners this._initPluginFieldListeners(); // Initialize the onBlur listener this._initOnBlurListener(); }, /** * This function disable the completion * by removing listeners on the keyboard. * */ _disableListeners: function() { // Remove the form listener this._removeFormListener(); // Remove all listeners in the input field this._removeFieldListeners(); // Remove plugin listeners this._removePluginFieldListeners(); // Remove the onBlur listener this._removeOnBlurListener(); }, /** * Disable the completion display * */ disableCompletionDisplay: function() { this._abortDisplay = true; }, /** * Enable the completion display * */ enableCompletionDisplay: function() { this._abortDisplay = false; }, /** * Initialize Listeners in the completion component. * Listen on the keyup, keydown, and click events. * */ _initFieldListeners: function() { // Declare default listener for keydown and keyup orangesearch.tools.addEventListener(this.field, "keydown", this.manageFieldOnKeyDown); orangesearch.tools.addEventListener(this.field, "keyup", this.manageFieldOnKeyUp); // Declare optional listener for completion on click if (this.onClickCompletion) orangesearch.tools.addEventListener(this.field, "click", this.manageFieldOnClick); }, /** * Remove Listeners in the completion component. * Remove on the keyup, keydown, and click events. * */ _removeFieldListeners: function() { orangesearch.tools.removeEventListener(this.field, "keydown", this.manageFieldOnKeyDown); orangesearch.tools.removeEventListener(this.field, "keyup", this.manageFieldOnKeyUp); orangesearch.tools.removeEventListener(this.field, "click", this.manageFieldOnClick); }, /** * Initialize plugin listeners in the completion * Plugin may overwrite listener on the keyup, * keydown event. */ _initPluginFieldListeners: function() { // Loop on plugin and check if a plugin implement the method for (var p = 0 ; p < this.plugins.length ; p++) { // If the plugin implement the method, return its result if (this.plugins[p].initFieldListeners) { this.plugins[p].initFieldListeners(); break; } } }, /** * Remove plugin listeners in the completion * Plugin may overwrite listener on the keyup, * keydown event. */ _removePluginFieldListeners: function() { // Loop on plugin and check if a plugin implement the method for (var p = 0 ; p < this.plugins.length ; p++) { // If the plugin implement the method, return its result if (this.plugins[p].removeFieldListeners) { this.plugins[p].removeFieldListeners(); break; } } }, /** * Initialize the on blur listener on the field * */ _initOnBlurListener: function() { orangesearch.tools.addEventListener(this.field, "blur", this.manageFieldOnBlur); }, /** * Remove the on blur listener of the field * */ _removeOnBlurListener: function() { orangesearch.tools.removeEventListener(this.field, "blur", this.manageFieldOnBlur); }, /** * Initialize the listener on the form submit * */ _initFormListener: function() { if (this._fieldForm) { this._submitListener = wink.bind(function(event) { // block the default submit orangesearch.tools.stopEvent(event); // call internal submit method if (this.manageFormOnSubmit(this, event)) this._fieldForm.submit(); }, this); orangesearch.tools.addEventListener(this._fieldForm, "submit", this._submitListener); } }, /** * Remove the optional listener on the * form (if the form exists) */ _removeFormListener: function() { if (this._fieldForm) { orangesearch.tools.removeEventListener(this._fieldForm, "submit", this._submitListener); this._submitListener = false; } }, /** * This function initialize the completion box in the DOM. * It checks if properties contains a specific container * or create a new one if not. * * @returns boolean true if correctly initialized */ _initDom: function() { // If the div is given by the user if (this._properties.divCompletion) { // Just add the CSS style orangesearch.tools.addClass(this._completionDiv, this.getCssPrefix()); this._positionReference = null; // Hide the div this._completionDiv.style.visibility = 'hidden'; this._completionDiv.style.display = 'none'; return true; } // Create the reference DIV with its CSS classname this._positionReference = document.createElement('div'); // Append mandatory style to make the reference this._positionReference.style.position = 'relative'; this._positionReference.style.margin = '0'; this._positionReference.style.padding = '0'; this._positionReference.style.width = '0'; this._positionReference.style.height = '0'; // Put the positionReference before the input field this.field.parentNode.insertBefore(this._positionReference, this.field); // Create the parent of the completion div that will be used to retrieve // global size like height, width, top and left this._completionDivContainer = document.createElement('div'); // Add mandatory style to make the relative container this._completionDivContainer.style.position = 'absolute'; this._completionDivContainer.style.margin = '0'; this._completionDivContainer.style.padding = '0'; this._completionDivContainer.style.border = '0'; this._completionDivContainer.style.top = this.field.offsetHeight + 'px'; // Put the container in the reference this._positionReference.appendChild(this._completionDivContainer); // Create the completion box this._completionDiv = document.createElement('div'); if (this._completionDivId) this._completionDiv.setAttribute('id', this._completionDivId); else this._completionDiv.setAttribute('id', "divCompletion_" + this._uid); // Add the CSS style of the completion Box orangesearch.tools.addClass(this._completionDiv, this.getCssPrefix() + " box"); // Add the mandatory style to allow manual positioning this._completionDiv.style.position = 'relative'; // Force the hide of the completion box this._completionDiv.style.visibility = 'hidden'; this._completionDiv.style.display = 'none'; // Finally append the completion box in its container this._completionDivContainer.appendChild(this._completionDiv); return true; }, /** * This function remove the completion box from the DOM. * */ _removeDom: function() { // Destroy the position reference if already exist (we have to re create it) if (this._positionReference) this._positionReference.parentNode.removeChild(this._positionReference); }, /** * Initialize the set of plugins. * * @returns boolean true if plugins properties are found */ _initPlugins: function() { if (!this._properties.plugins) return false; var pluginInstance = null; for (var p = 0 ; p < this._properties.plugins.length ; p++) { // Try to create the plugin using evaluation if (!orangesearch.completion.plugin[this._properties.plugins[p]]) { wink.log('The plugin ' + this._properties.plugins[p] + ' does not exists !'); continue; } // Retrieve and check an instance of plugin pluginInstance = new orangesearch.completion.plugin[this._properties.plugins[p]](); if (!pluginInstance) { wink.log('The plugin ' + this._properties.plugins[p] + ' does not exists !'); continue; } // Check if the plugin can be used in the current context if (pluginInstance.canBeUsed) { // Skip the plugin if it can not be used if (!pluginInstance.canBeUsed()) continue; } // Set the auto completer pluginInstance.setCompletionComponent(this); // Initialize the plugin if we have to if (pluginInstance.initPlugin) pluginInstance.initPlugin(); // Finally append the plugin this.plugins.push(pluginInstance); } return true; }, /** * Remove the set of plugins */ _removePlugins: function() { for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].removePlugin) this.plugins[p].removePlugin(); } this.plugins = []; }, /** * Initialize subscriptions. * First in main component then in plugins. * */ _initSubscriptions: function() { for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].initSubscription) this.plugins[p].initSubscription(); } }, /** * Remove all subscriptions * */ _removeSubscriptions: function() { for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].removeSubscription) this.plugins[p].removeSubscription(); } }, /** * Initialize plugins properties. * */ _initPluginProperties: function() { for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].initProperties) this.plugins[p].initProperties(); } }, /** * Set the debug mode * of the auto completer * * @param boolean value the debug mode value * * @return boolean */ setDebugMode: function(value) { if (!wink.error) return false; if (value) { this.debugMode = true; wink.error.logLevel = 1; } else { this.debugMode = false; wink.error.logLevel = 0; } }, /** * This function parse properties and set members values. * It checks the minimum required parameters. * * @returns boolean true if correctly set */ _initProperties: function() { if (!this._properties) { wink.log ("properties is missing"); return false; } // // Property URL (mandatory) // if (wink.isUndefined(this._properties.url) || !this._properties.url) { wink.log ("property 'url' cannot be null"); return false; } // Set the url this.url = this._properties.url; // // Property FIELD (mandatory) // if (wink.isUndefined(this._properties.field) || !this._properties.field) { wink.log ("property 'field' cannot be null"); return false; } // Set the field this.field = this._properties.field; // Check if the current form is different from the new field.form if (this.field.form) this._fieldForm = this.field.form; // Make a completion component reference in the input field this.field.autoCompleter = this; // Set the onClickCompletion before initialize field listeners if (!wink.isUndefined(this._properties.onClickCompletion)) this.onClickCompletion = this._properties.onClickCompletion; // // Property DIVCOMPLETION // if (!wink.isUndefined(this._properties.divCompletion)) { if (typeof this._properties.divCompletion == "object") this._completionDiv = this._properties.divCompletion; else if (typeof this._properties.divCompletion == "string") this._completionDiv = wink.byId(this._properties.divCompletion); else { wink.log ("property 'divCompletion' must be an HTMLElement or a string identifier"); return false; } } // // OTHERS PROPERTIES // // Update blocks if (!wink.isUndefined(this._properties.blocks)) { // Check for Object/Array if (this._properties.blocks.constructor != Object && this._properties.blocks.constructor != Array) return false; if (typeof this._properties.blocks.length != "undefined" && this._properties.blocks.length > 0) { // case numeric array: set the block content as default block this.blocks = { "default" : orangesearch.tools.clone(this._properties.blocks) }; } else if (this._properties.blocks.constructor == Object) { if (!this._properties.blocks["default"]) return false; this.blocks = orangesearch.tools.clone(this._properties.blocks); } // case associative array: not allowed else return false; } // Update callbacks if (this._properties.callbacks) this._callbacks = this._properties.callbacks; // Update others optional properties if (this._properties.nameOfInstanceForJsonP) this.nameOfInstanceForJsonP = this._properties.nameOfInstanceForJsonP; if (this._properties.onClickCompletionKeyword) this.onClickCompletionKeyword = this._properties.onClickCompletionKeyword; if (this._properties.onClickCompletionId) this.onClickCompletionId = this._properties.onClickCompletionId; if (!wink.isUndefined(this._properties.checkInput)) this.checkInput = this._properties.checkInput; if (this._properties.divCompletionId) this._completionDivId = this._properties.divCompletionId; if (this._properties.completionDivUnrollDelay) this.completionDivUnrollDelay = this._properties.completionDivUnrollDelay; if (this._properties.applyDelayOnMouseOver) this.setDelayOnMouseOver(this._properties.applyDelayOnMouseOver); if (!wink.isUndefined(this._properties.closeCompletionOnBlur)) this.closeCompletionOnBlur = this._properties.closeCompletionOnBlur; if (this._properties.disableCache) this._cacheActivated = false; if (this._properties.queryParamName) this.queryParamName = this._properties.queryParamName; if (this._properties.additionalParams) this.additionalParams = this._properties.additionalParams; if (this._properties.cssPrefix) this.cssPrefix = this._properties.cssPrefix; if (!wink.isUndefined(this._properties.maxNbChar) && (this._properties.maxNbChar === false || wink.isInteger(this._properties.maxNbChar) > 0)) // false if no maximum given or a number this.maxNbChar = this._properties.maxNbChar; if (this._properties.maxNbSuggestions && wink.isInteger(this._properties.maxNbSuggestions) > 0) this.maxNbSuggestions = this._properties.maxNbSuggestions; if (this._properties.minQueryLength) this.minQueryLength = this._properties.minQueryLength; if (!wink.isUndefined(this._properties.clearAccents)) this.clearAccents = this._properties.clearAccents; if (!wink.isUndefined(this._properties.clearSeparatorChars)) this.clearSeparatorChars = this._properties.clearSeparatorChars; if (this._properties.closeLink) this._completionCloseLink = true; if (this._properties.isFormSubmit === false) this.isFormSubmit = false; if (!wink.isUndefined(this._properties.defaultBlockLabel)) this.defaultBlockLabel = this._properties.defaultBlockLabel; if (!wink.isUndefined(this._properties.defaultBlockBaseId)) this.defaultBlockBaseId = this._properties.defaultBlockBaseId; if (this._properties.changeOnMouseOver) this.changeOnMouseOver = true; if (this._properties.useBoldInCompletion) this.useBoldInCompletion = true; // Initialize the cache this._resultCache = new Array(); return true; }, /** * Initialize the onClickCompletion URL * using the URL value * */ _initOnClickCompletionUrl: function() { this._onClickCompletionUrl = this.url + '?id=' + this.onClickCompletionId + '&' + this.queryParamName + '=' + this.onClickCompletionKeyword; // Append the name of instance if we use the JSON P if (this.nameOfInstanceForJsonP) this._onClickCompletionUrl += '&jsonp=' + this.nameOfInstanceForJsonP + '.completionCallBack'; }, /** * Initalize the URL value of the host * with basic values. * */ _initUrl: function() { // Set default value if block is not defined if (!this.blocks || this.blocks.length == 0) { this.blocks = { "default" : [{ "label" : this.defaultBlockLabel, "baseId" : this.defaultBlockBaseId, "max" : this.maxNbSuggestions }] }; } // First initialize the onClickCompletion URL ('before modifiying the url value) if (this.onClickCompletion) this._initOnClickCompletionUrl(); var defaultBlock = this.blocks["default"]; // If only one block : construction of simple url if (defaultBlock.length == 1) { var baseId = defaultBlock[0].baseId ? defaultBlock[0].baseId : this.defaultBlockBaseId; var an = defaultBlock[0].max ? defaultBlock[0].max : this.maxNbSuggestions; this.url = this.url + '?id=' + baseId + '&an=' + an; } else { // If more of one block : construction of complex url var baseIds = []; var ans = []; // Some blocks don't need base id in specific case : history block have no url... // So we separate block index (i), associated to json results array index, and block id (idBlock) // for create the completion url to call (with an1, an2, ...) for(var i = 0, idBlock = 0 ; i < defaultBlock.length ; i++) { if (!defaultBlock[i].baseId) continue; if (!defaultBlock[i].max) defaultBlock[i].max = this.maxNbSuggestions; baseIds[idBlock] = defaultBlock[i].baseId; ans[idBlock] = 'an' + (idBlock+1) + '=' + defaultBlock[i].max; idBlock++; } // Create the basic URL this.url = this.url + '?id=' + baseIds.join(',') + '&an=' + this.maxNbSuggestions + '&' + ans.join('&'); } // Append additionnal parameters var addParams = this.additionalParams ? '&' + this.additionalParams : ''; this.url += addParams; // Append the name of instance if we use the JSON P if (this.nameOfInstanceForJsonP) this.url += '&jsonp=' + this.nameOfInstanceForJsonP + '.completionCallBack'; return true; }, /** * This function unhighlight all suggestions. * */ _unHighlightAllSuggestions: function() { // Check if we have some suggestion if (this.getSizeOfSuggestionsList() <= 0) return; // Unselect all suggestion by removing the CSS style AutoCompleteHighlightedSuggestion for(var i = 1 ; i <= this.getSizeOfSuggestionsList() ; i++) orangesearch.tools.removeClass(wink.byId(this.getSuggestionPrefix() + i), "highlighted"); }, /** * This function reset the completion * by setting default style on all completion results * and resetting the current index and highlighted suggestion. * */ resetCompletion: function() { // Hide or show the completion DIV if (this.checkInput && (this._currentFieldValue == "" || this.getSizeOfSuggestionsList() == 0)) this.hideCompletionDiv(); // Publish the reset event wink.publish('/callback/resetCompletion/reset', { 'uid' : this._uid }); // Unselect all suggestions this._unHighlightAllSuggestions(); // Reset all highlighted suggestion parameters (DIV and index) this._highlightedSuggestionIndex = 0; this._selectedSuggestionIndex = 0; }, /** * Manage the onMouseDown event on a suggestion. * This function run an optional callback * and submit the form. * * WARNING: the context is a HTMLElement object (suggestion row) * * @param Component autoCompleter The auto completer component * @param Event event The mouse event * @param HTMLElement selectedHtmlElement The suggestion row */ manageDivOnMouseDown: function(autoCompleter, event, selectedHtmlElement) { // Retrieve the suggestion and put into the input field of the completer (also in the current value) autoCompleter.field.value = autoCompleter.getSuggestionParam(selectedHtmlElement.id, "v"); autoCompleter._currentFieldValue = autoCompleter.field.value; // Update the selected suggestion index autoCompleter._selectedSuggestionIndex = autoCompleter._highlightedSuggestionIndex; // Hide the completion autoCompleter.hideCompletionDiv(); // Reset the cursor autoCompleter.pushFieldCursor(); // Run the submit if we have a form if (autoCompleter.field.form) autoCompleter.callSubmit(event); }, /** * Manage the onMouseOver event on a suggestion. * This function check if we have to manage the mouse action with a delay. * If there is no delay, this function call directly the process * to update the highlighted element. * * WARNING: the context is a HTMLElement object (suggestion row) * * @param Component autoCompleter The auto completer component * @param Event event The mouse event * @param HTMLElement highlightedHtmlElement The suggestion row * */ manageDivOnMouseOver: function(autoCompleter, event, highlightedHtmlElement) { // Check if we have to apply the delay if (autoCompleter.applyDelayOnMouseOver) { // Retrieve the selected index from the select var highlightedIndex = autoCompleter.getSuggestionParam(highlightedHtmlElement.id, 'index'); autoCompleter._highlightedSuggestionIndex = highlightedIndex; setTimeout(function() { autoCompleter.checkDivOnMouseOverSuggestion(highlightedIndex, event, highlightedHtmlElement); }, autoCompleter.applyDelayOnMouseOver); } else { // Call directly the method autoCompleter.highlightNewValueOnMouseOver(event, highlightedHtmlElement); } }, /** * This function check if the current highlighted * index of the completion is the same as the specified * theoreticalNewIndex. * If its the case, run the highlight function. * In other case, do nothing (Another process will handle the index). * * @param integer theoreticalNewIndex The theoretical new index * @param Event event The onMouseOver event * @param HTMLElement highlightedHtmlElement The suggestion row * */ checkDivOnMouseOverSuggestion: function(theoreticalNewIndex, event, highlightedHtmlElement) { // Check if we have to show the suggestion if (this._highlightedSuggestionIndex == theoreticalNewIndex && document.activeElement == this.field) { // The current highlighted index is the same as theoretical index -> show this.highlightNewValueOnMouseOver(event, highlightedHtmlElement); } }, /** * This function highlight the new value * and update the field value if required (property changeOnMouseOver). * * @param Event event The onMouseOver event * @param HTMLElement highlightedHtmlElement The suggestion row */ highlightNewValueOnMouseOver: function(event, highlightedHtmlElement) { // Check the index based on the highlighted completion div var newIndex = this.getSuggestionParam(highlightedHtmlElement.id, 'index'); if (!newIndex) return; // Disable the cursor up/down this._cursorUpDownPressed = false; // Highlight the new value with the index this.highlightNewValue(newIndex); // If change on mouse over, change the search value (not on the mouseDown) if (this.changeOnMouseOver) { // Update the value of the input field this.field.value = this.getSuggestionParam(highlightedHtmlElement.id, "v"); // Update the selected suggestion index this._selectedSuggestionIndex = this._highlightedSuggestionIndex; } }, /** * This function highlight the new value * and update the field value. * * @param integer highlightedCompletionIndex The suggestion index */ highlightNewValueOnKeyDown: function(highlightedCompletionIndex) { // Highlight the new value with the index this.highlightNewValue(highlightedCompletionIndex); // Update the selected suggestion index this._selectedSuggestionIndex = this._highlightedSuggestionIndex; // Update the field value if there is a highlighted suggestion if (this._highlightedSuggestionIndex > 0) { var newValue = this.getSuggestionParam(this._getSuggestionId(this._highlightedSuggestionIndex), "v"); if (newValue) this.field.value = newValue; } }, /** * Manage the onMouseOut event on a suggestion. * Reset the input field value if required (property changeOnMouseOver). * * WARNING: the context is a HTMLElement object (suggestion row) * * @param Component autoCompleter The auto completer * @param Event event The mouse event * @param HTMLElement selectedHtmlElement The suggestion row */ manageDivOnMouseOut: function(autoCompleter, event, selectedHtmlElement) { // Reset the initial value if we have to if (autoCompleter.changeOnMouseOver) autoCompleter.field.value = autoCompleter._currentFieldValue; }, /** * Return the suggestion identifier * for a specific index. * * @param integer index The suggestion index * * @returns string The suggestion identifier */ _getSuggestionId: function(index) { if (!index) return null; return this.getSuggestionPrefix() + index; }, /** * Return the suggestion row * for a specific index. * * @param integer index The suggestion index * * @returns HTMLElement The HTML suggestion row */ getSuggestionNode: function(index) { if (!index) return null; return wink.byId(this._getSuggestionId(index)); }, /** * Return true if there is a selected suggestion index * or false in other case. * * @returns boolean */ isSelectedSuggestion: function() { return (this._selectedSuggestionIndex > 0); }, /** * This function is use to submit * manually the form. * */ callSubmit: function(event, params) { if (this.manageFormOnSubmit(this, event, params)) this.field.form.submit(); }, /** * Manage the submit on the form. * First publish a beforeSubmit notification. * Secondly call blocks and final user callbacks. * And finally submit the form if required. * * @param Component autoCompleter The auto completer * @param Event event The mouse event * * @returns boolean False if the form should not be submitted */ manageFormOnSubmit: function(autoCompleter, event, params) { // Hide the completion autoCompleter.hideCompletionDiv(); // Give the focus to the input of the auto completer orangesearch.tools.focusOnInputEnd(autoCompleter.field); autoCompleter._abortSubmit = false; // Add a notification before submitting the form wink.publish('/callback/manageFormOnSubmit/beforeSubmit', { 'uid' : autoCompleter._uid, 'autoCompleter': autoCompleter, 'event' : event, 'query' : autoCompleter.field.value }); // If a subscriber has cancelled the submit, stop the process if (autoCompleter._abortSubmit) { autoCompleter._abortSubmit = false; return false; } var index = autoCompleter._highlightedSuggestionIndex; // Manage the parameters if (!wink.isSet(params) || typeof params != "object") params = {}; params['id'] = autoCompleter.field.id; params['div'] = autoCompleter.getSuggestionNode(index); params['query'] = autoCompleter.field.value; // Check if we have a specific callback function depend on the block if (index > 0) { // Retrieve the current value and block id var highlightedValue = autoCompleter.getSuggestionParam(autoCompleter._getSuggestionId(index), "v"); var highlightedBlockId = autoCompleter.getSuggestionParam(autoCompleter._getSuggestionId(index), "blockId"); // Run the callback if it exists and // only if the query and the highlighted value are identical if (autoCompleter._currentBlocks[highlightedBlockId].submitCallback && autoCompleter._currentFieldValue == highlightedValue) { var cb = wink.callAndReturn(autoCompleter._currentBlocks[highlightedBlockId].submitCallback, params); // if the callback return false, so we stop the end of submitting if (cb === false) return false; } } // Check if we have a final callback to call if (autoCompleter._callbacks && autoCompleter._callbacks.submit && wink.isCallback(autoCompleter._callbacks.submit)) { var cb = wink.callAndReturn(autoCompleter._callbacks.submit, params); // if the callback return false, so we stop the end of submitting if (cb === false) return false; } // Finally check if we have to submit the form if (!autoCompleter.isFormSubmit) return false; return true; }, /** * Manage the onClick event. * Try to invoke the completion with specific * values * * WARNING: the context this is a HTMLElement object (input field) * * @param Event event the mouse event */ manageFieldOnClick: function(event) { // Check if the content of the field is empty if (this.value.length > 0) return; // Check if event come from window if (!event && window.event) event = window.event; // Check event if (!event) return false; // Finally call the basic manage function return this.autoCompleter.defaultFieldOnClickCallBack(event); }, /** * The default FieldOnClick callback. * It run the completion call with specific * parameters * * @param Event event the mouse event * */ defaultFieldOnClickCallBack: function(event) { if (this.nameOfInstanceForJsonP) { // Call writeScript version orangesearch.tools.writeScript(this._onClickCompletionUrl, 'jsonCompletionCallBackScriptId'); } else { // Call XmlHttpRequest var successCallback = { context: this, method: "completionCallBack" }; var failureCallback = { context: this, method: "completionFailureCallBack" }; this._xhrManager.sendData(url, null, 'GET', successCallback, failureCallback, null); } }, /** * Manage the onKeyUp event. * Try to invoke the plugin method and call the * component method if no plugin handle the key event. * * WARNING: the context this is a HTMLElement object (input field) * * @param Event event the key event */ manageFieldOnKeyUp: function(event) { // Check if event come from window if (!event && window.event) event = window.event; // Check event if (!event) return false; // Finally call the basic manage function return this.autoCompleter.defaultFieldOnKeyUpCallBack(event); }, /** * The default FieldOnKeyUp callback. * It check the keycode and handle the search. * * @param Event event the key event * */ defaultFieldOnKeyUpCallBack: function(event) { // Check the key code switch(event.keyCode) { case this.keyCode.keyAlt: case this.keyCode.keyCapsLock: case this.keyCode.keyTab: case this.keyCode.keyBad: case this.keyCode.keyEnter: case this.keyCode.keyOtherEnter: case this.keyCode.keyShift: return; case this.keyCode.keyEscape: // Reset the input field with the current field value this.field.value = this._currentFieldValue; this.hideCompletionDiv(); return; // if up or down key, blur the field and re-get focus just after case this.keyCode.keyDown: case this.keyCode.keyUp: this._cursorUpDownPressed = true; this.pushFieldCursor(); break; default: // If the key is not UP/DOWN store the value this._cursorUpDownPressed = false; this._currentFieldValue = this.field.value; break; } // Clear the current input field to avoid searching for " a " (" a " -> "a ") this._currentFieldValue = orangesearch.tools.cleanQuery(this._currentFieldValue); // Check if we have to update the completion by comparing field value if (this._oldInputFieldValue != this._currentFieldValue) { // Remove white space for the current value var query = encodeURIComponent(this._currentFieldValue); // Check if the value is not empty and > minimum requiered size if (query != '' && query.length >= this.minQueryLength && typeof(query) != 'undefined') { // Try to retrieve the previous suggestion (1 letter less). First check if value exists and is not a native function (map, key, ...) var previousWord = this._currentFieldValue.substring(0, this._currentFieldValue.length - 1); var previousSuggestion = this._resultCache.hasOwnProperty(previousWord) ? this._resultCache[previousWord] : false; // Check if the previous is not empty or null if (!previousSuggestion || previousSuggestion.length != 0) { // Retrieve current suggestion. First check if value exists and is not a native function (map, key, ..) var suggestions = this._resultCache.hasOwnProperty(this._currentFieldValue) ? this._resultCache[this._currentFieldValue]: false; // Check if current suggestion exist if (suggestions) { // Suggestion exist in cache, just show it this._currentJsonResultsData = suggestions; this.displayCompletionResults(this._currentFieldValue.toString); } else { // Call the service to retrieve completion suggestion for current value var url = this.url + '&' + this.queryParamName + '=' + query; if (this.nameOfInstanceForJsonP) { // Call writeScript version orangesearch.tools.writeScript(url, 'jsonCompletionCallBackScriptId'); } else { // Call XmlHttpRequest var successCallback = { context: this, method: "completionCallBack" }; var failureCallback = { context: this, method: "completionFailureCallBack" }; this._xhrManager.sendData(url, null, 'GET', successCallback, failureCallback, null); } // Give the focus to the field of the completer this.field.focus(); } } else { // The previous suggestion exist and is empty - there is no more completion result to show // Save the current completion as empty array in cache this.addCacheResult(this._currentFieldValue, new Array()); // Hide the completion div this.hideCompletionDiv(); } } else this.hideCompletionDiv(); } // Update the old input field value this._oldInputFieldValue = this._currentFieldValue; }, /** * Manage the onKeyDown event. * Try to invoke the plugin method and call the * component method if no plugin handle the key event. * * WARNING: the context this is a HTMLElement object (input field) * * @param Event event the key event */ manageFieldOnKeyDown: function(event) { // Check if event come from window if (!event && window.event) event = window.event; // Check event if (!event) return false; // Finally call the basic manage function return this.autoCompleter.defaultFieldOnKeyDownCallBack(event); }, /** * The default FieldOnKeyDown callback. * It check the keycode, try to handle special keys (up/down/right/enter...) * and reset the completion in other case. * * @param Event event the key event */ defaultFieldOnKeyDownCallBack: function(event) { // Try to handle up/down/enter key switch (event.keyCode) { case this.keyCode.keyDown: this.highlightNewValueOnKeyDown(this._highlightedSuggestionIndex + 1); break; case this.keyCode.keyUp: this.highlightNewValueOnKeyDown(this._highlightedSuggestionIndex - 1); break; case this.keyCode.keyEnter: case this.keyCode.keyOtherEnter: case this.keyCode.keyAlt: case this.keyCode.keyCapsLock: case this.keyCode.keyTab: case this.keyCode.keyBad: case this.keyCode.keyShift: break; default: this.resetCompletion(); return; } }, /** * Manage the onBlur event. * This function is called when the input field lose the focus. * Hide the completion box if required (property closeCompletionOnBlur * or auto completer disabled) * * WARNING: the context this is a HTMLElement object (input field) * * @param Event event the key event */ manageFieldOnBlur: function(event) { // Check that we have a completer in the input field if (!this.autoCompleter) return; // Check if we have to blur (user choice) if (!this.autoCompleter.closeCompletionOnBlur) return; // Check if the blur action is internally disabled if (this.autoCompleter._disableOnBlur) return; // If blur is not cause by up/down key -> hide the completion if (!this.autoCompleter._cursorUpDownPressed) this.autoCompleter.hideCompletionDiv(); // Reset _cursorUpDownPressed value this.autoCompleter._cursorUpDownPressed = false; }, /** * Highlight the new suggestion * using a specified index * * @param newIndexValue the row index value */ highlightNewValue: function(newIndexValue) { // If no result - return if (this.getSizeOfSuggestionsList() == 0) return; // Show the completion div box if not visible yet this.showCompletionDiv(); // Unselect all suggestions this._unHighlightAllSuggestions(); // Check if we are going out of bound of the list if (newIndexValue < 0) newIndexValue = this.getSizeOfSuggestionsList(); else if (newIndexValue > this.getSizeOfSuggestionsList()) newIndexValue = 0; // Check if we are in the user entry value if (newIndexValue == 0) { // Reset the highlighted suggestion this.field.value = this._currentFieldValue; this._highlightedSuggestionIndex = 0; } else { // Save the highlighted index this._highlightedSuggestionIndex = newIndexValue; // Finally set style for the new highlighted DIV and update the field value if (this._highlightedSuggestionIndex > 0) { // Set the selected style for the new div and update the input field value orangesearch.tools.addClass(this.getSuggestionNode(this._highlightedSuggestionIndex), "highlighted"); // Update the scroll top this._completionDiv.scrollTop = newIndexValue * this.getSuggestionNode(this._highlightedSuggestionIndex).offsetHeight; } } // Publish that we have highlighted one element wink.publish('/callback/highlightNewValue/suggestionHighlighted', { 'uid' : this._uid, 'div': this.getSuggestionNode(this._highlightedSuggestionIndex), 'index' : this._highlightedSuggestionIndex }); }, /** * This function is called by the Ajax request on success. * Fill the completion with the JSON data. * * @param jsonData the JSON response */ completionCallBack: function(response) { // Check if we have to display if (this._abortDisplay) return; var jsonData = null; // JSONP Request using WriteScript if (this.nameOfInstanceForJsonP) jsonData = response; else { // Ajax request using XHR object if (!response.xhrObject.responseText) return; jsonData = wink.parseJSON(response.xhrObject.responseText); } // Check the structure of the JSON data if (!jsonData['serviceStatus'] || wink.isUndefined(jsonData['data']) || wink.isUndefined(jsonData['query'])) return; // Switch on the service status switch(jsonData['serviceStatus']) { case "SUSPENDED": case "KO_CONFIG": case "KO_PARSE": case "KO_MISC": case "LOCK": // For some bad reason, stop the auto completer and hide it this.disable(); return; break; case "NO_STATUS": case "TO": case "KO_HTTP": case "KO_CONNECT": // For some bad reason, do not store response in cache and do not display anything this._currentJsonResultsData = this.getEmptyResultTemplate(); break; default: // Append current response to cache this.addCacheResult(jsonData['query'], jsonData['data']); // Display the result this._currentJsonResultsData = jsonData['data']; break; } // Check if we have an alternative block for this result if (jsonData['blocks']) this.displayCompletionResults(jsonData['query'], jsonData['blocks']); // Check if the input value and current request are the same else if (this._currentFieldValue == jsonData['query']) this.displayCompletionResults(jsonData['query']); }, /** * Return an empty result template * according to the blocks length * */ getEmptyResultTemplate: function() { var emptyResultTemplate = []; for (var i = 0 ; i < this.blocks.length ; i++) emptyResultTemplate.push([]); return emptyResultTemplate; }, /** * This function is run when the Ajax request fail * because an error occured * */ completionFailureCallBack: function() { return; }, /** * Put completion response in cache * for a specific begin. It the size has reach * the maximum size, remove the oldest value * * @param inputValue the begin as a part of a word * @param suggestions all suggestions */ addCacheResult: function(inputValue, suggestions) { // Check the input value if (!inputValue) return; // Store suggestion for specific inputValue if (this._cacheActivated == true) this._resultCache[inputValue] = suggestions; }, /** * Create a label span with specific content * and set its CSS class * * @param blockIndex the index of the blocks * @param position The position of the block (can be top or bottom) * * @private * * @returns a new label span */ _getNewBlockLabel: function(blockIndex, position) { // Create the new suggestion var newBlockLabelElement = document.createElement("div"); newBlockLabelElement.id = "blockLabel" + position + "_" + this.getUid() + "_" + blockIndex; // Get the type of suggestion var suggestionType = this._currentBlocks[blockIndex]['type']; // Set default style for the label orangesearch.tools.addClass(newBlockLabelElement, "label block" + position); // Check basic case if (!suggestionType) { this.populateNewBlockLabel(newBlockLabelElement, blockIndex, position); return newBlockLabelElement; } // Loop on plugin and check type for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].isValidType(suggestionType)) { this.plugins[p].populateNewBlockLabel(newBlockLabelElement, blockIndex, position); return newBlockLabelElement; } } // The plugin has not been found in the list of loaded plugins wink.log("Error: The plugin " + suggestionType + " has not been found in the list of loaded plugins !"); return null; }, /** * Create a label span with specific content * and set its CSS class * * @param HTMLElement The new bloc label element * @param blockIndex The index of the blocks * @param string The position of the label (can be 'Top' or 'Bottom') * * @returns a new label span */ populateNewBlockLabel: function(newBlockLabelElement, blockIndex, position) { var key = 'label'; if (position == 'Bottom') key = 'bottomLabel'; // Check if we have a decorator for the label if (this._currentBlocks[blockIndex].decoratorCallback && wink.isCallback(this._currentBlocks[blockIndex].decoratorCallback)) { var params = { "element": newBlockLabelElement, "position": position }; wink.call(this._currentBlocks[blockIndex].decoratorCallback, params); return; } // Default case, just add the text inside the container if (this._currentBlocks[blockIndex][key]) newBlockLabelElement.innerHTML = this._currentBlocks[blockIndex][key]; }, /** * Return the suggestion row corresponding to the type of the current * suggestion data. If the type has not been found, return basic one * * @private * * @param suggestionData * @param position * @returns */ _getNewSuggestionRow: function(query, suggestionData, position) { // Create the new suggestion var newSuggestion = document.createElement("div"); orangesearch.tools.addClass(newSuggestion, "suggestion"); // Set a unique identifier for the current answer div newSuggestion.id = this.getSuggestionPrefix() + position; // Get the type of suggestion var suggestionType = suggestionData["t"]; // Clear accent of value if property is given if (this.clearAccents) suggestionData['v'] = orangesearch.tools.stripAccents(suggestionData['v']); // Clear separator chars of value if property is given if (this.clearSeparatorChars) suggestionData['v'] = orangesearch.tools.stripSeparatorChars(suggestionData['v']); // Store the params before populate the new suggestion object if (suggestionData["d"] && suggestionData["d"]["params"]) this.addSuggestionParam(newSuggestion.id, 'params', suggestionData["d"]["params"]); // Loop on plugin and check type for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].isValidType(suggestionType)) { // Add specific class for valid plugin type orangesearch.tools.addClass(newSuggestion, suggestionType); this.plugins[p].populateNewSuggestionRow(query, newSuggestion, suggestionData, position); this.plugins[p].addEventListenerOnNewSuggestionRow(newSuggestion); return newSuggestion; } } // Alert the user if the plugin for the type is not loaded if (suggestionType) wink.log("Error: The plugin " + suggestionType + " has not been found in the list of loaded plugin !"); this.populateNewSuggestionRow(query, newSuggestion, suggestionData, position); this.addEventListenerOnNewSuggestionRow(newSuggestion); return newSuggestion; }, /** * Populate the new div line for a new suggestion * * @param suggestionData the data of the suggestion */ populateNewSuggestionRow : function(query, newSuggestionDiv, suggestionData, positionOfElementCount) { // Create the suggestion content var content = document.createElement("div"); // Create the span var textSpan = document.createElement("span"); // Retrieve the value var displayStr = suggestionData["v"]; // Check if the value is a string if (!wink.isString(displayStr)) displayStr = ""; // Cut the string if its too long if (this.maxNbChar) displayStr = displayStr.cutString(this.maxNbChar); // Protect the string from potential injection (thanks to createTextNode) // and append it in the span textSpan.appendChild(document.createTextNode(displayStr)); // Apply the bold if we have to, including the clear accent and special char of the current input field value // The chaineAff is already accent cleared if (this.useBoldInCompletion) textSpan = orangesearch.tools.getBoldInvNode(textSpan, query, this.clearAccents, this.clearSeparatorChars); // If we have the debug mode, we add the ids if (this.debugMode && suggestionData["d"] && suggestionData["d"]["baseId"]) textSpan.insertBefore(document.createTextNode('[' + suggestionData["d"]["baseId"] + '] '), textSpan.firstChild); // Add the suggestion value and type to the new div this.addSuggestionParam(newSuggestionDiv.id, "v", suggestionData["v"]); this.addSuggestionParam(newSuggestionDiv.id, "t", suggestionData["t"]); this.addSuggestionParam(newSuggestionDiv.id, "d", suggestionData["d"]); orangesearch.tools.addClass(textSpan, "content"); // Append the span to the new suggestion node content.appendChild(textSpan); newSuggestionDiv.appendChild(content); }, /** * Set the basic event listener * on a new suggestion row * * Add mouseDown, mouseOver and mouseOut */ addEventListenerOnNewSuggestionRow: function(newSuggestionDiv) { // Create a reference to the auto completer var autoCompleter = this; // Reaffect action for mouse down orangesearch.tools.addEventListener(newSuggestionDiv, "mousedown", function(event) { autoCompleter.manageDivOnMouseDown(autoCompleter, event, this); }); // Reaffect action for mouse over orangesearch.tools.addEventListener(newSuggestionDiv, "mouseover", function(event) { autoCompleter.manageDivOnMouseOver(autoCompleter, event, this); }); // Reaffect action for mouse out orangesearch.tools.addEventListener(newSuggestionDiv, "mouseout", function(event) { autoCompleter.manageDivOnMouseOut(autoCompleter, event, this); }); }, /** * Return the action bar at the * bottom of the completion list * * @private */ _getBottomActionBar: function() { var actionBarDiv = document.createElement("div"); orangesearch.tools.addClass(actionBarDiv, "footer actionBar"); for (var p = 0 ; p < this.plugins.length ; p++) { if (this.plugins[p].getBottomAction) { var bottomActionDiv = this.plugins[p].getBottomAction(); if (bottomActionDiv) { bottomActionDiv.style.display = 'inline-block'; bottomActionDiv.style.cssFloat = 'right'; bottomActionDiv.style.styleFloat = 'right'; actionBarDiv.appendChild(bottomActionDiv); } } } var autoCompleter = this; var closeDiv = document.createElement("a"); closeDiv.innerHTML = 'Fermer'; closeDiv.style.display = 'inline-block'; orangesearch.tools.addClass(closeDiv, "rightClose"); orangesearch.tools.addEventListener(closeDiv, "click", function(event) { autoCompleter.hideCompletionDiv() }); actionBarDiv.appendChild(closeDiv); return actionBarDiv; }, /** * Display the completion div box * with the new result list * * @param liste the result list */ displayCompletionResults: function(query, specificBlock) { // Check if we have an alternative block for this result this._currentBlocks = this.blocks['default']; if (typeof specificBlock != "undefined" && specificBlock.length > 0) { if (this.blocks[specificBlock]) this._currentBlocks = this.blocks[specificBlock]; } // Publish a notification before loop on suggestions list wink.publish('/callback/displayCompletionResults/beforeDisplay', { 'uid' : this._uid, 'query': query, 'block': this._currentBlocks }); // Empty the previous list this.emptyCompletionResults(); var list = this._currentJsonResultsData; // Check that we have at least one result in the list if (this._checkNotEmptyList(list)) { // Show the completion div if not visible yet this.showCompletionDiv(); // Start the recursive add of all elements var positionOfElementCount = 1; var blockIndex = 0; var suggestionIndex = 0; this._addNewSuggestionRowRecursive(query, suggestionIndex, list, blockIndex, positionOfElementCount); this.showCompletionDiv(); } else { // No suggestions this.hideCompletionDiv(); } }, /** * Check that we have at least * one array with some data and return * true if its the case. * * @param array list the JSON data list * @param boolean dontCheckInput indicate if we need to check the input length */ _checkNotEmptyList: function(list) { if (!list || list.length <= 0) return false; if (this.checkInput && !this.onClickCompletion) { if (this.field.value == '') return false; } for (var i = 0 ; i < list.length ; i++) { if (list[i].length > 0) return true; } return false; }, /** * This function add the new suggestion row * in the DOM and run the publish method after * a little time (in order to let the new DOM to be loaded) * */ _addNewSuggestionRowRecursive: function(query, suggestionIndex, list, blockIndex, positionOfElementCount) { // Check if the block index is out of bound if (blockIndex >= this._currentBlocks.length) { // We have finish the process - we are at the end of the last block return this._finalizeCompletionConstruction(positionOfElementCount); } // Check if the block exist in result (as in the configuration) - if not exist go to the next if (!list[blockIndex] || list[blockIndex].length == 0) { blockIndex++; suggestionIndex = 0; return this._addNewSuggestionRowRecursive(query, suggestionIndex, list, blockIndex, positionOfElementCount); } // Check if we have to add a label if (suggestionIndex == 0) { // Check if we have to manage a label if (!wink.isUndefined(this._currentBlocks[blockIndex].label) || !wink.isUndefined(this._currentBlocks[blockIndex].decoratorCallback)) { // Retrieve and check the label var newLabel = this._getNewBlockLabel(blockIndex, 'Top'); if (newLabel) this._completionDiv.appendChild(newLabel); } } // Create the new completion div var newDiv = this._getNewSuggestionRow(query, list[blockIndex][suggestionIndex], positionOfElementCount); if (!newDiv) return; // Add blockId param to the new div this.addSuggestionParam(newDiv.id, 'blockId', blockIndex); // Add index param to the new div this.addSuggestionParam(newDiv.id, 'index', positionOfElementCount); // Append the new div to the completion box this._completionDiv.appendChild(newDiv); // Check if we have to publish with a delay if (this.completionDivUnrollDelay && this.completionDivUnrollDelay > 0) { // Run the publish in this.completionDivRollDelay ms var autoCompleter = this; setTimeout(function() { autoCompleter._publishNewSuggestionRow(query, newDiv, suggestionIndex, list, blockIndex, positionOfElementCount); }, this.completionDivUnrollDelay); } else { // Run directly the publish this._publishNewSuggestionRow(query, newDiv, suggestionIndex, list, blockIndex, positionOfElementCount); } }, /** * This function publish the add of the new element * and update the suggestion index and the block index * Finally run again the _addNewSuggestionRowRecursive * if we have to. * */ _publishNewSuggestionRow: function(query, newDiv, suggestionIndex, list, blockIndex, positionOfElementCount) { // We can publish that we have added an element wink.publish('/callback/displayCompletionResults/elementAdded', { 'uid' : this._uid, 'position': positionOfElementCount, 'div' : newDiv, 'data' : list[blockIndex][suggestionIndex] }); // Update the position positionOfElementCount++; // Update the suggestion index and check it suggestionIndex++; if (suggestionIndex >= list[blockIndex].length) { // Append the bottom label if (!wink.isUndefined(this._currentBlocks[blockIndex].bottomLabel) || !wink.isUndefined(this._currentBlocks[blockIndex].decoratorCallback)) { // Retrieve and check the bottom label var newLabel = this._getNewBlockLabel(blockIndex, 'Bottom'); if (newLabel) this._completionDiv.appendChild(newLabel); } // Update the block index and Reset the suggestion index suggestionIndex = 0; blockIndex++; } // Run recursively the _addNewSuggestionRowRecursive this._addNewSuggestionRowRecursive(query, suggestionIndex, list, blockIndex, positionOfElementCount); }, /** * This function finalize the completion construction * by adding the bottom action if we have to and publish * the allAddedElement notification. * Finally reset the completion. * */ _finalizeCompletionConstruction: function(positionOfElementCount) { // Append the close link if we have to if (this._completionCloseLink) this._completionDiv.appendChild(this._getBottomActionBar()); // Initialize the completion display this.resetCompletion(); // We can publish that we have added all elements wink.publish('/callback/displayCompletionResults/allElementsAdded', { 'uid' : this._uid, 'position': positionOfElementCount }); return true; }, /** * Remove the completion content * while the div contain at least one child, * remove it * */ emptyCompletionResults: function() { // Check the completion div if (!this._completionDiv) return; // Remove suggestion params container this.resetSuggestionsList(); // Remove all children while(this._completionDiv.childNodes.length > 0) this._completionDiv.removeChild(this._completionDiv.childNodes[0]); }, /** * Show the completion div element * */ showCompletionDiv: function() { // Check the completion div if (!this._completionDiv) return; // Check that the completion div is actually hidden if (this._completionDiv.style.visibility == "visible" || this._completionDiv.style.display == "block") return; // Run the before completion callback if (this._callbacks && this._callbacks.show && wink.isCallback(this._callbacks.show)) wink.call(this._callbacks.show); // Publish the show wink.publish('/callback/showCompletionDiv/show', { 'uid' : this._uid }); // Show the completion div if (this._completionDiv) { this._completionDiv.style.display = "block"; this._completionDiv.style.visibility = "visible"; } }, /** * Hide the completion div element * */ hideCompletionDiv: function() { // Check the completion div if (!this._completionDiv) return; // Check that the completion div is actually visible if (this._completionDiv.style.visibility == "hidden") return; // Call the callback after hidding the completion if (this._callbacks && this._callbacks.hide && wink.isCallback(this._callbacks.hide)) wink.call(this._callbacks.hide); // Publish the hide wink.publish('/callback/hideCompletionDiv/hide', { 'uid' : this._uid }); // Hide the completion block if (this._completionDiv) { this._completionDiv.style.display = "none"; this._completionDiv.style.visibility = "hidden"; } }, /** * Reset the field value to * avoid cursor bug in IE * */ pushFieldCursor: function() { if (this.field.value) { oldValue = this.field.value; this.field.value = ""; this.field.value = oldValue; } }, /** * Enable the cache in the completion process * */ enableCache: function() { this._cacheActivated = true; }, /** * Disable the cache in the completion process * */ disableCache: function() { this._cacheActivated = false; }, /** * Return the suggestion prefix id * * @returns string */ getSuggestionPrefix: function() { return this._suggestionPrefixId + this._uid + '_'; }, /** * Return the current highlighted * suggestion index * * @returns integer the suggestion index */ getHighlightedSuggestionIndex: function() { return this._highlightedSuggestionIndex; }, /** * Return the current selected * suggestion index * * @returns integer the suggestion index */ getSelectedSuggestionIndex: function() { return this._selectedSuggestionIndex; }, /** * Set the new delay value * * @param integer newDelay the new delay */ setDelayOnMouseOver: function(newDelay) { var value = orangesearch.tools.integerValue(newDelay); if (!value || value <= 0) this.applyDelayOnMouseOver = 0; else this.applyDelayOnMouseOver = value; }, /** * Return the current delay value * * @returns integer */ getDelayOnMouseOver: function() { if (this.applyDelayOnMouseOver) return this.applyDelayOnMouseOver; return 0; }, /** * Return true if the auto completer * has a valid reference * * @returns boolean */ hasPositionReference: function() { if (this._positionReference) return true; return false; }, /** * Return the current position reference * The reference can be a HTML object or null * * @returns object */ getPositionReference: function() { return this._positionReference; }, /** * Return the current completion * div that contain completion results * * @returns object */ getCompletionDiv: function() { return this._completionDiv; }, /** * Return the css prefix * of the auto completer * * @return string */ getCssPrefix: function() { return this.cssPrefix; }, /** * Return the auto completer * unique identifier * * @returns integer */ getUid: function() { return this._uid; }, /** * Abort the current submit */ abortSubmit: function() { this._abortSubmit = true; }, /** * Update the current field value of the auto completer * with the input field value */ updateCurrentFieldValue: function() { this._currentFieldValue = this.field.value; }, /** * Update the selected index with * the highlighted index * */ updateSelectedIndex: function() { this._selectedSuggestionIndex = this._highlightedSuggestionIndex; }, /** * Return true if the auto completer's input field * is linked to a form or false in other case * * @return boolean */ hasForm: function() { if (this.field.form) return true; return false; }, /** * Set the check input value * of the auto completer * */ setCheckInputValue: function(value) { this.checkInput = value; }, /** * Set the suggestion parameter value for a specific * suggestion div identifier and key * and update the size of the list of parameters * * @param suggestionId the suggestion div identifier * @param key the key * @param value the value */ addSuggestionParam: function (suggestionId, key, value) { this._suggestionsList.addSuggestionParam(suggestionId, key, value); }, /** * Get the suggestion param value for a suggestion div id and a key * * @param suggestionId * @param key * * @returns mixed the value */ getSuggestionParam: function(suggestionId, key) { return this._suggestionsList.getSuggestionParam(suggestionId, key); }, /** * Get all suggestion params values associated to a suggestion div id * * @param suggestionId * * @returns array the key/value array */ getAllSuggestionParams: function(suggestionId) { return this._suggestionsList.getAllSuggestionParams(suggestionId); }, /** * Return the size of the suggestion parameters * * @returns integer the size of suggestion list */ getSizeOfSuggestionsList: function() { return this._suggestionsList.getSize(); }, /** * Return the complete suggestion list * * @return array the suggestion list */ getSuggestionsList: function() { return this._suggestionsList.getAllSuggestions(); }, /** * Reset the list of suggestions * * @returns void */ resetSuggestionsList: function() { this._suggestionsList.resetAllParams(); } }; /** * @memberOf orangesearch.completion * @type Component * */ orangesearch.completion.Component = Component; wink.callAndReturn = function(callback, parameters) { var context = window; var method = callback.method; var args = []; if (this.isSet(callback.context)) { context = callback.context; } if (arguments.length == 2) { args = [parameters]; } if (wink.isSet(callback.arguments)) { var additional = callback.arguments; if (!wink.isArray(additional)) { additional = [callback.arguments]; } args = args.concat(additional); } return context[method].apply(context, args); }; /** * Container object of all suggestions found for the current query * * @class SuggestionList class * */ var SuggestionList = function() { /** * Size of the suggestions list * @type integer */ this._size = 0; /** * Set of suggestions * * @type array */ this._suggestions = {}; }; SuggestionList.prototype = { /** * Set the suggestion parameter value for a specific * suggestion div identifier and key * and update the size of the list of parameters * * @param suggestionId the suggestion div identifier * @param key the key * @param value the value */ addSuggestionParam: function (suggestionId, key, value) { if (!this._suggestions[suggestionId]) { this._suggestions[suggestionId] = {}; this._size++; } this._suggestions[suggestionId][key] = value; }, /** * Get the suggestion param value for a suggestion div id and a key * * @param suggestionId * @param key * * @return mixed the value */ getSuggestionParam: function(suggestionId, key) { if (!suggestionId) return null; if (!this._suggestions[suggestionId] || wink.isUndefined(this._suggestions[suggestionId][key])) return null; return this._suggestions[suggestionId][key]; }, /** * Get all suggestion params values associated to a suggestion div id * * @param suggestionId * * @return array the key/value array */ getAllSuggestionParams: function(suggestionId) { if (!suggestionId) return null; if (wink.isUndefined(this._suggestions[suggestionId])) return null; return this._suggestions[suggestionId]; }, /** * Reset the list of parameters * and update the size */ resetAllParams: function() { this._size = 0; this._suggestions = {}; }, /** * Return the current size of the * suggestion list * * @return integer the size */ getSize: function() { return this._size; }, /** * Return all suggestion * * @return array */ getAllSuggestions: function() { return this._suggestions; } }; /** * @memberOf orangesearch.completion * @type SuggestionList */ orangesearch.completion.SuggestionList = SuggestionList; /** * Completion Toolbox * * This javascript contain a set of general function * that is used by the completion component and can * be used by other code and * */ orangesearch.tools = { /** * The list of event for attach event * Used to fix context problem with IE * */ eventHash: [], /** * Return the unique identifier * of the element * * WARNING: work only for IE */ ieGetUniqueID: function(element) { if (element === window) return 'theWindow'; else if (element === document) return 'theDocument'; else return element.uniqueID; }, /** * Return true if the key is found in the array/object * * @param string key the key value * @param array search the array/oject * * @return boolean true if found */ arrayKeyExists: function(key, search) { if(!search || (search.constructor !== Array && search.constructor !== Object)) return false; return search[key] !== undefined; }, /** * Return integer value of a mixed * value. If the type cannot be found * return 0 * * @param mixed value the value * @return integer the intval */ integerValue: function(value) { var type = typeof(value); if (type === 'number') return value; else if (type === 'string') return parseInt(value); else if (type === 'boolean') return (value) ? 1 : 0; else return 0; }, /** * Capitalize the first letter of the string * * @param string str */ ucFirst: function(str) { if (str.length > 0) return str.charAt(0).toUpperCase() + str.substring(1); else return str; }, /** * Minimize the fisrt letter of the string * * @param string str * * @return string */ lcFirst: function(str) { if (str.length > 0) return str.charAt(0).toLowerCase() + str.substring(1); else return str; }, /** * Clone a simple object * * @param object obj */ clone: function(obj) { var newObj = (obj instanceof Array) ? [] : {}; for (var i in obj) { if (obj[i] && typeof obj[i] == "object") newObj[i] = orangesearch.tools.clone(obj[i]); else newObj[i] = obj[i]; } return newObj; }, /** * Concat the properties of obj2 into obj1 */ mixin: function(obj1, obj2) { for (var key in obj2) { if (key.substr(0,1) == "_") continue; obj1[key] = obj2[key]; } return obj1; }, /** * Remove all accent and replace them by normal letter * First do a UTF8 parse * Then do a ASCII parse * * @param str initial string with accents * * @return string without accents */ stripAccents: function(str) { // Replace UTF8 accent char with non-accent char // Replace é è ê ë with e str = str.replace("/\xC3\xA9/g", "e"); str = str.replace("/\xC3\xA8/g", "e"); str = str.replace("/\xC3\xAA/g", "e"); str = str.replace("/\xC3\xAB/g", "e"); // Replace à â ä with a str = str.replace("/\xC3\xA0/g", "a"); str = str.replace("/\xC3\xA2/g", "a"); str = str.replace("/\xC3\xA4/g", "a"); // Replace ì î ï with i str = str.replace("/\xC3\xAC/g", "i"); str = str.replace("/\xC3\xAE/g", "i"); str = str.replace("/\xC3\xAF/g", "i"); // Replace ô ö ò with o str = str.replace("/\xC3\xB4/g", "o"); str = str.replace("/\xC3\xB6/g", "o"); str = str.replace("/\xC3\xB2/g", "o"); // Replace û ü ù with u str = str.replace("/\xC3\xBB/g", "u"); str = str.replace("/\xC3\xBC/g", "u"); str = str.replace("/\xC3\xB9/g", "u"); // Replace ç with c str = str.replace("/\xC3\xA7/g", "c"); // Replace ñ with n str = str.replace("/\xC3\xB1/g", "n"); // Replace ÿ with y str = str.replace("/\xC3\xBF/g", "y"); // Replace ASCII accent char with non-char without accent var a = "\xE0\xE2\xE4\xE1\xC0\xC1\xC4\xC2\xEB\xE8\xE9\xEA\xCA\xC9\xC8\xCB\xEF\xEE\xED\xEC\xCC\xCD\xCE\xCF\xF2\xF3\xF4\xF6\xD2\xD3\xD4\xD6\xFC\xF9\xFB\xFA\xDA\xD9\xDB\xDC\xE3\xF5\xF1\xC3\xD5\xD1\xE7"; var b = "aaaaAAAAeeeeEEEEiiiiIIIIooooOOOOuuuuUUUUaonAONc"; for(var i = 0 ; i < a.length ; i++) str = str.split(a.charAt(i)).join(b.charAt(i)); return str; }, stripSeparatorChars: function(str) { // replace separator chars by space return str.replace(/[-._']/g, ' '); }, /** * Clean a query by replacing multiple * space with one space and triming left * the query * * @param string query * * @return string */ cleanQuery: function(query) { return query.toLowerCase().replace(/[ ]+/g,' ').replace(/^ /,''); }, /** * Clear all space of a string by replacing * multiple space with one space and triming * left and right the str * * @param string str * * @return string */ clearSpace: function(str) { return str.toLowerCase().replace(/[ ]+/g,' ').replace(/^ /,'').replace(/ $/,''); }, /** * Give the focus and move to the end * of the input value * * @param field input field */ focusOnInputEnd: function(field) { field.focus(); // If this function exists... if (this.setSelectionRange) { // ... then use it // (Doesn't work in IE) // Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh. var len = field.value.length * 2; this.setSelectionRange(len, len); } else { // ... otherwise replace the contents with itself // (Doesn't work in Google Chrome) var tmp = field.value; field.value = ""; field.value = tmp; } }, /** * Append a script node in the * head or specific node on the current DOM * Set the URL params as source * The result of the URl will be executed (result sjould be JS code) * * @param url the url to be used */ writeScript: function(url, identifier) { var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); if(document.getElementById(identifier)) { var oldSrc = document.getElementById(identifier); head.removeChild(oldSrc); script.setAttribute('id', identifier); script.setAttribute('type','text/javascript'); script.setAttribute('src',url); head.appendChild(script); } else { script.setAttribute('id', identifier); script.setAttribute('type','text/javascript'); script.setAttribute('src',url); head.appendChild(script); } }, /** * Invert bold a string with the specified pattern * The str is considered accent cleared and special char cleared * if requiered - We dont have to clear it * * @param node the node containing the text to boldify. * The node must contain only one text node ! * @param patt the pattern * @param clearAccents if we have to clear accent * @param clearSeparatorChars if we have to clear separator chars * * @return string the revert bolded string */ getBoldInvNode: function(node, pattern, clearAccents, clearSeparatorChars) { var resultNode = document.createElement(node.tagName); var bNode; var str = node.firstChild.nodeValue; // Check for a non empty pattern if (wink.isUndefined(pattern) || pattern.length == 0) { bNode = document.createElement("b"); bNode.appendChild(document.createTextNode(str)); resultNode.appendChild(bNode); return resultNode; } // Always clear space at the beginning pattern = this.clearSpace(pattern); // Comparison is always between 2 cleared strings var clearedPatternLower = this.stripAccents(this.stripSeparatorChars(pattern)).toLowerCase(); var clearedStrLower = this.stripAccents(this.stripSeparatorChars(str)).toLowerCase(); // Save the current str to display according to the clearAccents and clearSeparatorChars parameters if (clearSeparatorChars && clearAccents) str = this.stripAccents(this.stripSeparatorChars(str)); else if (clearSeparatorChars) str = this.stripSeparatorChars(str); else if (clearAccents) str = this.stripAccents(str); // Search the pattern in the string (accent cleared) var startSearch = 0; var i = clearedStrLower.indexOf(clearedPatternLower, startSearch); while(i != -1) { // Check if this is the begin of a word if ((i == 0) || clearedStrLower.charAt(i - 1) == ' ') { // Bold all char before the index if (i > startSearch) { bNode = document.createElement("b"); bNode.appendChild(document.createTextNode(str.substring(startSearch, i))); resultNode.appendChild(bNode); } // Move to the end of the pattern var startSearch = i + clearedPatternLower.length; // Add the pattern part resultNode.appendChild(document.createTextNode(str.substring(i, startSearch))); } else { // Add the entire part until the startSearch bNode = document.createElement("b"); bNode.appendChild(document.createTextNode(str.substring(startSearch, i + clearedPatternLower.length))); resultNode.appendChild(bNode); // Update the start search var startSearch = i + clearedPatternLower.length; } // Run again the search var i = clearedStrLower.indexOf(clearedPatternLower, startSearch); } // Append the last part of the string (full string if no pattern found) if (clearedStrLower.length > startSearch) { bNode = document.createElement("b"); bNode.appendChild(document.createTextNode(str.substring(startSearch))); resultNode.appendChild(bNode); } return resultNode; }, /** * Add a css class to the node * * @param HTMLElement node the HTMLElement * @param string classStr the new CSS classname to add * */ addClass: function(node, classStr) { var cls = node.className; if ((" " + cls + " ").indexOf(" " + classStr + " ") < 0) { node.className = cls + (cls ? ' ' : '') + classStr; } }, /** * Remove a css class from the node * * @param HTMLElement node the HTMLElement * @param string classStr the CSS classname to remove */ removeClass: function(node, classStr) { var t = wink.trim((" " + node.className + " ").replace(" " + classStr + " ", " ")); if (node.className != t) { node.className = t; } }, /** * Return true if the CSS classname * has been found in the CSS list of the element * * @param HTMLElement node the HTMLElement * @param string classStr the CSS classname to remove * * @return boolean */ hasClass: function(node, classStr) { return ((" "+ node.className +" ").indexOf(" " + classStr + " ") >= 0); }, /** * Return the date * in the full format * */ fullDate: function(skipDay) { var monthLabel = new Array('janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'); var dayLabel = new Array('dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'); var date = new Date; if (typeof skipDay != "undefined" && skipDay == true) return date.getDate() + ' ' + monthLabel[date.getMonth()] + ' ' + date.getFullYear(); return dayLabel[date.getDay()] + ' ' + date.getDate() + ' ' + monthLabel[date.getMonth()] + ' ' + date.getFullYear(); }, /** * Return the day of the date * * @return string */ getNowDay: function() { var now = new Date(); var str = ""; switch(now.getDay()) { case 0: str = "dimanche"; break; case 1: str = "lundi"; break; case 2: str = "mardi"; break; case 3: str = "mercredi"; break; case 4: str = "jeudi"; break; case 5: str = "vendredi"; break; case 6: str = "samedi"; break; } return str; }, /** * Return the date of the day * in a toString french way * * @return string the date */ getNowDate: function() { var now = new Date(); str = now.getDate() + " "; switch(now.getMonth()) { case 0: str += "janvier"; break; case 1: str += "février"; break; case 2: str += "mars"; break; case 3: str += "avril"; break; case 4: str += "mai"; break; case 5: str += "juin"; break; case 6: str += "juillet"; break; case 7: str += "août"; break; case 8: str += "septembre"; break; case 9: str += "octobre"; break; case 10: str += "novembre"; break; case 11: str += "décembre"; break; } return str; }, /** * Return the fav icon URL * for the specific site URL * The URL should be simple (ex: actu.orange.fr, www.google.fr, http://wikipedia.orange.fr) * * @param string the site URL * * @return string the fav icon URL */ getSimpleFavIconUrl: function(url) { // Check that the URL is not empty if (!url || url.length <= 0) return false; // Check that the URL begin with "http://" if (url.substr(0, 7) != "http://") url = "http://" + url; // Finally build the URL return url + "/favicon.ico"; }, /** * Completely stop an event and * try to prevent for border effect * */ stopEvent: function(event) { event = event || window.event; if (event.stopPropagation) event.stopPropagation(); if (event.preventDefault) event.preventDefault(); event.cancelBubble = true; event.returnValue = false; }, /** * A simple hash function that * will hash a string with a hash size * * @param function fn The function to hash * @param integer hashSize The hash size * * @return string */ simpleHash: function(str, hashSize) { var i, hash = 0; for (i = 0 ; i < str.length ; i++) hash += (str.charCodeAt(i) * (i + 1)); return Math.abs(hash) % hashSize; }, /** * Add a specific event listener to element * * @return boolean */ addEventListener: function(element, eventType, callback, bubble) { if (typeof element == "undefined" || !element) return; if (typeof bubble == "undefined") bubble = true; if (element.attachEvent) { // Build the unique hash of the function var hash = '{FNKEY::obj_' + orangesearch.tools.ieGetUniqueID(element) + '::evt_' + eventType + '::fn_' + orangesearch.tools.simpleHash(callback.toString(), 1000000) + '}'; // Check if the callback exist for this hash (do not add twice) var existingCallback = orangesearch.tools.eventHash[hash]; if (existingCallback) return; // Define the anonymous function var hashFunction = function() { callback.call(element); }; // Store the function in the event hash table orangesearch.tools.eventHash[hash] = hashFunction; // Finally attach the event return element.attachEvent("on" + eventType, hashFunction); } else if (element.addEventListener) { // Webkit and FF - addEventListener return element.addEventListener(eventType, callback, bubble); } }, /** * Remove a specific event listener to element * * @return boolean */ removeEventListener: function(element, eventType, callback, bubble) { if (typeof element == "undefined" || !element) return; if (typeof bubble == "undefined") bubble = true; if (element.detachEvent) { // Build the unique hash of the function var hash = '{FNKEY::obj_' + orangesearch.tools.ieGetUniqueID(element) + '::evt_' + eventType + '::fn_' + orangesearch.tools.simpleHash(callback.toString(), 1000000) + '}'; // Retrieve the callback var existingCallback = orangesearch.tools.eventHash[hash]; if (!existingCallback) return; // Remove the callback from the list delete orangesearch.tools.eventHash[hash]; // Finally detach the event return element.detachEvent("on" + eventType, existingCallback); } else if (element.removeEventListener) { // Webkit and FF - addEventListener return element.removeEventListener(eventType, callback, bubble); } }, /** * Check if the event compatibility * * @return boolean */ checkEventCompatibility: function() { return (window.attachEvent || window.addEventListener); }, /** * Set a specific opacity to an * element specified by its identifier * * @param integer value * @param string identifier */ setOpacity: function(value, identifier) { // Retrieve the object and check it var object = wink.byId(identifier); if (!object) return; // Set the opcaity value object.style.opacity = (value / 100); object.style.MozOpacity = (value / 100); object.style.KhtmlOpacity = (value / 100); object.style.filter = "alpha(opacity=" + value + ")"; } }; if (!String.prototype.cutString) { /** * Cut a string from 0 to maxChar * * @param string str the initial string * @param integer maxChar the maximum number of characters * @return string the cut string */ String.prototype.cutString = function(maxChar) { if (maxChar > 0 && this.length > maxChar) return this.substring(0, maxChar - 3) + '...'; else return this; }; } /** * Define the abstract plugin for completion component * This allows to add extra features to the main completion component * * @class The abstract plugin class * @abstract * **/ var Plugin = function() { this.completionComponent = null; return this; }; Plugin._extendPluginChildren = []; Plugin.prototype = { /** * Set the completion component for the current plugin * * @param the associated completion component */ setCompletionComponent: function(completionComponent) { this.completionComponent = completionComponent; }, /** * Return the type of suggestion * Default value is NULL * * @return the type of suggestion */ getTypeOfSuggestion: function() { return null; }, /** * Populate the label span with specific content * and set its CSS class * * @param blockIndex the index of the blocks * @return */ populateNewBlockLabel: function(newBlockLabelElement, blockIndex) { this.completionComponent.populateNewBlockLabel(newBlockLabelElement, blockIndex); }, /** * Populate the new suggestion row * use the completion component job * * @param suggestionData * @param positionOfElementCount * @return */ populateNewSuggestionRow: function(query, newSuggestionDiv, suggestionData, positionOfElementCount) { this.completionComponent.populateNewSuggestionRow(query, newSuggestionDiv, suggestionData, positionOfElementCount); }, /** * Set the basic event listener on a new suggestion row. * Add mouseDown, mouseOver and mouseOut event listeners. * */ addEventListenerOnNewSuggestionRow: function(newSuggestionDiv) { this.completionComponent.addEventListenerOnNewSuggestionRow(newSuggestionDiv); }, /** * This function add a class as a child class * the child class inherit from orangesearch.completion.plugin.Plugin * * @param child the child class * @return boolean true if correctly inherited */ extendFromPlugin: function(child) { if (Plugin._extendPluginChildren[child]) return true; for (var element in orangesearch.completion.plugin.Plugin.prototype) { if ('extendFromPlugin' == element) continue; if (!child.prototype[element]) child.prototype[element] = orangesearch.completion.plugin.Plugin.prototype[element]; } Plugin._extendPluginChildren[child] = true; return true; } }; /** * @memberOf orangesearch.completion.plugin * @type Plugin */ orangesearch.completion.plugin.Plugin = Plugin; /** * Define the Autopromo plugin for completion component. * It used to follow direct url links when the suggestion type is "link" * and if a param url is defined. * * @class The Autopromo plugin class * @extends Plugin **/ var DirectLink = function() { // DirectLink class extends from plugin orangesearch.completion.plugin.Plugin.call(this); new orangesearch.completion.plugin.Plugin().extendFromPlugin(orangesearch.completion.plugin.DirectLink); // Specific attributes this.displayUrlAsTitle = false; this._currentLink = ""; this._textAppend = "  (acces direct)"; // DirectLink click event handler this._directLinkClickHandler = function(url) { window.location.href = url; } return this; }; DirectLink.prototype = { /** * Return the type of suggestion * * @return the type of suggestion */ getTypeOfSuggestion: function() { return 'link'; }, /** * Indicate if the suggestion type is valid for this plugin * * @param string suggestionType the suggestion type * * @return boolean */ isValidType: function(suggestionType) { return (suggestionType == this.getTypeOfSuggestion()); }, /** * Initialize properties for DirectLink * using the completion component properties * */ initProperties: function() { // Set the value to display in completion box, title or display_url if (this.completionComponent._properties.displayUrlAsTitle) this.displayUrlAsTitle = this.completionComponent._properties.displayUrlAsTitle; // Set the text append if exist in properties if (!wink.isUndefined(this.completionComponent._properties.directLinkTextAppend)) this._textAppend = this.completionComponent._properties.directLinkTextAppend; // Customize the default petale click action if (!wink.isUndefined(this.completionComponent._properties.directLinkClickHandler)) this._directLinkClickHandler = this.completionComponent._properties.directLinkClickHandler; }, /** * Create the DirectLink object container that will * contain all arrow and all petale container * */ initSubscription: function() { // Call before the form submit, to check if a direct link exists for highlighted element wink.subscribe('/callback/manageFormOnSubmit/beforeSubmit', {context: this, method: 'checkDirectLinks'}); }, /** * Remove subscriptions of DirectLink plugin * */ removeSubscription: function() { // Call before the form submit, to check if a direct link exists for highlighted element wink.unsubscribe('/callback/manageFormOnSubmit/beforeSubmit', {context: this, method: 'checkDirectLinks'}); }, /** * Create a new div line for a new DirectLink suggestion * and set its CSS class. * Saving the direct link url * * @param newSuggestionDiv the div element to populate (by reference) * @param suggestionData the data of the suggestion * @param positionOfElementCount index of the suggestion */ populateNewSuggestionRow: function(query, newSuggestionDiv, suggestionData, positionOfElementCount) { // Set the correct value to display if (this.displayUrlAsTitle && suggestionData["d"] && suggestionData["d"]["display_url"]) suggestionData["v"] = suggestionData["d"]["display_url"]; else if (suggestionData['d'] && suggestionData["d"]["title"]) suggestionData["v"] = suggestionData["d"]["title"]; // Call default populate function this.completionComponent.populateNewSuggestionRow(query, newSuggestionDiv, suggestionData, positionOfElementCount); // Append direct link CSS style on the first child of the new suggestion and append the text if exist if (newSuggestionDiv.firstChild) { var directlinkLabel = document.createElement("span"); orangesearch.tools.addClass(directlinkLabel, "rightLabel"); directlinkLabel.innerHTML = this._textAppend; newSuggestionDiv.insertBefore(directlinkLabel, newSuggestionDiv.firstChild); } // Then, append the href and a thumbnail css element if a link exists if (suggestionData["d"]["url"]) { this.completionComponent.addSuggestionParam(newSuggestionDiv.id, "url", suggestionData["d"]["url"]); var newPicAnchor = document.createElement("div"); orangesearch.tools.addClass(newPicAnchor, "icon"); newSuggestionDiv.insertBefore(newPicAnchor, newSuggestionDiv.firstChild); } }, /** * Check if an url can be called for the current submit * * @param properties the properties array * @return */ checkDirectLinks: function(properties) { if (properties.uid != this.completionComponent.getUid()) return false; // Retrieve the id of selected element var divId = this.completionComponent._getSuggestionId(this.completionComponent._selectedSuggestionIndex); // Check if the highlighted element is directlink if (this.completionComponent.getSuggestionParam(divId, "t") !== this.getTypeOfSuggestion()) return false; // Retrieve and check the URL var url = this.completionComponent.getSuggestionParam(divId, "url"); if (!url) return false; // In this case, reset the completion and stop submit process // before redirect and cancel the key event this.completionComponent.resetCompletion(); this.completionComponent.abortSubmit(); this._directLinkClickHandler(url); return true; } }; /** * @memberOf orangesearch.completion.plugin * @type DirectLink */ orangesearch.completion.plugin.DirectLink = DirectLink; /** * Define the "PETALE" plugin for completion component. * It is used to display a specific template at the right of suggestion row * containing additional informations related to suggestion (images, links, descriptions...) * * @class The Petale plugin class * @extends Plugin **/ var Petale = function() { // Petale class extends from plugin orangesearch.completion.plugin.Plugin.call(this); new orangesearch.completion.plugin.Plugin().extendFromPlugin(orangesearch.completion.plugin.Petale); // Array used to store the identifier, height, position and data of element this._identifierOfElement = []; this._idOfElement = []; this._petaleDdata = []; // Thematic list this._petaleThematics = { "boutique" : "mboutique", "people" : "mpeople", "shopping" : "mshopping", "meteo" : "mmeteo" }; // The smooth show values this._smoothShow = false; this._smoothShowDelay = 20; this._smoothShowIncrement = 3; // The size of information this._petaleInfoSize = 50; this._petaleTitleSize = 25; // The default images for all petale this._petaleDefaultImages = {}; // The border to remove when calculating placement (default is a border with 1 px) this._borderBottomOfCompletionContainer = 0; // Navigation status this._navigateInPetale = false; this._justExit = false; this._currentPetaleId = 0; this._currentPetaleLinkId = 0; // The Petale container identifier this._petaleContainerIdentifier = 'petaleContainerId_'; this._petaleSubContainerIdentifier = 'petaleSubContainerId_'; // All CSS classes this._petaleContainerCssClass = 'petaleBox'; this._petaleContainerTitleCssClass = 'petaleBoxTitle'; this._petaleSubContainerCssClass = 'petaleSubContainer'; this._petaleReferenceContainerCssClass = 'petaleReferenceContainer'; this._petaleReferenceContainerFullCssClass = 'petaleReferenceFullContainer'; this._petaleSubReferenceContainerCssClass = 'petaleSubReferenceContainer'; this._petaleSubReferenceContainerFullCssClass = 'petaleSubReferenceFullContainer'; this._petaleReferenceImageTableCssClass = 'petaleReferenceImageTable'; this._petaleReferenceImageTrCssClass = 'petaleReferenceImageTr'; this._petaleReferenceImageTdCssClass = 'petaleReferenceImageTd'; this._petaleReferenceImageContainerCssClass = 'petaleReferenceImageContainer'; this._petaleReferenceImageCssClass = 'petaleReferenceImage'; this._petaleReferenceInfoContainerCssClass = 'petaleReferenceInfoContainer'; this._petaleReferenceInfoTopContainerCssClass = 'petaleReferenceInfoTopContainer'; this._petaleReferenceInfoCssClass = 'petaleReferenceInfo'; this._petaleReferenceUrlDisplayCssClass = 'petaleReferenceUrlDisplay'; this._petaleReferenceUrlDisplayFaviconCssClass = 'petaleReferenceUrlDisplayFavicon'; this._petaleActionLinkContainerCssClass = 'petaleActionLinkContainer'; this._petaleActionLinkSeparatorCssClass = 'petaleActionLinkSeparator'; this._petaleActionLinkCssClass = 'petaleActionLink'; this._petaleActionUrlDisplayCssClass = 'petaleActionUrlDisplay'; this._petaleActionUrlDisplayTextCssClass = 'petaleActionUrlDisplayText'; this._petaleActionTextCssClass = 'petaleActionText'; // General style for 'over' in reference and actions this._petaleGeneralActionOverCssClass = 'highlighted'; // Petale click event handler this._petaleClickHandler = function(url) { window.location.href = url; } return this; }; Petale.prototype = { /** * Check if the current plugin can * be used or not in the current * context * * Petale can not be used in case * of touch screen */ canBeUsed: function() { return !('ontouchstart' in document.documentElement); }, /** * Return the type of suggestion * type is Petale * * @return string */ getTypeOfSuggestion: function() { return 'petale'; }, /** * Indicate if the suggestion type is valid for this plugin * * @param string suggestionType the suggestion type * * @return boolean */ isValidType: function(suggestionType) { return (suggestionType == this.getTypeOfSuggestion()); }, /** * Populate the current suggestion row * Append a right arrow at the end of the * suggestion HTML object * * @param object newSuggestionHtmlObject the new suggestion HTML object * @param array suggestionData the suggestion data * @param positionOfElementCount the position of the current suggestion * */ populateNewSuggestionRow: function(query, newSuggestionHtmlObject, suggestionData, positionOfElementCount) { // If there is Petale content to show, we add a ">" if (suggestionData["d"]["links"] || suggestionData["d"]["ref"]) { var divChevron = document.createElement("div"); orangesearch.tools.addClass(divChevron, "chevron"); newSuggestionHtmlObject.appendChild(divChevron); } // Run the populate of completion component this.completionComponent.populateNewSuggestionRow(query, newSuggestionHtmlObject, suggestionData, positionOfElementCount); }, /** * Initialize properties for Petale * using the completion component properties * */ initProperties: function() { // Set the value for the _smoothShow if (!wink.isUndefined(this.completionComponent._properties.petaleSmoothShow)) this._smoothShow = this.completionComponent._properties.petaleSmoothShow; // Set the value for the smooth show delay if (!wink.isUndefined(this.completionComponent._properties.petaleSmoothShowDelay) && wink.isInteger(this.completionComponent._properties.petaleSmoothShowDelay)) this._smoothShowDelay = this.completionComponent._properties.petaleSmoothShowDelay; // Set the value for the smooth show increment if (!wink.isUndefined(this.completionComponent._properties.petaleSmoothShowIncrement) && wink.isInteger(this.completionComponent._properties.petaleSmoothShowIncrement)) this._smoothShowIncrement = this.completionComponent._properties.petaleSmoothShowIncrement; // Set the value for the border bottom if (!wink.isUndefined(this.completionComponent._properties.petaleBorderBottomOfContainer) && wink.isInteger(this.completionComponent._properties.petaleBorderBottomOfContainer)) this._borderBottomOfCompletionContainer = this.completionComponent._properties.petaleBorderBottomOfContainer; // Set the petale image width height mapping if (!wink.isUndefined(this.completionComponent._properties.petaleImageWidthHeight)) this.petaleImageWidthHeight = this.completionComponent._properties.petaleImageWidthHeight; // Set the petale info size if (!wink.isUndefined(this.completionComponent._properties.petaleInfoSize) && this.completionComponent._properties.petaleInfoSize > 3) this._petaleInfoSize = this.completionComponent._properties.petaleInfoSize; // Set the petale title size if (!wink.isUndefined(this.completionComponent._properties.petaleTitleSize) && this.completionComponent._properties.petaleTitleSize > 3) this._petaleTitleSize = this.completionComponent._properties.petaleTitleSize; // Set the petale thematics if (!wink.isUndefined(this.completionComponent._properties.petaleThematics)) this._petaleThematics = this.completionComponent._properties.petaleThematics; // Set the default petale images if (!wink.isUndefined(this.completionComponent._properties.petaleDefaultImages)) this._petaleDefaultImages = this.completionComponent._properties.petaleDefaultImages; // Customize the default petale click action if (!wink.isUndefined(this.completionComponent._properties.petaleClickHandler)) this._petaleClickHandler = this.completionComponent._properties.petaleClickHandler; }, /** * Create the petale container that will * contain all petale elements * */ initPlugin: function() { // Create the petale object container and set it position relative var completionPetaleObjectContainer = document.createElement('div'); completionPetaleObjectContainer.style.position = 'relative'; completionPetaleObjectContainer.style.width = '0px'; completionPetaleObjectContainer.style.height = '0px'; completionPetaleObjectContainer.style.margin = '0px'; completionPetaleObjectContainer.style.padding = '0px'; completionPetaleObjectContainer.style.zIndex = '910'; completionPetaleObjectContainer.style.cssFloat = 'left'; completionPetaleObjectContainer.style.styleFloat = 'left'; //Save it and place it before the completion reference this._petaleDivObjectContainer = completionPetaleObjectContainer; if (this.completionComponent.hasPositionReference()) this.completionComponent.getPositionReference().parentNode.insertBefore(completionPetaleObjectContainer, this.completionComponent.getPositionReference()); else this.completionComponent.getCompletionDiv().parentNode.insertBefore(completionPetaleObjectContainer, this.completionComponent.getCompletionDiv()); }, /** * Remove the Petale container * */ removePlugin: function() { // Pre Remove the petale object container from the DOM if exists if (this._petaleDivObjectContainer) { if (this.completionComponent.hasPositionReference()) this.completionComponent.getPositionReference().parentNode.removeChild(this._petaleDivObjectContainer); else this.completionComponent.getCompletionDiv().parentNode.removeChild(this._petaleDivObjectContainer); } }, /** * Initialize subscriptions of the Petale plugin * */ initSubscription: function() { // Register highLightPetaleElement when a suggestion is selected wink.subscribe('/callback/highlightNewValue/suggestionHighlighted', {context: this, method: 'highLightPetaleElement'}); // Register _savePositionForPetaleSuggestion when a suggestion is added wink.subscribe('/callback/displayCompletionResults/elementAdded', {context: this, method: '_savePositionForPetaleSuggestion'}); // Register createAllPetaleElements when all suggestion are added in the list wink.subscribe('/callback/displayCompletionResults/allElementsAdded', {context: this, method: '_createAllPetaleElementsCall'}); // Subscribe to the hidediv event wink.subscribe('/callback/hideCompletionDiv/hide', {context: this, method: 'hideAllPetaleElements'}); // Subscribe to the reset event wink.subscribe('/callback/resetCompletion/reset', {context: this, method: 'hideAllPetaleElements'}); // Subscribe to the before display to destroy all existing petale element wink.subscribe('/callback/displayCompletionResults/beforeDisplay', {context: this, method: '_resetPetaleData'}); }, /** * Remove subscriptions of the Petale plugin * */ removeSubscription: function() { // Register highLightPetaleElement when a suggestion is selected wink.unsubscribe('/callback/highlightNewValue/suggestionHighlighted', {context: this, method: 'highLightPetaleElement'}); // Register _savePositionForPetaleSuggestion when a suggestion is added wink.unsubscribe('/callback/displayCompletionResults/elementAdded', {context: this, method: '_savePositionForPetaleSuggestion'}); // Register createAllPetaleElements when all suggestion are added in the list wink.unsubscribe('/callback/displayCompletionResults/allElementsAdded', {context: this, method: '_createAllPetaleElementsCall'}); // Subscribe to the hidediv event wink.unsubscribe('/callback/hideCompletionDiv/hide', {context: this, method: 'hideAllPetaleElements'}); // Subscribe to the reset event wink.unsubscribe('/callback/resetCompletion/reset', {context: this, method: 'hideAllPetaleElements'}); // Subscribe to the before display to destroy all existing petale element wink.unsubscribe('/callback/displayCompletionResults/beforeDisplay', {context: this, method: '_resetPetaleData'}); }, /** * Declare listeners on the field * of the completion component * */ initFieldListeners: function() { var petalePlugin = this; // Re Declare Petale listener for key down orangesearch.tools.removeEventListener(this.completionComponent.field, "keydown", this.completionComponent.manageFieldOnKeyDown); this.onKeyDownFunctionHandler = function(event) { petalePlugin.manageFieldOnKeyDown(event); }; orangesearch.tools.addEventListener(this.completionComponent.field, "keydown", this.onKeyDownFunctionHandler); // Re Declare Petale listener and key up orangesearch.tools.removeEventListener(this.completionComponent.field, "keyup", this.completionComponent.manageFieldOnKeyUp); this.onKeyUpFunctionHandler = function(event) { petalePlugin.manageFieldOnKeyUp(event); }; orangesearch.tools.addEventListener(this.completionComponent.field, "keyup", this.onKeyUpFunctionHandler); }, /** * Remove listeners on the field * of the completion component * */ removeFieldListeners: function() { orangesearch.tools.removeEventListener(this.completionComponent.field, "keydown", this.onKeyDownFunctionHandler); orangesearch.tools.removeEventListener(this.completionComponent.field, "keyup", this.onKeyUpFunctionHandler); }, /** * Handle the key up * Check if we are currently in a petale * navigation and do some specific job * In other case, do the basic component job * * @param Event event the key event * * @return boolean */ manageFieldOnKeyUp: function(event) { // Check if event come from window if (!event && window.event) event = window.event; // Check event and key code if (!event || !event.keyCode) return false; // Check if we are currently in a petale container if (this._justExit || this._navigateInPetale) { this._justExit = false; // Check the key code if (event.keyCode == this.completionComponent.keyCode.keyDown || event.keyCode == this.completionComponent.keyCode.keyUp || event.keyCode == this.completionComponent.keyCode.keyLeft || event.keyCode == this.completionComponent.keyCode.keyRight || event.keyCode == this.completionComponent.keyCode.keyEnter) { return false; } } // Finally do the component basic job return this.completionComponent.defaultFieldOnKeyUpCallBack(event); }, /** * Handle the key down * Check if we are currently in a petale * navigation and do some specific job * In other case, do the basic component job * * @param Event event the key event * * @return boolean */ manageFieldOnKeyDown: function(event) { // Check if event come from window if (!event && window.event) event = window.event; // Check event and key code if (!event || !event.keyCode) return false; switch (event.keyCode) { // RIGHT try to enter case this.completionComponent.keyCode.keyRight: // Try to enter in petale container and return if success if (this._enterInPetaleContainer(event)) return true; break; // LEFT try to exit case this.completionComponent.keyCode.keyLeft: // try to exit from petale container and return if success if (this._exitFromPetaleContainer(event)) return true; break; // UP case this.completionComponent.keyCode.keyUp: if (this._upInPetaleContainer(event)) return true; break; // DOWN case this.completionComponent.keyCode.keyDown: if (this._downInPetaleContainer(event)) return true; break; // ENTER KEY case this.completionComponent.keyCode.keyEnter: if (this._validateInPetaleContainer(event)) return true; break; // Other key default: // Do nothing for other key -> the component will do the job break; } // Finally do the component basic job return this.completionComponent.defaultFieldOnKeyDownCallBack(event); }, /** * Enter in the petale container * corresponding to the current highlighted * index in the auto completer * * @param Event event * * @return boolean */ _enterInPetaleContainer: function(event) { // Check if we are already in navigation if (this._navigateInPetale) return true; // Retrieve and check the current highlighted index var currentHighlightedIndex = this.completionComponent.getHighlightedSuggestionIndex(); if (!currentHighlightedIndex) return false; // Retrieve and check the corresponding petale container var content = wink.byId(this._petaleContainerIdentifier + currentHighlightedIndex); if (!content) return false; // Retrieve the link count (including the reference if its an action) var linkCount = this.completionComponent.getSuggestionParam(this.completionComponent.getSuggestionPrefix() + currentHighlightedIndex, 'linkCount'); if (!linkCount) return false; // Retrieve and check the first link (reference) in the current petale container var currentLink = wink.byId('petaleActionLink_' + currentHighlightedIndex + '_1'); if (!currentLink) return false; // Set the current petale id this._navigateInPetale = true; this._currentPetaleId = currentHighlightedIndex; this._currentPetaleLinkId = 1; // Append the style 'over' on the current link orangesearch.tools.addClass(currentLink, this._petaleGeneralActionOverCssClass); return true; }, /** * Exit from the petale container * corresponding to the current highlighted * index in the auto completer * * @param Event event * * @return boolean */ _exitFromPetaleContainer: function(event) { // Check if we are in navigation if (!this._navigateInPetale) return false; // Check the current petale id and link id are not false if (!this._currentPetaleId || !this._currentPetaleLinkId) return false; // Retrieve the current link and check it var currentLink = wink.byId('petaleActionLink_' + this._currentPetaleId + '_' + this._currentPetaleLinkId); if (!currentLink) return false; // Remove the style 'over' from the current style orangesearch.tools.removeClass(currentLink, this._petaleGeneralActionOverCssClass); // Reset values this._currentPetaleId = 0; this._currentPetaleLinkId = 0; this._justExit = true; // Change the navigate status in 10 ms (to avoid the key UP process) var plugin = this; setTimeout(function() { plugin._navigateInPetale = false; orangesearch.tools.focusOnInputEnd(plugin.completionComponent.field); }, 30); return true; }, /** * Manage the key up in the petale * container corresponding to the current * container * * @param Event event * * @return boolean */ _upInPetaleContainer: function(event) { // Check if we are in navigation if (!this._navigateInPetale) return false; // Check the current petale id and link id if (!this._currentPetaleId || !this._currentPetaleLinkId) return false; // Retrieve the link count for the current petale id and check it var linkCount = this.completionComponent.getSuggestionParam(this.completionComponent.getSuggestionPrefix() + this._currentPetaleId, 'linkCount'); if (!linkCount) return false; // Update link identifier var newPetaleLinkId = this._currentPetaleLinkId - 1; if (newPetaleLinkId < 1) newPetaleLinkId = linkCount; // Check that the new petale link index is not the same as before one if (newPetaleLinkId == this._currentPetaleLinkId) return true; // Retrieve the previous link and check it var currentLink = wink.byId('petaleActionLink_' + this._currentPetaleId + '_' + this._currentPetaleLinkId); if (!currentLink) return false; // Reset the previous link style orangesearch.tools.removeClass(currentLink, this._petaleGeneralActionOverCssClass); // Update the petale link this._currentPetaleLinkId = newPetaleLinkId; // Retrieve the new link and check it currentLink = wink.byId('petaleActionLink_' + this._currentPetaleId + '_' + this._currentPetaleLinkId); if (!currentLink) return false; // Set the new link style orangesearch.tools.addClass(currentLink, this._petaleGeneralActionOverCssClass); return true; }, /** * Manage the key up in the petale * container corresponding to the current * container * * @param Event event * * @return boolean */ _downInPetaleContainer: function(event) { // Check if we are in navigation if (!this._navigateInPetale) return false; // Check the current petale id and link id if (!this._currentPetaleId || !this._currentPetaleLinkId) return false; // Retrieve the link count for the current petale id and check it var linkCount = this.completionComponent.getSuggestionParam(this.completionComponent.getSuggestionPrefix() + this._currentPetaleId, 'linkCount'); if (!linkCount) return false; // Update link identifier var newPetaleLinkId = this._currentPetaleLinkId + 1; if (newPetaleLinkId > linkCount) newPetaleLinkId = 1; // Check that the new petale link index is not the same as before one if (newPetaleLinkId == this._currentPetaleLinkId) return true; // Retrieve the previous link and check it var currentLink = wink.byId('petaleActionLink_' + this._currentPetaleId + '_' + this._currentPetaleLinkId); if (!currentLink) return false; // Reset the previous link style orangesearch.tools.removeClass(currentLink, this._petaleGeneralActionOverCssClass); // Update the petale link this._currentPetaleLinkId = newPetaleLinkId; // Retrieve the new link and check it currentLink = wink.byId('petaleActionLink_' + this._currentPetaleId + '_' + this._currentPetaleLinkId); if (!currentLink) return false; // Set the new link style orangesearch.tools.addClass(currentLink, this._petaleGeneralActionOverCssClass); return true; }, /** * Validate the current petale * action link or return false if an error * occured * * @param Event event * * @return boolean */ _validateInPetaleContainer: function(event) { // Check if we are in navigation if (!this._navigateInPetale) return false; // Check the current petale id and link id if (!this._currentPetaleId || !this._currentPetaleLinkId) return false; this._justExit = true; // Retrieve the URL, check it and redirect var url = this.completionComponent.getSuggestionParam(this.completionComponent.getSuggestionPrefix() + this._currentPetaleId, 'link_url_action_' + this._currentPetaleLinkId); if (url) { // Stop the event to avoid form submit orangesearch.tools.stopEvent(event); // Redirect with a delay var _this = this; setTimeout(function() { _this._petaleClickHandler(url); }, 250); } return true; }, /** * Return the info string * cut if the length is too big * * @param string initial info * * @return string cut info */ _getFormattedText: function(info, size) { if (info.length > size) return info.substring(0, size - 3) + '...'; return info; }, /** * Create the petale element * relative to the thematic value * If the thematic is not found, * create a people petale element */ createOnePetaleElement: function(indexPosition, petaleData) { // Define the autocompleter and plugin in local var var autoCompleter = this.completionComponent; var petalePlugin = this; // Default thematic is people var thematic = this._petaleThematics['people']; if (petaleData["d"]["them"]) thematic = petaleData["d"]["them"].toLowerCase(); switch(thematic) { case this._petaleThematics['meteo']: this._createMeteoPetaleElement(autoCompleter, petalePlugin, indexPosition, petaleData); break; case this._petaleThematics['shopping']: this._createShoppingPetaleElement(autoCompleter, petalePlugin, indexPosition, petaleData); break; case this._petaleThematics['boutique']: this._createBoutiquePetaleElement(autoCompleter, petalePlugin, indexPosition, petaleData); break; case this._petaleThematics['people']: default: this._createPeoplePetaleElement(autoCompleter, petalePlugin, indexPosition, petaleData); break; } }, /** * Create the main petale container * that will be common to all petale type * * @param integer indexPosition the petale position * @param array petaleData the petale data * @param string type the petale thematic type * * @return HTMLElement the petale container in the DOM */ _createPetaleContainer: function(indexPosition, petaleData, type) { // Append the thematic in the parameters this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'them', type); // Create the petale container and set its identifier var petaleContainer = document.createElement("div"); orangesearch.tools.addClass(petaleContainer, this._petaleContainerCssClass + " " + this.completionComponent.getCssPrefix()); petaleContainer.id = this._petaleContainerIdentifier + this._identifierOfElement[indexPosition]; // Append the petale container (to load into DOM) this._petaleDivObjectContainer.appendChild(petaleContainer); // Create the title div container and append to the petale container var petaleTitle = document.createElement("div"); orangesearch.tools.addClass(petaleTitle, this._petaleContainerTitleCssClass); if (petaleData['d']['title']) { petaleTitle.innerHTML = this._getFormattedText(petaleData['d']['title'], this._petaleTitleSize); petaleTitle.title = petaleData['d']['title']; } else petaleTitle.innerHTML = ' '; petaleContainer.appendChild(petaleTitle); // Create the petale sub container and append to the petale container var petaleSubContainer = document.createElement("div"); orangesearch.tools.addClass(petaleSubContainer, this._petaleSubContainerCssClass); petaleSubContainer.id = this._petaleSubContainerIdentifier + this._identifierOfElement[indexPosition]; petaleContainer.appendChild(petaleSubContainer); // Finally return the petale sub container return petaleSubContainer; }, /** * Create the Reference container * and append good CSS style depend on hasAction value * Append behaviour (mouseOver, mouseOut, and click) depend on * referenceIsAction value * * @param integer indexPosition the petale position * @param boolean hasAction indicate if the petale has some actions * @param string petaleReferenceUrl the petale reference URL or false * @param string type the petale type * * @return HTMLElement the reference container */ _createPetaleReferenceContainer: function(indexPosition, hasAction, petaleReferenceUrl, type) { var autoCompleter = this.completionComponent; var petalePlugin = this; // Create the ref container and apply the good CSS class depend on the hasAction value var petaleReferenceContainer = document.createElement("div"); // Set the full dimension if no action, basic dimension in other case if (hasAction) { orangesearch.tools.addClass(petaleReferenceContainer, this._petaleReferenceContainerCssClass); orangesearch.tools.addClass(petaleReferenceContainer, this._petaleReferenceContainerCssClass + "_" + type); } else { orangesearch.tools.addClass(petaleReferenceContainer, this._petaleReferenceContainerFullCssClass); orangesearch.tools.addClass(petaleReferenceContainer, this._petaleReferenceContainerFullCssClass + "_" + type); } // Append the behaviour if there is a URL if (petaleReferenceUrl !== false) { // Append an ID petaleReferenceContainer.id = 'petaleActionLink_' + this._identifierOfElement[indexPosition] + '_1'; // Append the on mouse over and mouse out behaviour orangesearch.tools.addEventListener(petaleReferenceContainer, "mouseover", function() { orangesearch.tools.addClass(this, petalePlugin._petaleGeneralActionOverCssClass); }); orangesearch.tools.addEventListener(petaleReferenceContainer, "mouseout", function() { orangesearch.tools.removeClass(this, petalePlugin._petaleGeneralActionOverCssClass); }); // Append the on click behaviour using the URL orangesearch.tools.addEventListener(petaleReferenceContainer, "click", (function(url) { return function(e) { orangesearch.tools.removeClass(this, petalePlugin._petaleGeneralActionOverCssClass); petalePlugin._petaleClickHandler(url); }; })(petaleReferenceUrl)); // Store the reference url in the suggestion this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'link_url_action_1', petaleReferenceUrl); } // Finally return the reference container return petaleReferenceContainer; }, /** * Create the Reference sub container * and append good CSS style depend on hasAction value * and type value * * @param boolean hasAction indicate if we have an action for this petale * @param string type the petale type * * @return HTMLElement the reference sub container */ _createPetaleReferenceSubContainer: function(hasAction, type) { // Create the sub ref container and apply the good CSS class depend on the hasAction value var petaleSubReferenceContainer = document.createElement("div"); if (hasAction) { orangesearch.tools.addClass(petaleSubReferenceContainer, this._petaleSubReferenceContainerCssClass); orangesearch.tools.addClass(petaleSubReferenceContainer, this._petaleSubReferenceContainerCssClass + "_" + type); } else { orangesearch.tools.addClass(petaleSubReferenceContainer, this._petaleSubReferenceContainerFullCssClass); orangesearch.tools.addClass(petaleSubReferenceContainer, this._petaleSubReferenceContainerFullCssClass + "_" + type); } return petaleSubReferenceContainer; }, /** * Create the petale reference URL bottom part * * @param integer indexPosition the petale position * @param array petaledata the petale data * @param string petaleReferenceUrl the petale reference Url value * @param string type the petale type * * @return HTMLElement */ _createPetaleReferenceUrl: function(indexPosition, petaleData, petaleReferenceUrl, type) { var petaleReferenceUrlDisplay = document.createElement("div"); orangesearch.tools.addClass(petaleReferenceUrlDisplay, this._petaleReferenceUrlDisplayCssClass); orangesearch.tools.addClass(petaleReferenceUrlDisplay, this._petaleReferenceUrlDisplayCssClass + "_" + type); if (petaleReferenceUrl && petaleReferenceUrl.length > 0) { // Retrieve the URL display for building the favicon URL var urlDisplay = petaleData["d"]["ref"]["urlDisplay"]; if (urlDisplay) { var petaleReferenceUrlDisplayFavIcon = document.createElement("img"); orangesearch.tools.addClass(petaleReferenceUrlDisplayFavIcon, this._petaleReferenceUrlDisplayFaviconCssClass); petaleReferenceUrlDisplayFavIcon.src = orangesearch.tools.getSimpleFavIconUrl(urlDisplay); orangesearch.tools.addEventListener(petaleReferenceUrlDisplayFavIcon, "error", function() { this.parentNode.removeChild(this); }); petaleReferenceUrlDisplay.appendChild(petaleReferenceUrlDisplayFavIcon); } } var petaleReferenceUrlDisplayText = document.createElement("span"); petaleReferenceUrlDisplayText.innerHTML = petaleData["d"]["ref"]["urlDisplay"]; petaleReferenceUrlDisplay.appendChild(petaleReferenceUrlDisplayText); // Finally return the reference url display return petaleReferenceUrlDisplay; }, /** * Create all petale action link * using the petale data * * @param integer indexPosition the petale position * @param array petaleData the petale data * @param string petaleReferenceUrl the petale reference URL * @param string type the petale type * * @return HTMLElement */ _createPetaleActionLinks: function(indexPosition, petaleData, petaleReferenceUrl, type) { var autoCompleter = this.completionComponent; var petalePlugin = this; // Append the link count (count the reference if its an action) if (petaleReferenceUrl !== false) this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'linkCount', petaleData["d"]["links"].length + 1); else this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'linkCount', petaleData["d"]["links"].length); // Create the petale action link container var petaleActionLinkContainer = document.createElement("div"); orangesearch.tools.addClass(petaleActionLinkContainer, this._petaleActionLinkContainerCssClass); orangesearch.tools.addClass(petaleActionLinkContainer, this._petaleActionLinkContainerCssClass + "_" + type); // Initialize the action identifier depend on the reference action status var actionIdentifier = 1; if (petaleReferenceUrl !== false) actionIdentifier++; // Append all actions for(var i = 0 ; i < petaleData["d"]["links"].length ; i++) { // Check the data if (!petaleData["d"]["links"][i]["act"] || !petaleData["d"]["links"][i]["type"] || !petaleData["d"]["links"][i]["url"] || !petaleData["d"]["links"][i]["urlDisplay"]) continue; // Create and append a separator var petaleActionLinkSeparator = document.createElement("div"); orangesearch.tools.addClass(petaleActionLinkSeparator, this._petaleActionLinkSeparatorCssClass); orangesearch.tools.addClass(petaleActionLinkSeparator, this._petaleActionLinkSeparatorCssClass + "_" + type); petaleActionLinkContainer.appendChild(petaleActionLinkSeparator); // Create an action link var petaleActionLink = document.createElement("div"); petaleActionLink.id = 'petaleActionLink_' + this._identifierOfElement[indexPosition] + '_' + actionIdentifier; orangesearch.tools.addClass(petaleActionLink, this._petaleActionLinkCssClass); orangesearch.tools.addClass(petaleActionLink, this._petaleActionLinkCssClass + "_" + type); // Append the CSS style changes on mouse over and mouse out orangesearch.tools.addEventListener(petaleActionLink, "mouseover", function() { orangesearch.tools.addClass(this, petalePlugin._petaleGeneralActionOverCssClass); }); orangesearch.tools.addEventListener(petaleActionLink, "mouseout", function() { orangesearch.tools.removeClass(this, petalePlugin._petaleGeneralActionOverCssClass); }); // Add the action var petaleAction = document.createElement("div"); orangesearch.tools.addClass(petaleAction, petaleData["d"]["links"][i]["type"] + 'ActionClass'); // Append the link url for the current link action var urlActionLink = petaleData["d"]["links"][i]["url"]; this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'link_url_' + (i+1), urlActionLink); orangesearch.tools.addEventListener(petaleActionLink, "click", (function(url) { return function() { orangesearch.tools.removeClass(this, petalePlugin._petaleGeneralActionOverCssClass); petalePlugin._petaleClickHandler(url); }; })(urlActionLink)); this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'link_url_action_' + actionIdentifier, urlActionLink); var petaleActionText = document.createElement("span"); petaleActionText.innerHTML = petaleData["d"]["links"][i]["act"]; orangesearch.tools.addClass(petaleActionText, this._petaleActionTextCssClass); petaleAction.appendChild(petaleActionText); petaleActionLink.appendChild(petaleAction); // Add the url display of the action var petaleActionUrlDisplay = document.createElement('div'); orangesearch.tools.addClass(petaleActionUrlDisplay, this._petaleActionUrlDisplayCssClass); var petaleActionText = document.createElement("span"); petaleActionText.innerHTML = petaleData["d"]["links"][i]["urlDisplay"]; orangesearch.tools.addClass(petaleActionText, this._petaleActionUrlDisplayTextCssClass); petaleActionUrlDisplay.appendChild(petaleActionText); petaleActionLink.appendChild(petaleActionUrlDisplay); // Append the action in the petale container petaleActionLinkContainer.appendChild(petaleActionLink); // Update the action identifier actionIdentifier++; } return petaleActionLinkContainer; }, /** * Create the petale image section * using the image URL specified in argument * * @param object petalePlugin the petale plugin itself * @param string imageUrl the URL of the image * @param integer type the petale type * * @return HTMLElement the petale image container */ _createPetaleImage: function(petalePlugin, imageUrl, type) { // Create the image table container and append its CSS class var petaleReferenceImageTable = document.createElement("table"); orangesearch.tools.addClass(petaleReferenceImageTable, this._petaleReferenceImageTableCssClass); orangesearch.tools.addClass(petaleReferenceImageTable, this._petaleReferenceImageTableCssClass + '_' + type); petaleReferenceImageTable.cellPadding = 0; petaleReferenceImageTable.cellSpacing = 0; // Insert a row at the last position available and append CSS class var petaleReferenceImageRow = petaleReferenceImageTable.insertRow(-1); orangesearch.tools.addClass(petaleReferenceImageRow, this._petaleReferenceImageTrCssClass); orangesearch.tools.addClass(petaleReferenceImageRow, this._petaleReferenceImageTrCssClass + '_' + type); // Insert a cell at the last position available in the row and append a CSS class var petaleReferenceImageCell = petaleReferenceImageRow.insertCell(-1); orangesearch.tools.addClass(petaleReferenceImageCell, this._petaleReferenceImageTdCssClass); orangesearch.tools.addClass(petaleReferenceImageCell, this._petaleReferenceImageTdCssClass + '_' + type); // Create the image container var petaleReferenceImageContainer = document.createElement("div"); orangesearch.tools.addClass(petaleReferenceImageContainer, this._petaleReferenceImageContainerCssClass); orangesearch.tools.addClass(petaleReferenceImageContainer, this._petaleReferenceImageContainerCssClass + '_' + type); // Initialize the default image src onError var defaultImageOnErrorSrc = false; if (petalePlugin._petaleDefaultImages && petalePlugin._petaleDefaultImages["default"]) defaultImageOnErrorSrc = petalePlugin._petaleDefaultImages["default"]; if (petalePlugin._petaleDefaultImages && petalePlugin._petaleDefaultImages[type]) defaultImageOnErrorSrc = petalePlugin._petaleDefaultImages[type]; // Create the image var petaleReferenceImage = document.createElement("img"); orangesearch.tools.addClass(petaleReferenceImage, this._petaleReferenceImageCssClass); if (typeof imageUrl == "string" && imageUrl.length > 0) { petaleReferenceImage.src = imageUrl; orangesearch.tools.addClass(petaleReferenceImage, this._petaleReferenceImageCssClass + '_' + type); } else { if (defaultImageOnErrorSrc) petaleReferenceImage.src = defaultImageOnErrorSrc; orangesearch.tools.addClass(petaleReferenceImage, this._petaleReferenceImageCssClass + '_' + type + '_default'); } // On error callback function orangesearch.tools.addEventListener(petaleReferenceImage, "error", function() { // Load the default image onError src if (defaultImageOnErrorSrc) this.src = defaultImageOnErrorSrc; // Remove type CSS and append type_default CSS orangesearch.tools.removeClass(this, petalePlugin.completionComponent.getCssPrefix() + petalePlugin._petaleReferenceImageCssClass + '_' + type); orangesearch.tools.addClass(this, petalePlugin.completionComponent.getCssPrefix() + petalePlugin._petaleReferenceImageCssClass + '_' + type + '_default'); }); // Finally append the image in the cell and return the table petaleReferenceImageContainer.appendChild(petaleReferenceImage); petaleReferenceImageCell.appendChild(petaleReferenceImageContainer); return petaleReferenceImageTable; }, /** * Create the info section for the specified petale type * and append the section to the petale sub reference node * * @param HTMLElement petaleSubReferenceContainer the petale sub reference containing infos * @param integer indexPosition the petale position * @param array petaleData the petale data array * @param string type the petale type * @param string petaleReferenceUrl the petale reference URL */ _createPetaleInfos: function(petaleSubReferenceContainer, indexPosition, petaleData, type, petaleReferenceUrl) { // Create the info container var petaleReferenceInfoContainer = document.createElement("div"); orangesearch.tools.addClass(petaleReferenceInfoContainer, this._petaleReferenceInfoContainerCssClass); orangesearch.tools.addClass(petaleReferenceInfoContainer, this._petaleReferenceInfoContainerCssClass + "_" + type); petaleSubReferenceContainer.appendChild(petaleReferenceInfoContainer); // Create the info top container var petaleReferenceInfoTopContainer = document.createElement("div"); orangesearch.tools.addClass(petaleReferenceInfoTopContainer, this._petaleReferenceInfoTopContainerCssClass); orangesearch.tools.addClass(petaleReferenceInfoTopContainer, this._petaleReferenceInfoTopContainerCssClass + "_" + type); petaleReferenceInfoContainer.appendChild(petaleReferenceInfoTopContainer); // Append all basic informations this._createPetaleBasicInfos(petaleData, petaleReferenceInfoTopContainer, type); // Append the display URL at the bottom if (petaleData["d"]["ref"]["urlDisplay"] && petaleReferenceUrl) { var petaleReferenceUrlDisplay = this._createPetaleReferenceUrl(indexPosition, petaleData, petaleReferenceUrl, type); petaleReferenceInfoContainer.appendChild(petaleReferenceUrlDisplay); } }, /** * Create a basic info section using info data * in the petale data and append the section to the * petale sub reference container * * @param array petaleData the petale data array * @param HTMLElement container the container * @param string type the petale type */ _createPetaleBasicInfos: function(petaleData, container, type) { for(var i = 0 ; i < petaleData["d"]["ref"]["info"].length ; i++) { // Skip unconsistent info if (petaleData["d"]["ref"]["info"][i].length == 0) continue; // Create the petale reference info and add style var petaleReferenceInfo = document.createElement("div"); orangesearch.tools.addClass(petaleReferenceInfo, this._petaleReferenceInfoCssClass + "_" + (i + 1)); // Switch on the type for specific behaviour switch(type) { case 'boutique': // Check if we have a specific suffix if (i == 1) { // handle the mark for boutique (mark is between 0 and 5 so we have to cast into float, double and cast to int) var markValue = Math.round(parseFloat(petaleData["d"]["ref"]["info"][i]) * 2); if (markValue < 0) markValue = 0; else if (markValue > 10) markValue = 10; orangesearch.tools.addClass(petaleReferenceInfo, this._petaleReferenceInfoCssClass + "_" + type + "_mark"); petaleReferenceInfo.style.backgroundPosition = 'left -' + (14 * markValue) + 'px'; break; } default: orangesearch.tools.addClass(petaleReferenceInfo, this._petaleReferenceInfoCssClass + "_" + type + "_" + (i + 1)); petaleReferenceInfo.innerHTML = this._getFormattedText(petaleData["d"]["ref"]["info"][i], this._petaleInfoSize); break; } container.appendChild(petaleReferenceInfo); } }, /** * Finalize the petale * disable the input field blur when user enter on the petale * set an absolute position and high index * hide the petale * * @param Component autoCompleter the auto completer component * @param integer indexPosition the petale position * @param array petaleData the petale data * @param HTMLElement petaleSubContainer the petale sub container */ _finalizePetale: function(autoCompleter, indexPosition, petaleData, petaleSubContainer) { // Disable the input field blur action when user is on the petale container var completionIdentifier = this._identifierOfElement[indexPosition]; orangesearch.tools.addEventListener(petaleSubContainer.parentNode, "mouseover", function() { autoCompleter._disableOnBlur = true; autoCompleter._highlightedSuggestionIndex = completionIdentifier; }); orangesearch.tools.addEventListener(petaleSubContainer.parentNode, "mouseout", function() { autoCompleter._disableOnBlur = false; }); // Keep the focus on the input field of the auto completer orangesearch.tools.addEventListener(petaleSubContainer.parentNode, "click", function() { autoCompleter.field.focus(); }); // Set style (absolute position, zIndex and hide) petaleSubContainer.parentNode.style.position = 'absolute'; petaleSubContainer.parentNode.style.zIndex = '950'; petaleSubContainer.parentNode.style.visibility = 'hidden'; }, /** * Check the petale data * * @param boolean checkLink indicate if we have to check link * * @return boolean */ _checkPetaleData: function(petaleData, checkLink) { if (checkLink) { if (!petaleData["d"]["ref"] || !petaleData["d"]["links"] || petaleData["d"]["links"].length == 0) return false; } else { if (!petaleData["d"]["ref"]) return false; } return true; }, /** * Create a petale with action link * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin it self * @param integer indexPosition the position of the petale * @param array petaleData the petale data * @param array params the petale params containing the type, auto resize... */ _createPetaleWithAction: function(autoCompleter, petalePlugin, indexPosition, petaleData, params) { // Check mandatory params if (typeof params == "undefined" || typeof params['type'] == "undefined" || typeof params['centerImage'] == "undefined") { wink.log("Error: petale must have a type and centerImage value"); return; } // Check petale data including links if (!this._checkPetaleData(petaleData, true)) { wink.log("Error: people petale must have reference and at least one action link"); return; } // Create the petale container and save the thematic var petaleSubContainer = this._createPetaleContainer(indexPosition, petaleData, params['type']); // Retrieve the petale reference URL var petaleReferenceUrl = petaleData["d"]["ref"]["url"]; // Create the reference container and the sub reference container var petaleReferenceContainer = this._createPetaleReferenceContainer(indexPosition, true, petaleReferenceUrl, params['type']); var petaleSubReferenceContainer = this._createPetaleReferenceSubContainer(true, params['type']); petaleReferenceContainer.appendChild(petaleSubReferenceContainer); petaleSubContainer.appendChild(petaleReferenceContainer); // Create the image (even if not exist) if (petaleData["d"]["ref"]["image"]) petaleSubReferenceContainer.appendChild(this._createPetaleImage(petalePlugin, petaleData["d"]["ref"]["image"], params['type'], params['centerImage'])); else petaleSubReferenceContainer.appendChild(this._createPetaleImage(petalePlugin, false, params['type'], params['centerImage'])); // Create the info is exist if (petaleData["d"]["ref"]["info"]) this._createPetaleInfos(petaleSubReferenceContainer, indexPosition, petaleData, params['type'], petaleReferenceUrl); // Create and append other action var petaleActionLinkContainer = this._createPetaleActionLinks(indexPosition, petaleData, petaleReferenceUrl, params['type']); petaleSubContainer.appendChild(petaleActionLinkContainer); // Finalize the petale (blur beahaviour and absolute positionning) this._finalizePetale(autoCompleter, indexPosition, petaleData, petaleSubContainer); }, /** * Create a petale with action link * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin it self * @param integer indexPosition the position of the petale * @param array petaleData the petale data * @param string type the petale type */ _createPetaleBasic: function(autoCompleter, petalePlugin, indexPosition, petaleData, params) { // Check mandatory params if (typeof params == "undefined" || typeof params['type'] == "undefined" || typeof params['centerImage'] == "undefined") { wink.log("Error: petale must have a type and centerImage value"); return; } // Check petale data including links if (!this._checkPetaleData(petaleData, false)) { wink.log("Error: people petale must have reference and at least one action link"); return; } // Create the petale container and save the thematic var petaleSubContainer = this._createPetaleContainer(indexPosition, petaleData, params['type']); // Retrieve the petale reference URL var petaleReferenceUrl = petaleData["d"]["ref"]["url"]; // Create the reference container and the sub reference container var petaleReferenceContainer = this._createPetaleReferenceContainer(indexPosition, false, petaleReferenceUrl, params['type']); var petaleSubReferenceContainer = this._createPetaleReferenceSubContainer(false, params['type']); petaleReferenceContainer.appendChild(petaleSubReferenceContainer); petaleSubContainer.appendChild(petaleReferenceContainer); // Create the image if exist if (petaleData["d"]["ref"]["image"]) petaleSubReferenceContainer.appendChild(this._createPetaleImage(petalePlugin, petaleData["d"]["ref"]["image"], params['type'], params['centerImage'])); else petaleSubReferenceContainer.appendChild(this._createPetaleImage(petalePlugin, false, params['type'], params['centerImage'])); // Create the info is exist if (petaleData["d"]["ref"]["info"]) this._createPetaleBasicInfos(petaleData, petaleSubReferenceContainer, params['type']); // Append a clean div (the image could be float and have a big height) var cleanDiv = document.createElement("div"); cleanDiv.style.clear = 'left'; cleanDiv.style.height = '0px'; petaleSubReferenceContainer.appendChild(cleanDiv); // Create the url display if exist if (petaleData["d"]["ref"]["urlDisplay"]) { var petaleReferenceUrlDisplay = this._createPetaleReferenceUrl(indexPosition, petaleData, petaleReferenceUrl, params['type']); petaleReferenceContainer.appendChild(petaleReferenceUrlDisplay); } // No action, just count the reference action status if (petaleReferenceUrl !== false) this.completionComponent.addSuggestionParam(this._idOfElement[indexPosition], 'linkCount', 1); // Finalize the petale (blur beahaviour and absolute positionning) this._finalizePetale(autoCompleter, indexPosition, petaleData, petaleSubContainer); }, /** * Create the petale people element with the current * position and the current petale data * and attach it on the Petale container * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin itself * @param integer indexPosition the petale position * @param array petaleData the petale data * */ _createPeoplePetaleElement: function(autoCompleter, petalePlugin, indexPosition, petaleData) { var params = { "type": "people", "centerImage": false }; this._createPetaleWithAction(autoCompleter, petalePlugin, indexPosition, petaleData, params); }, /** * Create the petale boutique element with the current * position and the current petale data * and attach it on the Petale container * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin itself * @param integer indexPosition the petale position * @param array petaleData the petale data * */ _createBoutiquePetaleElement: function(autoCompleter, petalePlugin, indexPosition, petaleData) { var params = { "type": "boutique", "centerImage": true }; this._createPetaleWithAction(autoCompleter, petalePlugin, indexPosition, petaleData, params); }, /** * Create the petale shopping element with the current * position and the current petale data * and attach it on the Petale container * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin itself * @param integer indexPosition the petale position * @param array petaleData the petale data * */ _createShoppingPetaleElement: function(autoCompleter, petalePlugin, indexPosition, petaleData) { var params = { "type": "shopping", "centerImage": false }; this._createPetaleBasic(autoCompleter, petalePlugin, indexPosition, petaleData, params); }, /** * Create the petale meteo element with the current * position and the current petale data * and attach it on the Petale container * * @param object autoCompleter the auto completer object * @param object petalePlugin the petale plugin itself * @param integer indexPosition the petale position * @param array petaleData the petale data * * @return boolean */ _createMeteoPetaleElement: function(autoCompleter, petalePlugin, indexPosition, petaleData) { var params = { "type": "meteo", "centerImage": false }; this._createPetaleBasic(autoCompleter, petalePlugin, indexPosition, petaleData, params); }, /** * Hide all petale element * in the petale container * */ hideAllPetaleElements: function(properties) { // Check that we have the good uid if (!properties.uid || properties.uid != this.completionComponent.getUid()) return; if (this._petaleDivObjectContainer) { this._currentPetaleId = 0; this._currentPetaleLinkId = 0; this._navigateInPetale = false; var allPetaleArrowAndElement = this._petaleDivObjectContainer.children; if (allPetaleArrowAndElement.length > 0) { for(var i = 0 ; i < allPetaleArrowAndElement.length ; i++) allPetaleArrowAndElement[i].style.visibility = 'hidden'; } } return true; }, /** * Remove all petale element * from the petale div container * */ _destroyAllPetaleElements: function() { // Reset the content of the petale div container if (this._petaleDivObjectContainer) this._petaleDivObjectContainer.innerHTML = ""; }, /** * Reset all array used * to save the petale data * */ _resetPetaleData: function() { // Re initialize all internal arrays this._identifierOfElement = []; this._idOfElement = []; this._petaleDdata = []; }, /** * Show a petale element specified by its identfier * If the petale element is already visible, do nothing * If the smooth option is activated, show the petale * element in a smooth way * * @param object properties containing the index */ highLightPetaleElement: function(properties) { // Check if we have a valid identifier if (!wink.isInteger(properties.index)) return; var index = properties.index; // Retrieve petale block to show it var content = wink.byId(this._petaleContainerIdentifier + index); // If there is no petale element for current id, just hide and return if (!content) { this.hideAllPetaleElements(properties); return; } // Check if the arrow and content is already visible if (content.style.visibility == 'visible') return; // First hide all petale element this.hideAllPetaleElements(properties); if (this._smoothShow) orangesearch.tools.setOpacity(0, this._petaleContainerIdentifier + index); // Display the arrow and the petale block content.style.visibility = 'visible'; if (this._smoothShow) this._upOpacityRecursive(this._smoothShowIncrement, 100, this._petaleContainerIdentifier + index, this._smoothShowDelay); }, /** * Save the position and dimension of the current element * and the current completion block * This will be usefull for the petale positionnement * * @param properties properties containing position and HTML object */ _savePositionForPetaleSuggestion: function(properties) { // Retrieve position, div and data from properties var positionOfElementCount = properties.position; var newSuggestionLine = properties.div; var suggestionData = properties.data; // Check if the type is valid for petale if (!suggestionData["t"] || !this.isValidType(suggestionData["t"])) return; // Save the identifier, the id and the data for petale this._identifierOfElement[positionOfElementCount] = positionOfElementCount; this._idOfElement[positionOfElementCount] = newSuggestionLine.id; this._petaleDdata[positionOfElementCount] = suggestionData; }, /** * Call the _createAllPetaleElements in a tieout * and return directly * */ _createAllPetaleElementsCall: function(properties) { var plugin = this; setTimeout(function() { plugin._createAllPetaleElements(properties); }, 1); }, /** * Create all petale elements * and publish an event when all elements * are created * * @param properties properties containing element max position */ _createAllPetaleElements: function(properties) { // Destruct all petale element this._destroyAllPetaleElements(); // Extract the total number of elements var maxPosition = properties.position; // Loop on each eventuel petale entry and add petale element in container for(var i = 1 ; i < maxPosition ; i++) { // Extract the index data var currentpetaleData = this._petaleDdata[i]; if (!currentpetaleData) continue; // Create the petale element this.createOnePetaleElement(i, currentpetaleData); } // Publish that all petale element wink.publish('/callback/createAllPetaleElements/allPetaleElementsAdded'); }, /** * Up the opacity with an increment value * If the value is more than the max, set to * the max and stop recusion. If the object is hidden * the process is stopped and the object opacity * is reset to 0. * * @param integer increment the increment value (0 to 100) * @param integer maxValue the max value * @param string identifier the object identifier * @param integer time the time for the setTimeout (recursion) */ _upOpacityRecursive: function(increment, maxValue, identifier, time) { // Retrieve and check the object var object = wink.byId(identifier); if (!object) return; // Check if the display is invisible or hidden - in this case stop the process if (object.style.visibility == "hidden") { // Set opacitiy to 100/1 and stop recursion object.style.opacity = 1; object.style.MozOpacity = 1; object.style.KhtmlOpacity = 1; object.style.filter = "alpha(opacity=" + 100 + ")"; return; } // Retrieve the current opacity and check if we should be recursive var recursive = true; var currentOpacity = object.style.opacity * 100; currentOpacity += increment; if (currentOpacity > maxValue) { currentOpacity = maxValue; recursive = false; } // Set current opacity object.style.opacity = (currentOpacity / 100); object.style.MozOpacity = (currentOpacity / 100); object.style.KhtmlOpacity = (currentOpacity / 100); object.style.filter = "alpha(opacity=" + currentOpacity + ")"; // Run recursively if we have to if (recursive == true) { var plugin = this; setTimeout(function() { plugin._upOpacityRecursive(increment, maxValue, identifier, time); }, time); } } }; /** * @memberOf orangesearch.completion.plugin * @type Petale */ orangesearch.completion.plugin.Petale = Petale; /** * Define the "TOP trend" plugin for completion component. * It is used to display a specific template showing the top trend * * @class The toptrend plugin class * @extends Plugin **/ var TopTrend = function() { // toptrend class extends from plugin orangesearch.completion.plugin.Plugin.call(this); new orangesearch.completion.plugin.Plugin().extendFromPlugin(orangesearch.completion.plugin.TopTrend); // default top trend title this.topTrendTitle = "Top Requête"; // default max nb chars for topTrend this.topTrendMaxNbChar = false; return this; }; TopTrend.prototype = { /** * Return the type of suggestion * type is TopTrend * @return string */ getTypeOfSuggestion: function() { return 'top'; }, /** * Indicate if the suggestion type is valid for this plugin * * @param string suggestionType the suggestion type * * @return boolean */ isValidType: function(suggestionType) { return (suggestionType == this.getTypeOfSuggestion()); }, /** * Initialize properties for TopTrend * using the completion component properties * */ initProperties: function() { // Set the top trend title if exist if (!wink.isUndefined(this.completionComponent._properties.topTrendTitle)) this.topTrendTitle = this.completionComponent._properties.topTrendTitle; // Set the top trend maximum number of chars if exist // Else the value will based on the main component property if (!wink.isUndefined(this.completionComponent._properties.topTrendMaxNbChar)) this.topTrendMaxNbChar = this.completionComponent._properties.topTrendMaxNbChar; else this.topTrendMaxNbChar = this.completionComponent.maxNbChar; }, /** * Populate the current suggestion row * Append a right arrow at the end of the * suggestion HTML object * * @param object newSuggestionHtmlObject the new suggestion HTML object * @param array suggestionData the suggestion data * @param positionOfElementCount the position of the current suggestion * */ populateNewSuggestionRow: function(query, newSuggestionHtmlObject, suggestionData, positionOfElementCount) { // Append the border if required if (positionOfElementCount > 1) { var topTrendBorder = document.createElement("div"); orangesearch.tools.addClass(topTrendBorder, "header"); newSuggestionHtmlObject.appendChild(topTrendBorder); } // Create the sub container var topTrendContainer = document.createElement("div"); orangesearch.tools.addClass(topTrendContainer, "container"); // Create the span number var newSpanNumber = document.createElement("span"); orangesearch.tools.addClass(newSpanNumber, "number"); newSpanNumber.innerHTML = positionOfElementCount; // Create the span text var newSpanText = document.createElement("span"); orangesearch.tools.addClass(newSpanText, "text"); var suggestion = suggestionData["d"]["request"]; if (this.topTrendMaxNbChar > 0) suggestion = suggestion.cutString(this.topTrendMaxNbChar); newSpanText.innerHTML = suggestion; // Append the text and number to the container topTrendContainer.appendChild(newSpanNumber); topTrendContainer.appendChild(newSpanText); // Append the container in the new suggestion newSuggestionHtmlObject.appendChild(topTrendContainer); // Add the suggestion value and type to the new div this.completionComponent.addSuggestionParam(newSuggestionHtmlObject.id, "v", suggestionData["v"]); this.completionComponent.addSuggestionParam(newSuggestionHtmlObject.id, "t", suggestionData["t"]); this.completionComponent.addSuggestionParam(newSuggestionHtmlObject.id, "d", suggestionData["d"]); }, /** * Create a label span with specific content * and set its CSS class * * @param blockIndex the index of the blocks * * @returns a new label span */ populateNewBlockLabel: function(newBlockLabelElement, blockIndex, position) { // Add a special class to label orangesearch.tools.addClass(newBlockLabelElement, "toptrend"); if (position == 'Bottom') { // populate basically this.completionComponent.populateNewBlockLabel(newBlockLabelElement, blockIndex, position); return; } // Create left span with label var spanTopTrendLabel = document.createElement("span"); orangesearch.tools.addClass(spanTopTrendLabel, "text"); if (this.completionComponent._currentBlocks[blockIndex]['label']) spanTopTrendLabel.innerHTML = this.completionComponent._currentBlocks[blockIndex]['label']; else spanTopTrendLabel.innerHTML = this.topTrendTitle; // Create right span with date var spanTopTrendDate = document.createElement("span"); var spanTopTrendDay = document.createElement("span"); var spanTopTrendSimpleDate = document.createTextNode(orangesearch.tools.fullDate(true)); spanTopTrendDay.innerHTML = orangesearch.tools.getNowDay() + " "; orangesearch.tools.addClass(spanTopTrendDate, "date"); orangesearch.tools.addClass(spanTopTrendDay, "day"); spanTopTrendDate.appendChild(spanTopTrendDay); spanTopTrendDate.appendChild(spanTopTrendSimpleDate); // Add the date and label in the block newBlockLabelElement.appendChild(spanTopTrendLabel); newBlockLabelElement.appendChild(spanTopTrendDate); } }; /** * @memberOf orangesearch.completion.plugin * @type TopTrend */ orangesearch.completion.plugin.TopTrend = TopTrend;