2 Scripts to create interactive buttons in SVG using ECMA script
3 Copyright (C) <2006> <Andreas Neumann>
4 Version 1.1.3, 2006-10-30
5 neumann@karto.baug.ethz.ch
7 http://www.carto.net/neumann/
10 * Bruce Rindahl and Virgis Globis for fixing an error with vertical positioning of the button texts
11 * Olaf Schnabel for suggesting a bugfix where type conversion was missing when button text was of type number
15 Documentation: http://www.carto.net/papers/svg/gui/button/
19 current version: 1.1.3
26 changed parameters of constructor (styling system): now an array of literals containing presentation attributes. This allows for more flexibility in Styling. Added check for number of arguments.
29 added the option to create multiline textbuttons, changed the behaviour of the y placement of the text in the button
32 added .resize(width,height) and .moveTo(x.y) methods, added new constructor parameter parentNode, fixed a bug from version 1.0.2 in the method .setTextValue()
35 fixed an error with vertical positioning of button texts (thanks Bruce and Virgis)
36 changed DOM hierarchy slightly. Every button now has its own group "this.parentGroup". This is necessary to hide and show a button. This group should not be shared by other SVG elements, unless on purpose.
37 adopted .resize() and .moveTo() methods to make use of the corrected function this.createButtonTexts();
40 corrected a bug where type conversion in buttonText was missing when buttonText was of type number
43 corrected a bug where the .deactivate() method did not work properly when called after the method .setTextValue()
44 added support for "rx" and "ry" attribute in "this.deActivateRect"
49 This ECMA script library is free software; you can redistribute it and/or
50 modify it under the terms of the GNU Lesser General Public
51 License as published by the Free Software Foundation; either
52 version 2.1 of the License, or (at your option) any later version.
54 This library is distributed in the hope that it will be useful,
55 but WITHOUT ANY WARRANTY; without even the implied warranty of
56 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
57 Lesser General Public License for more details.
59 You should have received a copy of the GNU Lesser General Public
60 License along with this library (lesser_gpl.txt); if not, write to the Free Software
61 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
65 original document site: http://www.carto.net/papers/svg/gui/button/
66 Please contact the author in case you want to use code or ideas commercially.
67 If you use this code, please include this copyright header, the included full
68 LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
69 (http://www.gnu.org/copyleft/lesser.txt)
71 -------------------------------
73 Please report bugs and send improvements to neumann@karto.baug.ethz.ch
74 If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/button/)
75 somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
79 function button(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset) {
81 if (arguments.length > 0) {
82 if (arguments.length == nrArguments) {
83 this.init(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset);
86 alert("Error in Button ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
91 button.prototype.init = function(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset) {
92 this.id = id; //the id where all new content is appended to
93 this.parentNode = parentNode; //the id or node reference of the parent group where the button can be appended
94 this.functionToCall = functionToCall; //function to be called if button was pressed
95 this.buttonType = buttonType; //button type: currently either "rect" or "ellipse"
96 this.buttonText = buttonText; //default value to be filled in when button is created
97 this.buttonSymbolId = buttonSymbolId; //id to a symbol to be used as a button graphics
98 this.x = x; //left of button rectangle
99 this.y = y; //top of button rectangle
100 this.width = width; //button rectangle width
101 this.height = height; //button rectangle height
102 this.textStyles = textStyles; //array of literals containing text styles
103 if (!this.textStyles["font-size"]) {
104 this.textStyles["font-size"] = 12;
106 this.buttonStyles = buttonStyles; //the fill color of the button rectangle or ellipse
107 this.shadeLightStyles = shadeLightStyles; //light fill color simulating 3d effect
108 this.shadeDarkStyles = shadeDarkStyles; //dark fill color simulating 3d effect
109 this.shadowOffset = shadowOffset; //shadow offset in viewBox units
110 this.upperLeftLine = null; //later a reference to the upper left line simulating 3d effect
111 this.buttonRect = null; //later a reference to the button area (rect)
112 this.buttonTextElement = null; //later a reference to the button text
113 this.buttonSymbolInstance = null; //later a reference to the button symbol
114 this.deActivateRect = null; //later a reference to a rectangle that can be used to deactivate the button
115 this.activated = true; //a property indicating if button is activated or not
116 this.lowerRightLine = null; //later a reference to the lower right line simulating 3d effect
117 this.createButton(); //method to initialize button
118 this.timer = new Timer(this); //a Timer instance for calling the functionToCall
119 this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms
123 button.prototype.createButton = function() {
124 var result = this.testParent();
126 //create upper left button line or ellipse
127 if (this.buttonType == "rect") {
128 this.upperLeftShadow = document.createElementNS(svgNS,"rect");
129 this.upperLeftShadow.setAttributeNS(null,"x",this.x - this.shadowOffset);
130 this.upperLeftShadow.setAttributeNS(null,"y",this.y - this.shadowOffset);
131 this.upperLeftShadow.setAttributeNS(null,"width",this.width);
132 this.upperLeftShadow.setAttributeNS(null,"height",this.height);
133 this.upperLeftShadow.setAttributeNS(null,"points",this.x+","+(this.y+this.height)+" "+this.x+","+this.y+" "+(this.x+this.width)+","+this.y);
135 else if (this.buttonType == "ellipse") {
136 this.upperLeftShadow = document.createElementNS(svgNS,"ellipse");
137 this.upperLeftShadow.setAttributeNS(null,"cx",this.x + this.width * 0.5 - this.shadowOffset);
138 this.upperLeftShadow.setAttributeNS(null,"cy",this.y + this.height * 0.5 - this.shadowOffset);
139 this.upperLeftShadow.setAttributeNS(null,"rx",this.width * 0.5);
140 this.upperLeftShadow.setAttributeNS(null,"ry",this.height * 0.5);
143 alert("buttonType '"+this.buttonType+"' not supported. You need to specify 'rect' or 'ellipse'");
145 for (var attrib in this.shadeLightStyles) {
146 this.upperLeftShadow.setAttributeNS(null,attrib,this.shadeLightStyles[attrib]);
148 this.parentGroup.appendChild(this.upperLeftShadow);
150 //create lower right button line or ellipse
151 if (this.buttonType == "rect") {
152 this.lowerRightShadow = document.createElementNS(svgNS,"rect");
153 this.lowerRightShadow.setAttributeNS(null,"x",this.x + this.shadowOffset);
154 this.lowerRightShadow.setAttributeNS(null,"y",this.y + this.shadowOffset);
155 this.lowerRightShadow.setAttributeNS(null,"width",this.width);
156 this.lowerRightShadow.setAttributeNS(null,"height",this.height);
157 this.lowerRightShadow.setAttributeNS(null,"points",this.x+","+(this.y+this.height)+" "+this.x+","+this.y+" "+(this.x+this.width)+","+this.y);
159 else if (this.buttonType == "ellipse") {
160 this.lowerRightShadow = document.createElementNS(svgNS,"ellipse");
161 this.lowerRightShadow.setAttributeNS(null,"cx",this.x + this.width * 0.5 + this.shadowOffset);
162 this.lowerRightShadow.setAttributeNS(null,"cy",this.y + this.height * 0.5 + this.shadowOffset);
163 this.lowerRightShadow.setAttributeNS(null,"rx",this.width * 0.5);
164 this.lowerRightShadow.setAttributeNS(null,"ry",this.height * 0.5);
166 for (var attrib in this.shadeDarkStyles) {
167 this.lowerRightShadow.setAttributeNS(null,attrib,this.shadeDarkStyles[attrib]);
169 this.parentGroup.appendChild(this.lowerRightShadow);
172 if (this.buttonType == "rect") {
173 this.buttonRect = document.createElementNS(svgNS,"rect");
174 this.buttonRect.setAttributeNS(null,"x",this.x);
175 this.buttonRect.setAttributeNS(null,"y",this.y);
176 this.buttonRect.setAttributeNS(null,"width",this.width);
177 this.buttonRect.setAttributeNS(null,"height",this.height);
179 else if (this.buttonType == "ellipse") {
180 this.buttonRect = document.createElementNS(svgNS,"ellipse");
181 this.buttonRect.setAttributeNS(null,"cx",this.x + this.width * 0.5);
182 this.buttonRect.setAttributeNS(null,"cy",this.y + this.height * 0.5);
183 this.buttonRect.setAttributeNS(null,"rx",this.width * 0.5);
184 this.buttonRect.setAttributeNS(null,"ry",this.height * 0.5);
186 for (var attrib in this.buttonStyles) {
187 this.buttonRect.setAttributeNS(null,attrib,this.buttonStyles[attrib]);
189 this.buttonRect.setAttributeNS(null,"cursor","pointer");
190 this.buttonRect.addEventListener("mousedown",this,false);
191 this.buttonRect.addEventListener("mouseup",this,false);
192 this.buttonRect.addEventListener("click",this,false);
193 this.parentGroup.appendChild(this.buttonRect);
195 //call a helper method to create the text elements
196 this.createButtonTexts();
198 if (this.buttonSymbolId != undefined) {
199 this.buttonSymbolInstance = document.createElementNS(svgNS,"use");
200 this.buttonSymbolInstance.setAttributeNS(null,"x",(this.x + this.width / 2));
201 this.buttonSymbolInstance.setAttributeNS(null,"y",(this.y + this.height / 2));
202 this.buttonSymbolInstance.setAttributeNS(xlinkNS,"href","#"+this.buttonSymbolId);
203 this.buttonSymbolInstance.setAttributeNS(null,"pointer-events","none");
204 this.parentGroup.appendChild(this.buttonSymbolInstance);
207 //create rectangle to deactivate the button
208 if (this.buttonType == "rect") {
209 this.deActivateRect = document.createElementNS(svgNS,"rect");
210 this.deActivateRect.setAttributeNS(null,"x",this.x - this.shadowOffset);
211 this.deActivateRect.setAttributeNS(null,"y",this.y - this.shadowOffset);
212 this.deActivateRect.setAttributeNS(null,"width",this.width + this.shadowOffset * 2);
213 this.deActivateRect.setAttributeNS(null,"height",this.height + this.shadowOffset * 2);
215 else if (this.buttonType == "ellipse") {
216 this.deActivateRect = document.createElementNS(svgNS,"ellipse");
217 this.deActivateRect.setAttributeNS(null,"cx",this.x + this.width * 0.5);
218 this.deActivateRect.setAttributeNS(null,"cy",this.y + this.height * 0.5);
219 this.deActivateRect.setAttributeNS(null,"rx",this.width * 0.5 + this.shadowOffset);
220 this.deActivateRect.setAttributeNS(null,"ry",this.height * 0.5 + this.shadowOffset);
223 this.deActivateRect.setAttributeNS(null,"fill","white");
224 this.deActivateRect.setAttributeNS(null,"fill-opacity","0.5");
225 this.deActivateRect.setAttributeNS(null,"stroke","none");
226 this.deActivateRect.setAttributeNS(null,"display","none");
227 this.deActivateRect.setAttributeNS(null,"cursor","default");
228 if (this.buttonStyles["rx"]) {
229 this.deActivateRect.setAttributeNS(null,"rx",this.buttonStyles["rx"]);
231 if (this.buttonStyles["ry"]) {
232 this.deActivateRect.setAttributeNS(null,"ry",this.buttonStyles["ry"]);
234 this.parentGroup.appendChild(this.deActivateRect);
237 alert("could not create or reference 'parentNode' of button with id '"+this.id+"'");
241 //test if parent group exists
242 button.prototype.testParent = function() {
243 //test if of type object
244 var nodeValid = false;
245 this.parentGroup = document.createElementNS(svgNS,"g");
246 if (typeof(this.parentNode) == "object") {
247 if (this.parentNode.nodeName == "svg" || this.parentNode.nodeName == "g" || this.parentNode.nodeName == "svg:svg" || this.parentNode.nodeName == "svg:g") {
248 this.parentNode.appendChild(this.parentGroup);
252 else if (typeof(this.parentNode) == "string") {
253 //first test if button group exists
254 if (!document.getElementById(this.parentNode)) {
255 this.parentGroup.setAttributeNS(null,"id",this.parentNode);
256 document.documentElement.appendChild(this.parentGroup);
260 document.getElementById(this.parentNode).appendChild(this.parentGroup);
267 //move button to a new position
268 button.prototype.moveTo = function(moveX,moveY) {
272 if (this.buttonType == "rect") {
273 this.upperLeftShadow.setAttributeNS(null,"x",this.x - this.shadowOffset);
274 this.upperLeftShadow.setAttributeNS(null,"y",this.y - this.shadowOffset);
275 this.lowerRightShadow.setAttributeNS(null,"x",this.x + this.shadowOffset);
276 this.lowerRightShadow.setAttributeNS(null,"y",this.y + this.shadowOffset);
277 this.buttonRect.setAttributeNS(null,"x",this.x);
278 this.buttonRect.setAttributeNS(null,"y",this.y);
280 else if (this.buttonType == "ellipse") {
281 this.upperLeftShadow.setAttributeNS(null,"cx",this.x + this.width * 0.5 - this.shadowOffset);
282 this.upperLeftShadow.setAttributeNS(null,"cy",this.y + this.height * 0.5 - this.shadowOffset);
283 this.lowerRightShadow.setAttributeNS(null,"cx",this.x + this.width * 0.5 + this.shadowOffset);
284 this.lowerRightShadow.setAttributeNS(null,"cy",this.y + this.height * 0.5 + this.shadowOffset);
285 this.buttonRect.setAttributeNS(null,"cx",this.x + this.width * 0.5);
286 this.buttonRect.setAttributeNS(null,"cy",this.y + this.height * 0.5);
288 //reposition button text
289 if (this.buttonText != undefined) {
290 this.parentGroup.removeChild(this.buttonTextElement);
291 this.createButtonTexts();
295 //resize button to a new width and height
296 button.prototype.resize = function(newWidth,newHeight) {
297 this.width = newWidth;
298 this.height = newHeight;
300 if (this.buttonType == "rect") {
301 this.upperLeftShadow.setAttributeNS(null,"width",this.width);
302 this.upperLeftShadow.setAttributeNS(null,"height",this.height);
303 this.lowerRightShadow.setAttributeNS(null,"width",this.width);
304 this.lowerRightShadow.setAttributeNS(null,"height",this.height);
305 this.buttonRect.setAttributeNS(null,"width",this.width);
306 this.buttonRect.setAttributeNS(null,"height",this.height);
308 else if (this.buttonType == "ellipse") {
309 this.upperLeftShadow.setAttributeNS(null,"rx",this.width * 0.5);
310 this.upperLeftShadow.setAttributeNS(null,"ry",this.height * 0.5);
311 this.lowerRightShadow.setAttributeNS(null,"rx",this.width * 0.5);
312 this.lowerRightShadow.setAttributeNS(null,"ry",this.height * 0.5);
313 this.buttonRect.setAttributeNS(null,"rx",this.width * 0.5);
314 this.buttonRect.setAttributeNS(null,"ry",this.height * 0.5);
316 if (this.buttonText != undefined) {
317 this.parentGroup.removeChild(this.buttonTextElement);
318 this.createButtonTexts();
322 //remove all button elements
323 button.prototype.removeButton = function() {
324 this.parentGroup.removeChild(this.upperLeftShadow);
325 this.parentGroup.removeChild(this.lowerRightShadow);
326 this.parentGroup.removeChild(this.buttonRect);
327 if (this.buttonTextElement) {
328 this.parentGroup.removeChild(this.buttonTextElement);
330 if (this.buttonSymbolInstance) {
331 this.parentGroup.removeChild(this.buttonSymbolInstance);
333 this.parentGroup.removeChild(this.deActivateRect);
337 button.prototype.hideButton = function() {
338 this.parentGroup.setAttributeNS(null,"display","none");
342 button.prototype.showButton = function() {
343 this.parentGroup.setAttributeNS(null,"display","inherit");
347 button.prototype.handleEvent = function(evt) {
348 if (evt.type == "mousedown") {
349 this.togglePressed("pressed");
350 document.documentElement.addEventListener("mouseup",this,false);
352 if (evt.type == "mouseup") {
353 this.togglePressed("released");
354 document.documentElement.removeEventListener("mouseup",this,false);
356 if (evt.type == "click") {
357 //for some strange reasons I could not forward the evt object here ;-(, the code below using a literal is a workaround
358 //attention: only some of the evt properties are forwarded here, you can add more, if you need them
359 var timerEvt = {x:evt.clientX,y:evt.clientY,type:evt.type,detail:evt.detail,timeStamp:evt.timeStamp}
360 this.timer.setTimeout("fireFunction",this.timerMs,timerEvt);
364 button.prototype.togglePressed = function(type) {
365 if (type == "pressed") {
366 for (var attrib in this.shadeDarkStyles) {
367 this.upperLeftShadow.setAttributeNS(null,attrib,this.shadeDarkStyles[attrib]);
369 for (var attrib in this.shadeLightStyles) {
370 this.lowerRightShadow.setAttributeNS(null,attrib,this.shadeLightStyles[attrib]);
373 if (type == "released") {
374 for (var attrib in this.shadeDarkStyles) {
375 this.lowerRightShadow.setAttributeNS(null,attrib,this.shadeDarkStyles[attrib]);
377 for (var attrib in this.shadeLightStyles) {
378 this.upperLeftShadow.setAttributeNS(null,attrib,this.shadeLightStyles[attrib]);
383 button.prototype.fireFunction = function(evt) {
384 if (typeof(this.functionToCall) == "function") {
385 if (this.buttonTextElement) {
386 this.functionToCall(this.id,evt,this.buttonText);
388 if (this.buttonSymbolInstance) {
389 this.functionToCall(this.id,evt);
392 if (typeof(this.functionToCall) == "object") {
393 if (this.buttonTextElement) {
394 this.functionToCall.buttonPressed(this.id,evt,this.buttonText);
396 if (this.buttonSymbolInstance) {
397 this.functionToCall.buttonPressed(this.id,evt);
400 if (typeof(this.functionToCall) == undefined) {
405 button.prototype.getTextValue = function() {
406 return this.buttonText;
409 button.prototype.setTextValue = function(value) {
410 this.buttonText = value;
411 //remove previous buttonTextElement
412 if (this.buttonTextElement) {
413 this.parentGroup.removeChild(this.buttonTextElement);
415 this.createButtonTexts();
418 //this is a helper function to create the text elements, it is used from two different methods, .createButton() and .setTextValue()
419 button.prototype.createButtonTexts = function() {
420 if (this.buttonText != undefined) {
421 //first see if there is a multiline button
422 var buttonTexts = String(this.buttonText).split("\n");
424 //set a start y-offset
425 var dy = this.textStyles["font-size"]*1.1;
427 //if you don't like our method for y-positioning here, you can experiment with the formula here and define your own "initY"
428 var initY = (this.height - dy * buttonTexts.length) / 2 + this.textStyles["font-size"];
430 //create text element
431 this.buttonTextElement = document.createElementNS(svgNS,"text");
432 this.buttonTextElement.setAttributeNS(null,"x",(this.x + this.width / 2));
433 this.buttonTextElement.setAttributeNS(null,"y",(this.y + initY));
434 for (var attrib in this.textStyles) {
435 value = this.textStyles[attrib];
436 this.buttonTextElement.setAttributeNS(null,attrib,value);
438 this.buttonTextElement.setAttributeNS(null,"pointer-events","none");
439 this.buttonTextElement.setAttributeNS(null,"text-anchor","middle");
440 this.buttonTextElement.setAttributeNS("http://www.w3.org/XML/1998/namespace","space","preserve");
441 for (var j=0;j<buttonTexts.length;j++) {
442 var tspan = document.createElementNS(svgNS,"tspan");
443 tspan.setAttributeNS(null,"x",(this.x + this.width / 2));
445 tspan.setAttributeNS(null,"dy",0);
448 tspan.setAttributeNS(null,"dy",dy);
450 var textNode = document.createTextNode(buttonTexts[j]);
451 tspan.appendChild(textNode);
452 this.buttonTextElement.appendChild(tspan);
454 this.parentGroup.appendChild(this.buttonTextElement);
458 button.prototype.activate = function(value) {
459 this.deActivateRect.setAttributeNS(null,"display","none");
460 this.activated = true;
463 button.prototype.deactivate = function(value) {
464 this.deActivateRect.setAttributeNS(null,"display","inherit");
465 this.parentGroup.appendChild(this.deActivateRect);
466 this.activated = false;
470 //initialize inheritance
471 switchbutton.prototype = new button();
472 switchbutton.prototype.constructor = switchbutton;
473 switchbutton.superclass = button.prototype;
475 function switchbutton(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset) {
476 var nrArguments = 15;
477 if (arguments.length > 0) {
478 if (arguments.length == nrArguments) {
479 this.init(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset);
482 alert("Error in Switchbutton ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
487 switchbutton.prototype.init = function(id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset) {
488 switchbutton.superclass.init.call(this,id,parentNode,functionToCall,buttonType,buttonText,buttonSymbolId,x,y,width,height,textStyles,buttonStyles,shadeLightStyles,shadeDarkStyles,shadowOffset);
492 //overwriting handleEventcode
493 switchbutton.prototype.handleEvent = function(evt) {
494 //for some strange reasons I could not forward the evt object here ;-(, the code below using a literal is a workaround
495 //attention: only some of the evt properties are forwarded here, you can add more, if you need them
496 var timerEvt = {x:evt.clientX,y:evt.clientY,type:evt.type,detail:evt.detail,timeStamp:evt.timeStamp}
497 if (evt.type == "click") {
500 this.togglePressed("released");
501 this.timer.setTimeout("fireFunction",this.timerMs,timerEvt);
505 this.togglePressed("pressed");
506 this.timer.setTimeout("fireFunction",this.timerMs,timerEvt);
511 switchbutton.prototype.getSwitchValue = function() {
515 switchbutton.prototype.setSwitchValue = function(onOrOff,firefunction) {
517 //artificial timer event - don't use the values!
518 var timerEvt = {x:0,y:0,type:"click",detail:1,timeStamp:0}
520 this.togglePressed("pressed");
522 this.timer.setTimeout("fireFunction",this.timerMs,timerEvt);
526 this.togglePressed("released");
528 this.timer.setTimeout("fireFunction",this.timerMs,timerEvt);
533 //overwriting fireFunction code
534 switchbutton.prototype.fireFunction = function(evt) {
535 if (typeof(this.functionToCall) == "function") {
536 if (this.buttonTextElement) {
537 this.functionToCall(this.id,evt,this.on,this.buttonText);
539 if (this.buttonSymbolInstance) {
540 this.functionToCall(this.id,evt,this.on);
543 if (typeof(this.functionToCall) == "object") {
544 if (this.buttonTextElement) {
545 this.functionToCall.buttonPressed(this.id,evt,this.on,this.buttonText);
547 if (this.buttonSymbolInstance) {
548 this.functionToCall.buttonPressed(this.id,evt,this.on);
551 if (typeof(this.functionToCall) == undefined) {