main page - page initiale - hauptseite - página principal projects - projets - projekte - proyectos authors - auteurs - autoren - autores papers - exposés - berichte - papeles help - aide - hilfe - ayuda

carto:papers:svg:examples:interactivity and svg gui:event handling

Event Handling in SVG Applications

Tutorial and Examples provided by Andreas Neumann

Version 0.9 (2006-03-04), see history at the bottom of the page

This tutorial introduces event handling in SVG and ECMAScript applications. Events are corner stones for interactive SVG applications. Events are necessary to trigger script execution and react to user input. The event handling in SVG is largely parallel to event handling in HTML with a few additional event types.

Definitions regarding Events:

Event:
an event is a message about a user or system input that notifies state changes or user input within an application. Event-driven applications are permanently looking for user or system events through an event loop. Event driven programming is usually flexible and asynchronous, as the system cannot know in advance if, how and how often events happen. Typical event driven applications are GUI, daemons and the operating system. GUI applications wait for user input events, daemons wait for network or system events/requests and the operating system waits for hardware, software, network and user events.

Event Object:
after an event has occured, an event object is created containing all associated methods and properties. Available methods and properties vary according to the event that created the object. Properties of a mouse event might be the coordinates where the mouse was when the event occured, which button was pressed or how often a button was pressed at the same location. Properties of a key event might be the keyCode of the key that triggered the event.

Event Target
a reference to the element that receives the event, e.g. a SVG geometry, text, image or the document element.

Event Handler Attribute:
an attribute that indicates that an event should be attached to an element. Event Handler Attributes always start with "on...", e.g. "onmousemove", "onload" or "onkeypress". The "on" prefix is always added to the event type name.

Event Handler:
a piece of Javascript code specified within an event handler attribute that is called after an event has occured, often a very small piece of code. Often the event handlers call additional Javascript functions or objects that do more complex things. Objects may have event handlers as well. This is typically a special method called .handleEvent(). If an object is given as an event handler, the method .handleEvent() is automatically called if no other method has been specified.

Event Listener (observers):
a•s the link between an event object that was triggered and the interested event handlers (javscript functions). Event listeners forward the event object to the javascript functions that registered themselves as interested recipients, typically the event handlers. An event can call multiple event listeners. Event listeners can be added and removed during the lifetime of a program.

Event Listener List (observer list):
an ordered collection of event listeners. The order of the registrations matters. Listeners that were registered first will be called first.

Event Dispatcher:
is a lower level permanent background thread provided by the operating system, browser or java windowing toolkit (AWT) that listens for events and forwards them to interested functions or methods

Event Flow:
the process of the event travelling through the document object hierarchy. Event flow rules follow either the event capturing or event bubbling method

Event Capturing:
an event starts at the top of the tree (document level) and travels down the hierarchy until an element is interested in the event (has an event handler attribute or event listener registered). needs to be further explained ...

Event Bubbling:
the opposite rule. An event travels from a nested hierarchy up to interested ancestor elements until it reaches the document element. This is the more common rule than the capturing rule. needs to be further explained ...

Available Events in SVG by type:

The following event types are available in SVG (grouped into categories). The "on"-values in the parenthesis indicate the name to be used in event handler attributes:

General Properties and Methods of the Event Object:

Following is a list of basic properties and methods available for every event:

Properties

Methods

Note the difference between evt.target and evt.currentTarget. If a group has an event handler attribute and a nested geometry has none, the reference "evt.target" will point to the nested geometry while "evt.currentTarget" will point to the group that has the event handler attribute assigned. Likewise, in case of a use-reference, evt.currentTarget will point to the use-element, while evt.target points to the original element (e.g. symbol definition). This is an important distinction that needs consideration while designing interactive SVG apps.

Example 1: Basic Event Properties and Methods

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 400 250" onload="init()">
    <title>Demonstration of basic evt properties and methods</title>
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var evtText2;
            function init() {
                evtText1 = document.getElementById("evtText1");
                evtText2 = document.getElementById("evtText2");
            }
            function showEvtData(evt) {
                evtText1.firstChild.nodeValue = ".type="+evt.type+", .target.id="+evt.target.getAttributeNS(null,"id")+", .currentTarget.id="+evt.currentTarget.getAttributeNS(null,"id");
                evtText2.firstChild.nodeValue = ".timeStamp="+evt.timeStamp+", .bubbles="+evt.bubbles+", .cancelable="+evt.cancelable+", .eventPhase="+evt.eventPhase;
                if (evt.target.getAttributeNS(null,"id") == "myBlackRect") {
                    evt.stopPropagation();
                }
            }
        ]]>
    </script>
    <rect x="20" y="40" width="50" height="30" fill="red" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)" id="myRedRect" />
    <g id="ancestorOfGreenRect" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)">
        <rect x="80" y="40" width="50" height="30" fill="green" id="myGreenRect" />
    </g>
    <g id="ancestorOfGrayRect" onclick="alert('you clicked the ancestor of the gray rectangle')">
        <rect x="190" y="40" width="50" height="30" fill="darkgray" id="myGrayRect" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)" />
    </g>
    <g id="ancestorOfBlackRect" onclick="alert('you clicked the ancestor of the black rectangle')">
        <rect x="250" y="40" width="50" height="30" fill="black" id="myBlackRect" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)" />
    </g>
    <g font-size="10px" font-family="Arial">
        <text x="20" y="30" font-size="12px" font-weight="bold">Demonstration of basic evt properties and methods</text>
        <text x="20" y="90">Click, mouseover and mouseout<tspan x="20" dy="15">on the above red and green</tspan><tspan x="20" dy="15">rectangles</tspan></text>
        <text x="190" y="90">Both the rects and parents of the<tspan x="190" dy="15">gray and black rects have events attached</tspan>
          <tspan x="190" dy="15">for "onclick" event handler attributes</tspan><tspan x="190" dy="15">but for the black rect the method</tspan><tspan x="190" dy="15">.stopPropagation() is called</tspan></text>
        <text x="20" y="170" id="evtText1"> </text>
        <text x="20" y="185" id="evtText2"> </text>
    </g>
</svg>

Link to example 1 (example1_basic_evt_properties_and_methods.svg) - in a separate window)

The above example contains four rectangles. If you mouseover, mouseout or click on the rectangle you will receive all event details that are shared by all events. The green rectangle has the event assigned to a parent group, hence evt.target and evt.currentTarget are different, so is the .eventPhase since the event was trapped in the bubbling phase. The two rectangles on the right show the effect of evt.stopPropagation(). Both the rectangle and the parent group have an event handler attribute, but if the black rectangle was hit, the propagation of the event up the DOM hierarchy was stopped.

MouseEvent Properties and Methods:

Next, we discuss properties and methods available to all mouse specific events, some of the properties and methods are inherited from the general Events or UIEvents:

Properties

Methods

Example 2: Demonstration of mouse specific Event Properties

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 450 250" onload="init()">
    <title>Demonstration of MouseEvent properties and methods</title>
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var evtText2;
            var evtText3;
            var evtText4;
            function init() {
                evtText1 = document.getElementById("evtText1");
                evtText2 = document.getElementById("evtText2");
                evtText3 = document.getElementById("evtText3");
                evtText4 = document.getElementById("evtText4");
            }
            function showEvtData(evt) {
                evt.preventDefault();
                evtText1.firstChild.nodeValue = ".type="+evt.type+", .screenX="+evt.screenX+", .screenY="+evt.screenY+", .clientX="+evt.clientX+", .clientY="+evt.clientY;
                var text = ".target.id="+evt.target.getAttributeNS(null,"id")+", .currentTarget.id="+evt.currentTarget.getAttributeNS(null,"id");
                if (evt.relatedTarget) {
                    if (evt.relatedTarget.hasAttributeNS(null,"id")) {
                        text += ", .relatedTarget.id="+evt.relatedTarget.getAttributeNS(null,"id");
                    }
                    else {
                        text += ", .relatedTarget.id=no id available";
                    }
                }
                else {
                    text += ", .relatedTarget=undefined";
                }
                evtText2.firstChild.nodeValue = text;
                evtText3.firstChild.nodeValue = ".button="+evt.button+", .detail="+evt.detail+", .timeStamp="+evt.timeStamp;
                evtText4.firstChild.nodeValue = ".altKey="+evt.altKey+", .shiftKey="+evt.shiftKey+", .ctrlKey="+evt.ctrlKey+", .metaKey="+evt.metaKey;
           }
        ]]>
    </script>
    <rect id="bgRect" x="-500" y="-500" width="1500" height="1500" fill="yellow" stroke="none" />
    <rect x="5" y="40" width="50" height="30" fill="red" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)" id="myRedRect" />
    <g id="ancestorOfGreenRect" onclick="showEvtData(evt)" onmouseover="showEvtData(evt)" onmouseout="showEvtData(evt)">
        <rect x="55" y="40" width="50" height="30" fill="green" id="myGreenRect" />
    </g>
    <rect x="190" y="40" width="50" height="30" fill="darkgray" id="myGrayRect" onmousemove="showEvtData(evt)" />
    <rect x="250" y="40" width="50" height="30" fill="black" id="myBlackRect" onmousemove="showEvtData(evt)" />
    <g font-size="10px" font-family="Arial">
        <text x="5" y="30" font-size="12px" font-weight="bold">Demonstration of MouseEvent properties and methods</text>
        <text x="5" y="90">Click, mouseover and mouseout<tspan x="5" dy="15">on the above red and green</tspan><tspan x="5" dy="15">rectangles</tspan></text>
        <text x="190" y="90">Mousemove over the<tspan x="190" dy="15">gray and black rects to get</tspan><tspan x="190" dy="15">properties of the mousemove event</tspan></text>
        <text x="5" y="160" id="evtText1"> </text>
        <text x="5" y="175" id="evtText2"> </text>
        <text x="5" y="190" id="evtText3"> </text>
        <text x="5" y="205" id="evtText4"> </text>
    </g>
</svg>

Link to example 2 (example2_mouseevent_properties_and_methods.svg) - in a separate window)

The above example shows MouseEvent specific properties. Note that for some properties the behaviour of the various browsers or SVG UAs is different. Some UAs don't display the .timeStamp property for all MouseEvent types, but only for some (e.g. click). Some don't report the middle or right mouse button but open context menues. Also, some UAs handle the 'ctrlKey' and/or 'metaKey' wrong on MacOSX. The .detail property does not always work in all UAs, unfortunately. The most complete implementation regarding events (as often) is the Apache Batik implementation. Note that .relatedTarget property makes only really sense for onmouseover and onmouseout combinations of neighbour elements or elements behind each other, such as is the case with our background rectangle.

Converting clientX to viewBox coordinates:

In the above example (example2) we discovered that both screenX/screenY and clientX/clientY don't report the coordinates used in our viewBox coordinate system. However, the viewBox coordinates are usually the ones we want to get, since we perhaps want to drag elements within the viewBox, create new elements or show tooltips where the mouse event happened. The following example shows how this calculation works. As this method is almost always required, we should link to the following external ecmascript-file (mapApp.js) in our SVG files. Additionally, the function getTransformToRootElement() from the external ecmascript-file (helper_functions.js) is needed.

Example 3: Demonstration of clientX/Y to viewBox Coordinate Conversion

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 450 250" onload="init()" onmousemove="showCoords(evt)">
    <title>Demonstration of clientX/Y to viewBox Coordinate Conversion</title>
    <script type="text/ecmascript" xlink:href="../resources/helper_functions.js" />
    <script type="text/ecmascript" xlink:href="../resources/mapApp.js" />
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var evtText2;
            var myMapApp = new mapApp();
            function init() {
                myRedRect = document.getElementById("myRedRect");
                evtText1 = document.getElementById("evtText1");
                evtText2 = document.getElementById("evtText2");
            }
            function showCoords(evt) {
                var coords = myMapApp.calcCoord(evt);
                evtText1.firstChild.nodeValue = ".screenX="+evt.screenX+", .screenY="+evt.screenY+", .clientX="+evt.clientX+", .clientY="+evt.clientY+", viewBoxX="+coords.x.toFixed(1)+", viewBoxY="+coords.y.toFixed(1);
            }
        ]]>
    </script>
    <rect id="bgRect" x="-500" y="-500" width="1500" height="1500" fill="yellow" stroke="none" />
    <g font-size="10px" font-family="Arial">
        <text x="10" y="30" font-size="12px" font-weight="bold">Demonstration of clientX/Y to viewBox Coordinate Conversion</text>
        <text x="10" y="130" onclick="updateRectColor()">Move your mouse to get clientX/Y, screenX/Y and viewBox coordinates.</text>
        
        <text x="10" y="170" id="evtText1"> </text>
        <text x="10" y="185" id="evtText2"> </text>
    </g>
    <g id="gridlines" stroke="black" stroke-width="1">
        <line x1="0" y1="0" x2="450" y2="0" />
        <line x1="0" y1="100" x2="450" y2="100" />
        <line x1="0" y1="200" x2="450" y2="200" />
        <line x1="0" y1="250" x2="450" y2="250" />
        <line x1="0" y1="0" x2="0" y2="250" />
        <line x1="100" y1="0" x2="100" y2="250" />
        <line x1="200" y1="0" x2="200" y2="250" />
        <line x1="300" y1="0" x2="300" y2="250" />
        <line x1="400" y1="0" x2="400" y2="250" />
        <line x1="450" y1="0" x2="450" y2="250" />
    </g>
    <g id="gridLineLettering" font-size="8px">
        <g text-anchor="start">
            <text x="2" y="8">x=0/y=0</text>
            <text x="2" y="97">x=0/y=100</text>
            <text x="2" y="197">x=0/y=200</text>
            <text x="2" y="247">x=0/y=250</text>
            <text x="102" y="8">x=100/y=0</text>
            <text x="202" y="8">x=200/y=0</text>
            <text x="302" y="8">x=300/y=0</text>
            <text x="102" y="247">x=100/y=250</text>
            <text x="202" y="247">x=200/y=250</text>
            <text x="302" y="247">x=300/y=250</text>
        </g>
        <g text-anchor="end">
            <text x="448" y="8">x=450/y=0</text>
            <text x="448" y="97">x=450/y=100</text>
            <text x="448" y="197">x=450/y=200</text>
            <text x="448" y="247">x=450/y=250</text>
            <text x="398" y="8">x=400/y=0</text>
            <text x="398" y="247">x=400/y=250</text>
        </g>
    </g>
</svg>		
		

Link to example 3 (example3_clientXY_to_viewBox_coordinate_conversion.svg) - in a separate window)

The example above shows how to convert the clientX/Y coordinates to our viewBox coordinates. With conforming viewers this also works after zoomin and panning. We do not explain the conversion in detail in this tutorial but explain how you use the mapApp object. In order to use this object you must link the external scripts mapApp.js and helper_functions.js. In the global section of the script you should include the line

var myMapApp = new mapApp();

to make an instance of the mapApp object. In order to convert the coordinates you should use the method mapApp.calcCoord(evt) and pass the evt object as parameter. As a result, you get a converted SVGPoint object where you can use the .x and .y properties. You use this method as follows:

var coords = myMapApp.calcCoord(evt);

Within the file mapApp.js there is a fork for SVG UAs that understand the method .getScreenCTM() and those who don't. ASV3 is a representative of the group of UAs who don't understand it. Most of the code is used to handle these non-conforming viewers. All other viewers just make use of the simple and straight-forward document.getScreenCTM() method. The mapApp object hides the complexity of handling the different viewers from the SVG content developer.

MutationEvent Properties and Methods:

Next, we discuss properties and methods available to all mutation events, some of the properties and methods are inherited from the general Events:

Properties

Methods

Example 4: Demonstration of MutationEvent specific Properties

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 450 250" onload="init()">
    <title>Demonstration of MutationEvent properties and methods</title>
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var evtText2;
            var evtText3;
            var myRedRect;
            function init() {
                myRedRect = document.getElementById("myRedRect");
                evtText1 = document.getElementById("evtText1");
                evtText2 = document.getElementById("evtText2");
                evtText3 = document.getElementById("evtText3");
                //add mutation event to myRedRect that listens to attribute changes
                document.getElementById("myRedRect").addEventListener("DOMAttrModified",showColorUpdate,false);
            }
            function showColorUpdate(evt) {
                evtText1.firstChild.nodeValue = ".attrName="+evt.attrName+", .prevValue="+evt.prevValue+", .newValue="+evt.newValue;
                evtText2.firstChild.nodeValue = ".target.id="+evt.target.getAttributeNS(null,"id")+", .currentTarget.id="+evt.currentTarget.getAttributeNS(null,"id") + ", .relatedNode.nodeName="+evt.relatedNode.nodeName;;
                var changeType = "";
                switch(evt.attrChange) {
                    case 1:
                        changeType = "attribute was modified";
                        break;
                    case 2:
                        changeType = "attribute was added";
                        break;
                    case 3:
                        changeType = "attribute was removed";
                        break;
                 }
                 evtText3.firstChild.nodeValue = ".timeStamp="+evt.timeStamp+", .attrChange=" + evt.attrChange+" ("+changeType+")";
            }
           function updateRectColor() {
                var changeAttr = "fill";
                if (Math.random() < 0.5) {
                    changeAttr = "stroke";
                }
                var red = parseInt(Math.random()*255);
                var green = parseInt(Math.random()*255);
                var blue = parseInt(Math.random()*255);
                myRedRect.setAttributeNS(null,changeAttr,"rgb("+red+","+green+","+blue+")");
           }
           function removeRectColor() {
                var changeAttr = "fill";
                if (Math.random() < 0.5) {
                    changeAttr = "stroke";
                }               
                if (myRedRect.hasAttributeNS(null,changeAttr)) {
                	myRedRect.removeAttributeNS(null,changeAttr);
                }
           }
        ]]>
    </script>
    <rect x="5" y="40" width="50" height="30" fill="red" id="myRedRect" stroke="blue" stroke-width="5"/>
    <g font-size="10px" font-family="Arial">
        <text x="5" y="30" font-size="12px" font-weight="bold">Demonstration of MutationEvent properties and methods</text>
        <text x="5" y="90" onclick="updateRectColor()">Click repeatedly on this text to change the stroke or fill color of the above rectangle using
          <tspan x="5" dy="15">a random generator. A mutation event is triggered that displays the attribute that was changed</tspan><tspan x="5" dy="15">as well as the old and new values.</tspan></text>
        <text x="5" y="140" onclick="removeRectColor()">Click this text to remove the fill or stroke color</text>
        
        <text x="5" y="170" id="evtText1"> </text>
        <text x="5" y="185" id="evtText2"> </text>
        <text x="5" y="200" id="evtText3"> </text>
    </g>
</svg>
		

Link to example 4 (example4_mutationevent_properties_and_methods.svg) - in a separate window)

The above example shows all mutation specific properties. One can see which attribute was changed, whether it was added, removed or changed. Finally, one gets the previous and new values. Note that mutation events do not work in ASV3, but they work in ASV6, Batik, Opera9 and MozillaSVG. Opera9 does not return the original values in .prevValue and .newValue but normalized values (e.g. hex values for colors).

The SVGResize Event - Detecting when the size of the SVG canvas changed:

The SVGResize Event is very usefult for resetting coordinate system and re-positioning UI elements after the window size changed.

Example 5: Demonstration of the SVGResize Event

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 450 250" onload="repositionRects(evt)" onresize="repositionRects(evt)">
    <title>Demonstration of clientX/Y to viewBox Coordinate Conversion</title>
    <script type="text/ecmascript">
        <![CDATA[
            function repositionRects(evt) {
                var svgroot = document.documentElement;
                //some viewers don't know window.innerWidth/Height, some don't know viewport ...
                var vbWidth;
                var vbHeight;
                if (!window.innerWidth) {
                    vbWidth = svgroot.viewport.width;
                }
                else {
                    vbWidth = window.innerWidth;
                }                
                if (!window.innerHeight) {
                    vbHeight = svgroot.viewport.height;
                }
                else {
                    vbHeight = window.innerHeight;
                }                
                //reset viewBox to reflect different embed/window size
                var vbString = "0 0 "+vbWidth+" "+vbHeight;
                svgroot.setAttributeNS(null,"viewBox",vbString);
                //reset text that displays the viewBox attribute
                var vbText = document.getElementById("vbText");
                vbText.setAttributeNS(null,"x",vbWidth / 2);
                vbText.setAttributeNS(null,"y",vbHeight / 2);
                vbText.firstChild.nodeValue = "viewBox=\""+vbString+"\"";
                //reposition rects
                var rectSide = 50;
                document.getElementById("rectLowerLeft").setAttributeNS(null,"y",vbHeight-rectSide);
                document.getElementById("rectUpperRight").setAttributeNS(null,"x",vbWidth-rectSide);
                var rectLowerRight = document.getElementById("rectLowerRight");
                rectLowerRight.setAttributeNS(null,"x",vbWidth-rectSide);
                rectLowerRight.setAttributeNS(null,"y",vbHeight-rectSide);
            }
        ]]>
    </script>
    <rect id="rectUpperLeft" x="0" y="0" width="50" height="50" fill="red" />
    <rect id="rectLowerLeft" x="0" y="0" width="50" height="50" fill="red" />
    <rect id="rectUpperRight" x="0" y="0" width="50" height="50" fill="red" />
    <rect id="rectLowerRight" x="0" y="0" width="50" height="50" fill="red" />
    <text id="vbText" x="0" y="0" width="50" height="50" font-family="Arial" font-size="15" text-anchor="middle"> </text>
</svg>
		

Link to example 5 (example5_svgresize_event.svg) - in a separate window)

The SVGResize event does not yet work in MozillaSVG!

In the above example we assign an SVGResize event to the svg root element. Onload and each time the user resizes the window, the viewBox is adapted to the window width and the four squares are re-positioned. Re-positioning is only necessary for the right and bottom rectangles, since 0/0 is always top-left. Some UAs support the window.innerWidth and window.innerHeight properties, some support the viewport property of the svg root element. Both values lead to the inner proportions of the window/frame or embed size. The effect of the SVGResize event is best visible if you open the example 5 in a separate window and resize the window. A text in the center of the viewBox always displays the viewBox coordinate system.

The SVGScroll and SVGZoom Events - Detecting when the user or a script zoomed or panned:

These two events are useful if one wants to make elements "immune" to zooming and panning. We can detect scrolling and panning and then compensate with the inverse of the screenCTM, compensated by the viewBox coordinate system

Example 6: Demonstration of the SVGScroll and SVGZoom Events

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 450 337" onload="init()" onmousemove="showCoords(evt)" overflow="visible">
    <title>Demonstration of using the SVGScroll and SVGZoom Event</title>
    <script type="text/ecmascript" xlink:href="../resources/helper_functions.js" />
    <script type="text/ecmascript" xlink:href="../resources/mapApp.js" />
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var textGroup;
            var myMapApp = new mapApp();
            var myPhoto;
            var viewBox;
            function init(evt) {
                var svgroot = document.documentElement;
                //getting references to dynamic text elements
                evtText1 = document.getElementById("evtText1");
                textGroup = document.getElementById("textGroup");
                myPhoto = document.getElementById("myPhoto");
                viewBox = new ViewBox(svgroot);
                svgroot.addEventListener("SVGScroll",resetTransformation,false);
                svgroot.addEventListener("SVGZoom",resetTransformation,false);
                resetTransformation(evt);
            }
            function resetTransformation(evt) {
                //need to compensate for viewBox coordinate system
                var factorX = viewBox.windowWidth / parseFloat(document.documentElement.getAttributeNS(null,"viewBox").split(" ")[2]);
                var factorY = viewBox.windowHeight / parseFloat(document.documentElement.getAttributeNS(null,"viewBox").split(" ")[3]);
                if (factorX > factorY) {
                	factor = factorY;
                }
                else {
                	factor = factorX;
                }
                if (!document.documentElement.getScreenCTM) {
                	//replacement for missing .getScreenCTM()
                	var mat = getTransformToRootElement(textGroup.parentNode);
                	mat = mat.inverse().multiply(myMapApp.m);
                }
                else {
                 	var mat = textGroup.parentNode.getScreenCTM().inverse();               
                }
                textGroup.setAttributeNS(null,"transform","matrix("+(mat.a*factor)+" "+mat.b+" "+mat.c+" "+(mat.d*factor)+" "+mat.e+" "+mat.f+")");
           }
            function showCoords(evt) {
                var coords = myMapApp.calcCoord(evt);
                evtText1.firstChild.nodeValue = "Your viewBox coordinates: "+coords.x.toFixed(1)+", "+coords.y.toFixed(1);
            }
       ]]>
    </script>
    <rect fill="yellow" width="10000" height="10000" x="-4000" y="-4000" />
    <image id="myPhoto" x="0" y="0" width="450" height="337" xlink:href="../gui/selectionlist/roses/whisky_mac.jpg" />
    <g id="textGroup" font-size="10px" font-family="Arial" text-anchor="middle" pointer-events="none" fill="royalblue">
        <text x="225" y="30" font-size="12px" font-weight="bold">Demonstration of using the SVGScroll and SVGZoom Event</text>
        <text x="225" y="60">Zoom in or Pan and Move your mouse.<tspan x="225" dy="15">The text elements will always remain at the same position</tspan></text>
        <text x="225" y="330" id="evtText1">Your viewBox coordinates:</text>
    </g>
</svg>
		

Link to example 6 (example6_svgscroll_and_svgzoom.svg) - in a separate window)

Adding and Removing Event Listeners, Keyboard Events:

Often it is necessary to add and remove event listeners to existing or newly created elements during the runtime of the program. For this purpose the DOM offers the methods .addEventListener() and .removeEventListener

In the following examples we create a very primitive textbox that allows to type in text. A click on the textbox activates the keyboard events, a click outside the box or the enter key disables the typing.

Example 7: Demonstration of Adding and Removing of Event Listeners, Keyboard Events

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 450 250" onload="init()" xml:space="preserve">
    <title>Demonstration of adding and Removing Event Listeners as well as KeyEvents</title>
    <script type="text/ecmascript">
        <![CDATA[
            var evtText1;
            var evtText2;
            var evtText3;
            var typeInitialized = false;
            var text = "";
            function init() {
                //getting references to dynamic text elements
                evtText1 = document.getElementById("evtText1");
                evtText2 = document.getElementById("evtText2");
                evtText3 = document.getElementById("evtText3");
            }
            //this function handles the keyboard events
            function typeText(evt) {
               //handle "keypress" for all "real characters"
               if (evt.type == "keypress") {
                   //some browsers support evt.charCode, some only evt.keyCode
                   if (evt.charCode) {
                        var charCode = evt.charCode;
                   }
                   else {
                        var charCode = evt.keyCode;
                   }
                   //all real characters
                   if (charCode > 31 && charCode != 127 && charCode < 65535) {
                        text += String.fromCharCode(charCode);
                   }
                    //backspace key
		           if (charCode == 8) {
                        //shorten text
                        text = text.substring(0,text.length-1);
                   }
                   //enter key
                   else if (charCode == 10 || charCode == 13) {
                         stopTyping(evt);
                   }
               }
               //update dynamic text
               evtText1.firstChild.nodeValue = text; 
               //suppress unwanted browser shortcuts. e.g. in Opera or Mozilla
               evt.preventDefault();
            }
            //this function adds the event listeners
            function initTyping(evt) {
                if (!typeInitialized) {
                    document.documentElement.addEventListener("keypress",typeText,false);
                    document.documentElement.addEventListener("click",stopTyping,false);
                    evtText2.firstChild.nodeValue = "Typing Active";
                    typeInitialized = true;
                }
                //we don't want the click event on the document level to
                //immediately stop the typing mode
                evt.stopPropagation();
            }
            function stopTyping(evt) {
                    document.documentElement.removeEventListener("keypress",typeText,false);
                    document.documentElement.removeEventListener("click",stopTyping,false);
                    typeInitialized = false;
                    evtText2.firstChild.nodeValue = "Typing Inactive";
                    evtText3.firstChild.nodeValue = "You typed: "+text;
            }
        ]]>
    </script>
    <rect id="bgRect" x="-500" y="-500" width="1500" height="1500" fill="yellow" stroke="none" />
    <rect x="10" y="50" width="150" height="20" fill="white" stroke="black" onclick="initTyping(evt)" />
    <g font-size="10px" font-family="Arial">
        <text x="10" y="30" font-size="12px" font-weight="bold">Demonstration of adding and removing Event Listeners</text>
        <text x="10" y="130">Click in the white rectangle to start typing text.
          <tspan x="10" dy="15">Click outside the textbox or press enter to stop writing text.</tspan>
          <tspan x="10" dy="15">Press backspace to delete the last character when typing is active</tspan></text>
        
        <text x="15" y="65" id="evtText1" pointer-events="none"> </text>
        <text x="15" y="90" id="evtText2">Typing Inactive</text>
        <text x="15" y="105" id="evtText3">You Typed: </text>
    </g>
</svg>		
		

Link to example 7 (example7_add_and_remove_event_listener.svg) - in a separate window)

Note that for the above example the backspace and enter key does not work in ASV3 on Macintosh. It works in all other browsers and SVG UAs.

In this example we have an onclick event handler attribute assigned to the "texbox" rectangle. If the user clicks on this rectangle, the function initTyping() is executed. This function first checks if the textbox is already initialized (boolean variable typeInitialized) and adds event listeners if it wasn't already initialized. In our case we add a key and a mouse event listener of the types "keypress" and "click" to our svg root element. The syntax for adding event listeners is as follows:

element.addEventListener(eventType,listenerFunctionOrObject,useCapture);

The eventType is the eventName without the "on"-prefix. The listener function or object is the function or object to be called when the event was triggered. In case of an object, the general method .handleEvent() is called if not specified otherwise. Per default, the function or object receives the evt object as an argument to allow the developer to use the properties and methods provided by the evt object. Note that there is no way to provide arguments other than the evt object to the function or object. If you need additional arguments, you should set them as object properties prior to triggering this event. The third argument useCapture defines if the event is treated in capture or bubble mode. Most of the time you want the bubble mode and therefore set this argument to false.

After setting the event listeners we set the content of a status text to "Typing Active" to indicate that the user can now type in the textbox. Additionally we set the value of the variable typeInitialized to true to tell our script that typing is already initialized. Finally, we call the method .stopPropagation of our evt object. This tells the SVG UA not to bubble this event up to it's ancestors. If we omit this method, the event would arrive at the svg root element, which has a newly defined "click" event listener which would cancel typing immediately.

Likewise we can use the method .removeEventListener() which has the same syntax. We use it after the user hits the enter key or clicks on any element except the textbox (refer to the "click" event listener we just added on the root element level). These event listeners call the function stopTyping() which removes the temporary event listeners, sets the status text to inactive and the status variable typeInitialized to false. Syntax of Event Listener Removal:

element.removeEventListener(eventType,listenerFunctionOrObject,useCapture);

Next, we discuss the keyboard events. Keyboard events aren't standardized in DOM2, but are part of DOM3 which is not yet implemented in all browsers/SVG UAs. Therefore the behaviour of key events varies widely. Some browsers and UAs support the evt.charCode property, some the evt.keyCode property. We first test, if charCode is successful, if not, we use keyCode. For regular characters we test the range (>31 and >65535, except 127) and use the String.fromCharCode(charCode) method to get a character. We concatenate the new character with the old existing string in the global text variable. In this primitive textbox we test for two special charCodes: 8 for the backspace key (in this case we remove the last character), 10 or 13 for the enter key (in this case we call the stopTyping() function). The evt.preventDefault() method stops to forward the event to the browser. This method is necessary to avoid confusing user type-ins with browser shortcuts. As an example: when hitting the backspace key Mozilla would go back to the previous webpage. evt.preventDefault() avoids that.

Artificially creating events and dispatching them:

Recommended further resources:




Last modified: Tuesday, 10-Dec-2019 21:33:41 CET
© carto:net (andreas neumann & andré m. winter)
original URL for reference: https://old.carto.net/papers/svg/eventhandling/index.shtml