"use strict"; /** * Tooltip. * * @class */ var GW2Tooltip = (function() { /** * Private functions for DOM interaction. * * @type {object} */ var Element = { /** * Converts a string to a DOM element and returns this. If the string * contains more than one element on the highest level, only the * first node is returned. * * @method stringToNode * @param {string} string the string. * @return {object} a DOM element. */ stringToNode : function(string) { var parent = document.createElement("div"); parent.innerHTML = string; return parent.firstChild; }, /** * Inserts an element in the DOM after another element. * * @method insertAfter * @param {object} e1 the element after which is to be * inserted. * @param {object} e2 the element to be inserted. */ insertAfter : function(e1, e2) { e1.parentNode.insertBefore(e2, e1.nextSibling); } }; /** * API access. * * @class */ var APIAccess = (function() { var queue = {}; /** * Returns a cross-domain request object, or undefined if the browser * sucks. * * @method getCORSRequest * @return {(XMLHttpRequest|ActiveXObject)} a cross-domain request * object, or undefined * if the browser * sucks. */ var getCORSRequest = (function() { var validFactory = -1, factories = [ //function() { return new XDomainRequest(); }, function() { return new XMLHttpRequest(); }, function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, function() { return new ActiveXObject("Msxml3.XMLHTTP"); }, function() { return new ActiveXObject("Microsoft.XMLHTTP"); } ]; // Try out all factories and save index of first factory that works. var factory, i, length = factories.length; for(i = 0; i < length; i++) { try { factory = factories[i](); validFactory = i; break; } catch(e) { continue; } } // Return a function that returns the cross-domain request object. return function() { if(validFactory >= 0) return factories[validFactory](); else return undefined; }; })(); /** * Reads JSON from the given URL and passes this to the callback. * * @method getJSONP * @param {string} url the URL of the JSON. * @param {Function} callback the function to pass the JSON to. */ var getJSONP = function(url, callback) { var _baseURL = "https://api.guildwars2.com/"; var xmlhttp = getCORSRequest(); xmlhttp.onreadystatechange = function() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) if(typeof callback === "function") callback(Polyfill.JSONParse(xmlhttp.response)); }; xmlhttp.open("GET", _baseURL + url, true); xmlhttp.send(); }; /** * Public functions. * * @type {object} */ var exports = {}; /** * Requests data for a single tooltip from the API right away. * * @method requestData * @param {number} id the id of the data. * @param {class} generator the generator to use for the * tooltip. * @param {Function} callback the function to pass the JSON to. */ exports.requestData = function(id, generator, callback) { getJSONP("/v2/" + generator.type + "/" + id, callback); }; /** * Adds the id for a single tooltip to the request queue. * * @method queueData * @param {object} e the element to add the tooltip to * after completing the queue. * @param {number} id the id of the data. * @param {class} generator the generator to use for the * tooltip. */ exports.queueData = function(e, id, generator) { var type = generator.type; if(queue[type] === undefined) queue[type] = []; queue[type].push([e, id]); }; /** * Executes the queue and adds all tooltips to the right elements, and * then empties the queue. * * @method executeQueue * @param {class} generator the generator to use for the * tooltip. * @param {Function} callback the function to pass the JSONs to. */ exports.executeQueue = function(generator, options, callback) { var type = generator.type; if(queue.length === 0) return; if(queue[type] === undefined) return; if(queue[type].length === 0) return; // Build URL var url = [], i, length = queue[type].length; for(i = 0; i < length; i++) url.push(queue[type][i][1]); // Get JSON and add respective tooltips getJSONP("/v2/" + type + "?ids=" + url.join(","), function(json) { var i, length1 = queue[type].length, j, length2 = json.length; for(i = 0; i < length1; i++) for(j = 0; j < length2; j++) if(json[j].id == queue[type][i][1]) callback(queue[type][i][0], json[j], generator, options); queue[type] = []; }); }; return exports; })(); var exports = {}; /** * Adds a tooltip to the given element. * * @method addDirect * @param {object} e the element to add the tooltip to. * @param {object} json the data. Should have the structure as * specified in the Guild Wars 2 API * version 2 documentation. * @param {class} generator the generator to use for the tooltip. * @param {class} options additional parameters for the tooltip. * Options starting with "link" are * used to manipulate the contents of * the tooltip link, which is the * element to which the tooltip is * added. These options can be applied * together. Use option "linkReplace" * to indicate whether the contents of * the link should be replaced with the * new information or should be * prepended instead. * The following options are * recognised: * "linkIcon" : * true if the link info should * contain an * element with the * tooltip's icon; should * be false or undefined * otherwise. * Use option * "linkIconClass" to * optionally set the class * of these images. * "linkIconClass": * if "linkIcon" is true, the * added icons will have * the class defined in * this option. * "linkInfo" : * false or undefined if no * info should be added to * the link; should be a * string to add info from * the tooltip (e.g. * "name", "id", * "chat_link" etc.). See * the Guild Wars 2 API for * all possible values. * "linkReplace" : * true to replace the link's * contents with the * content defined with * other options starting * with "link"; false to * prepend this content to * the link instead. If no * new content is defined, * an empty string will * replace or prepend the * link's contents. */ exports.addDirect = function(e, json, generator, options) { // Default parameter values options = (options || {}); options.linkIcon = (options.linkIcon || false); options.linkIconClass = (options.linkIconClass || ""); options.linkInfo = (options.linkInfo || false); // If no link contents are generated, prepend with empty string options.linkReplace = (options.linkReplace || false); // Parse JSON if needed if(typeof json === "string") json = Polyfill.JSONParse(json); Polyfill.addClass(e, "GW2TooltipLink"); var tooltip = new generator(json); // Remove current tooltip if it is there if(e.nextSibling !== null && Polyfill.hasClass(e.nextSibling, "GW2Tooltip")) e.parentNode.removeChild(e.nextSibling); // Change tooltip link if needed var innerHTML = (options.linkReplace ? "" : e.innerHTML); // Addition should be in reversed order of appearance if(options.linkInfo !== false) { innerHTML = json[options.linkInfo] + innerHTML; } if(options.linkIcon === true) { innerHTML = "" + " " + innerHTML; } e.innerHTML = innerHTML; // Insert tooltip Element.insertAfter(e, Element.stringToNode(tooltip.getTooltip())); }; /** * Adds a tooltip to the given element. * * @method addNow * @param {object} e the element to add the tooltip to. * @param {number} id the id of the data. * @param {class} generator the generator to use for the tooltip. */ exports.addNow = function(e, id, generator, options) { APIAccess.requestData(id, generator, function(json) { exports.addDirect(e, json, generator, options); }); }; /** * Adds a tooltip to the given element as soon as the user hovers over the * link. Decreases page load time and increases tooltip appear time. * * @method addLater * @param {object} e the element to add the tooltip to. * @param {number} id the id of the data. * @param {class} generator the generator to use for the tooltip. */ exports.addLater = function(e, dataId, generator, options) { var curEvent = e.onmouseover; e.onmouseover = function() { if(typeof curEvent === "function") curEvent(); APIAccess.requestData(dataId, generator, function(json) { exports.addDirect(e, json, generator, options); }); }; }; /** * Adds tooltips for all elements with the data-[type]id tag. This is * usually done directly after loading the page. * * @method addAllNow * @param {class} generator the generator to use for the tooltip. */ exports.addAllNow = function(generator, options) { var eList, i, length; // Get all elements with proper data attribute. if(document.querySelectorAll) { eList = document.querySelectorAll("[data-" + generator.attr + "]"); } else { // Custom polyfill var e, eAll = document.getElementsByTagName("*"); length = eAll.length; for(i = 0; i < length; i++) { e = eAll[i]; if(e.dataset !== undefined && e.dataset[generator.attr] !== undefined) { eList.push(e); } } } // Queue tooltips length = eList.length; for(i = 0; i < length; i++) { APIAccess.queueData( eList[i], eList[i].dataset[generator.attr], generator ); } // Add tooltips APIAccess.executeQueue(generator, options, exports.addDirect); }; /** * Static tooltip data. Used by other Tooltip classes. * * @type {object} */ exports.TooltipData = { // List of icons. icons : { base : "https://render.guildwars2.com/file/", ui_coin_gold : "090A980A96D39FD36FBB004903644C6DBEFB1FFB/156904.png", ui_coin_silver : "E5A2197D78ECE4AE0349C8B3710D033D22DB0DA6/156907.png", ui_coin_copper : "6CF8F96A3299CFC75D5CC90617C3C70331A1EF0E/156902.png", ui_infusion_slot_offensive : "FD212A4A36BEA799E3BDCDFFDC55DEC2A50FE19D/517203.png", ui_infusion_slot_defensive : "3D941C5F3C0BE9FFAFE27A2C4AE70ABC94AAE724/517202.png", ui_infusion_slot_utility : "0B5F38D94AAA1539EC6009F4CF37DF5673C81DA0/517204.png", ui_infusion_slot_agony : "F54BC4283FFAB7531073FF08F470A9730E47FB4D/683590.png" }, // List of attribute names. attributes : { BoonDuration : "Concentration", ConditionDamage : "Condition Damage", ConditionDutation : "Expertise", CritDamage : "Ferocity", Healing : "Healing Power", Power : "Power", Precision : "Precision", Toughness : "Toughness", Vitality : "Vitality" } }; /** * Polyfill functions for browser compatibility. Used by other Tooltip * classes. * * @type {object} */ var Polyfill = { /** * Sets the JSON parser to JSON4 if present, or falls back to JSON3, * then to JSON2, then to default JSON and then to eval. * * @class * @return {method} sets the JSON parser. */ JSONParse : function() { if(typeof JSON4 !== "undefined") return JSON4.parse; else if(typeof JSON3 !== "undefined") return JSON3.parse; else if(typeof JSON2 !== "undefined") return JSON2.parse; else if(typeof JSON !== "undefined") return JSON.parse; else return function(sJSON) { // Provide a JSON library to prevent this eval from // executing. return eval("(" + sJSON + ")"); }; }(), /** * Returns the first index at which a given element can be found in the * object, or -1 if it is not present. * * @method indexOf * @param {object} object the object to traverse. * @param {object} searchElement the element to locate in the * object. * @param {number} fromIndex the index to start the search * at. * @return {number} the first index at which a given element can be * found in the object, or -1 if it is not * present. */ indexOf : function(object, searchElement, fromIndex) { var k; //if(object == null) if(object === undefined || object === null) throw new TypeError("\"this\" is null or not defined"); var o = Object(object), len = o.length >>> 0; if(len === 0) return -1; var n = +fromIndex || 0; if(Math.abs(n) === Infinity) n = 0; if(n >= len) return -1; k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); while(k < len) { if(k in o && o[k] === searchElement) return k; k++; } return -1; }, /** * Returns true if the element has the given class. * * @method hasClass * @param {object} element the element. * @param {string} cls the class. * @return {string} true if the element has the given class. */ hasClass : function(element, cls) { return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1; }, /** * Adds a class to the given element. * * @method addClass * @param {object} element the element. * @param {string} cls the class. */ addClass : function(element, cls) { if(!Polyfill.hasClass(element, cls)) element.className += (" " + cls); }, /** * Returns the comma-separated string of a number. * * @method commaSeparate * @param {number} val the number to separate. * @return {string} the comma-separated string of a number. */ commaSeparate : function(val) { val = val.toString(); var str = val, dif = str.length - (3 * Math.floor(str.length / 3)); if(dif !== 0) str = str.substr(dif, str.length); var arr = str.match(/.{3}/g); if(dif !== 0) arr.unshift(val.substr(0, dif)); return arr.join(","); } }; exports.Polyfill = Polyfill; return exports; })();