2 Scripts to create interactive textboxes in SVG using ECMA script
3 Copyright (C) <2006> <Andreas Neumann>
4 Version 1.1.2, 2006-10-04
5 neumann@karto.baug.ethz.ch
7 http://www.carto.net/neumann/
10 * Initial code was taken from Olaf Schnabel --> schnabel@karto.baug.ethz.ch (thanks!)
11 * Thanks also to many people of svgdevelopers@yahoogroups.com
12 * bug report and fix from Volker Gersabeck (make textbox namespace aware and corrected .setValue method when text was transformed)
13 * bug report and fix from David Boyd (pressing delete key in ASV would fail if cursor is at end of textbox)
14 * enhancement suggestion and bug report by Michael Mehldorn (callback function was called twice in case of enter key, accept also integer values in method .setValue())
18 Documentation: http://www.carto.net/papers/svg/gui/textbox/
22 current version: 1.1.2
29 fixed a bug with the delete key after clicking into the textbox
30 fixed a bug with removing text selection when clicking again after the selectionbox was visible
33 made object multi-namespace aware (hopefully), this probably needs more testing
34 it is now allowed to pass numbers in the constructor and .setValue() method
37 fixed a bug in internal method .testSupportsChar() when using an initially empty textbox
40 added constructor parameter this.parentNode; this.parentNode can be of type String (id) or a node reference (g or svg element)
41 replaced this.textboxGroup with this.parentGroup to be compatible with other GUI elements; fixed an "out of index" bug that occasionally appeared in Opera 9 when calculating the cursor position
44 fixed a bug with the delete key (ASV only) if cursor was at the end (thanks to David Boyd), fixed another bug with delete key if cursor was at the end it accidentally deleted the first character, fixed a bug in the method .setValue() (thanks to Volker Gersabeck)
45 added constructor parameter textYOffset, added methods ".moveTo(x,y)" and ".resize(width)"
48 changed the internal structure a bit. An additional group element is now created. This allows several textboxes to be placed in the same parent group. Previously this had failed. No changes in constructor parameters and methods in this release.
51 added parameter "fireFunction" to the "setValue()" method in order to determine whether the associated callback function should be fired or not
52 introduced new "changetype" with value "set" which indicates that the textbox value was set by method "setValue"
56 This ECMA script library is free software; you can redistribute it and/or
57 modify it under the terms of the GNU Lesser General Public
58 License as published by the Free Software Foundation; either
59 version 2.1 of the License, or (at your option) any later version.
61 This library is distributed in the hope that it will be useful,
62 but WITHOUT ANY WARRANTY; without even the implied warranty of
63 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
64 Lesser General Public License for more details.
66 You should have received a copy of the GNU Lesser General Public
67 License along with this library (lesser_gpl.txt); if not, write to the Free Software
68 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
72 original document site: http://www.carto.net/papers/svg/gui/textbox/
73 Please contact the author in case you want to use code or ideas commercially.
74 If you use this code, please include this copyright header, the included full
75 LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
76 (http://www.gnu.org/copyleft/lesser.txt)
78 -------------------------------
80 Please report bugs and send improvements to neumann@karto.baug.ethz.ch
81 If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/textbox/)
82 somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
86 function textbox(id,parentNode,defaultVal,maxChars,x,y,boxWidth,boxHeight,textYOffset,textStyles,boxStyles,cursorStyles,selBoxStyles,allowedChars,functionToCall) {
88 var createTextBox= true;
89 if (arguments.length == nrArguments) {
90 this.id = id; //the id of the textbox
91 this.parentNode = parentNode; //can be of type string (id) or node reference (svg or g node)
92 this.maxChars = maxChars; //maximum characters allowed
93 this.defaultVal = defaultVal.toString(); //default value to be filled in when textbox is created
94 this.x = x; //left of background rectangle
95 this.y = y; //top of background rectangle
96 this.boxWidth = boxWidth; //background rectangle width
97 this.boxHeight = boxHeight; //background rectangle height
98 this.textYOffset = textYOffset; //the offset of the text element in relation to the upper side of the textbox rectangle
99 this.textStyles = textStyles; //array containing text attributes
100 if (!this.textStyles["font-size"]) {
101 this.textStyles["font-size"] = 15;
103 this.boxStyles = boxStyles; //array containing box styles attributes
104 this.cursorStyles = cursorStyles; //array containing text attributes
105 this.selBoxStyles = selBoxStyles; //array containing box styles attributes
106 //allowedChars contains regular expressions of allowed character ranges
108 if (typeof(allowedChars) == "string") {
109 if (allowedChars.length > 0) {
110 this.RegExp = new RegExp(allowedChars);
115 this.RegExp = undefined;
117 this.functionToCall = functionToCall; //function to be called if textbox looses focus or enter key is pressed
118 this.textboxRect = null; //later holds reference to rect element
119 this.textboxText = null; //later holds reference to text element
120 this.textboxTextContent = null; //later holds reference to content of text element (first child)
121 this.textboxCursor = null; //later holds reference to cursor
122 this.textboxStatus = 0; //status 0 means unitialized, 1 means partially initalized, 2 means fully initialized and ready to remove event listeners again, 5 means new value was set by method .setValue()
123 this.cursorPosition = 0; //position in whole string
124 this.transX = 0; //offset on the left if text string is larger than box
125 this.textVal = this.defaultVal; //this is the current text string of the content
126 this.shiftDown = false; //boolean value that says if shift was pressed
127 this.mouseDown = false; //boolean value that says if mousedown is active
128 this.startSelection = 0; //position of the start of the selection
129 this.startOrigSelection = 0; //original start position of selection
130 this.endSelection = 0; //position of the end of the selection
131 this.selectionRectVisible = false; //indicates if selection rect is visible or not
132 this.svg = null; //later a nested svg that does clipping
133 this.supportsCharGeom = true; //defines if viewer supports geometry calculations on individual characters, such as .getCharAtPosition(SVGPoint)
136 createTextBox = false;
137 alert("Error ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
140 this.timer = new Timer(this); //a Timer instance for calling the functionToCall
141 this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms
142 this.createTextbox(); //method to initialize textbox
145 alert("Could not create textbox with id '"+id+"' due to errors in the constructor parameters");
150 textbox.prototype.createTextbox = function() {
151 var result = this.testParent();
153 //create a textbox parent group
154 this.textboxParent = document.createElementNS(svgNS,"g");
155 this.parentGroup.appendChild(this.textboxParent);
157 //create background rect
158 this.textboxRect = document.createElementNS(svgNS,"rect");
159 this.textboxRect.setAttributeNS(null,"x",this.x);
160 this.textboxRect.setAttributeNS(null,"y",this.y);
161 this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
162 this.textboxRect.setAttributeNS(null,"height",this.boxHeight);
163 this.textboxRect.setAttributeNS(null,"pointer-events","fill");
164 for (var attrib in this.boxStyles) {
165 this.textboxRect.setAttributeNS(null,attrib,this.boxStyles[attrib]);
167 this.textboxParent.appendChild(this.textboxRect);
169 this.svg = document.createElementNS(svgNS,"svg");
170 this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
171 this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
172 this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
173 this.svg.setAttributeNS(null,"height",this.boxHeight * 0.96);
174 this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
175 this.textboxParent.appendChild(this.svg);
177 //create group to hold text, selectionRect and cursor
178 this.textboxTextGroup = document.createElementNS(svgNS,"g");
179 this.svg.appendChild(this.textboxTextGroup);
181 //create text element
182 this.textboxText = document.createElementNS(svgNS,"text");
183 this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
184 this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
185 for (var attrib in this.textStyles) {
186 value = this.textStyles[attrib];
187 if (attrib == "font-size") {
190 this.textboxText.setAttributeNS(null,attrib,value);
192 this.textboxText.setAttributeNS(null,"id",this.id+"Text");
193 if (myMapApp.navigator != "Opera") {
194 this.textboxText.setAttributeNS(null,"pointer-events","none");
196 this.textboxText.setAttributeNS("http://www.w3.org/XML/1998/namespace","space","preserve");
197 //check if defaultVal is longer than maxChars and truncate if necessary
198 if (this.defaultVal.length <= this.maxChars) {
199 this.textboxTextContent = document.createTextNode(this.defaultVal);
200 this.cursorPosition = this.defaultVal.length - 1;
203 alert("the default textbox value is longer than the maximum of allowed characters\nDefault val will be truncated.");
204 this.textVal = this.defaultVal.substr(0,(this.maxChars - 1));
205 this.textboxTextContent = document.createTextNode(this.textVal);
206 this.cursorPosition = this.maxChars - 1;
208 this.textboxText.appendChild(this.textboxTextContent);
209 this.textboxTextGroup.appendChild(this.textboxText);
211 //create selection rectangle
212 this.selectionRect = document.createElementNS(svgNS,"rect");
213 this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
214 this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
215 this.selectionRect.setAttributeNS(null,"width",(this.textStyles["font-size"] * 2));
216 this.selectionRect.setAttributeNS(null,"height",this.textStyles["font-size"] * 1.1);
217 for (var attrib in this.selBoxStyles) {
218 this.selectionRect.setAttributeNS(null,attrib,this.selBoxStyles[attrib]);
220 this.selectionRect.setAttributeNS(null,"display","none");
221 this.textboxTextGroup.appendChild(this.selectionRect);
223 //create cursor element
224 this.textboxCursor = document.createElementNS(svgNS,"line");
225 this.textboxCursor.setAttributeNS(null,"x1",this.x);
226 this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
227 this.textboxCursor.setAttributeNS(null,"x2",this.x);
228 this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
229 for (var attrib in this.cursorStyles) {
230 this.textboxCursor.setAttributeNS(null,attrib,this.cursorStyles[attrib]);
232 this.textboxCursor.setAttributeNS(null,"id",this.id+"Cursor");
233 this.textboxCursor.setAttributeNS(null,"visibility","hidden");
234 this.textboxTextGroup.appendChild(this.textboxCursor);
236 // add event listeners to the textbox group
237 this.textboxParent.addEventListener("mousedown",this,false);
238 this.textboxParent.addEventListener("mousemove",this,false);
239 this.textboxParent.addEventListener("mouseup",this,false);
240 this.textboxParent.setAttributeNS(null,"cursor","text");
242 //test if the svgviewer supports getting geometries of individual characters
243 this.timer.setTimeout("testSupportsChar",this.timerMs);
246 alert("could not create or reference 'parentNode' of textbox with id '"+this.id+"'");
250 textbox.prototype.testSupportsChar = function() {
251 //determine whether viewer is capable of charGeom functions
253 //temporarily create a space to test if getStartPosition is available
254 if (this.textVal.length == 0) {
256 this.textboxTextContent.nodeValue = " ";
259 var dummy = this.textboxText.getStartPositionOfChar(0).x;
262 this.supportsCharGeom = false;
265 this.textboxTextContent.nodeValue = "";
269 //test if window group exists or create a new group at the end of the file
270 textbox.prototype.testParent = function() {
271 //test if of type object
272 var nodeValid = false;
273 if (typeof(this.parentNode) == "object") {
274 if (this.parentNode.nodeName == "svg" || this.parentNode.nodeName == "g" || this.parentNode.nodeName == "svg:svg" || this.parentNode.nodeName == "svg:g") {
275 this.parentGroup = this.parentNode;
279 else if (typeof(this.parentNode) == "string") {
280 //first test if textbox group exists
281 if (!document.getElementById(this.parentNode)) {
282 this.parentGroup = document.createElementNS(svgNS,"g");
283 this.parentGroup.setAttributeNS(null,"id",this.parentNode);
284 document.documentElement.appendChild(this.parentGroup);
288 this.parentGroup = document.getElementById(this.parentNode);
295 //remove all textbox elements
296 textbox.prototype.removeTextbox = function() {
297 this.parentGroup.removeChild(this.textboxParent);
300 //event handler functions
301 textbox.prototype.handleEvent = function(evt) {
302 if (evt.type == "mousedown") {
303 //this case is when the user mousedowns outside the textbox; in this case the textbox should behave like the user
304 //pressed the enter key
305 if ((evt.currentTarget.nodeName == "svg" || evt.currentTarget.nodeName == "svg:svg") && this.textboxStatus == 2) {
309 //this is for preparing the textbox with first mousedown and to reposition cursor with each subsequent mousedowns
310 if (evt.currentTarget.nodeName == "g" || evt.currentTarget.nodeName == "svg:g") {
311 this.calcCursorPosFromMouseEvt(evt);
312 // set event listeners, this is only done on first mousedown in the textbox
313 if (this.textboxStatus == 0) {
314 if (myMapApp.navigator == "Adobe") {
315 document.documentElement.addEventListener("keydown",this,false);
317 document.documentElement.addEventListener("keypress",this,false);
318 document.documentElement.addEventListener("mousedown",this,false);
319 document.documentElement.addEventListener("mouseup",this,false);
320 document.documentElement.addEventListener("mousemove",this,false);
321 // set textbox status
322 this.textboxStatus = 1;
323 // set cursor visibility
324 this.textboxCursor.setAttributeNS(null,"visibility","visible");
327 evt.stopPropagation();
330 //start text selection
331 this.startOrigSelection = this.cursorPosition + 1;
332 this.startSelection = this.cursorPosition + 1;
333 this.endSelection = this.cursorPosition + 2;
334 //remove previous selections
335 this.selectionRect.setAttributeNS(null,"display","none");
336 this.selectionRectVisible = false;
337 //set status of shiftDown and mouseDown
338 this.shiftDown = true;
339 this.mouseDown = true;
341 //this mouseup should be received from background rectangle (received via document element)
343 this.textboxStatus = 2;
347 if (evt.type == "mousemove") {
348 if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown && this.supportsCharGeom) {
349 this.calcCursorPosFromMouseEvt(evt);
351 if (this.cursorPosition + 1 != this.startOrigSelection) {
352 if (this.cursorPosition + 1 < this.startOrigSelection) {
353 this.endSelection = this.startOrigSelection;
354 this.startSelection = this.cursorPosition + 1;
357 this.startSelection = this.startOrigSelection;
358 this.endSelection = this.cursorPosition + 1;
360 this.selectionRect.setAttributeNS(null,"display","inherit");
361 this.selectionRectVisible = true;
362 var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
363 this.selectionRect.setAttributeNS(null,"x",rectX);
364 this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
365 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
366 //if cursor runs out on the right, scroll to the right
367 if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
368 this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
369 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
371 //if cursor runs out on the left, scroll to the left
372 if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
373 this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
374 if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
377 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
382 if (evt.type == "mouseup") {
383 if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown) {
384 this.mouseDown = false;
387 if (evt.type == "keypress") {
389 var charCode = evt.keyCode;
392 var charCode = evt.charCode;
394 var keyCode = parseInt(charCode);
395 var charCode = undefined;
396 this.changed = false; //this tracks if the text was actually changed (if the content was changed)
397 //alert("keyCode="+evt.keyCode+", charCode="+evt.charCode+", shiftKey"+evt.shiftKey);
399 if (myMapApp.navigator != "Adobe") {
400 //note that Adobe SVG enters this method through the keydown event
401 this.specialCharacters(evt);
404 if (myMapApp.navigator == "Opera") {
405 if (evt.keyCode > 31 && evt.keyCode != 35 && evt.keyCode != 36 && evt.keyCode != 37 && evt.keyCode != 39 && evt.keyCode != 46) {
406 evt.charCode = evt.keyCode;
410 //all real characters
411 if (keyCode > 31 && keyCode != 127 && keyCode < 65535 && evt.charCode && evt.charCode < 65535) {
412 var textChanged = false;
413 var keychar = String.fromCharCode(keyCode);
416 result = keychar.search(this.RegExp);
419 if (this.shiftDown && this.selectionRectVisible) {
420 var tempText = this.textVal.substring(0,this.startSelection) + keychar + this.textVal.substring(this.endSelection,this.textVal.length);
421 this.textVal = tempText;
422 this.cursorPosition = this.startSelection - 1;
426 else if (this.textVal.length < this.maxChars) {
427 if (this.cursorPosition == this.textVal.length -1) {
428 this.textVal += keychar; // append new input character
431 var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + keychar + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
432 this.textVal = tempText;
436 if (this.textVal.length < this.maxChars) {
437 this.cursorPosition++;
441 if (this.cursorPosition < this.textVal.length) {
442 this.cursorPosition++;
445 this.cursorPosition = this.textVal.length - 1;
449 //make sure that the selections and shift key are resetted
450 this.startSelection = this.cursorPosition;
451 this.endSelection = this.cursorPosition;
452 this.shiftDown = false;
454 this.textboxTextContent.nodeValue=this.textVal;
456 //update cursor position
458 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
459 if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
460 this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - (cursorX + this.transX) + this.transX;
461 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
466 //fire function if text changed
468 this.timer.setTimeout("fireFunction",this.timerMs);
470 //suppress unwanted browser shortcuts. e.g. in Opera or Mozilla
471 evt.preventDefault();
473 //this part is only because the Adobe viewer doesn't return certain keys "onkeypress"
474 if (evt.type == "keydown") {
475 this.specialCharacters(evt);
479 textbox.prototype.specialCharacters = function(evt) {
481 var charCode = evt.keyCode;
484 var charCode = evt.charCode;
486 var keyCode = parseInt(charCode);
487 var charCode = undefined;
491 //only do it if there is still text and cursor is not at start position
492 if (this.textVal.length > 0 && this.cursorPosition > -2) {
493 //first deal with text content, delete chars according to cursor position
494 if (this.shiftDown && this.selectionRectVisible) {
495 var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
496 this.textVal = tempText;
497 this.cursorPosition = this.startSelection - 1;
501 if (this.cursorPosition == this.textVal.length - 1) {
502 //cursor is at the end of textVal
503 this.textVal=this.textVal.substring(0,this.textVal.length-1);
506 //cursor is in between
507 var tempText = this.textVal.substring(0,(this.cursorPosition)) + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
508 this.textVal = tempText;
510 //decrease cursor position
511 if (this.cursorPosition > -1) {
512 this.cursorPosition--;
515 this.textboxTextContent.nodeValue=this.textVal;
517 if (this.cursorPosition > 0) {
518 //retransform text element when cursor is at the left side of the box
519 if (this.supportsCharGeom) {
520 var cursorX = this.textboxText.getStartPositionOfChar(this.cursorPosition).x;
523 var bbox = this.textboxText.getBBox();
524 var cursorX = bbox.x + bbox.width;
526 if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
527 this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
528 if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
531 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
538 else if (keyCode == 10 || keyCode == 13) { // press return (enter)
542 else if (keyCode == 35 && !(charCode)) {
544 if (this.shiftDown == false) {
545 this.startOrigSelection = this.cursorPosition + 1;
546 this.startSelection = this.cursorPosition + 1;
547 this.shiftDown = true;
550 this.cursorPosition = this.textVal.length - 1;
552 //if text string is too long
553 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
554 if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
555 this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
556 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
560 if (this.shiftDown == false) {
561 this.startOrigSelection = this.cursorPosition;
562 this.startSelection = this.cursorPosition;
563 this.shiftDown = true;
565 this.endSelection = this.cursorPosition + 1;
566 this.selectionRect.setAttributeNS(null,"display","inherit");
567 this.selectionRectVisible = true;
568 if (this.supportsCharGeom) {
569 var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
570 var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
573 var bbox = this.textboxText.getBBox();
574 var rectX = this.x + this.textStyles["font-size"] / 3;
575 var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
577 this.selectionRect.setAttributeNS(null,"x",rectX);
578 this.selectionRect.setAttributeNS(null,"width",width);
580 if (this.shiftDown && evt.shiftKey == false) {
585 else if (keyCode == 36 && !(charCode)) {
587 if (this.shiftDown == false) {
588 this.startOrigSelection = this.cursorPosition + 1;
589 this.startSelection = this.cursorPosition + 1;
590 this.shiftDown = true;
593 this.cursorPosition = -1;
594 this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
595 this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
599 if (this.shiftDown == false) {
600 this.startOrigSelection = this.cursorPosition;
601 this.startSelection = this.cursorPosition;
602 this.shiftDown = true;
604 this.endSelection = this.startSelection;
605 this.startSelection = 0;
606 this.selectionRect.setAttributeNS(null,"display","inherit");
607 this.selectionRectVisible = true;
608 if (this.supportsCharGeom) {
609 var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
610 var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
613 var bbox = this.textboxText.getBBox();
614 var rectX = this.x + this.textStyles["font-size"] / 3;
615 var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
617 this.selectionRect.setAttributeNS(null,"x",rectX);
618 this.selectionRect.setAttributeNS(null,"width",width);
620 if (this.shiftDown && evt.shiftKey == false) {
625 else if (keyCode == 37 && !(charCode)) {
626 if (this.cursorPosition > -1) {
627 this.cursorPosition--;
629 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
630 if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
631 this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
632 if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
635 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
637 //do selection if shift key is pressed
638 if (evt.shiftKey && this.supportsCharGeom) {
639 if (this.shiftDown == false) {
640 this.startOrigSelection = this.cursorPosition + 2;
641 this.startSelection = this.cursorPosition + 2;
642 this.shiftDown = true;
644 this.endSelection = this.startOrigSelection;
645 this.startSelection = this.cursorPosition + 1;
646 this.selectionRect.setAttributeNS(null,"display","inherit");
647 this.selectionRectVisible = true;
648 var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
649 this.selectionRect.setAttributeNS(null,"x",rectX);
650 this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
653 if (this.shiftDown) {
660 else if (keyCode == 39 && !(charCode)) {
661 if (this.cursorPosition < this.textVal.length - 1) {
662 this.cursorPosition++;
664 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
665 if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
666 this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
667 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
669 //do selection if shift key is pressed
670 if (evt.shiftKey && this.supportsCharGeom) {
671 if (this.shiftDown == false) {
672 this.startOrigSelection = this.cursorPosition;
673 this.startSelection = this.cursorPosition;
674 this.shiftDown = true;
676 this.endSelection = this.cursorPosition + 1;
677 this.selectionRect.setAttributeNS(null,"display","inherit");
678 this.selectionRectVisible = true;
679 var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
680 this.selectionRect.setAttributeNS(null,"x",rectX);
681 this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
684 if (this.shiftDown) {
691 else if ((keyCode == 127 || keyCode == 12 || keyCode == 46) && !(charCode)) {
692 if ((this.textVal.length > 0) && (this.cursorPosition < (this.textVal.length))) {
694 if (this.shiftDown && evt.shiftKey == false && this.startSelection < this.textVal.length) {
695 //we need to delete selected text
696 var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
697 this.cursorPosition = this.startSelection - 1;
702 if (this.cursorPosition < (this.textVal.length - 1)) {
703 //we need to delete the next character, if cursor is not at the end of the textstring
704 var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + this.textVal.substring((this.cursorPosition + 2),(this.textVal.length));
709 if(tempText != null) {
710 this.textVal = tempText;
711 this.textboxTextContent.nodeValue=this.textVal;
717 //fire function if text changed
718 if (myMapApp.navigator == "Adobe") {
720 this.timer.setTimeout("fireFunction",this.timerMs);
725 textbox.prototype.setCursorPos = function() {
726 //cursor is not at first position
727 if (this.cursorPosition > -1) {
728 if (this.supportsCharGeom) {
729 if (this.textVal.length > 0) {
730 var cursorPos = this.textboxText.getEndPositionOfChar(this.cursorPosition).x;
733 var cursorPos = (this.x + this.textStyles["font-size"] / 3);
735 this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
736 this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
739 //case MozillaSVG 1.5 or other SVG viewers not implementing .getEndPositionOfChar
740 var bbox = this.textboxText.getBBox();
741 this.textboxCursor.setAttributeNS(null,"x1",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
742 this.textboxCursor.setAttributeNS(null,"x2",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
746 //cursor is at first position
747 //reset transformations
748 this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
749 this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
751 if (this.supportsCharGeom) {
752 if (this.textboxTextContent.length > 0) {
753 var cursorPos = this.textboxText.getStartPositionOfChar(0).x;
756 var cursorPos = this.x + this.textStyles["font-size"] / 3;
760 var cursorPos = this.x + this.textStyles["font-size"] / 3;
762 this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
763 this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
767 textbox.prototype.fireFunction = function() {
768 var changeType = "change";
769 if (this.textboxStatus == 0) {
770 changeType = "release";
772 if (this.textboxStatus == 5) {
773 this.textboxStatus = 0;
776 if (typeof(this.functionToCall) == "function") {
777 this.functionToCall(this.id,this.textVal,changeType);
779 if (typeof(this.functionToCall) == "object") {
780 this.functionToCall.textboxChanged(this.id,this.textVal,changeType);
782 if (typeof(this.functionToCall) == undefined) {
787 textbox.prototype.getValue = function() {
791 textbox.prototype.setValue = function(value,fireFunction) {
792 this.textVal = value.toString();
793 this.textboxTextContent.nodeValue=this.textVal;
794 //set the cursor to beginning and remove previous transforms
795 this.cursorPosition = -1;
797 if (fireFunction == true) {
798 this.textboxStatus = 5; //5 means is set by setValue
803 textbox.prototype.release = function() {
804 // set textbox status
805 this.textboxStatus = 0;
806 // remove event listeners
807 document.documentElement.removeEventListener("keypress",this,false);
808 if (myMapApp.navigator == "Adobe") {
809 document.documentElement.removeEventListener("keydown",this,false);
811 document.documentElement.removeEventListener("mousedown",this,false);
812 document.documentElement.removeEventListener("mousemove",this,false);
813 document.documentElement.removeEventListener("mouseup",this,false);
814 //set cursor and text selection to invisible
815 this.textboxCursor.setAttributeNS(null,"visibility","hidden");
817 this.timer.setTimeout("fireFunction",this.timerMs);
820 textbox.prototype.releaseShift = function() {
821 this.selectionRect.setAttributeNS(null,"display","none");
822 this.selectionRectVisible = false;
823 this.shiftDown = false;
826 textbox.prototype.calcCursorPosFromMouseEvt = function(evt) {
827 //determine cursor position of mouse event
828 var myCoords = myMapApp.calcCoord(evt,this.textboxText);
829 //create an SVG Point object
830 var mySVGPoint = document.documentElement.createSVGPoint();
831 mySVGPoint.x = myCoords.x;
832 mySVGPoint.y = myCoords.y;
833 //set new cursor position
834 if (this.textboxTextContent.length > 0) {
835 if (this.supportsCharGeom) {
836 //for regular SVG viewers that support .getCharNumAtPosition
837 this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
838 if (this.cursorPosition > this.textVal.length - 1) {
839 this.cursorPosition = this.textVal.length - 1;
841 //in this case the user did not correctly touch the text element
842 if (this.cursorPosition == -1) {
843 //first check if we can fix the position by moving the y-coordinate
844 mySVGPoint.y = (this.textboxText.getBBox().y + this.textStyles["font-size"] * 0.5);
845 this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
846 //check if cursor is to the right of the end of the text
847 if (this.cursorPosition == -1) {
848 if (mySVGPoint.x > (this.textboxText.getBBox().x + this.textboxText.getBBox().width)) {
849 this.cursorPosition = this.textVal.length - 1;
855 //workaround for firefox 1.5/2.0 and other viewers not supporting .getCharNumAtPosition
856 var bbox = this.textboxText.getBBox();
857 var diffLeft = Math.abs(mySVGPoint.x - bbox.x);
858 var diffRight = Math.abs(mySVGPoint.x - (bbox.x + bbox.width));
859 if (diffLeft < diffRight) {
860 this.cursorPosition = -1;
863 this.cursorPosition = this.textVal.length - 1;
868 //in case the text is empty
869 this.cursorPosition = -1;
873 textbox.prototype.moveTo = function(moveX,moveY) {
877 this.textboxRect.setAttributeNS(null,"x",this.x);
878 this.textboxRect.setAttributeNS(null,"y",this.y);
879 //reposition svg element
880 this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
881 this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
882 this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
883 //reposition text element
884 this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
885 this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
886 //reposition selection element
887 this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
888 this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
890 this.textboxCursor.setAttributeNS(null,"x1",this.x);
891 this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
892 this.textboxCursor.setAttributeNS(null,"x2",this.x);
893 this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
894 //set the cursor to beginning and remove previous transforms
895 this.cursorPosition = -1;
899 textbox.prototype.resize = function(newWidth) {
900 this.boxWidth = newWidth;
901 //resize textbox rectangle
902 this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
904 this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
905 this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
906 //set the cursor to beginning and remove previous transforms
907 this.cursorPosition = -1;