From 34202691e70973d8c256543e76d3912326219999 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 16:00:00 +0200 Subject: [PATCH 01/17] Add data-type to elements --- js/jquery.mapael.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index d78340e..31c24e0 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -201,7 +201,7 @@ (self.options.areas[id] ? self.options.areas[id] : {}), self.options.legend.area ); - self.initElem(self.areas[id], elemOptions, id); + self.initElem(self.areas[id], elemOptions, id, 'area'); }); // Draw links @@ -363,7 +363,7 @@ /* * Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...) */ - initElem: function (elem, elemOptions, id) { + initElem: function (elem, elemOptions, id, type) { var self = this; var bbox = {}; var textPosition = {}; @@ -381,6 +381,7 @@ elemOptions.text.attrs["text-anchor"] = textPosition.textAnchor; elem.textElem = self.paper.text(textPosition.x, textPosition.y, elemOptions.text.content).attr(elemOptions.text.attrs); $(elem.textElem.node).attr("data-id", id); + $(elem.textElem.node).attr("data-type", type + '-text'); } // Set user event handlers @@ -428,6 +429,7 @@ } $(elem.mapElem.node).attr("data-id", id); + $(elem.mapElem.node).attr("data-type", type); }, /* @@ -1237,7 +1239,7 @@ } elem.mapElem = self.paper.path("m " + xa + "," + ya + " C " + x + "," + y + " " + xb + "," + yb + " " + xb + "," + yb + "").attr(elemOptions.attrs); - self.initElem(elem, elemOptions, id); + self.initElem(elem, elemOptions, id, 'link'); return elem; }, @@ -1420,7 +1422,7 @@ } else { // Default = circle plot = {"mapElem": self.paper.circle(coords.x, coords.y, elemOptions.size / 2).attr(elemOptions.attrs)}; } - self.initElem(plot, elemOptions, id); + self.initElem(plot, elemOptions, id, 'plot'); return plot; }, From c4438e6ba9e86af73331db0d3897cf11f9be5125 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 16:08:48 +0200 Subject: [PATCH 02/17] Delegate all mouseover/mousemove/mouseleave events --- js/jquery.mapael.js | 337 ++++++++++++++++++++++++-------------------- 1 file changed, 185 insertions(+), 152 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 31c24e0..f849f12 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -243,6 +243,9 @@ self.onShowElementsInRange(e, opt); }); + // Attach delegated events + self.initDelegatedMapEvents(); + // Hook that allows to add custom processing on the map if (self.options.map.afterInit) self.options.map.afterInit(self.$container, self.paper, self.areas, self.plots, self.options); @@ -360,6 +363,83 @@ return options; }, + /* + * Init all delegated events for the whole map: + * mouseover + * mousemove + * mouseout + */ + initDelegatedMapEvents: function() { + var self = this; + + /* Attach mouseover event delegation + * Note: we filter the event with a timeout to reduce the firing when the mouse moves quickly + */ + var mapMouseOverTimeoutID; + self.$map.on("mouseover." + pluginName, "[data-id]", function () { + var elem = this; + clearTimeout(mapMouseOverTimeoutID); + mapMouseOverTimeoutID = setTimeout(function(){ + var $elem = $(elem); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); + + if (type === 'area' || type === 'area-text') { + self.elemEnter(self.areas[id]); + } else if (type === 'plot' || type === 'plot-text') { + self.elemEnter(self.plots[id]); + } else if (type === 'link' || type === 'link-text') { + self.elemEnter(self.links[id]); + } + }, 120); + }); + + /* Attach mousemove event delegation + * Note: timeout filtering is small to update the Tooltip position fast + */ + var mapMouseMoveTimeoutID; + self.$map.on("mousemove." + pluginName, "[data-id]", function (event) { + var elem = this; + clearTimeout(mapMouseMoveTimeoutID); + mapMouseMoveTimeoutID = setTimeout(function(){ + var $elem = $(elem); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); + + if (type === 'area' || type === 'area-text') { + self.elemHover(self.areas[id], event); + } else if (type === 'plot' || type === 'plot-text') { + self.elemHover(self.plots[id], event); + } else if (type === 'link' || type === 'link-text') { + self.elemHover(self.links[id], event); + } + + }, 10); + }); + + /* Attach mouseout event delegation + * Note: we don't perform any timeout filtering to clear & reset elem ASAP + * Otherwise an element may be stuck in 'hover' state (which is NOT good) + */ + self.$map.on("mouseout." + pluginName, "[data-id]", function () { + var elem = this; + // Clear any + clearTimeout(mapMouseOverTimeoutID); + clearTimeout(mapMouseMoveTimeoutID); + var $elem = $(elem); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); + + if (type === 'area' || type === 'area-text') { + self.elemOut(self.areas[id]); + } else if (type === 'plot' || type === 'plot-text') { + self.elemOut(self.plots[id]); + } else if (type === 'link' || type === 'link-text') { + self.elemOut(self.links[id]); + } + }); + }, + /* * Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...) */ @@ -393,22 +473,9 @@ // Set hover option for textElem if (elem.textElem) self.setHoverOptions(elem.textElem, elemOptions.text.attrs, elemOptions.text.attrsHover); - // Set hover behavior only if attrsHover is set for area or for text - if (($.isEmptyObject(elemOptions.attrsHover) === false) || - (elem.textElem && $.isEmptyObject(elemOptions.text.attrsHover) === false)) { - // Set hover behavior - self.setHover(elem.mapElem, elem.textElem); - } - // Init the tooltip if (elemOptions.tooltip) { - elem.mapElem.tooltip = elemOptions.tooltip; - self.setTooltip(elem.mapElem); - - if (elemOptions.text && elemOptions.text.content !== undefined) { - elem.textElem.tooltip = elemOptions.tooltip; - self.setTooltip(elem.textElem); - } + elem.tooltip = elemOptions.tooltip; } // Init the link @@ -912,8 +979,6 @@ // This function remove an element using animation (or not, depending on animDuration) // Used for deletePlotKeys and deleteLinkKeys var fnRemoveElement = function (elem) { - // Unset all event handlers - self.unsetHover(elem.mapElem, elem.textElem); if (animDuration > 0) { elem.mapElem.animate({"opacity": 0}, animDuration, "linear", function () { elem.mapElem.remove(); @@ -1335,12 +1400,7 @@ // Update the tooltip if (elemOptions.tooltip) { - if (elem.mapElem.tooltip === undefined) { - self.setTooltip(elem.mapElem); - if (elem.textElem) self.setTooltip(elem.textElem); - } - elem.mapElem.tooltip = elemOptions.tooltip; - if (elem.textElem) elem.textElem.tooltip = elemOptions.tooltip; + elem.tooltip = elemOptions.tooltip; } // Update the link @@ -1438,74 +1498,6 @@ }); }, - /* - * Set a tooltip for the areas and plots - * @param elem area or plot element - * @param content the content to set in the tooltip - */ - setTooltip: function (elem) { - var self = this; - var tooltipTO = 0; - var cssClass = self.$tooltip.attr('class'); - - - - var updateTooltipPosition = function (x, y) { - - var offsetLeft = 10; - var offsetTop = 20; - - if (typeof elem.tooltip.offset === "object") { - if (typeof elem.tooltip.offset.left !== "undefined") { - offsetLeft = elem.tooltip.offset.left; - } - if (typeof elem.tooltip.offset.top !== "undefined") { - offsetTop = elem.tooltip.offset.top; - } - } - - var tooltipPosition = { - "left": Math.min(self.$map.width() - self.$tooltip.outerWidth() - 5, x - self.$map.offset().left + offsetLeft), - "top": Math.min(self.$map.height() - self.$tooltip.outerHeight() - 5, y - self.$map.offset().top + offsetTop) - }; - - if (typeof elem.tooltip.overflow === "object") { - if (elem.tooltip.overflow.right === true) { - tooltipPosition.left = x - self.$map.offset().left + 10; - } - if (selem.tooltip.overflow.bottom === true) { - tooltipPosition.top = y - self.$map.offset().top + 20; - } - } - - self.$tooltip.css(tooltipPosition); - }; - - $(elem.node).on("mouseover." + pluginName, function (e) { - tooltipTO = setTimeout( - function () { - self.$tooltip.attr("class", cssClass); - if (elem.tooltip !== undefined) { - if (elem.tooltip.content !== undefined) { - // if tooltip.content is function, call it. Otherwise, assign it directly. - var content = (typeof elem.tooltip.content === "function") ? elem.tooltip.content(elem) : elem.tooltip.content; - self.$tooltip.html(content).css("display", "block"); - } - if (elem.tooltip.cssClass !== undefined) { - self.$tooltip.addClass(elem.tooltip.cssClass); - } - } - updateTooltipPosition(e.pageX, e.pageY); - }, 120 - ); - }).on("mouseout." + pluginName, function () { - clearTimeout(tooltipTO); - self.$tooltip.css("display", "none"); - }).on("mousemove." + pluginName, function (e) { - updateTooltipPosition(e.pageX, e.pageY); - }); - }, - /* * Set user defined handlers for events on areas and plots * @param id the id of the element @@ -1732,7 +1724,6 @@ self.setHoverOptions(elem, sliceOptions[i].attrs, sliceOptions[i].attrs); self.setHoverOptions(label, legendOptions.labelAttrs, legendOptions.labelAttrsHover); - self.setHover(elem, label); self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], label, elem, elems, legendIndex); } } @@ -1880,87 +1871,129 @@ }, /* - * Set the hover behavior (mouseover & mouseout) for plots and areas - * @param mapElem the map element - * @param textElem the optional text element (within the map element) + * Set the behaviour when mouse enters element ("mouseover" event) + * @param elem the map element */ - setHover: function (mapElem, textElem) { + elemEnter: function (elem) { var self = this; - var $mapElem = {}; - var $textElem = {}; - var mouseoverTimeout = 0; - var mouseoutTimeout = 0; - var overBehaviour = function () { - clearTimeout(mouseoutTimeout); - mouseoverTimeout = setTimeout(function () { - self.elemHover(mapElem, textElem); - }, 120); - }; - var outBehaviour = function () { - clearTimeout(mouseoverTimeout); - mouseoutTimeout = setTimeout(function(){ - self.elemOut(mapElem, textElem); - }, 120); - }; + if (elem === undefined) return; - $mapElem = $(mapElem.node); - $mapElem.on("mouseover." + pluginName, overBehaviour); - $mapElem.on("mouseout." + pluginName, outBehaviour); + /* Handle mapElem Hover attributes */ + if (elem.mapElem !== undefined) { + // Set mapElem + if (elem.mapElem.attrsHover.animDuration > 0) elem.mapElem.animate(elem.mapElem.attrsHover, elem.mapElem.attrsHover.animDuration); + else elem.mapElem.attr(elem.mapElem.attrsHover); + } - if (textElem) { - $textElem = $(textElem.node); - $textElem.on("mouseover." + pluginName, overBehaviour); - $(textElem.node).on("mouseout." + pluginName, outBehaviour); + /* Handle textElem Hover attributes */ + if (elem.textElem !== undefined) { + if (elem.textElem.attrsHover.animDuration > 0) elem.textElem.animate(elem.textElem.attrsHover, elem.textElem.attrsHover.animDuration); + else elem.textElem.attr(elem.textElem.attrsHover); } - }, - /* - * Remove the hover behavior for plots and areas - * @param mapElem the map element - * @param textElem the optional text element (within the map element) - */ - unsetHover: function (mapElem, textElem) { - $(mapElem.node).off("." + pluginName); - if (textElem) $(textElem.node).off("." + pluginName); + /* Handle tooltip init */ + if (elem.tooltip !== undefined) { + var content = ''; + // Reset classes + self.$tooltip.removeClass().addClass(self.options.map.tooltip.cssClass); + // Get content + if (elem.tooltip.content !== undefined) { + // if tooltip.content is function, call it. Otherwise, assign it directly. + if (typeof elem.tooltip.content === "function") content = elem.tooltip.content(elem.mapElem); + else content = elem.tooltip.content; + } + if (elem.tooltip.cssClass !== undefined) { + self.$tooltip.addClass(elem.tooltip.cssClass); + } + self.$tooltip.html(content).css("display", "block"); + } + + // workaround for older version of Raphael + if (elem.mapElem !== undefined || elem.textElem !== undefined) { + if (self.paper.safari) self.paper.safari(); + } }, /* - * Set he behaviour for "mouseover" event - * @param mapElem mapElem the map element - * @param textElem the optional text element (within the map element) + * Set the behaviour when mouse moves in element ("mousemove" event) + * @param elem the map element */ - elemHover: function (mapElem, textElem) { + elemHover: function (elem, event) { var self = this; - // Set mapElem - if (mapElem.attrsHover.animDuration > 0) mapElem.animate(mapElem.attrsHover, mapElem.attrsHover.animDuration); - else mapElem.attr(mapElem.attrsHover); - // Set textElem - if (textElem) { - if (textElem.attrsHover.animDuration > 0) textElem.animate(textElem.attrsHover, textElem.attrsHover.animDuration); - else textElem.attr(textElem.attrsHover); + if (elem === undefined) return; + + /* Handle tooltip position update */ + if (elem.tooltip !== undefined) { + console.log(elem); + + var mouseX = event.pageX; + var mouseY = event.pageY; + + var offsetLeft = 10; + var offsetTop = 20; + if (typeof elem.tooltip.offset === "object") { + if (typeof elem.tooltip.offset.left !== "undefined") { + offsetLeft = elem.tooltip.offset.left; + } + if (typeof elem.tooltip.offset.top !== "undefined") { + offsetTop = elem.tooltip.offset.top; + } + } + + var tooltipPosition = { + "left": Math.min(self.$map.width() - self.$tooltip.outerWidth() - 5, + mouseX - self.$map.offset().left + offsetLeft), + "top": Math.min(self.$map.height() - self.$tooltip.outerHeight() - 5, + mouseY - self.$map.offset().top + offsetTop) + }; + + if (typeof elem.tooltip.overflow === "object") { + if (elem.tooltip.overflow.right === true) { + tooltipPosition.left = mouseX - self.$map.offset().left + 10; + } + if (selem.tooltip.overflow.bottom === true) { + tooltipPosition.top = mouseY - self.$map.offset().top + 20; + } + } + + self.$tooltip.css(tooltipPosition); } - // workaround for older version of Raphael - if (self.paper.safari) self.paper.safari(); }, /* - * Set he behaviour for "mouseout" event - * @param mapElem the map element - * @param textElem the optional text element (within the map element) + * Set the behaviour when mouse leaves element ("mouseout" event) + * @param elem the map element */ - elemOut: function (mapElem, textElem) { + elemOut: function (elem) { var self = this; - // Set mapElem - if (mapElem.attrsHover.animDuration > 0) mapElem.animate(mapElem.originalAttrs, mapElem.attrsHover.animDuration); - else mapElem.attr(mapElem.originalAttrs); - // Set textElem - if (textElem) { - if (textElem.attrsHover.animDuration > 0) textElem.animate(textElem.originalAttrs, textElem.attrsHover.animDuration); - else textElem.attr(textElem.originalAttrs); + if (elem === undefined) return; + + /* reset mapElem attributes */ + if (elem.mapElem !== undefined) { + // Set mapElem + if (elem.mapElem.attrsHover.animDuration > 0) elem.mapElem.animate(elem.mapElem.originalAttrs, elem.mapElem.attrsHover.animDuration); + else elem.mapElem.attr(elem.mapElem.originalAttrs); + } + + /* reset textElem attributes */ + if (elem.textElem !== undefined) { + if (elem.textElem.attrsHover.animDuration > 0) elem.textElem.animate(elem.textElem.originalAttrs, elem.textElem.attrsHover.animDuration); + else elem.textElem.attr(elem.textElem.originalAttrs); + } + + /* reset tooltip */ + if (elem.tooltip !== undefined) { + self.$tooltip.css({ + 'display': 'none', + 'top': -1000, + 'left': -1000 + }); } // workaround for older version of Raphael - if (self.paper.safari) self.paper.safari(); + if (elem.mapElem !== undefined || elem.textElem !== undefined) { + if (self.paper.safari) self.paper.safari(); + } }, /* From 30165539f82a28726e0dd13d289da9ef31859990 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 16:29:50 +0200 Subject: [PATCH 03/17] Delegate click events --- js/jquery.mapael.js | 79 +++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index f849f12..8856807 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -438,6 +438,29 @@ self.elemOut(self.links[id]); } }); + + /* Attach click event delegation + * Note: we filter the event with a timeout to avoid double click + */ + var mapClickTimeoutID; + self.$map.on("click." + pluginName, "[data-id]", function () { + var elem = this; + clearTimeout(mapClickTimeoutID); + mapClickTimeoutID = setTimeout(function(){ + var $elem = $(elem); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); + + if (type === 'area' || type === 'area-text') { + self.elemClick(self.areas[id]); + } else if (type === 'plot' || type === 'plot-text') { + self.elemClick(self.plots[id]); + } else if (type === 'link' || type === 'link-text') { + self.elemClick(self.links[id]); + } + + }, 200); + }); }, /* @@ -480,15 +503,10 @@ // Init the link if (elemOptions.href) { - elem.mapElem.href = elemOptions.href; - elem.mapElem.target = elemOptions.target; - self.setHref(elem.mapElem); - - if (elemOptions.text && elemOptions.text.content !== undefined) { - elem.textElem.href = elemOptions.href; - elem.textElem.target = elemOptions.target; - self.setHref(elem.textElem); - } + elem.href = elemOptions.href; + elem.target = elemOptions.target; + elem.mapElem.attr({cursor: "pointer"}); + if (elem.textElem) elem.textElem.attr({cursor: "pointer"}); } if (elemOptions.cssClass !== undefined) { @@ -1405,16 +1423,13 @@ // Update the link if (elemOptions.href !== undefined) { - if (elem.mapElem.href === undefined) { - self.setHref(elem.mapElem); - if (elem.textElem) self.setHref(elem.textElem); - } - elem.mapElem.href = elemOptions.href; - elem.mapElem.target = elemOptions.target; - if (elem.textElem) { - elem.textElem.href = elemOptions.href; - elem.textElem.target = elemOptions.target; - } + elem.href = elemOptions.href; + elem.target = elemOptions.target; + elem.mapElem.attr({cursor: "pointer"}); + if (elem.textElem) elem.textElem.attr({cursor: "pointer"}); + } else { + elem.mapElem.attr({cursor: "auto"}); + if (elem.textElem) elem.textElem.attr({cursor: "auto"}); } // Update the cssClass @@ -1486,18 +1501,6 @@ return plot; }, - /* - * Set target link on elem - */ - setHref: function (elem) { - var self = this; - elem.attr({cursor: "pointer"}); - $(elem.node).on("click." + pluginName, function () { - if (!self.panning && elem.href) - window.open(elem.href, elem.target); - }); - }, - /* * Set user defined handlers for events on areas and plots * @param id the id of the element @@ -1996,6 +1999,20 @@ } }, + /* + * Set the behaviour when mouse clicks element ("click" event) + * @param elem the map element + */ + elemClick: function (elem) { + var self = this; + if (elem === undefined) return; + + /* Handle click when href defined */ + if (!self.panning && elem.href !== undefined) { + window.open(elem.href, elem.target); + } + }, + /* * Get element options by merging default options, element options and legend options * @param defaultOptions From dc417dea05f6657d03ca5c6e7d4cc34c652a56c7 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 16:36:21 +0200 Subject: [PATCH 04/17] Fix typo --- js/jquery.mapael.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 8856807..de0ba13 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -1927,8 +1927,6 @@ /* Handle tooltip position update */ if (elem.tooltip !== undefined) { - console.log(elem); - var mouseX = event.pageX; var mouseY = event.pageY; @@ -1954,7 +1952,7 @@ if (elem.tooltip.overflow.right === true) { tooltipPosition.left = mouseX - self.$map.offset().left + 10; } - if (selem.tooltip.overflow.bottom === true) { + if (elem.tooltip.overflow.bottom === true) { tooltipPosition.top = mouseY - self.$map.offset().top + 20; } } From e1c7ebb4c264e067a39e1b849107e773572f94b2 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 18:59:57 +0200 Subject: [PATCH 05/17] Set better data-type for legend elems and canvas --- js/jquery.mapael.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index de0ba13..139962f 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -1034,7 +1034,7 @@ // IF we update areas, plots or legend, then reset all legend state to "show" if (opt.mapOptions.areas !== undefined || opt.mapOptions.plots !== undefined || opt.mapOptions.legend !== undefined) { - $("[data-type='elem']", self.$container).each(function (id, elem) { + $("[data-type='legend-elem']", self.$container).each(function (id, elem) { if ($(elem).attr('data-hidden') === "1") { // Toggle state of element by clicking $(elem).trigger("click", [false, animDuration]); @@ -1187,7 +1187,7 @@ || (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultPlot === "object") )) { // Show all elements on the map before updating the legends - $("[data-type='elem']", self.$container).each(function (id, elem) { + $("[data-type='legend-elem']", self.$container).each(function (id, elem) { if ($(elem).attr('data-hidden') === "1") { $(elem).trigger("click", [false, animDuration]); } @@ -1212,7 +1212,7 @@ var $legend = self.$container.find("." + legendCSSClass)[0]; if ($legend !== undefined) { // Select all elem inside this legend - $("[data-type='elem']", $legend).each(function (id, elem) { + $("[data-type='legend-elem']", $legend).each(function (id, elem) { if (($(elem).attr('data-hidden') === "0" && action === "hide") || ($(elem).attr('data-hidden') === "1" && action === "show")) { // Toggle state of element by clicking @@ -1226,7 +1226,7 @@ // Default : "show" var action = (opt.setLegendElemsState === "hide") ? "hide" : "show"; - $("[data-type='elem']", self.$container).each(function (id, elem) { + $("[data-type='legend-elem']", self.$container).each(function (id, elem) { if (($(elem).attr('data-hidden') === "0" && action === "hide") || ($(elem).attr('data-hidden') === "1" && action === "show")) { // Toggle state of element by clicking @@ -1561,7 +1561,7 @@ legendPaper = new Raphael($legend.get(0)); // Set some data to object - $(legendPaper.canvas).attr({"data-type": legendType, "data-index": legendIndex}); + $(legendPaper.canvas).attr({"data-legend-type": legendType, "data-legend-id": legendIndex}); height = width = 0; @@ -1716,8 +1716,8 @@ height += legendOptions.marginBottom + elemBBox.height; } - $(elem.node).attr({"data-type": "elem", "data-index": i, "data-hidden": 0}); - $(label.node).attr({"data-type": "label", "data-index": i, "data-hidden": 0}); + $(elem.node).attr({"data-type": "legend-elem", "data-id": i, "data-hidden": 0}); + $(label.node).attr({"data-type": "legend-label", "data-id": i, "data-hidden": 0}); // Hide map elements when the user clicks on a legend item if (legendOptions.hideElemsOnClick.enabled) { @@ -1818,8 +1818,8 @@ if ((hideOtherElems === undefined || hideOtherElems === true) && legendOptions.exclusive !== undefined && legendOptions.exclusive === true ) { - $("[data-type='elem'][data-hidden=0]", self.$container).each(function () { - if ($(this).attr('data-index') !== $(elem.node).attr('data-index')) { + $("[data-type='legend-elem'][data-hidden=0]", self.$container).each(function () { + if ($(this).attr('data-id') !== $(elem.node).attr('data-id')) { $(this).trigger("click", false); } }); @@ -2004,7 +2004,7 @@ elemClick: function (elem) { var self = this; if (elem === undefined) return; - + /* Handle click when href defined */ if (!self.panning && elem.href !== undefined) { window.open(elem.href, elem.target); From 67f6cd1ec114fcec644cbf74642cddc95a20748f Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 19:02:17 +0200 Subject: [PATCH 06/17] Run delegated events on container (to also handle legends) --- js/jquery.mapael.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 139962f..1a6275e 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -376,7 +376,7 @@ * Note: we filter the event with a timeout to reduce the firing when the mouse moves quickly */ var mapMouseOverTimeoutID; - self.$map.on("mouseover." + pluginName, "[data-id]", function () { + self.$container.on("mouseover." + pluginName, "[data-id]", function () { var elem = this; clearTimeout(mapMouseOverTimeoutID); mapMouseOverTimeoutID = setTimeout(function(){ @@ -398,7 +398,7 @@ * Note: timeout filtering is small to update the Tooltip position fast */ var mapMouseMoveTimeoutID; - self.$map.on("mousemove." + pluginName, "[data-id]", function (event) { + self.$container.on("mousemove." + pluginName, "[data-id]", function (event) { var elem = this; clearTimeout(mapMouseMoveTimeoutID); mapMouseMoveTimeoutID = setTimeout(function(){ @@ -421,7 +421,7 @@ * Note: we don't perform any timeout filtering to clear & reset elem ASAP * Otherwise an element may be stuck in 'hover' state (which is NOT good) */ - self.$map.on("mouseout." + pluginName, "[data-id]", function () { + self.$container.on("mouseout." + pluginName, "[data-id]", function () { var elem = this; // Clear any clearTimeout(mapMouseOverTimeoutID); @@ -443,7 +443,7 @@ * Note: we filter the event with a timeout to avoid double click */ var mapClickTimeoutID; - self.$map.on("click." + pluginName, "[data-id]", function () { + self.$container.on("click." + pluginName, "[data-id]", function () { var elem = this; clearTimeout(mapClickTimeoutID); mapClickTimeoutID = setTimeout(function(){ From c5011c5983d83c296c617ed549969a8545f61466 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 19:20:36 +0200 Subject: [PATCH 07/17] Store all generated legends --- js/jquery.mapael.js | 88 ++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 1a6275e..1801a96 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -91,9 +91,6 @@ // Save initial HTML content (used by destroy method) self.initialMapHTMLContent = self.$map.html(); - // Allow to store legend containers and initial contents (used by destroy method) - self.createdLegends = {}; - // The tooltip jQuery object self.$tooltip = {}; @@ -109,6 +106,9 @@ // The links object list self.links = {}; + // The legends list + self.legends = {}; + // The map configuration object (taken from map file) self.mapConf = {}; @@ -281,10 +281,10 @@ self.$map.html(self.initialMapHTMLContent); // Empty legend containers and replace initial HTML content - for (var id in self.createdLegends) { - self.createdLegends[id].container.empty(); - self.createdLegends[id].container.html(self.createdLegends[id].initialHTMLContent); - } + $.each(self.legends, function(legendIndex) { + self.legends[legendIndex].container.empty(); + self.legends[legendIndex].container.html(self.legends[legendIndex].initialHTMLContent); + }); // Remove mapael class self.$container.removeClass(pluginName); @@ -1538,9 +1538,8 @@ var width = 0; var height = 0; var title = null; - var elem = {}; - var elemBBox = {}; - var label = {}; + var legendElems = {}; + var legendElemBBox = {}; var i = 0; var x = 0; var y = 0; @@ -1550,13 +1549,8 @@ $legend = $("." + legendOptions.cssClass, self.$container); - if (typeof self.createdLegends[legendOptions.cssClass] ==='undefined') { - self.createdLegends[legendOptions.cssClass] = { - container: $legend, - initialHTMLContent: $legend.html() - }; - } - + // Save content for later + var initialHTMLContent = $legend.html(); $legend.empty(); legendPaper = new Raphael($legend.get(0)); @@ -1628,6 +1622,11 @@ // Draw legend elements (circle, square or image in vertical or horizontal mode) for (i = 0, length = sliceOptions.length; i < length; ++i) { + // Init element content + legendElems[i] = { + elem: {}, + label: {} + }; if (sliceOptions[i].display === undefined || sliceOptions[i].display === true) { if (legendType == "area") { if (legendOptions.mode == "horizontal") { @@ -1638,7 +1637,7 @@ y = height; } - elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); + legendElems[i].elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); } else if (sliceOptions[i].type == "square") { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft; @@ -1648,7 +1647,7 @@ y = height; } - elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); + legendElems[i].elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); } else if (sliceOptions[i].type == "image" || sliceOptions[i].type == "svg") { if (legendOptions.mode == "horizontal") { @@ -1660,15 +1659,16 @@ } if (sliceOptions[i].type == "image") { - elem = legendPaper.image( + legendElems[i].elem = legendPaper.image( sliceOptions[i].url, x, y, scale * sliceOptions[i].attrs.width, scale * sliceOptions[i].attrs.height); } else { - elem = legendPaper.path(sliceOptions[i].path); + legendElems[i].elem = legendPaper.path(sliceOptions[i].path); if (sliceOptions[i].attrs.transform === undefined) { sliceOptions[i].attrs.transform = ""; } - sliceOptions[i].attrs.transform = "m" + ((scale * sliceOptions[i].width) / elem.getBBox().width) + ",0,0," + ((scale * sliceOptions[i].height) / elem.getBBox().height) + "," + x + "," + y + sliceOptions[i].attrs.transform; + legendElemBBox = legendElems[i].elem.getBBox(); + sliceOptions[i].attrs.transform = "m" + ((scale * sliceOptions[i].width) / legendElemBBox.width) + ",0,0," + ((scale * sliceOptions[i].height) / legendElemBBox.height) + "," + x + "," + y + sliceOptions[i].attrs.transform; } } else { if (legendOptions.mode == "horizontal") { @@ -1678,31 +1678,31 @@ x = legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r); y = height + scale * (sliceOptions[i].attrs.r); } - elem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r)); + legendElems[i].elem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r)); } // Set attrs to the element drawn above delete sliceOptions[i].attrs.width; delete sliceOptions[i].attrs.height; delete sliceOptions[i].attrs.r; - elem.attr(sliceOptions[i].attrs); - elemBBox = elem.getBBox(); + legendElems[i].elem.attr(sliceOptions[i].attrs); + legendElemBBox = legendElems[i].elem.getBBox(); // Draw the label associated with the element if (legendOptions.mode == "horizontal") { - x = width + legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel; + x = width + legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel; y = yCenter; } else { - x = legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel; - y = height + (elemBBox.height / 2); + x = legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel; + y = height + (legendElemBBox.height / 2); } - label = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs); + legendElems[i].label = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs); // Update the width and height for the paper if (legendOptions.mode == "horizontal") { - var currentHeight = legendOptions.marginBottom + elemBBox.height; - width += legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width; + var currentHeight = legendOptions.marginBottom + legendElemBBox.height; + width += legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendElems[i].label.getBBox().width; if (sliceOptions[i].type != "image" && legendType != "area") { currentHeight += legendOptions.marginBottomTitle; } @@ -1712,22 +1712,22 @@ } height = Math.max(height, currentHeight); } else { - width = Math.max(width, legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width); - height += legendOptions.marginBottom + elemBBox.height; + width = Math.max(width, legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendElems[i].label.getBBox().width); + height += legendOptions.marginBottom + legendElemBBox.height; } - $(elem.node).attr({"data-type": "legend-elem", "data-id": i, "data-hidden": 0}); - $(label.node).attr({"data-type": "legend-label", "data-id": i, "data-hidden": 0}); + $(legendElems[i].elem.node).attr({"data-type": "legend-elem", "data-id": i, "data-hidden": 0}); + $(legendElems[i].label.node).attr({"data-type": "legend-label", "data-id": i, "data-hidden": 0}); // Hide map elements when the user clicks on a legend item if (legendOptions.hideElemsOnClick.enabled) { // Hide/show elements when user clicks on a legend element - label.attr({cursor: "pointer"}); - elem.attr({cursor: "pointer"}); + legendElems[i].label.attr({cursor: "pointer"}); + legendElems[i].elem.attr({cursor: "pointer"}); - self.setHoverOptions(elem, sliceOptions[i].attrs, sliceOptions[i].attrs); - self.setHoverOptions(label, legendOptions.labelAttrs, legendOptions.labelAttrsHover); - self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], label, elem, elems, legendIndex); + self.setHoverOptions(legendElems[i].elem, sliceOptions[i].attrs, sliceOptions[i].attrs); + self.setHoverOptions(legendElems[i].label, legendOptions.labelAttrs, legendOptions.labelAttrsHover); + self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], legendElems[i].label, legendElems[i].elem, elems, legendIndex); } } } @@ -1738,6 +1738,12 @@ width = legendOptions.VMLWidth; legendPaper.setSize(width, height); + + return { + container: $legend, + initialHTMLContent: initialHTMLContent, + elems: legendElems + }; }, /* @@ -1853,7 +1859,7 @@ throw new Error("The legend class `" + legendsOptions[j].cssClass + "` doesn't exists."); } if (legendsOptions[j].display === true && $.isArray(legendsOptions[j].slices) && legendsOptions[j].slices.length > 0) { - self.drawLegend(legendsOptions[j], legendType, elems, scale, j); + self.legends[j] = self.drawLegend(legendsOptions[j], legendType, elems, scale, j); } } }, From 209b04418c546776b80a9b871395560bbcb35a19 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 19:30:33 +0200 Subject: [PATCH 08/17] Set legends as mapElem / textElem --- js/jquery.mapael.js | 64 ++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 1801a96..a7a4bd0 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -1539,7 +1539,6 @@ var height = 0; var title = null; var legendElems = {}; - var legendElemBBox = {}; var i = 0; var x = 0; var y = 0; @@ -1622,11 +1621,10 @@ // Draw legend elements (circle, square or image in vertical or horizontal mode) for (i = 0, length = sliceOptions.length; i < length; ++i) { - // Init element content - legendElems[i] = { - elem: {}, - label: {} - }; + var legendElem = {}; + var legendElemBBox = {}; + var legendLabel = {}; + if (sliceOptions[i].display === undefined || sliceOptions[i].display === true) { if (legendType == "area") { if (legendOptions.mode == "horizontal") { @@ -1637,7 +1635,7 @@ y = height; } - legendElems[i].elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); + legendElem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); } else if (sliceOptions[i].type == "square") { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft; @@ -1647,7 +1645,7 @@ y = height; } - legendElems[i].elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); + legendElem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height)); } else if (sliceOptions[i].type == "image" || sliceOptions[i].type == "svg") { if (legendOptions.mode == "horizontal") { @@ -1659,15 +1657,15 @@ } if (sliceOptions[i].type == "image") { - legendElems[i].elem = legendPaper.image( + legendElem = legendPaper.image( sliceOptions[i].url, x, y, scale * sliceOptions[i].attrs.width, scale * sliceOptions[i].attrs.height); } else { - legendElems[i].elem = legendPaper.path(sliceOptions[i].path); + legendElem = legendPaper.path(sliceOptions[i].path); if (sliceOptions[i].attrs.transform === undefined) { sliceOptions[i].attrs.transform = ""; } - legendElemBBox = legendElems[i].elem.getBBox(); + legendElemBBox = legendElem.getBBox(); sliceOptions[i].attrs.transform = "m" + ((scale * sliceOptions[i].width) / legendElemBBox.width) + ",0,0," + ((scale * sliceOptions[i].height) / legendElemBBox.height) + "," + x + "," + y + sliceOptions[i].attrs.transform; } } else { @@ -1678,15 +1676,15 @@ x = legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r); y = height + scale * (sliceOptions[i].attrs.r); } - legendElems[i].elem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r)); + legendElem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r)); } // Set attrs to the element drawn above delete sliceOptions[i].attrs.width; delete sliceOptions[i].attrs.height; delete sliceOptions[i].attrs.r; - legendElems[i].elem.attr(sliceOptions[i].attrs); - legendElemBBox = legendElems[i].elem.getBBox(); + legendElem.attr(sliceOptions[i].attrs); + legendElemBBox = legendElem.getBBox(); // Draw the label associated with the element if (legendOptions.mode == "horizontal") { @@ -1697,12 +1695,12 @@ y = height + (legendElemBBox.height / 2); } - legendElems[i].label = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs); + legendLabel = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs); // Update the width and height for the paper if (legendOptions.mode == "horizontal") { var currentHeight = legendOptions.marginBottom + legendElemBBox.height; - width += legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendElems[i].label.getBBox().width; + width += legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendLabel.getBBox().width; if (sliceOptions[i].type != "image" && legendType != "area") { currentHeight += legendOptions.marginBottomTitle; } @@ -1712,23 +1710,41 @@ } height = Math.max(height, currentHeight); } else { - width = Math.max(width, legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendElems[i].label.getBBox().width); + width = Math.max(width, legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendLabel.getBBox().width); height += legendOptions.marginBottom + legendElemBBox.height; } - $(legendElems[i].elem.node).attr({"data-type": "legend-elem", "data-id": i, "data-hidden": 0}); - $(legendElems[i].label.node).attr({"data-type": "legend-label", "data-id": i, "data-hidden": 0}); + // Set some data to elements + $(legendElem.node).attr({ + "data-legend-id": legendIndex, + "data-type": "legend-elem", + "data-id": i, + "data-hidden": 0 + }); + $(legendLabel.node).attr({ + "data-legend-id": legendIndex, + "data-type": "legend-label", + "data-id": i, + "data-hidden": 0 + }); // Hide map elements when the user clicks on a legend item if (legendOptions.hideElemsOnClick.enabled) { // Hide/show elements when user clicks on a legend element - legendElems[i].label.attr({cursor: "pointer"}); - legendElems[i].elem.attr({cursor: "pointer"}); + legendLabel.attr({cursor: "pointer"}); + legendElem.attr({cursor: "pointer"}); - self.setHoverOptions(legendElems[i].elem, sliceOptions[i].attrs, sliceOptions[i].attrs); - self.setHoverOptions(legendElems[i].label, legendOptions.labelAttrs, legendOptions.labelAttrsHover); - self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], legendElems[i].label, legendElems[i].elem, elems, legendIndex); + self.setHoverOptions(legendElem, sliceOptions[i].attrs, sliceOptions[i].attrs); + self.setHoverOptions(legendLabel, legendOptions.labelAttrs, legendOptions.labelAttrsHover); + self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], legendLabel, legendElem, elems, legendIndex); } + + // Set array content + // We use similar names like map/plots/links + legendElems[i] = { + mapElem: legendElem, + textElem: legendLabel + }; } } From c97293542e7450570fb83d0a7f2cf8919b87ca37 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 19:34:06 +0200 Subject: [PATCH 09/17] Event delegation mouseover/mousemove/mouseout for legends --- js/jquery.mapael.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index a7a4bd0..7770aec 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -390,6 +390,13 @@ self.elemEnter(self.plots[id]); } else if (type === 'link' || type === 'link-text') { self.elemEnter(self.links[id]); + } else if (type === 'legend-elem' || type === 'legend-label') { + var legendIndex = $elem.attr('data-legend-id'); + if (self.legends[legendIndex] !== undefined && + self.legends[legendIndex].elems[id] !== undefined) + { + self.elemEnter(self.legends[legendIndex].elems[id]); + } } }, 120); }); @@ -412,6 +419,8 @@ self.elemHover(self.plots[id], event); } else if (type === 'link' || type === 'link-text') { self.elemHover(self.links[id], event); + } else if (type === 'legend-elem' || type === 'legend-label') { + /* Nothing to do */ } }, 10); @@ -436,6 +445,13 @@ self.elemOut(self.plots[id]); } else if (type === 'link' || type === 'link-text') { self.elemOut(self.links[id]); + } else if (type === 'legend-elem' || type === 'legend-label') { + var legendIndex = $elem.attr('data-legend-id'); + if (self.legends[legendIndex] !== undefined && + self.legends[legendIndex].elems[id] !== undefined) + { + self.elemOut(self.legends[legendIndex].elems[id]); + } } }); From 8f3ba398a786072e57b6049bf98b8a359bacec20 Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 19:36:06 +0200 Subject: [PATCH 10/17] No event filtering for click events --- js/jquery.mapael.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 7770aec..1af9138 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -458,24 +458,18 @@ /* Attach click event delegation * Note: we filter the event with a timeout to avoid double click */ - var mapClickTimeoutID; self.$container.on("click." + pluginName, "[data-id]", function () { - var elem = this; - clearTimeout(mapClickTimeoutID); - mapClickTimeoutID = setTimeout(function(){ - var $elem = $(elem); - var id = $elem.attr('data-id'); - var type = $elem.attr('data-type'); - - if (type === 'area' || type === 'area-text') { - self.elemClick(self.areas[id]); - } else if (type === 'plot' || type === 'plot-text') { - self.elemClick(self.plots[id]); - } else if (type === 'link' || type === 'link-text') { - self.elemClick(self.links[id]); - } + var $elem = $(this); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); - }, 200); + if (type === 'area' || type === 'area-text') { + self.elemClick(self.areas[id]); + } else if (type === 'plot' || type === 'plot-text') { + self.elemClick(self.plots[id]); + } else if (type === 'link' || type === 'link-text') { + self.elemClick(self.links[id]); + } }); }, From 331a93abb47b218889630c191e98946d2eb4f8dc Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 21:48:05 +0200 Subject: [PATCH 11/17] self.legends uses legendType --- js/jquery.mapael.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 1af9138..fdc806e 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -281,9 +281,12 @@ self.$map.html(self.initialMapHTMLContent); // Empty legend containers and replace initial HTML content - $.each(self.legends, function(legendIndex) { - self.legends[legendIndex].container.empty(); - self.legends[legendIndex].container.html(self.legends[legendIndex].initialHTMLContent); + $.each(self.legends, function(legendType) { + $.each(self.legends[legendType], function(legendIndex) { + var legend = self.legends[legendType][legendIndex]; + legend.container.empty(); + legend.container.html(legend.initialHTMLContent); + }); }); // Remove mapael class @@ -1879,13 +1882,14 @@ legendsOptions = [self.options.legend[legendType]]; } + self.legends[legendType] = {}; for (var j = 0; j < legendsOptions.length; ++j) { // Check for class existence if (legendsOptions[j].cssClass === "" || $("." + legendsOptions[j].cssClass, self.$container).length === 0) { throw new Error("The legend class `" + legendsOptions[j].cssClass + "` doesn't exists."); } if (legendsOptions[j].display === true && $.isArray(legendsOptions[j].slices) && legendsOptions[j].slices.length > 0) { - self.legends[j] = self.drawLegend(legendsOptions[j], legendType, elems, scale, j); + self.legends[legendType][j] = self.drawLegend(legendsOptions[j], legendType, elems, scale, j); } } }, From ecc133a3cf78accdabd66c794b73d32dd627e3fd Mon Sep 17 00:00:00 2001 From: Indigo Date: Mon, 2 Oct 2017 21:48:31 +0200 Subject: [PATCH 12/17] Event delegation for legend click --- js/jquery.mapael.js | 185 +++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 89 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index fdc806e..0110a7a 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -461,7 +461,7 @@ /* Attach click event delegation * Note: we filter the event with a timeout to avoid double click */ - self.$container.on("click." + pluginName, "[data-id]", function () { + self.$container.on("click." + pluginName, "[data-id]", function (evt, opts) { var $elem = $(this); var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); @@ -472,6 +472,10 @@ self.elemClick(self.plots[id]); } else if (type === 'link' || type === 'link-text') { self.elemClick(self.links[id]); + } else if (type === 'legend-elem' || type === 'legend-label') { + var legendIndex = $elem.attr('data-legend-id'); + var legendType = $elem.attr('data-legend-type'); + self.handleClickOnLegendElem(self.legends[legendType][legendIndex].elems[id], id, legendIndex, legendType, opts); } }); }, @@ -1050,7 +1054,7 @@ $("[data-type='legend-elem']", self.$container).each(function (id, elem) { if ($(elem).attr('data-hidden') === "1") { // Toggle state of element by clicking - $(elem).trigger("click", [false, animDuration]); + $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration}); } }); } @@ -1202,7 +1206,7 @@ // Show all elements on the map before updating the legends $("[data-type='legend-elem']", self.$container).each(function (id, elem) { if ($(elem).attr('data-hidden') === "1") { - $(elem).trigger("click", [false, animDuration]); + $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration}); } }); @@ -1229,7 +1233,7 @@ if (($(elem).attr('data-hidden') === "0" && action === "hide") || ($(elem).attr('data-hidden') === "1" && action === "show")) { // Toggle state of element by clicking - $(elem).trigger("click", [false, animDuration]); + $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration}); } }); } @@ -1243,7 +1247,7 @@ if (($(elem).attr('data-hidden') === "0" && action === "hide") || ($(elem).attr('data-hidden') === "1" && action === "show")) { // Toggle state of element by clicking - $(elem).trigger("click", [false, animDuration]); + $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration}); } }); } @@ -1730,17 +1734,26 @@ // Set some data to elements $(legendElem.node).attr({ "data-legend-id": legendIndex, + "data-legend-type": legendType, "data-type": "legend-elem", "data-id": i, "data-hidden": 0 }); $(legendLabel.node).attr({ "data-legend-id": legendIndex, + "data-legend-type": legendType, "data-type": "legend-label", "data-id": i, "data-hidden": 0 }); + // Set array content + // We use similar names like map/plots/links + legendElems[i] = { + mapElem: legendElem, + textElem: legendLabel + }; + // Hide map elements when the user clicks on a legend item if (legendOptions.hideElemsOnClick.enabled) { // Hide/show elements when user clicks on a legend element @@ -1749,15 +1762,11 @@ self.setHoverOptions(legendElem, sliceOptions[i].attrs, sliceOptions[i].attrs); self.setHoverOptions(legendLabel, legendOptions.labelAttrs, legendOptions.labelAttrsHover); - self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], legendLabel, legendElem, elems, legendIndex); - } - // Set array content - // We use similar names like map/plots/links - legendElems[i] = { - mapElem: legendElem, - textElem: legendLabel - }; + if (sliceOptions[i].clicked !== undefined && sliceOptions[i].clicked === true) { + self.handleClickOnLegendElem(legendElems[i], i, legendIndex, legendType, {hideOtherElems: false}); + } + } } } @@ -1777,95 +1786,93 @@ /* * Allow to hide elements of the map when the user clicks on a related legend item - * @param legendOptions options for the legend to draw - * @param sliceOptions options of the slice - * @param label label of the legend item - * @param elem element of the legend item - * @param elems collection of plots or areas displayed on the map - * @param legendIndex index of the legend in the conf array + * @param elem legend element + * @param id legend element ID + * @param legendIndex corresponding legend index + * @param legendType corresponding legend type (area or plot) + * @param opts object additionnal options + * hideOtherElems boolean, if other elems shall be hidden + * animDuration duration of animation */ - handleClickOnLegendElem: function (legendOptions, sliceOptions, label, elem, elems, legendIndex) { + handleClickOnLegendElem: function(elem, id, legendIndex, legendType, opts) { var self = this; + var legendOptions; + opts = opts || {}; - /** - * - * @param e - * @param hideOtherElems : option used for the 'exclusive' mode to enabled only one item from the legend - * at once - * @param animDuration : used in the 'update' event in order to apply the same animDuration on the legend items - */ - var hideMapElems = function (e, hideOtherElems, animDuration) { - var elemValue = 0; - var hidden = $(label.node).attr('data-hidden'); - var hiddenNewAttr = (hidden === '0') ? {"data-hidden": '1'} : {"data-hidden": '0'}; + if (!$.isArray(self.options.legend[legendType])) { + legendOptions = self.options.legend[legendType]; + } else { + legendOptions = self.options.legend[legendType][legendIndex]; + } - // Check animDuration: if not set, this is a regular click, use the value specified in options - if (animDuration === undefined) animDuration = legendOptions.hideElemsOnClick.animDuration; + var legendElem = elem.mapElem; + var legendLabel = elem.textElem; + var $legendElem = $(legendElem.node); + var $legendLabel = $(legendLabel.node); + var sliceOptions = legendOptions.slices[id]; + var mapElems = legendType === 'area' ? self.areas : self.plots; + // Check animDuration: if not set, this is a regular click, use the value specified in options + var animDuration = opts.animDuration !== undefined ? opts.animDuration : legendOptions.hideElemsOnClick.animDuration ; - if (hidden === '0') { - if (animDuration > 0) label.animate({"opacity": 0.5}, animDuration); - else label.attr({"opacity": 0.5}); - } else { - if (animDuration > 0) label.animate({"opacity": 1}, animDuration); - else label.attr({"opacity": 1}); - } + var hidden = $legendElem.attr('data-hidden'); + var hiddenNewAttr = (hidden === '0') ? {"data-hidden": '1'} : {"data-hidden": '0'}; - $.each(elems, function (id) { - // Retreive stored data of element - // 'hidden-by' contains the list of legendIndex that is hiding this element - var hiddenBy = elems[id].mapElem.data('hidden-by'); - // Set to empty object if undefined - if (hiddenBy === undefined) hiddenBy = {}; - - if ($.isArray(elems[id].value)) { - elemValue = elems[id].value[legendIndex]; - } else { - elemValue = elems[id].value; - } + if (hidden === '0') { + if (animDuration > 0) legendLabel.animate({"opacity": 0.5}, animDuration); + else legendLabel.attr({"opacity": 0.5}); + } else { + if (animDuration > 0) legendLabel.animate({"opacity": 1}, animDuration); + else legendLabel.attr({"opacity": 1}); + } - // Hide elements whose value matches with the slice of the clicked legend item - if (self.getLegendSlice(elemValue, legendOptions) === sliceOptions) { - (function (id) { - if (hidden === '0') { // we want to hide this element - hiddenBy[legendIndex] = true; // add legendIndex to the data object for later use - self.setElementOpacity(elems[id], legendOptions.hideElemsOnClick.opacity, animDuration); - } else { // We want to show this element - delete hiddenBy[legendIndex]; // Remove this legendIndex from object - // Check if another legendIndex is defined - // We will show this element only if no legend is no longer hiding it - if ($.isEmptyObject(hiddenBy)) { - self.setElementOpacity( - elems[id], - elems[id].mapElem.originalAttrs.opacity !== undefined ? elems[id].mapElem.originalAttrs.opacity : 1, - animDuration - ); - } - } - // Update elem data with new values - elems[id].mapElem.data('hidden-by', hiddenBy); - })(id); - } - }); + $.each(mapElems, function (y) { + var elemValue; - $(elem.node).attr(hiddenNewAttr); - $(label.node).attr(hiddenNewAttr); + // Retreive stored data of element + // 'hidden-by' contains the list of legendIndex that is hiding this element + var hiddenBy = mapElems[y].mapElem.data('hidden-by'); + // Set to empty object if undefined + if (hiddenBy === undefined) hiddenBy = {}; - if ((hideOtherElems === undefined || hideOtherElems === true) - && legendOptions.exclusive !== undefined && legendOptions.exclusive === true - ) { - $("[data-type='legend-elem'][data-hidden=0]", self.$container).each(function () { - if ($(this).attr('data-id') !== $(elem.node).attr('data-id')) { - $(this).trigger("click", false); + if ($.isArray(mapElems[y].value)) { + elemValue = mapElems[y].value[legendIndex]; + } else { + elemValue = mapElems[y].value; + } + + // Hide elements whose value matches with the slice of the clicked legend item + if (self.getLegendSlice(elemValue, legendOptions) === sliceOptions) { + if (hidden === '0') { // we want to hide this element + hiddenBy[legendIndex] = true; // add legendIndex to the data object for later use + self.setElementOpacity(mapElems[y], legendOptions.hideElemsOnClick.opacity, animDuration); + } else { // We want to show this element + delete hiddenBy[legendIndex]; // Remove this legendIndex from object + // Check if another legendIndex is defined + // We will show this element only if no legend is no longer hiding it + if ($.isEmptyObject(hiddenBy)) { + self.setElementOpacity( + mapElems[y], + mapElems[y].mapElem.originalAttrs.opacity !== undefined ? mapElems[y].mapElem.originalAttrs.opacity : 1, + animDuration + ); } - }); + } + // Update elem data with new values + mapElems[y].mapElem.data('hidden-by', hiddenBy); } - }; - $(label.node).on("click." + pluginName, hideMapElems); - $(elem.node).on("click." + pluginName, hideMapElems); + }); + + $legendElem.attr(hiddenNewAttr); + $legendLabel.attr(hiddenNewAttr); - if (sliceOptions.clicked !== undefined && sliceOptions.clicked === true) { - $(elem.node).trigger("click", false); + if ((opts.hideOtherElems === undefined || opts.hideOtherElems === true) && legendOptions.exclusive === true ) { + $("[data-type='legend-elem'][data-hidden=0]", self.$container).each(function () { + if ($(this).attr('data-id') !== id) { + $(this).trigger("click", {hideOtherElems: false}); + } + }); } + }, /* From 74f5d978642da6c27cef7b98c11845893f5dd3ec Mon Sep 17 00:00:00 2001 From: Indigo744 <> Date: Tue, 3 Oct 2017 16:45:48 +0200 Subject: [PATCH 13/17] Fix tests --- test/const.js | 8 ++- test/index.html | 6 +- test/test-areas.js | 151 ++++++++++++++++++++++++--------------------- test/test-basic.js | 77 ++++++++++++----------- 4 files changed, 127 insertions(+), 115 deletions(-) diff --git a/test/const.js b/test/const.js index 22ff3ef..25e46a8 100644 --- a/test/const.js +++ b/test/const.js @@ -1,6 +1,6 @@ /* * Unit Test for Mapael - * + * * Constants */ @@ -8,6 +8,10 @@ CST_NB_OF_FRANCE_DPTMT = 96; CST_MAP_MAX_WIDTH = 800; CST_MAP_MAX_HEIGHT = 834.8948306319708; // Calculated +CST_MOUSEOVER_TIMEOUT_MS = 300; // 120 in code + +CST_NB_OF_HOVER_CHECK = CST_NB_OF_FRANCE_DPTMT / 6 ; // We wont check all dptmt as it takes lot of times + CST_MAPCONF_NOANIMDURATION = { map : { defaultArea : { @@ -45,4 +49,4 @@ CST_MAPCONF_NOANIMDURATION = { } } }; - + diff --git a/test/index.html b/test/index.html index a94cf4a..3b9a5bd 100644 --- a/test/index.html +++ b/test/index.html @@ -3,8 +3,7 @@ Mapael Unit Testing - - + - + @@ -42,7 +41,6 @@ - diff --git a/test/test-areas.js b/test/test-areas.js index bce9527..f666483 100644 --- a/test/test-areas.js +++ b/test/test-areas.js @@ -1,25 +1,25 @@ /* * Unit Test for Mapael * Module: Areas - * + * * Here are tested: * - options.map.defaultArea - * - options.map.areas + * - options.map.areas */ $(function() { - + QUnit.module("Areas"); - + QUnit.test("defaultArea option override", function(assert) { - var mouseover_async_done = assert.async(CST_NB_OF_FRANCE_DPTMT); - + var mouseover_async_done = assert.async(CST_NB_OF_HOVER_CHECK); + var CST_DEFAULTAREA = { attrs: { - fill: "#f4f4e8", + fill: "#f4f4e8", stroke: "#ced8d0" } , attrsHover: { - fill: "#a4e100", + fill: "#a4e100", stroke: "#aaaaaa" } , text: { @@ -31,47 +31,49 @@ $(function() { } } }; - + $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments", defaultArea: CST_DEFAULTAREA } - })); - + })); + assert.ok($(".mapcontainer .map svg")[0], "Map created" ); - - $(".mapcontainer svg path").each(function(id, elem) { + + var counter = 0; + $(".mapcontainer svg path").slice(0, CST_NB_OF_HOVER_CHECK).each(function(id, elem) { var $elem = $(elem); var data_id = $elem.attr("data-id"); - assert.equal($elem.attr("fill"), CST_DEFAULTAREA.attrs.fill, "Check overriden fill before mouseover for " + data_id); assert.equal($elem.attr("stroke"), CST_DEFAULTAREA.attrs.stroke, "Check overriden stroke before mouseover for " + data_id); - - $elem.trigger("mouseover"); + setTimeout(function() { - - assert.equal($elem.attr("fill"), CST_DEFAULTAREA.attrsHover.fill, "Check overriden hover fill after mouseover for " + data_id); - assert.equal($elem.attr("stroke"), CST_DEFAULTAREA.attrsHover.stroke, "Check overriden hover stroke after mouseover for " + data_id); - - $elem.trigger("mouseout"); + $elem.trigger("mouseover"); setTimeout(function() { - assert.equal($elem.attr("fill"), CST_DEFAULTAREA.attrs.fill, "Check overriden fill after mouseout for " + data_id); - mouseover_async_done(); - }, 500); - }, 500); + assert.equal($elem.attr("fill"), CST_DEFAULTAREA.attrsHover.fill, "Check overriden hover fill after mouseover for " + data_id); + assert.equal($elem.attr("stroke"), CST_DEFAULTAREA.attrsHover.stroke, "Check overriden hover stroke after mouseover for " + data_id); + + $elem.trigger("mouseout"); + setTimeout(function() { + assert.equal($elem.attr("fill"), CST_DEFAULTAREA.attrs.fill, "Check overriden fill after mouseout for " + data_id); + mouseover_async_done(); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, counter * (CST_MOUSEOVER_TIMEOUT_MS * 2)); + counter++; }); }); - + QUnit.test("Area custom option override", function(assert) { - var mouseover_async_done = assert.async(CST_NB_OF_FRANCE_DPTMT); + var mouseover_async_done = assert.async(2); var text_mouseover_async_done = assert.async(); - + var CST_CUSTOMAREA = { "department-56": { text: { - content: "TEXT_department-56", - attrs: {"font-size": 5}, + content: "TEXT_department-56", + attrs: {"font-size": 5}, attrsHover: {"font-size": 20} }, tooltip: {content: "TOOLTIP_department-56"} @@ -79,14 +81,14 @@ $(function() { "department-21": { attrs: { fill: "#488402" - }, + }, attrsHover: { fill: "#a4e100" } }, "department-74": { text: { - content: "TEXT_department-74", + content: "TEXT_department-74", position:"top", margin:0 }, @@ -94,62 +96,67 @@ $(function() { target: "_blank" } }; - + $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments" }, areas: CST_CUSTOMAREA - })); - + })); + assert.ok($(".mapcontainer .map svg")[0], "Map created" ); - + var $text_56 = $(".mapcontainer .map svg text[data-id='department-56']"); var $text_74 = $(".mapcontainer .map svg text[data-id='department-74']"); - + assert.ok($text_56[0], "Text created for department-56" ); assert.equal($text_56.attr("font-size"), CST_CUSTOMAREA["department-56"].text.attrs["font-size"] + "px", "Font-size ok for department-56" ); $text_56.trigger("mouseover"); setTimeout(function() { assert.equal($text_56.attr("font-size"), CST_CUSTOMAREA["department-56"].text.attrsHover["font-size"] + "px", "Font-size hover ok for department-56" ); text_mouseover_async_done(); - }, 500); - - assert.ok($text_74[0], "Text created for department-74" ); - - $(".mapcontainer svg path").each(function(id, elem) { - var $elem = $(elem); - var data_id = $elem.attr("data-id"); - - if (data_id === "department-21") { - assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrs.fill, "Check special overriden fill before mouseover for " + data_id); - } else if (data_id === "department-74") { - /* Can't check more than that since href and target values are handled by onClick event... */ - assert.ok(jQuery._data( elem, "events" )["click"], "Link created for " + data_id); - } - - $elem.trigger("mouseover"); - setTimeout(function() { - + + assert.ok($text_74[0], "Text created for department-74" ); + + var counter = 0; + $(".mapcontainer svg path").each(function(id, elem) { + var $elem = $(elem); + var data_id = $elem.attr("data-id"); + if (data_id === "department-21") { - assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrsHover.fill, "Check special overriden hover fill after mouseover for " + data_id); - } else if (data_id === "department-56") { - assert.ok($(".mapcontainer .map .mapTooltip").is( ":visible" ), "Check tooltip visible for " + data_id); - assert.equal($(".mapcontainer .map .mapTooltip").html(), CST_CUSTOMAREA[data_id].tooltip.content, "Check special tooltip content for " + data_id); + assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrs.fill, "Check special overriden fill before mouseover for " + data_id); + } else if (data_id === "department-74") { + /* Can't check more than that since href and target values are handled by onClick event... */ } - - $elem.trigger("mouseout"); - setTimeout(function() { - if (data_id === "department-21") { - assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrs.fill, "Check special overriden fill after mouseout for " + data_id); - } else if (data_id === "department-56") { - assert.ok($(".mapcontainer .map .mapTooltip").is( ":hidden" ), "Check tooltip hidden after mouseout for " + data_id); - } - - mouseover_async_done(); - }, 500); - }, 500); - }); + + if ((data_id === "department-21") || (data_id === "department-56")) { + setTimeout(function () { + $elem.trigger("mouseover"); + $elem.trigger("mousemove"); + setTimeout(function () { + if (data_id === "department-21") { + assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrsHover.fill, "Check special overriden hover fill after mouseover for " + data_id); + } else if (data_id === "department-56") { + assert.ok($(".mapcontainer .map .mapTooltip").is(":visible"), "Check tooltip visible for " + data_id); + assert.equal($(".mapcontainer .map .mapTooltip").html(), CST_CUSTOMAREA[data_id].tooltip.content, "Check special tooltip content for " + data_id); + } + + $elem.trigger("mouseout"); + setTimeout(function () { + if (data_id === "department-21") { + assert.equal($elem.attr("fill"), CST_CUSTOMAREA[data_id].attrs.fill, "Check special overriden fill after mouseout for " + data_id); + } else if (data_id === "department-56") { + assert.ok($(".mapcontainer .map .mapTooltip").is(":hidden"), "Check tooltip hidden after mouseout for " + data_id); + } + + mouseover_async_done(); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, counter * (CST_MOUSEOVER_TIMEOUT_MS * 2)); + counter++; + } + }); + }, CST_MOUSEOVER_TIMEOUT_MS); }); }); diff --git a/test/test-basic.js b/test/test-basic.js index 5b1ad31..fcb4a84 100644 --- a/test/test-basic.js +++ b/test/test-basic.js @@ -1,14 +1,14 @@ /* * Unit Test for Mapael * Module: Basic - * + * * Here are tested: * - Basic map creation/destruction * - Basic map interaction * - options.map.name */ $(function() { - + QUnit.module("Basic"); QUnit.test("Default instance creation", function(assert) { @@ -31,38 +31,38 @@ $(function() { assert.ok($(".mapcontainer .map .mapTooltip")[0], "Has tooltip div" ); }); - + QUnit.test("Instance destruction", function(assert) { - + $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments" } })); - + assert.ok($(".mapcontainer svg")[0], "Map existing" ); - + $(".mapcontainer").data("mapael").destroy(); - + assert.notOk($(".mapcontainer .map svg")[0], "SVG map not created" ); assert.notOk($(".mapcontainer").hasClass("mapael"), "Has not mapael class" ); assert.notOk(typeof $(".mapcontainer").data("mapael") === "object", "Has not mapael data" ); assert.notOk($(".mapcontainer .map .mapTooltip")[0], "Has not tooltip div" ); }); - + QUnit.test("Instance creation: existing map", function(assert) { - + $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments" } })); - + assert.ok($(".mapcontainer svg")[0], "First map existing" ); - + $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments" } })); - + assert.ok($(".mapcontainer svg")[0], "Second map existing" ); }); - + QUnit.test("Creation fail: wrong map", function(assert) { /* Error if wrong map name */ @@ -71,13 +71,13 @@ $(function() { map: { name: "not_existing_map" } })); }, "Throw error" ); - + assert.notOk($(".mapcontainer svg")[0], "Map not existing" ); - + }); - + QUnit.test("Creation fail: hidden map", function(assert) { - + $(".container").hide(); /* Error if map is hidden */ @@ -86,14 +86,14 @@ $(function() { map: { name: "france_departments" } })); }, "Throw error" ); - + assert.notOk($(".mapcontainer svg")[0], "Map not existing" ); - + }); QUnit.test("Mouseover", function(assert) { - var mouseover_async_done = assert.async(CST_NB_OF_FRANCE_DPTMT); - + var mouseover_async_done = assert.async(CST_NB_OF_HOVER_CHECK); + /* Create the map */ $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { @@ -103,36 +103,39 @@ $(function() { /* mouseover event check (background changement) */ var default_fill = $(".mapcontainer svg path:first").attr("fill"); - $(".mapcontainer svg path").each(function(id, elem) { + var counter = 0; + $(".mapcontainer svg path").slice(0, CST_NB_OF_HOVER_CHECK).each(function(id, elem) { var $elem = $(elem); - - $elem.trigger("mouseover"); setTimeout(function() { - var new_fill = $elem.attr("fill"); - assert.notEqual(default_fill, new_fill, "Check new background" ); - assert.ok($(".mapcontainer .map .mapTooltip").is( ":hidden" ), "Check tooltip hidden" ); - - $elem.trigger("mouseout"); + $elem.trigger("mouseover"); setTimeout(function() { var new_fill = $elem.attr("fill"); - assert.equal(new_fill, default_fill, "Check old background" ); - mouseover_async_done(); - }, 500); - }, 500); + assert.notEqual(default_fill, new_fill, "Check new background" ); + assert.ok($(".mapcontainer .map .mapTooltip").is( ":hidden" ), "Check tooltip hidden" ); + + $elem.trigger("mouseout"); + setTimeout(function() { + var new_fill = $elem.attr("fill"); + assert.equal(new_fill, default_fill, "Check old background" ); + mouseover_async_done(); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, CST_MOUSEOVER_TIMEOUT_MS); + }, counter * (CST_MOUSEOVER_TIMEOUT_MS * 2)); + counter++; }); - + }); QUnit.test("Responsive", function(assert) { var responsive_async_done = assert.async(); - + /* Create the map */ $(".mapcontainer").mapael($.extend(true, {}, CST_MAPCONF_NOANIMDURATION, { map: { name: "france_departments" } })); - + /* Responsive checks */ $(".mapcontainer").width(CST_MAP_MAX_WIDTH/2); $(window).trigger('resize'); @@ -140,7 +143,7 @@ $(function() { var $svg = $(".mapcontainer .map svg"); assert.equal($svg.attr("width"), CST_MAP_MAX_WIDTH/2, "Check new map width size" ); assert.equal($svg.attr("height"), CST_MAP_MAX_HEIGHT/2, "Check new map height size" ); - + $(".mapcontainer").width(CST_MAP_MAX_WIDTH); $(window).trigger('resize'); setTimeout(function() { From 35eaed4e1c751d36e67cba946874dbdb6ce7f7d8 Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Fri, 6 Oct 2017 15:48:23 +0200 Subject: [PATCH 14/17] Implements delegation for custom events --- js/jquery.mapael.js | 65 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 0110a7a..56da725 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -112,6 +112,9 @@ // The map configuration object (taken from map file) self.mapConf = {}; + // Holds all custom event handlers + self.customEventHandlers = {}; + // Let's start the initialization self.init(); }; @@ -245,6 +248,8 @@ // Attach delegated events self.initDelegatedMapEvents(); + // Attach delegated custom events + self.initDelegatedCustomEvents(); // Hook that allows to add custom processing on the map if (self.options.map.afterInit) self.options.map.afterInit(self.$container, self.paper, self.areas, self.plots, self.options); @@ -480,6 +485,40 @@ }); }, + /* + * Init all delegated custom events + */ + initDelegatedCustomEvents: function() { + var self = this; + + $.each(self.customEventHandlers, function(eventName) { + // Namespace the custom event + // This allow to easily unbound only custom events and not regular ones + var fullEventName = eventName + '.' + pluginName + ".custom"; + self.$container.off(fullEventName).on(fullEventName, "[data-id]", function (e) { + var $elem = $(this); + var id = $elem.attr('data-id'); + var type = $elem.attr('data-type'); + + if (!self.panning && + self.customEventHandlers[eventName][type] !== undefined && + self.customEventHandlers[eventName][type][id] !== undefined) + { + var customEventHandler = self.customEventHandlers[eventName][type][id]; + // Run callback provided by user + customEventHandler.elemOptions.eventHandlers[eventName]( + e, id, + customEventHandler.mapElem, + customEventHandler.textElem, + customEventHandler.elemOptions + ); + } + }); + }); + + + }, + /* * Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...) */ @@ -505,7 +544,7 @@ } // Set user event handlers - if (elemOptions.eventHandlers) self.setEventHandlers(id, elemOptions, elem.mapElem, elem.textElem); + if (elemOptions.eventHandlers) self.setEventHandlers(id, type, elemOptions, elem.mapElem, elem.textElem); // Set hover option for mapElem self.setHoverOptions(elem.mapElem, elemOptions.attrs, elemOptions.attrsHover); @@ -1251,6 +1290,10 @@ } }); } + + // Always rebind custom events on update + self.initDelegatedCustomEvents(); + if (opt.afterUpdate) opt.afterUpdate(self.$container, self.paper, self.areas, self.plots, self.options); }, @@ -1521,23 +1564,21 @@ /* * Set user defined handlers for events on areas and plots * @param id the id of the element + * @param type the type of the element (area, plot, link) * @param elemOptions the element parameters * @param mapElem the map element to set callback on * @param textElem the optional text within the map element */ - setEventHandlers: function (id, elemOptions, mapElem, textElem) { + setEventHandlers: function (id, type, elemOptions, mapElem, textElem) { var self = this; $.each(elemOptions.eventHandlers, function (event) { - (function (event) { - $(mapElem.node).on(event, function (e) { - if (!self.panning) elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions); - }); - if (textElem) { - $(textElem.node).on(event, function (e) { - if (!self.panning) elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions); - }); - } - })(event); + if (self.customEventHandlers[event] === undefined) self.customEventHandlers[event] = {}; + if (self.customEventHandlers[event][type] === undefined) self.customEventHandlers[event][type] = {}; + self.customEventHandlers[event][type][id] = { + mapElem: mapElem, + textElem: textElem, + elemOptions: elemOptions + }; }); }, From 61812c7faa05c60f2cb1a19d153e910c59518e13 Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Tue, 10 Oct 2017 21:03:58 +0200 Subject: [PATCH 15/17] Add space between () and { --- js/jquery.mapael.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 56da725..3ea3842 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -387,7 +387,7 @@ self.$container.on("mouseover." + pluginName, "[data-id]", function () { var elem = this; clearTimeout(mapMouseOverTimeoutID); - mapMouseOverTimeoutID = setTimeout(function(){ + mapMouseOverTimeoutID = setTimeout(function() { var $elem = $(elem); var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); @@ -416,7 +416,7 @@ self.$container.on("mousemove." + pluginName, "[data-id]", function (event) { var elem = this; clearTimeout(mapMouseMoveTimeoutID); - mapMouseMoveTimeoutID = setTimeout(function(){ + mapMouseMoveTimeoutID = setTimeout(function() { var $elem = $(elem); var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); @@ -2327,7 +2327,7 @@ * Wants to override this behavior? Use prototype overriding: * $.mapael.prototype.isRaphaelBBoxBugPresent = function() {return false;}; */ - isRaphaelBBoxBugPresent: function(){ + isRaphaelBBoxBugPresent: function() { var self = this; // Draw text, then get its boundaries var text_elem = self.paper.text(-50, -50, "TEST"); From 596cd4b46dc5b6e92451a4ed0c3292f19921e21f Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Tue, 10 Oct 2017 21:16:07 +0200 Subject: [PATCH 16/17] Reduce settimeout value for tooltips hover to avoid jerkiness --- js/jquery.mapael.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index 40807c5..f45c3de 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -431,7 +431,7 @@ /* Nothing to do */ } - }, 10); + }, 0); }); /* Attach mouseout event delegation From a4b473f3db2900e8af5ef844b941b20ed9917332 Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Tue, 10 Oct 2017 21:22:56 +0200 Subject: [PATCH 17/17] Simplify event delegation computation function --- js/jquery.mapael.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/js/jquery.mapael.js b/js/jquery.mapael.js index f45c3de..11383ef 100644 --- a/js/jquery.mapael.js +++ b/js/jquery.mapael.js @@ -380,6 +380,18 @@ initDelegatedMapEvents: function() { var self = this; + // Mapping between data-type value and the corresponding elements array + // Note: legend-elem and legend-label are not in this table because + // they need a special processing + var dataTypeToElementMapping = { + 'area' : self.areas, + 'area-text' : self.areas, + 'plot' : self.plots, + 'plot-text' : self.plots, + 'link' : self.links, + 'link-text' : self.links + }; + /* Attach mouseover event delegation * Note: we filter the event with a timeout to reduce the firing when the mouse moves quickly */ @@ -392,12 +404,8 @@ var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); - if (type === 'area' || type === 'area-text') { - self.elemEnter(self.areas[id]); - } else if (type === 'plot' || type === 'plot-text') { - self.elemEnter(self.plots[id]); - } else if (type === 'link' || type === 'link-text') { - self.elemEnter(self.links[id]); + if (dataTypeToElementMapping[type] !== undefined) { + self.elemEnter(dataTypeToElementMapping[type][id]); } else if (type === 'legend-elem' || type === 'legend-label') { var legendIndex = $elem.attr('data-legend-id'); if (self.legends[legendIndex] !== undefined && @@ -421,12 +429,8 @@ var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); - if (type === 'area' || type === 'area-text') { - self.elemHover(self.areas[id], event); - } else if (type === 'plot' || type === 'plot-text') { - self.elemHover(self.plots[id], event); - } else if (type === 'link' || type === 'link-text') { - self.elemHover(self.links[id], event); + if (dataTypeToElementMapping[type] !== undefined) { + self.elemHover(dataTypeToElementMapping[type][id], event); } else if (type === 'legend-elem' || type === 'legend-label') { /* Nothing to do */ } @@ -447,12 +451,8 @@ var id = $elem.attr('data-id'); var type = $elem.attr('data-type'); - if (type === 'area' || type === 'area-text') { - self.elemOut(self.areas[id]); - } else if (type === 'plot' || type === 'plot-text') { - self.elemOut(self.plots[id]); - } else if (type === 'link' || type === 'link-text') { - self.elemOut(self.links[id]); + if (dataTypeToElementMapping[type] !== undefined) { + self.elemOut(dataTypeToElementMapping[type][id]); } else if (type === 'legend-elem' || type === 'legend-label') { var legendIndex = $elem.attr('data-legend-id'); if (self.legends[legendIndex] !== undefined &&