Discussion:
[Inkscape-user] SVG Clickmap Landmines
Steve Litt
2017-05-08 03:02:45 UTC
Permalink
Hi all,

I think I understand SVG clickmaps now, how to make them, how to
troubleshoot them, and the like. But it took a long time to learn
because I kept running into time consuming problems. There's
documentation about those problems, but it's scattered all over the
web, and much of it's contradictory. I'd still be trying to put it
together if not for the help of several people on this mailing list,
and I thank you for that.

So to ease the path of those who follow me, I put up a map of the
landmines one's likely to run into while learning/using SVG clickmaps.
Once a person knows these landmines exist, he/she can go about his/her
business, and if something goes wrong, a simple reference to the
landmines page substitutes for hours of web search and experimentation.
Here's the URL of the SVG clickmaps landmines page:

http://troubleshooters.com/codecorn/svg_clickmaps/landmines.htm

SteveT

Steve Litt
May 2017 featured book: Twenty Eight Tales of Troubleshooting
http://www.troubleshooters.com/28
Mark Crutch
2017-05-08 10:24:57 UTC
Permalink
Steve,
Post by Steve Litt
So to ease the path of those who follow me, I put up a map of the
landmines one's likely to run into while learning/using SVG clickmaps.
Once a person knows these landmines exist, he/she can go about his/her
business, and if something goes wrong, a simple reference to the
landmines page substitutes for hours of web search and experimentation.
http://troubleshooters.com/codecorn/svg_clickmaps/landmines.htm
I have a few suggestions with regard to some of your "landmines" that might
make things a little easier...

1) "Use alert() commands"
Actually don't do this. Not for general logging of position or values in
the script, at least. Alerts are intrusive and can only show you a text
string. Get familiar with the developer console in all modern browsers, and
especially the console.log() function. It's non-modal, and you can log out
rich objects and data structures, then interrogate their properties,
attributes and values interactively in the console. It's a much better
experience for most cases when you might want to log via alert().


2) getElementsByTagName(), getElementsByClassName(), getElementById()
These work, but are somewhat out of fashion now. document.querySelector()
and document.querySelectorAll() are the preferred approach these days. They
take a CSS selector as a parameter, which allows for much richer ways to
select objects:

document.querySelectorAll(".myclass"); // Gets all the elements with a
class of "myclass"
document.querySelector(".myclass"); // The same, but only gets the
first one

document.querySelector("#myID"); // Select by ID

document.querySelectorAll("circle"); // All the circles

document.querySelector("circle.myclass"); // The first circle with a class
of "myclass"

document.querySelectorAll("g.myclass > circle"); // All the circles that
are immediate children of a group with a class of "myclass"


CSS selectors are powerful things these days (though still with some
limitations), and a well crafted querySelector() can save a lot of DOM
traversal in code.


3) "Group Clicking Gotchas"
"Let me start with this piece of advice: You'll probably *never* want to
put events on the group itself."

I disagree with this advice. There's nothing wrong with putting event
handlers on the group, or on the items within it, or on both. You just need
to understand how events propagate within browsers; it's a slightly odd
process, borne of a need to combine the old Internet Explorer way of
handling them with the W3C standard way. In short, the things you need to
know are these:

* "evt" will be the name of the event in the handler.
* "evt.target" will be the object that fired the event (i.e. the object
that was clicked on), not necessarily the one the handler is on.
* "this" will be the name of the object the handler is attached to.
* The handler can call another function, passing the event, but also
passing other parameters.
* If a child object wants to consume an event and not pass it up to its
ancestors, you must call evt.stopPropagation()


I've put an example file up on my site which shows some of this at work:

http://peppertop.com/interactive_groups.svg

The whole file is a single group, but there are four onclick handlers (one
on the group, one on each circle). Clicking a circle will change its colour
randomly; clicking on the group (i.e. either lozenge shape) will change all
three circles. There are also onmouseover and onmouseout handlers on the
circles to thicken their borders when the mouse moves over them.

By putting a click handler on the group, and passing both "evt" and "this"
you can get a reference to the object that was clicked ("evt.target") and
the group itself ("this").


Regards,

Mark

P.S. Did you see the message I posted a couple of weeks ago about making
AJAX calls from SVG? I saw no feedback on it, so I'm not sure if it made it
to the mailing list or not.
Steve Litt
2017-05-09 00:19:31 UTC
Permalink
On Mon, 8 May 2017 11:24:57 +0100
Post by Mark Crutch
Steve,
Post by Steve Litt
So to ease the path of those who follow me, I put up a map of the
landmines one's likely to run into while learning/using SVG
clickmaps. Once a person knows these landmines exist, he/she can go
about his/her business, and if something goes wrong, a simple
reference to the landmines page substitutes for hours of web search
and experimentation. Here's the URL of the SVG clickmaps landmines
http://troubleshooters.com/codecorn/svg_clickmaps/landmines.htm
I have a few suggestions with regard to some of your "landmines" that
might make things a little easier...
1) "Use alert() commands"
Actually don't do this. Not for general logging of position or values
in the script, at least. Alerts are intrusive and can only show you a
text string. Get familiar with the developer console in all modern
browsers, and especially the console.log() function. It's non-modal,
and you can log out rich objects and data structures, then
interrogate their properties, attributes and values interactively in
the console. It's a much better experience for most cases when you
might want to log via alert().
Yes. I'll need to add an exhortation to use console.log(), as well as
to reiterate that alert() is only for situations so unknown that only
immediate feedback with stoppage of action will sufice.
Post by Mark Crutch
2) getElementsByTagName(), getElementsByClassName(), getElementById()
These work, but are somewhat out of fashion now.
document.querySelector() and document.querySelectorAll() are the
preferred approach these days. They take a CSS selector as a
document.querySelectorAll(".myclass"); // Gets all the elements
with a class of "myclass"
document.querySelector(".myclass"); // The same, but only gets
the first one
document.querySelector("#myID"); // Select by ID
document.querySelectorAll("circle"); // All the circles
document.querySelector("circle.myclass"); // The first circle with a
class of "myclass"
document.querySelectorAll("g.myclass > circle"); // All the circles
that are immediate children of a group with a class of "myclass"
CSS selectors are powerful things these days (though still with some
limitations), and a well crafted querySelector() can save a lot of DOM
traversal in code.
How about browser support? What are the limitations you mention? These
CSS type queries certainly seem more intuitive to someone knowing CSS,
but before I switch to them I'd like to check for any downsides.
Post by Mark Crutch
3) "Group Clicking Gotchas"
"Let me start with this piece of advice: You'll probably *never* want
to put events on the group itself."
I disagree with this advice. There's nothing wrong with putting event
handlers on the group, or on the items within it, or on both. You
just need to understand how events propagate within browsers; it's a
slightly odd process, borne of a need to combine the old Internet
Explorer way of handling them with the W3C standard way. In short,
* "evt" will be the name of the event in the handler.
* "evt.target" will be the object that fired the event (i.e. the
object that was clicked on), not necessarily the one the handler is
on.
* "this" will be the name of the object the handler is attached to.
Thanks. I missed that one and was using evt.currentTarget, which seemed
iffy, before I decided not to put events in groups at all.
Post by Mark Crutch
* The handler can call another function, passing the event, but also
passing other parameters.
* If a child object wants to consume an event and not pass it up to
its ancestors, you must call evt.stopPropagation()
Thanks. I didn't know about that one either.
Post by Mark Crutch
http://peppertop.com/interactive_groups.svg
The whole file is a single group, but there are four onclick handlers
(one on the group, one on each circle). Clicking a circle will change
its colour randomly; clicking on the group (i.e. either lozenge
shape) will change all three circles. There are also onmouseover and
onmouseout handlers on the circles to thicken their borders when the
mouse moves over them.
By putting a click handler on the group, and passing both "evt" and
"this" you can get a reference to the object that was clicked
("evt.target") and the group itself ("this").
Other than demonstrating "this" and "if (e) e.stopPropagation();", is
there a benefit to putting "onclick=change_all_colours();" on the group
rather than on each losenge? I think the behavior would be identical,
it would have simplified change_fill(), and would have required only
one argument for change_fill().

I can see a benefit for my screw group, namely, that I wouldn't need to
include the transparent cover. My screw group is somewhat atypical in
that it should perform the identical action no matter what part of the
group is clicked, hovered or unhovered, and there are no "blank" places
in the group the way there are in your peppertop page in the area
between the two losenges.

Let me give this some more thought, and thanks for another supremely
instructive interactive SVG.
Post by Mark Crutch
P.S. Did you see the message I posted a couple of weeks ago about
making AJAX calls from SVG? I saw no feedback on it, so I'm not sure
if it made it to the mailing list or not.
Yes. I had so much momentum on the stuff I was doing, that instead of
exploring it I put it on my "later" pile. When I start making a back
end for my apps, I'll study your AJAX methodology. Thanks for cluing me
in about AJAX.

SteveT

Steve Litt
May 2017 featured book: Twenty Eight Tales of Troubleshooting
http://www.troubleshooters.com/28
Mark Crutch
2017-05-09 08:45:43 UTC
Permalink
Post by Steve Litt
Post by Mark Crutch
CSS selectors are powerful things these days (though still with some
limitations), and a well crafted querySelector() can save a lot of DOM
traversal in code.
How about browser support? What are the limitations you mention? These
CSS type queries certainly seem more intuitive to someone knowing CSS,
but before I switch to them I'd like to check for any downsides.
Support is excellent - all the way back to IE9 (and IE8, if you limit the
choice of selectors).

http://caniuse.com/#search=querySelector

The limitations are purely those inherent in CSS selectors in general -
e.g. that you can only select descendants of your chosen node, not its
ancestors, and that there's no CSS selector for "the current node itself"
("this", if you will). None if this is likely to affect your use of them,
and as a replacement for getElement(s)By... there are no real downsides.
Post by Steve Litt
By putting a click handler on the group, and passing both "evt" and
Post by Mark Crutch
"this" you can get a reference to the object that was clicked
("evt.target") and the group itself ("this").
Other than demonstrating "this" and "if (e) e.stopPropagation();", is
there a benefit to putting "onclick=change_all_colours();" on the group
rather than on each losenge? I think the behavior would be identical,
it would have simplified change_fill(), and would have required only
one argument for change_fill().
No, it was purely demonstrative to show that you can put a handler on the
<g> and use "this" to get a reference to the group.

Regards,

Mark
Steve Litt
2017-05-09 18:57:29 UTC
Permalink
On Tue, 9 May 2017 09:45:43 +0100
Post by Mark Crutch
Post by Steve Litt
Post by Mark Crutch
CSS selectors are powerful things these days (though still with
some limitations), and a well crafted querySelector() can save a
lot of DOM traversal in code.
How about browser support? What are the limitations you mention?
These CSS type queries certainly seem more intuitive to someone
knowing CSS, but before I switch to them I'd like to check for any
downsides.
Support is excellent - all the way back to IE9 (and IE8, if you limit
the choice of selectors).
http://caniuse.com/#search=querySelector
This caniuse.com seems like an excellent resource. Thanks!

It looks like .querySelector() works with major browsers back at least
5 years, and that's good enough. Actually, according to this page, my
user of <embed/> is a bigger problem.
Post by Mark Crutch
The limitations are purely those inherent in CSS selectors in general
- e.g. that you can only select descendants of your chosen node, not
its ancestors,
Sounds pretty good to me. If I wanted to cast a wider net, I'd do
document.querySelector('.whatever') instead of
myElement.querySelector('.whatever').
Post by Mark Crutch
and that there's no CSS selector for "the current node
itself" ("this", if you will). None if this is likely to affect your
use of them, and as a replacement for getElement(s)By... there are no
real downsides.
Thanks.
Post by Mark Crutch
Post by Steve Litt
By putting a click handler on the group, and passing both "evt" and
Post by Mark Crutch
"this" you can get a reference to the object that was clicked
("evt.target") and the group itself ("this").
Other than demonstrating "this" and "if (e) e.stopPropagation();",
is there a benefit to putting "onclick=change_all_colours();" on
the group rather than on each losenge? I think the behavior would
be identical, it would have simplified change_fill(), and would
have required only one argument for change_fill().
No, it was purely demonstrative to show that you can put a handler on
the <g> and use "this" to get a reference to the group.
After considerable thought, my screwhead and LED objects will have the
methods on the group, use "this" instead of e.currentTarget or a
parent-looping use of e.target to find the group, and
mygroup.querySelector('.screwhead') to find the subcomponent that will
change colors. By doing this, I eliminate the transparent cover, which
could become somewhat important when I'm making over 100 copies of
these group objects.

When you clued me in to 'this', it opened a world of possibilities.

Thanks

SteveT

Steve Litt
May 2017 featured book: Twenty Eight Tales of Troubleshooting
http://www.troubleshooters.com/28
Mark Crutch
2017-05-10 09:07:47 UTC
Permalink
Post by Steve Litt
Post by Mark Crutch
The limitations are purely those inherent in CSS selectors in general
- e.g. that you can only select descendants of your chosen node, not
its ancestors,
Sounds pretty good to me. If I wanted to cast a wider net, I'd do
document.querySelector('.whatever') instead of
myElement.querySelector('.whatever').
The limitation of not being able to select ancestors can sometimes be a
problem if you've got an event handler. Consider this example of a few
groups within groups:

<g id="myParent">
<g onclick="clicked(this);">
<g class="inner">
<circle id="circle1" ... />
</g>
</g>

<g onclick="clicked(this);">
<g class="inner">
<circle id="circle2"... />
</g>
</g>
</g>


In your event handler you get a handle to the clicked group, but if you
wanted to get a handle to the parent element ("myParent"), there's no way
to use querySelector() to get to it. E.g.

function clicked(oGroup) {
var c = oGroup.querySelector(".inner > circle"); // Gets the circle -
selecting into the tree is fine
var c = oGroup.querySelector("g > circle"); // Does the same
var c = oGroup.querySelector("circle"); // So does this (in
this case)
var c = oGroup.querySelector("g circle"); // As does this (in
this case), but a little less efficiently

// If I want to get the parent group, this would be a nice option...
var g = oGroup.querySelector("this < g"); // There's no "this" in CSS,
and no "<" as a parent selector. This will fail.

// Instead I end up doing this...
var g = oGroup.parentNode;
}


In this simple example that's not too bad, but when you start getting more
complex DOM structures, you can end up having to maintain code that has
"this.parentNode.parentNode.nextElementSibling.parentNode" and similarly
fragile structures.


So yes, you can use document.querySelector() to select from the entire
document, or node.querySelector() to restrict the selection, but depending
on your "starting point" it you might not have the luxury of choice. It's
generally a lot easier going down into the tree than walking back up it.
Post by Steve Litt
When you clued me in to 'this', it opened a world of possibilities.
Just be a little careful with "this", as it can change depending on how a
function is called. It's often worth logging it out to make sure it
actually represents the thing you think it does. This can be a particular
problem if you start trying to do anything asynchronously, using
setTimeout() or similar.


Regards,

Mark

Loading...