Events work in Firefox/Chrome but fail in IE - javascript

I've just built an SVG map of New Zealand for use with the excellent javascript library Raphael, but unfortunately have stumbled upon what I can only imagine is a bug or syntactic variation in IE's javascript interpreter.
In Firefox and other browsers the onlick and onmouseover events work perfectly - however they do not fire in IE (tested in IE 7). Unfortunately there is no javascript error to help me debug this, so I can only assume IE handles these events in some fundamentally different way.
<script type="text/javascript" charset="utf-8">
window.onload = function() {
var R = Raphael("paper", 450, 600);
var attr = {
fill: "#3f3f40",
stroke: "#666",
"stroke-width": 1,
"stroke-linejoin": "round"
};
var nz = {};
nz.northland = R.path(attr, "M 193.34222,3.7847503 C 194.65463");
// SVG data stripped for sake of brevity
var current = null;
for (var region in nz) {
nz[region].color = Raphael.getColor();
(function(rg, region) {
rg[0].style.cursor = "pointer";
rg[0].onmouseover = function() {
current && nz[current].animate({ fill: "#3f3f40", stroke: "#666" }, 500) && (document.getElementById(current).style.display = "");
rg.animate({ fill: rg.color, stroke: "#ccc" }, 500);
rg.toFront();
R.safari();
document.getElementById(region).style.display = "block";
current = region;
};
rg[0].onclick = function() {
alert("IE never gets this far.");
//window.location.href = "my-page.aspx?District=" + region;
};
rg[0].onmouseout = function() {
rg.animate({ fill: "#3f3f40", stroke: "#666" }, 500);
};
if (region == "northland") {
rg[0].onmouseover();
}
})(nz[region], region);
}
};
</script>
Many thanks :)

The fix appears to be using the onmousedown event instead of onclick.
Changing:
rg[0].onclick = function() {
alert("IE never gets this far, but Firefox is happy.");
};
to
rg[0].onmousedown = function() {
alert("This works in IE and Firefox.");
};
resolved the issue. Thanks for everyone's input - got there in the end. If anyone actually knows why IE doesn't like onclick, I'd be interested to hear!

Have you tried attaching the events?
if (rg[0].attachEvent)
rg[0].attachEvent("onclick", function(){ /* IE */ });
else
rg[0].addEventListener("click", function(){ /* other */ }, false);

IE is not exactly known for working correctly. It would help if you mentioned which IE version you are using.

Generally an abstraction framework, like jquery or prototype is your best bet. They handle the browser differences for you. Also, you can subscribe to your events at a higher level... it's less expensive in the browser to subscribe to the mousemove/click, and determine what you're over, or clicking on from the event bubble than to many objects.
Joel Potter mentioned the subscriber model, using the dom and IE methods, which is better practice than the method assignment is.

Related

iOS SVG lag when free-hand drawing

I'm writing a simple drawing "thing" for our users. It uses SVG. All of the users have iPads, either 4s or Air 2s. I'm testing on a 2 (2nd gen, not Air 2). All iPads are running iOS 9.2 and using Safari.
When free-hand drawing on the SVG element, there is substantial lag. On the 2nd gen it's "almost tolerable", on the 4s it's "incredibly painful". Not sure about the Air 2s, but if it's scaling the way the 4s did, I'd say it's "kill me now". Interesting how the better hardware lags more, but I suppose it could have something to do with the increased resolution, but still...
Is there anything I can do to improve the performance? The lag is also noticible when drawing ready shapes (rectangle, line (path), and ellipse), but it's something we can live with.
Here's the code I'm using to bind the event handlers (using jQuery) for the free-hand drawing.
Floor.bindFreeHand = function (e) {
e.preventDefault();
var sPB, d, p1, segments;
Floor._jElement.off(EventTypes.pointerDown + " " + EventTypes.pointerUp).on(EventTypes.pointerDown, function (e) {
var pI1 = PointerInfo.parse(e);
sPB = new SvgPathsBuilder();
d = sPB.moveTo(pI1.x, pI1.y).d();
p1 = Svg.path(d).attr({
"fill": "none",
"stroke": Floor._color,
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-opacity": Floor._opacity,
"stroke-width": Floor._width
});
segments = p1.element.pathSegList;
Floor._sElement.add(p1);
Floor._jElement.on(EventTypes.pointerMove, function (e) {
e.preventDefault();
e.stopPropagation();
segments.appendItem(p1.element.createSVGPathSegLinetoAbs(e.offsetX, e.offsetY));
//var pI2 = PointerInfo.parse(e);
//d = sPB.lineTo(pI2.x, pI2.y).d();
//p1.attr("d", d);
});
}).on(EventTypes.pointerUp, function (e) {
Floor._jElement.off(EventTypes.pointerMove);
});
};
And here's the PointerInfo and EventTypes objects:
var PointerInfo = (function () {
function PointerInfo(x, y) {
this.x = x;
this.y = y;
}
PointerInfo.parse = function (e) {
var x = e.offsetX - 2, y = e.offsetY - 1;
return new PointerInfo(x, y);
};
return PointerInfo;
})();
var EventTypes = (function () {
function EventTypes() {
}
EventTypes.touch = EventTypes.touch || ("ontouchstart" in window);
EventTypes.pointerDown = EventTypes.touch ? "touchstart" : "mousedown";
EventTypes.pointerMove = EventTypes.touch ? "touchmove" : "mousemove";
EventTypes.pointerUp = EventTypes.touch ? "touchend" : "mouseup";
EventTypes.pointerLeave = EventTypes.touch ? "touchleave" : "mouseout";
return EventTypes;
})();
All of these are compiled down from TypeScript.
I figured it out, after spending two days on it and disabling everything else on the page and every other script being loaded. It turned out that it was CSS that was causing the lag. The SVG element that was being drawn on had a background pattern created via gradients to look like a grid. Apparently as path was being updated on touchmove the element's layout was invalidated and Safari was force redrawing it constantly.
So, the moral of the story is that the performance issues in iOS Safari aren't always due to JavaScript. Fancy CSS effects also contribute to it especially if the element they're on is being manipulated constantly.
UPDATE
With Chrome 48, you can no longer set the path's segments because that API was deprecated and removed. The solution now is to constantly update the d attribute as the pointer moves. From my testing, there's no noticeable performance penalty, even on older devices such as the iPad 2nd gen.

Zooming in/out with animate on QUICKLY hover with snap.svg IE and EDGE web-browser

I cannot understund, why IE and EDGE has strange behavior.
If you rapidly move the mouse over and out of pentagram -- animation is distancing, but in other NORMAL web-browsers all in order.
http://jsfiddle.net/b1Lhjo4k/
var svgElement = Snap("#svg");
var pent = svgElement.select('#pentagram-one');
var hoverover = function() {
pent.stop().animate({transform: 'r180,500,515'}, 400);
};
var hoverout = function() {
pent.stop().animate({transform: 'r0,500,515'}, 400);
};
pent.hover(hoverover, hoverout);
Solved by prototype function; ie and edge works now correctly.
I don't have Edge to test, but I suspect its because its doing something funky with Matrix/transform interpolation when starting an animation before the previous one has finished (this may be wrong, but just what initial thoughts would be to look at), or it could be something odd with very small number strings sometime as I've seen those before with IE.
One way around this, would be to not let Snap do the interpolation using the browsers matrix values, but control it yourself. You could try a small plugin for it, if you will use it a lot, something like this...
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.animRotate = function( from, to, dur ) {
var el = this;
this.stop();
Snap.animate(from, to, function( val ) {
el.transform('r' + val + ',500,515')
},dur)
return this;
};
});
var pent = svgElement.select('#pentagram-one');
var hoverover = function() {
this.animRotate(0, 180, 400)
};
var hoverout = function() {
this.animRotate(180, 0, 400)
};
jsfiddle

How to change pop-up location of google charts tooltip

I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.

the red circle's click event is invoked twice in IE8

when I click the red circle, the window will popup a alert once.
It works fine in firefox and chrome, but in ie8, it's popup alerts twice.
How could I fix it?
please see my code in the following:
Raphael("world", 1000, 400, function () {
var r = this;
r.rect(0, 0, 1000, 400, 0).attr({
stroke: "none",
fill: "0-#9bb7cb-#adc8da"
});
var click = function(){
alert(this.type);
};
r.setStart();
var hue = Math.random();
for (var country in worldmap.shapes) {
r.path(worldmap.shapes[country]).attr({stroke: "#ccc6ae", fill: "#f0efeb", "stroke-opacity": 0.25});
}
var dot = r.circle(772.9870633333333, 166.90446666666668).attr({
title: "Point",
fill: "red",
stroke: "#fff",
"stroke-width": 2,
r: 5
});
var world = r.setFinish();
world.click(click);
});
I've had this problem a few time. Solved it by using a mouse up event instead of a click event. IE sucks.
I find a way to fix this issue, replace the Raphael Set Object with a array Object to push all Rahpael Elements Object, then loop the array to add click event for each Element.
see the following code:
var set = [];
// r.setStart();
for (var country in worldmap.shapes) {
var element = r.path(worldmap.shapes[country]).attr({stroke: "#ccc6ae", fill: "#f0efeb", "stroke-opacity": 0.25});
set.push(element);
}
var dot = r.circle(772.9870633333333, 160.90446666666668 , 5).attr({
title: "Point",
fill: "red",
stroke: "#fff",
"stroke-width": 2
});
set.push(dot);
for(var i = 0; i < set.length; i++){
var element = set[i];
element.click(click);
}
// var world = r.setFinish();
// world.click(click);
I had a similar problem in Firefox, although what I was doing was completely different.
The way I solved it was to prevent the event from firing twice in too short of an interval.
I would use this function:
function rateLimit(func) {
var lastcall = func.lastcall || 0,
now = new Date().getTime();
if( now-lastcall < 250) return false;
func.lastcall = now;
return true;
}
Then in the function I want to limit from firing too often, I can do this:
if( !rateLimit(arguments.callee)) return false;
However, you might have a small issue if you are using alert(), since that will completely block execution and the second run will still fire. I would strongly suggest using console.log() instead of alert() to keep track of values, as this will avoid interrupting the flow of the program (especially when you get into asynchronous stuff, you can get real mysteries if you stop things with an alert)
Hope this helps!

How to update the source of an Image

I'm using the Raphaël Javascript lib (awesome stuff for SVG rendering, by the way) and am currently trying to update the source of an image as the mouse goes over it.
The thing is I can't find anything about it (it's probably not even possible, considering I've read a huge part of the Raphaël's source without finding anything related to that).
Does someone knows a way to do this ?
Maybe it can be done without directly using the Raphaël's API, but as the generated DOM elements doesn't have IDs I don't know how to manually change their properties.
I'm actually doing CoffeeScript, but it's really easy to understand. CoffeeScript is Javascript after all.
This is what I'm doing right know, and I would like the MouseOver and MouseOut methods to change the source of the "bg" attribute.
class Avatar
constructor: (father, pic, posx, posy) ->
#bg = father.container.image "pics/avatar-bg.png", posx, posy, 112, 112
#avatar = father.container.image pic, posx + 10, posy + 10, 92, 92
mouseOver = => #MouseOver()
mouseOut = => #MouseOut()
#bg.mouseover mouseOver
#bg.mouseout mouseOut
MouseOver: ->
#bg.src = "pics/avatar-bg-hovered.png"
alert "Hover"
MouseOut: ->
#bg.src = "pics/avatar-bg.png"
alert "Unhovered"
class Slider
constructor: ->
#container = Raphael "raphael", 320, 200
#sliderTab = new Array()
AddAvatar: (pic) ->
#sliderTab.push new Avatar this, pic, 10, 10
window.onload = ->
avatar = new Slider()
avatar.AddAvatar "pics/daAvatar.png"
This actually works, except for the "#bg.src" part : I wrote it knowing that it wouldn't work, but well...
var paper = Raphael("placeholder", 800, 600);
var c = paper.image("apple.png", 100, 100, 600, 400);
c.node.href.baseVal = "cherry.png"
I hope, you get the idea.
This works for me (and across all browsers):
targetImg.attr({src: "http://newlocation/image.png"})
I was using rmflow's answer until I started testing in IE8 and below which returned undefined for image.node.href.baseVal. IE8 and below did see image.node.src though so I wrote functions getImgSrc, setImgSrc so I can target all browsers.
function getImgSrc(targetImg) {
if (targetImg.node.src) {
return targetImg.node.src;
} else {
return targetImg.node.href.baseVal;
}
}
function setImgSrc(targetImg, newSrc) {
if (targetImg.node.src) {
targetImg.node.src = newSrc;
} else {
targetImg.node.href.baseVal = newSrc;
}
}

Categories