2 Scripts to create interactive selectionLists/Dropdown boxes in SVG using ECMA script
3 Copyright (C) <2006> <Andreas Neumann>
4 Version 1.4, 2006-07-18
5 neumann@karto.baug.ethz.ch
7 http://www.carto.net/neumann/
10 * Initial code was taken from Michel Hirtzler --> pilat.free.fr (thanks!)
11 * Thanks also to many people of svgdevelopers@yahoogroups.com
12 * Suggestions by Bruce Rindahl
16 Documentation: http://www.carto.net/papers/svg/gui/selectionlist/
27 allow undefined values in callback function for selectionlists that need not react on changes (suggestion by Bruce Rindahl)
28 allow selectionlist to be placed in a transformed group
29 now also works in MozillaSVG (due to changes in mapApp.js)
32 removed "string" option in functionToCall (not important any more). function and object should be good enough
33 timer object from http://www.codingforums.com/showthread.php?s=&threadid=10531 is now used; attention: you need to link this script as well!
34 due to the new timer object we can now also mouse down and scroll more than one entry until we mouseup again, previously one had to click for forwarding each individual element
37 removed style attributes and replaced them with individual XML attributes
38 initial tests on Opera 9 TP1, it seems to work except for the scrolling - due to a mouse-move event bug
41 upgraded the documentation and fixed documentation errors
44 added option to open selectionList to the top of the original box, this is useful for layouts where the selectionList is at the bottom of a page
45 added option to always put the selectionList on the top of it's parent group after activation in order to force the geometry to be on the top of all other geometry within the same group
46 improved background rectangle: these are now two rectangles: one on the bottom for the fill, one on the top for stroking
47 tested with Opera 9 TP2, works now fine, except keyboard input
50 removed dependency on replaceSpecialChars (in file helper_functions.js) as this is not really needed in a true utf8 environment.
51 improved key event handling. People can now enter more than one key (within one second for each consequent key) and the list will jump to entries that match the keys; key events now also work in opera 9+ - however they sometimes collide with keyboard shortcuts.
54 changed parameters of constructor (styling system): now an array of literals containing presentation attributes. Not hardcoded in selectionList.js anymore. Added check for number of arguments.
57 added additional constructor parameter "parentNode", changed groupId to id, added methods .resize(width) and .moveTo(x,y)
58 improved scrolling (now also possible outside of the scrollbar, once the mouse is down)
63 This ECMA script library is free software; you can redistribute it and/or
64 modify it under the terms of the GNU Lesser General Public
65 License as published by the Free Software Foundation; either
66 version 2.1 of the License, or (at your option) any later version.
68 This library is distributed in the hope that it will be useful,
69 but WITHOUT ANY WARRANTY; without even the implied warranty of
70 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
71 Lesser General Public License for more details.
73 You should have received a copy of the GNU Lesser General Public
74 License along with this library (lesser_gpl.txt); if not, write to the Free Software
75 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
79 original document site: http://www.carto.net/papers/svg/gui/selectionlist/
80 Please contact the author in case you want to use code or ideas commercially.
81 If you use this code, please include this copyright header, the included full
82 LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
83 (http://www.gnu.org/copyleft/lesser.txt)
85 -------------------------------
87 If you wish, you can modify parameters (such as "look and feel") in the function "selectionList" (constructor).
88 You can adapt colors, fonts, cellpaddings, etc.
90 -------------------------------
92 Please report bugs and send improvements to neumann@karto.baug.ethz.ch
93 If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/selectionlist/)
94 somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
98 function selectionList(id,parentNode,elementsArray,width,xOffset,yOffset,cellHeight,textPadding,heightNrElements,textStyles,boxStyles,scrollbarStyles,smallrectStyles,highlightStyles,triangleStyles,preSelect,openAbove,putOnTopOfParent,functionToCall) {
100 var createSelbox= true;
101 if (arguments.length == nrArguments) {
102 //get constructor variables
103 this.id = id; //the id or node reference of the group where the
104 this.parentNode = parentNode; //can be of type string (id) or node reference (svg or g node)
105 this.elementsArray = elementsArray; //the array containing the text elements of the selectionList
106 this.width = width; //width of the selectionList in viewBox coordinates
107 this.xOffset = xOffset; //upper left corner (xOffset) of selection list in viewBox coordinates
108 this.yOffset = yOffset; //upper left corner (yOffset) of selection list in viewBox coordinates
109 this.cellHeight = cellHeight; //cellHeight in viewBox coordinates
110 this.heightNrElements = heightNrElements; //nr of elements for unfolded selectionList (number, integer)
111 this.textStyles = textStyles; //array of literals containing presentation attributes for text
112 if (!this.textStyles["font-size"]) {
113 this.textStyles["font-size"] = "11";
115 this.boxStyles = boxStyles; //array of literals containing presentation attributes for the sel box
116 if (!this.boxStyles["fill"]) {
117 this.boxStyles["fill"] = "white"; //this fill value is also used for boxes under indiv. text elements (unselected status)
119 if (!this.boxStyles["stroke"]) {
120 this.boxStyles["stroke"] = "dimgray";
122 if (!this.boxStyles["stroke-width"]) {
123 this.boxStyles["stroke-width"] = 1;
125 this.scrollbarStyles = scrollbarStyles; //array of literals containing presentation attributes for the scrollbar
126 this.smallrectStyles = smallrectStyles; //array of literals containing presentation attributes for the small rectangle next to selectbox
127 this.highlightStyles = highlightStyles; //array of literals containing presentation attributes for the highlighted rectangles
128 this.triangleStyles = triangleStyles; //array of literals containing presentation attributes for the triangle in the selectionList, also applies to fill of scroller
129 this.preSelect = preSelect;
130 this.openAbove = openAbove;
131 this.putOnTopOfParent = putOnTopOfParent;
132 this.functionToCall = functionToCall;
134 //calculate other values
135 this.triangleFourth = Math.round(this.cellHeight / 4); //this is only used internally
136 this.textPadding = textPadding; //this is relative to the left of the cell
137 this.textPaddingVertical = this.cellHeight - (this.cellHeight - this.textStyles["font-size"]) * 0.7; //this is relative to the top of the cell
138 this.scrollerMinHeight = this.cellHeight * 0.5; //minimal height of the scroller rect
141 this.scrollBar = undefined; //later a reference to the scrollbar rectangle
144 this.activeSelection = this.preSelect; //holds currently selected index value
145 this.listOpen = false; //status folded=false, open=true - previously been sLselectionVisible
146 this.curLowerIndex = this.preSelect; //this value is adapted if the user moves scrollbar
147 this.scrollStep = 0; //y-value to go for one element
148 this.scrollerHeight = 0; //height of dragable scroller bar
149 this.scrollActive = false; //determines if scrolling per up/down button is active
150 this.panY = false; //stores the y value of event
151 this.scrollCumulus = 0; //if value is less then a scrollstep we need to accumulate scroll values
152 this.scrollDir = ""; //later holds "up" and "down"
153 this.exists = true; //true means it exists, gets value false if method "removeSelectionList" is called
154 this.pressedKeys = ""; //stores key events (pressed char values)
155 this.keyTimer = undefined; //timer for resetting character strings after a given time period
158 createSelbox = false;
159 alert("Error ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
163 this.timer = new Timer(this); //a Timer instance for calling the functionToCall
164 this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms
165 //createSelectionList
166 this.createSelectionList();
169 alert("Could not create selectionlist with id '"+id+"' due to errors in the constructor parameters");
173 selectionList.prototype.createSelectionList = function() {
174 var result = this.testParent();
176 //initial Rect, visible at the beginning
177 var node = document.createElementNS(svgNS,"rect");
178 node.setAttributeNS(null,"x",this.xOffset);
179 node.setAttributeNS(null,"y",this.yOffset);
180 node.setAttributeNS(null,"width",this.width);
181 node.setAttributeNS(null,"height",this.cellHeight);
182 for (var attrib in this.boxStyles) {
183 node.setAttributeNS(null,attrib,this.boxStyles[attrib]);
185 node.setAttributeNS(null,"id","selRect_"+this.id);
186 node.addEventListener("click", this, false);
187 this.parentGroup.appendChild(node);
189 this.selectedText = document.createElementNS(svgNS,"text");
190 this.selectedText.setAttributeNS(null,"x",this.xOffset + this.textPadding);
191 this.selectedText.setAttributeNS(null,"y",this.yOffset + this.textPaddingVertical);
193 for (var attrib in this.textStyles) {
194 value = this.textStyles[attrib];
195 if (attrib == "font-size") {
198 this.selectedText.setAttributeNS(null,attrib,value);
200 this.selectedText.setAttributeNS(null,"pointer-events","none");
201 var selectionText = document.createTextNode(this.elementsArray[this.activeSelection]);
202 this.selectedText.appendChild(selectionText);
203 this.parentGroup.appendChild(this.selectedText);
204 //small Rectangle to the right, onclick unfolds the selectionList
205 var node = document.createElementNS(svgNS,"rect");
206 node.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
207 node.setAttributeNS(null,"y",this.yOffset);
208 node.setAttributeNS(null,"width",this.cellHeight);
209 node.setAttributeNS(null,"height",this.cellHeight);
210 for (var attrib in this.smallrectStyles) {
211 node.setAttributeNS(null,attrib,this.smallrectStyles[attrib]);
213 node.setAttributeNS(null,"id","selPulldown_"+this.id);
214 node.addEventListener("click", this, false);
215 this.parentGroup.appendChild(node);
217 var node=document.createElementNS(svgNS,"path");
218 var myTrianglePath = "M"+(this.xOffset + this.width - 3 * this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - 2 * this.triangleFourth)+" "+(this.yOffset + 3 * this.triangleFourth)+" Z";
219 node.setAttributeNS(null,"d",myTrianglePath);
220 for (var attrib in this.triangleStyles) {
221 node.setAttributeNS(null,attrib,this.triangleStyles[attrib]);
223 node.setAttributeNS(null,"id","selTriangle_"+this.id);
224 node.setAttributeNS(null,"pointer-events","none");
225 this.parentGroup.appendChild(node);
226 //rectangle below unfolded selectBox, at begin invisible
227 this.rectBelowBox = document.createElementNS(svgNS,"rect");
228 this.rectBelowBox.setAttributeNS(null,"x",this.xOffset);
229 this.rectBelowBox.setAttributeNS(null,"y",this.yOffset + this.cellHeight);
230 this.rectBelowBox.setAttributeNS(null,"width",this.width - this.cellHeight);
231 this.rectBelowBox.setAttributeNS(null,"height",0);
232 this.rectBelowBox.setAttributeNS(null,"fill",this.boxStyles["fill"]);
233 this.rectBelowBox.setAttributeNS(null,"display","none");
234 this.parentGroup.appendChild(this.rectBelowBox);
235 //rectangle below unfolded selectBox, at begin invisible
236 this.rectAboveBox = document.createElementNS(svgNS,"rect");
237 this.rectAboveBox.setAttributeNS(null,"x",this.xOffset);
238 this.rectAboveBox.setAttributeNS(null,"y",this.yOffset + this.cellHeight);
239 this.rectAboveBox.setAttributeNS(null,"width",this.width - this.cellHeight);
240 this.rectAboveBox.setAttributeNS(null,"height",0);
241 this.rectAboveBox.setAttributeNS(null,"fill","none");
242 this.rectAboveBox.setAttributeNS(null,"stroke",this.boxStyles["stroke"]);
243 this.rectAboveBox.setAttributeNS(null,"stroke-width",this.boxStyles["stroke-width"]);
244 this.rectAboveBox.setAttributeNS(null,"display","none");
245 this.parentGroup.appendChild(this.rectAboveBox);
248 alert("could not create or reference 'parentNode' of selectionList with id '"+this.id+"'");
252 //test if parent group exists or create a new group at the end of the file
253 selectionList.prototype.testParent = function() {
254 //test if of type object
255 var nodeValid = false;
256 if (typeof(this.parentNode) == "object") {
257 if (this.parentNode.nodeName == "svg" || this.parentNode.nodeName == "g") {
258 this.parentGroup = this.parentNode;
262 else if (typeof(this.parentNode) == "string") {
263 //first test if Windows group exists
264 if (!document.getElementById(this.parentNode)) {
265 this.parentGroup = document.createElementNS(svgNS,"g");
266 this.parentGroup.setAttributeNS(null,"id",this.parentNode);
267 document.documentElement.appendChild(this.parentGroup);
271 this.parentGroup = document.getElementById(this.parentNode);
278 selectionList.prototype.handleEvent = function(evt) {
279 var el = evt.currentTarget;
280 var callerId = el.getAttributeNS(null,"id");
281 var myRegExp = new RegExp(this.id);
282 if (evt.type == "click") {
283 if (myRegExp.test(callerId)) {
284 if (callerId.match(/\bselPulldown|\bselRect/)) {
285 if (this.listOpen == false) {
286 if (this.putOnTopOfParent) {
287 //put list on top of the parent group (bottom in the DOM tree)
288 this.parentGroup.parentNode.appendChild(this.parentGroup);
291 this.listOpen = true;
295 this.listOpen = false;
296 evt.stopPropagation();
299 if (callerId.match(/\bselHighlightSelection_/)) {
300 this.selectAndClose(evt);
301 evt.stopPropagation();
305 //case that the event comes from the documentRoot element
306 //but not from the selectionList itself
307 if (!myRegExp.test(evt.target.getAttributeNS(null,"id"))) {
309 this.listOpen = false;
313 if (evt.type == "mouseover") {
314 if (myRegExp.test(callerId)) {
315 if (callerId.match(/\bselHighlightSelection_/)) {
316 for (var attrib in this.highlightStyles) {
317 el.setAttributeNS(null,attrib,this.highlightStyles[attrib]);
322 if (evt.type == "mouseout") {
323 if (myRegExp.test(callerId)) {
324 if (callerId.match(/\bselHighlightSelection_/) ){
325 el.setAttributeNS(null,"fill",this.boxStyles["fill"]);
327 if (callerId.match(/\bselScrollbarRect_/)) {
328 this.scrollBarMove(evt);
332 if (evt.type == "mousedown") {
333 if (myRegExp.test(callerId)) {
334 if (callerId.match(/\bselScrollUpperRect_|\bselScrollLowerRect/)) {
335 this.scrollDir = "down";
336 this.scrollActive = true;
337 if (callerId.match(/UpperRect/)) {
338 this.scrollDir = "up";
340 this.scroll(this.scrollDir,1,true); //1 is one element to scroll, true says that we should move scrollbar
341 this.timer.setTimeout("scrollPerButton",400)
343 if (callerId.match(/\bselScrollbarRect_/)) {
344 //add events to document.documentElement
345 document.documentElement.addEventListener("mousemove",this,false);
346 document.documentElement.addEventListener("mouseup",this,false);
347 this.scrollBarMove(evt);
351 if (evt.type == "mouseup") {
352 if (myRegExp.test(callerId)) {
353 if (callerId.match(/\bselScrollUpperRect_|\bselScrollLowerRect/)) {
354 this.scrollActive = false;
357 if (el.nodeName == "svg" || el.nodeName == "svg:svg") {
358 document.documentElement.removeEventListener("mousemove",this,false);
359 document.documentElement.removeEventListener("mouseup",this,false);
360 this.scrollBarMove(evt);
363 if (evt.type == "mousemove") {
364 if (el.nodeName == "svg" || el.nodeName == "svg:svg") {
365 this.scrollBarMove(evt);
367 } //add keypress event
368 if (evt.type == "keypress") {
370 var character = String.fromCharCode(evt.charCode).toLowerCase();
373 //case opera and others
374 var character = String.fromCharCode(evt.keyCode).toLowerCase();
375 if (evt.keyCode == 0 || evt.keyCode == 16 || evt.keyCode == 17 || evt.keyCode == 18) {
381 this.pressedKeys += character;
383 if (character.length > 0) {
385 this.timer.clearTimeout(this.keyTimer);
387 this.keyTimer = this.timer.setTimeout("resetPressedKeys",1000);
393 selectionList.prototype.resetPressedKeys = function() {
394 this.keyTimer = undefined;
395 this.pressedKeys = "";
398 //this function is called when selectionList is unfolded
399 selectionList.prototype.unfoldList = function() {
400 //create temporary group to hold temp geometry
401 if (!this.dynamicTextGroup) {
402 this.dynamicTextGroup = document.createElementNS(svgNS,"g");
403 this.parentGroup.insertBefore(this.dynamicTextGroup,this.rectAboveBox);
405 var nrSelectionsOrig = this.elementsArray.length;
406 if (this.heightNrElements < nrSelectionsOrig) {
407 nrSelections = this.heightNrElements;
410 nrSelections = nrSelectionsOrig;
412 var selectionHeight = this.cellHeight * nrSelections;
413 //build textElements from array
414 //hold currentSelection Index to unroll list at new offset
415 if ((nrSelectionsOrig - this.activeSelection) >= nrSelections) {
416 this.curLowerIndex = this.activeSelection;
419 this.curLowerIndex = nrSelectionsOrig - nrSelections;
421 //following is a temporary YOffset that serves to distinquish between selectionLists that open above or below
422 var YOffset = this.yOffset + this.cellHeight;
423 if (this.openAbove) {
424 YOffset = this.yOffset - this.heightNrElements * this.cellHeight;
426 //adopt background rectangle
427 this.rectBelowBox.setAttributeNS(null,"height",selectionHeight);
428 this.rectBelowBox.setAttributeNS(null,"display","inherit");
429 this.rectBelowBox.setAttributeNS(null,"y",YOffset);
430 this.rectAboveBox.setAttributeNS(null,"height",selectionHeight);
431 this.rectAboveBox.setAttributeNS(null,"display","inherit");
432 this.rectAboveBox.setAttributeNS(null,"y",YOffset);
434 for (var i=0;i<nrSelections;i++) {
435 //add rectangles to capture events
436 var node = document.createElementNS(svgNS,"rect");
437 node.setAttributeNS(null,"x",this.xOffset + this.textPadding / 2);
438 node.setAttributeNS(null,"y",YOffset + this.cellHeight * i);
439 node.setAttributeNS(null,"width",this.width - this.cellHeight - this.textPadding);
440 node.setAttributeNS(null,"height",this.cellHeight);
441 node.setAttributeNS(null,"fill",this.boxStyles["fill"]);
442 node.setAttributeNS(null,"id","selHighlightSelection_" + this.id + "_" + (i + this.curLowerIndex));
444 node.addEventListener("mouseover", this, false);
445 node.addEventListener("mouseout", this, false);
446 node.addEventListener("click", this, false);
447 node.addEventListener("keypress", this, false);
448 this.dynamicTextGroup.appendChild(node);
450 var node = document.createElementNS(svgNS,"text");
451 node.setAttributeNS(null,"id","selTexts_" + this.id + "_" + (i + this.curLowerIndex));
452 node.setAttributeNS(null,"x",this.xOffset + this.textPadding);
453 node.setAttributeNS(null,"y",YOffset + this.textPaddingVertical + this.cellHeight * i);
454 node.setAttributeNS(null,"pointer-events","none");
456 for (var attrib in this.textStyles) {
457 value = this.textStyles[attrib];
458 if (attrib == "font-size") {
461 node.setAttributeNS(null,attrib,value);
463 var selectionText = document.createTextNode(this.elementsArray[this.curLowerIndex + i]);
464 node.appendChild(selectionText);
465 this.dynamicTextGroup.appendChild(node);
469 if (this.heightNrElements < nrSelectionsOrig) {
470 //calculate scrollstep
471 this.scrollerHeight = (this.heightNrElements / nrSelectionsOrig) * (selectionHeight - 2 * this.cellHeight);
472 if (this.scrollerHeight < this.scrollerMinHeight) {
473 this.scrollerHeight = this.scrollerMinHeight;
475 this.scrollStep = (selectionHeight - 2 * this.cellHeight - this.scrollerHeight) / (nrSelectionsOrig - this.heightNrElements);
477 this.scrollBar = document.createElementNS(svgNS,"rect");
478 this.scrollBar.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
479 this.scrollBar.setAttributeNS(null,"y",YOffset + this.cellHeight);
480 this.scrollBar.setAttributeNS(null,"width",this.cellHeight);
481 this.scrollBar.setAttributeNS(null,"height",selectionHeight - this.cellHeight * 2);
482 for (var attrib in this.scrollbarStyles) {
483 this.scrollBar.setAttributeNS(null,attrib,this.scrollbarStyles[attrib]);
485 this.scrollBar.setAttributeNS(null,"id","selScrollbarRect_"+this.id);
486 this.scrollBar.addEventListener("mousedown", this, false);
487 //node.addEventListener("mouseup", this, false);
488 //node.addEventListener("mousemove", this, false);
489 //node.addEventListener("mouseout", this, false);
490 this.dynamicTextGroup.appendChild(this.scrollBar);
492 var node = document.createElementNS(svgNS,"rect");
493 node.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
494 node.setAttributeNS(null,"y",YOffset);
495 node.setAttributeNS(null,"width",this.cellHeight);
496 node.setAttributeNS(null,"height",this.cellHeight);
497 for (var attrib in this.smallrectStyles) {
498 node.setAttributeNS(null,attrib,this.smallrectStyles[attrib]);
500 node.setAttributeNS(null,"id","selScrollUpperRect_"+this.id);
501 node.addEventListener("mousedown", this, false);
502 node.addEventListener("mouseup", this, false);
503 this.dynamicTextGroup.appendChild(node);
505 var node = document.createElementNS(svgNS,"path");
506 var myPath = "M"+(this.xOffset + this.width - 3 * this.triangleFourth)+" "+(YOffset + 3 * this.triangleFourth)+" L"+(this.xOffset + this.width - this.triangleFourth)+" "+(YOffset + 3 * this.triangleFourth)+" L"+(this.xOffset + this.width - 2 * this.triangleFourth)+" "+(YOffset + this.triangleFourth)+" Z";
507 node.setAttributeNS(null,"d",myPath);
508 for (var attrib in this.triangleStyles) {
509 node.setAttributeNS(null,attrib,this.triangleStyles[attrib]);
511 node.setAttributeNS(null,"pointer-events","none");
512 this.dynamicTextGroup.appendChild(node);
514 var node = document.createElementNS(svgNS,"rect");
515 node.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
516 node.setAttributeNS(null,"y",YOffset - this.cellHeight + selectionHeight);
517 node.setAttributeNS(null,"width",this.cellHeight);
518 node.setAttributeNS(null,"height",this.cellHeight);
519 for (var attrib in this.smallrectStyles) {
520 node.setAttributeNS(null,attrib,this.smallrectStyles[attrib]);
522 node.setAttributeNS(null,"id","selScrollLowerRect_" + this.id);
523 node.addEventListener("mousedown", this, false);
524 node.addEventListener("mouseup", this, false);
525 this.dynamicTextGroup.appendChild(node);
527 var node = document.createElementNS(svgNS,"path");
528 var myPath = "M"+(this.xOffset + this.width - 3 * this.triangleFourth)+" "+(YOffset - this.cellHeight + selectionHeight + this.triangleFourth)+" L"+(this.xOffset + this.width - this.triangleFourth)+" "+(YOffset - this.cellHeight + selectionHeight + this.triangleFourth)+" L"+(this.xOffset + this.width - 2 * this.triangleFourth)+" "+(YOffset - this.cellHeight + selectionHeight + 3 * this.triangleFourth)+" Z";
529 node.setAttributeNS(null,"d",myPath);
530 for (var attrib in this.triangleStyles) {
531 node.setAttributeNS(null,attrib,this.triangleStyles[attrib]);
533 node.setAttributeNS(null,"pointer-events","none");
534 this.dynamicTextGroup.appendChild(node);
536 var node = document.createElementNS(svgNS,"rect");
537 node.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
538 node.setAttributeNS(null,"y",YOffset + this.cellHeight + this.scrollStep * this.curLowerIndex);
539 node.setAttributeNS(null,"width",this.cellHeight);
540 node.setAttributeNS(null,"height",this.scrollerHeight);
541 for (var attrib in this.smallrectStyles) {
542 node.setAttributeNS(null,attrib,this.smallrectStyles[attrib]);
544 node.setAttributeNS(null,"pointer-events","none");
545 node.setAttributeNS(null,"id","selScroller_"+this.id);
546 this.dynamicTextGroup.appendChild(node);
548 //add event handler to root element to close selectionList if one clicks outside
549 document.documentElement.addEventListener("click",this,false);
550 document.documentElement.addEventListener("keypress",this,false);
553 //this function folds/hides the selectionList again
554 selectionList.prototype.foldList = function() {
555 this.parentGroup.removeChild(this.dynamicTextGroup);
556 this.dynamicTextGroup = null;
557 this.rectBelowBox.setAttributeNS(null,"display","none");
558 this.rectAboveBox.setAttributeNS(null,"display","none");
559 document.documentElement.removeEventListener("click",this,false);
560 document.documentElement.removeEventListener("keypress",this,false);
561 this.scrollBar = undefined;
564 selectionList.prototype.selectAndClose = function(evt) {
565 var mySelEl = evt.target;
566 var result = mySelEl.getAttributeNS(null,"id").split("_");
567 this.activeSelection = parseInt(result[2]);
568 this.curLowerIndex = this.activeSelection;
570 this.listOpen = false;
571 this.selectedText.firstChild.nodeValue = this.elementsArray[this.activeSelection];
572 this.timer.setTimeout("fireFunction",this.timerMs);
575 selectionList.prototype.fireFunction = function() {
576 if (typeof(this.functionToCall) == "function") {
577 this.functionToCall(this.id,this.activeSelection,this.elementsArray[this.activeSelection]);
579 if (typeof(this.functionToCall) == "object") {
580 this.functionToCall.getSelectionListVal(this.id,this.activeSelection,this.elementsArray[this.activeSelection]);
582 //suggestion by Bruce Rindahl
583 //allows to initialize a selectionlist without a specific callback function on changing the selectionlist entry
584 if (typeof(this.functionToCall) == undefined) {
589 selectionList.prototype.scrollPerButton = function() {
590 if (this.scrollActive == true) {
591 this.scroll(this.scrollDir,1,true); //1 is one element to scroll, true says that we should move scrollbar
592 this.timer.setTimeout("scrollPerButton",100)
596 selectionList.prototype.scroll = function(scrollDir,scrollNr,scrollBool) {
597 var nrSelections = this.elementsArray.length;
598 var scroller = document.getElementById("selScroller_"+this.id);
599 //following is a temporary YOffset that serves to distinquish between selectionLists that open above or below
600 var YOffset = this.yOffset + this.cellHeight;
601 if (this.openAbove) {
602 YOffset = this.yOffset - this.heightNrElements * this.cellHeight;
604 if (scrollNr < this.heightNrElements) {
605 if ((this.curLowerIndex > 0) && (scrollDir == "up")) {
606 if (scrollNr > this.curLowerIndex) {
607 scrollNr = this.curLowerIndex;
609 //decrement current index
610 this.curLowerIndex = this.curLowerIndex - scrollNr;
612 if (scrollBool == true) {
613 scroller.setAttributeNS(null,"y",parseFloat(scroller.getAttributeNS(null,"y"))+ this.scrollStep * -1);
615 //add upper rect elements
616 for (var i=0;i<scrollNr;i++) {
617 var node = document.createElementNS(svgNS,"rect");
618 node.setAttributeNS(null,"x",this.xOffset + this.textPadding / 2);
619 node.setAttributeNS(null,"y",YOffset + this.cellHeight * i);
620 node.setAttributeNS(null,"width",this.width - this.cellHeight - this.textPadding);
621 node.setAttributeNS(null,"height",this.cellHeight);
622 node.setAttributeNS(null,"fill",this.boxStyles["fill"]);
623 node.setAttributeNS(null,"id","selHighlightSelection_" + this.id + "_" + (i + this.curLowerIndex));
625 node.addEventListener("mouseover", this, false);
626 node.addEventListener("mouseout", this, false);
627 node.addEventListener("click", this, false);
628 node.addEventListener("keypress", this, false);
629 this.dynamicTextGroup.appendChild(node);
631 var node = document.createElementNS(svgNS,"text");
632 node.setAttributeNS(null,"id","selTexts_" + this.id + "_" + (i + this.curLowerIndex));
633 node.setAttributeNS(null,"x",this.xOffset + this.textPadding);
634 node.setAttributeNS(null,"y",YOffset + this.textPaddingVertical + this.cellHeight * i);
635 node.setAttributeNS(null,"pointer-events","none");
637 for (var attrib in this.textStyles) {
638 value = this.textStyles[attrib];
639 if (attrib == "font-size") {
642 node.setAttributeNS(null,attrib,value);
644 var selectionText = document.createTextNode(this.elementsArray[this.curLowerIndex + i]);
645 node.appendChild(selectionText);
646 this.dynamicTextGroup.appendChild(node);
648 //move middle elements
649 for (var j=i;j<this.heightNrElements;j++) {
650 var node = document.getElementById("selTexts_" + this.id + "_" + (j + this.curLowerIndex));
651 node.setAttributeNS(null,"y",parseFloat(node.getAttributeNS(null,"y")) + (this.cellHeight * scrollNr));
652 var node = document.getElementById("selHighlightSelection_" + this.id + "_" + (j + this.curLowerIndex));
653 node.setAttributeNS(null,"y",parseFloat(node.getAttributeNS(null,"y")) + (this.cellHeight * scrollNr));
655 //remove lower elements
656 for (var k=j;k<(j+scrollNr);k++) {
657 var node = document.getElementById("selTexts_" + this.id + "_" + (k + this.curLowerIndex));
658 this.dynamicTextGroup.removeChild(node);
659 var node = document.getElementById("selHighlightSelection_" + this.id +"_" + (k + this.curLowerIndex));
660 this.dynamicTextGroup.removeChild(node);
663 else if ((this.curLowerIndex < nrSelections - this.heightNrElements) && (scrollDir == "down")) {
665 if (scrollBool == true) {
666 scroller.setAttributeNS(null,"y",parseFloat(scroller.getAttributeNS(null,"y")) + this.scrollStep);
668 //remove most upper element
669 for (var i=0;i<scrollNr;i++) {
670 var node = document.getElementById("selTexts_" + this.id + "_" + (this.curLowerIndex + i));
671 this.dynamicTextGroup.removeChild(node);
672 var node = document.getElementById("selHighlightSelection_" + this.id + "_" + (this.curLowerIndex + i));
673 this.dynamicTextGroup.removeChild(node);
675 //move middle elements
676 for (var j=i;j<this.heightNrElements;j++) {
677 var node = document.getElementById("selTexts_" + this.id + "_" + (j + this.curLowerIndex));
678 node.setAttributeNS(null,"y",parseFloat(node.getAttributeNS(null,"y")) - (this.cellHeight * scrollNr));
679 var node = document.getElementById("selHighlightSelection_" + this.id + "_" + (j + this.curLowerIndex));
680 node.setAttributeNS(null,"y",parseFloat(node.getAttributeNS(null,"y")) - (this.cellHeight * scrollNr));
682 //add most lower element
683 for (var k=j;k<(j+scrollNr);k++) {
684 var node = document.createElementNS(svgNS,"rect");
685 node.setAttributeNS(null,"x",this.xOffset + this.textPadding / 2);
686 node.setAttributeNS(null,"y",YOffset + this.cellHeight * (k - scrollNr));
687 node.setAttributeNS(null,"width",this.width - this.cellHeight - this.textPadding);
688 node.setAttributeNS(null,"height",this.cellHeight);
689 node.setAttributeNS(null,"fill",this.boxStyles["fill"]);
690 node.setAttribute("id","selHighlightSelection_" + this.id + "_" + (k + this.curLowerIndex));
692 node.addEventListener("mouseover", this, false);
693 node.addEventListener("mouseout", this, false);
694 node.addEventListener("click", this, false);
695 node.addEventListener("keypress", this, false);
696 this.dynamicTextGroup.appendChild(node);
698 var node = document.createElementNS(svgNS,"text");
699 node.setAttributeNS(null,"id","selTexts_" + this.id + "_" + (k + this.curLowerIndex));
700 node.setAttributeNS(null,"x",this.xOffset + this.textPadding);
701 node.setAttributeNS(null,"y",YOffset + this.textPaddingVertical + this.cellHeight * (k - scrollNr));
702 node.setAttributeNS(null,"pointer-events","none");
704 for (var attrib in this.textStyles) {
705 value = this.textStyles[attrib];
706 if (attrib == "font-size") {
709 node.setAttributeNS(null,attrib,value);
711 var selectionText = document.createTextNode(this.elementsArray[this.curLowerIndex + k]);
712 node.appendChild(selectionText);
713 this.dynamicTextGroup.appendChild(node);
715 //increment current index
716 this.curLowerIndex = this.curLowerIndex + scrollNr;
720 //remove lower elements
721 for (var i=0;i<this.heightNrElements;i++) {
722 var node = document.getElementById("selTexts_" + this.id + "_" + (i + this.curLowerIndex));
723 this.dynamicTextGroup.removeChild(node);
724 var node = document.getElementById("selHighlightSelection_" + this.id +"_" + (i + this.curLowerIndex));
725 this.dynamicTextGroup.removeChild(node);
727 if (scrollDir == "down") {
728 this.curLowerIndex = this.curLowerIndex + scrollNr;
731 this.curLowerIndex = this.curLowerIndex - scrollNr;
733 for (var i=0;i<this.heightNrElements;i++) {
734 var node = document.createElementNS(svgNS,"rect");
735 node.setAttributeNS(null,"x",this.xOffset + this.textPadding / 2);
736 node.setAttributeNS(null,"y",YOffset + this.cellHeight * i);
737 node.setAttributeNS(null,"width",this.width - this.cellHeight - this.textPadding);
738 node.setAttributeNS(null,"height",this.cellHeight);
739 node.setAttributeNS(null,"fill",this.boxStyles["fill"]);
740 node.setAttributeNS(null,"id","selHighlightSelection_" + this.id + "_" + (i + this.curLowerIndex));
742 node.addEventListener("mouseover", this, false);
743 node.addEventListener("mouseout", this, false);
744 node.addEventListener("click", this, false);
745 node.addEventListener("keypress", this, false);
746 this.dynamicTextGroup.appendChild(node);
748 var node = document.createElementNS(svgNS,"text");
749 node.setAttributeNS(null,"id","selTexts_" + this.id + "_" + (i + this.curLowerIndex));
750 node.setAttributeNS(null,"x",this.xOffset + this.textPadding);
751 node.setAttributeNS(null,"y",YOffset + this.textPaddingVertical + this.cellHeight * i);
752 node.setAttributeNS(null,"pointer-events","none");
754 for (var attrib in this.textStyles) {
755 value = this.textStyles[attrib];
756 if (attrib == "font-size") {
759 node.setAttributeNS(null,attrib,value);
761 var selectionText = document.createTextNode(this.elementsArray[this.curLowerIndex + i]);
762 node.appendChild(selectionText);
763 this.dynamicTextGroup.appendChild(node);
768 //event listener for Scrollbar element
769 selectionList.prototype.scrollBarMove = function(evt) {
770 var scroller = document.getElementById("selScroller_" + this.id);
771 var scrollerHeight = parseFloat(scroller.getAttributeNS(null,"height"));
772 var scrollerMinY = parseFloat(this.scrollBar.getAttributeNS(null,"y"));
773 var scrollerMaxY = parseFloat(this.scrollBar.getAttributeNS(null,"y")) + parseFloat(this.scrollBar.getAttributeNS(null,"height")) - scrollerHeight;
774 //following is a temporary YOffset that serves to distinquish between selectionLists that open above or below
775 var YOffset = this.yOffset + this.cellHeight;
776 if (this.openAbove) {
777 YOffset = this.yOffset - this.heightNrElements * this.cellHeight;
779 if (evt.type == "mousedown") {
780 this.panStatus = true;
781 for (var attrib in this.triangleStyles) {
782 scroller.setAttributeNS(null,attrib,this.triangleStyles[attrib]);
784 var coordPoint = myMapApp.calcCoord(evt,this.scrollBar);
785 this.panY = coordPoint.y;
786 var oldY = parseFloat(scroller.getAttributeNS(null,"y"));
787 var newY = this.panY - parseFloat(scroller.getAttributeNS(null,"height")) / 2;
788 if (newY < scrollerMinY) {
790 //maybe recalculate this.panY ??
792 if (newY > scrollerMaxY) {
795 var panDiffY = newY - oldY;
796 var scrollDir = "down";
797 this.scrollCumulus = 0;
798 if(Math.abs(panDiffY) > this.scrollStep) {
799 var scrollNr = Math.abs(Math.round(panDiffY / this.scrollStep));
801 this.scrollCumulus = panDiffY - this.scrollStep * scrollNr;
804 this.scrollCumulus = panDiffY + this.scrollStep * scrollNr;
807 newY = oldY + panDiffY;
808 scroller.setAttributeNS(null,"y",newY);
809 this.scroll(scrollDir,scrollNr,false);
812 if (evt.type == "mouseup" || evt.type == "mouseout") {
813 if (this.panStatus == true) {
814 var newY = parseFloat(scroller.getAttributeNS(null,"y"));
815 for (var attrib in this.smallrectStyles) {
816 scroller.setAttributeNS(null,attrib,this.smallrectStyles[attrib]);
818 scroller.setAttributeNS(null,"y",YOffset + this.cellHeight + this.scrollStep * this.curLowerIndex);
820 this.panStatus = false;
822 if (evt.type == "mousemove") {
823 if (this.panStatus == true) {
824 var coordPoint = myMapApp.calcCoord(evt,this.scrollBar);
825 var panNewEvtY = coordPoint.y;
826 var panDiffY = panNewEvtY - this.panY;
827 var oldY = parseFloat(scroller.getAttributeNS(null,"y"));
828 var newY = oldY + panDiffY;
829 if (newY < scrollerMinY) {
832 if (newY > scrollerMaxY) {
835 var panDiffY = newY - oldY;
836 if ((panDiffY < 0 && panNewEvtY <= (scrollerMaxY+scrollerHeight)) || (panDiffY > 0 && panNewEvtY >= scrollerMinY)) {
837 this.scrollCumulus += panDiffY;
838 var scrollDir = "down";
840 if(Math.abs(this.scrollCumulus) >= this.scrollStep) {
841 scrollNr = Math.abs(Math.round(this.scrollCumulus / this.scrollStep));
842 if (this.scrollCumulus > 0) {
843 this.scrollCumulus = this.scrollCumulus - this.scrollStep * scrollNr;
846 this.scrollCumulus = this.scrollCumulus + this.scrollStep * scrollNr;
849 this.scroll(scrollDir,scrollNr,false);
852 if (Math.abs(this.scrollCumulus) > this.scrollStep) {
856 this.scrollCumulus = this.scrollCumulus + this.scrollStep;
859 this.scrollCumulus = this.scrollCumulus - this.scrollStep;
861 panDiffY = this.scrollCumulus;
862 this.scroll(scrollDir,scrollNr,false);
865 if (newY == scrollerMinY && this.curLowerIndex != 0) {
866 this.scroll("up",1,false);
868 else if (newY == scrollerMaxY && this.curLowerIndex != (this.elementsArray.length - this.heightNrElements)) {
869 this.scroll("down",1,false);
873 newY = oldY + panDiffY;
874 scroller.setAttributeNS(null,"y",newY);
875 this.panY = panNewEvtY;
881 selectionList.prototype.scrollToKey = function(pressedKey) {
882 var oldActiveSelection = this.activeSelection;
883 for (var i=0;i<this.elementsArray.length;i++) {
884 if (this.elementsArray[i].toLowerCase().substr(0,this.pressedKeys.length) == this.pressedKeys) {
885 if (this.listOpen == true) {
888 this.activeSelection = i;
890 this.listOpen = true;
891 this.activeSelection = oldActiveSelection;
897 selectionList.prototype.elementExists = function(elementName) {
899 for (i=0;i<this.elementsArray.length;i++) {
900 if (this.elementsArray[i] == elementName) {
907 selectionList.prototype.selectElementByName = function(elementName,fireFunction) {
908 //fireFunction: (true|false); determines whether to execute selectFunction or not
909 existsPosition = this.elementExists(elementName);
910 if (existsPosition != -1) {
911 if (this.listOpen == true) {
914 this.activeSelection = existsPosition;
915 this.selectedText.firstChild.nodeValue = this.elementsArray[this.activeSelection];
916 if (this.listOpen == true) {
919 if (fireFunction == true) {
923 return existsPosition;
926 selectionList.prototype.selectElementByPosition = function(position,fireFunction) {
927 //fireFunction: (true|false); determines whether to execute selectFunction or not
928 if (position < this.elementsArray.length) {
929 if (this.listOpen == true) {
932 this.activeSelection = position;
933 this.selectedText.firstChild.nodeValue = this.elementsArray[this.activeSelection];
934 if (this.listOpen == true) {
937 if (fireFunction == true) {
947 selectionList.prototype.sortList = function(direction) {
948 //direction: asc|desc, for ascending or descending
949 var mySelElementString = this.elementsArray[this.activeSelection];
950 if (this.listOpen == true) {
953 this.elementsArray.sort();
954 if (direction == "desc") {
955 this.elementsArray.reverse();
957 this.activeSelection = this.elementExists(mySelElementString);
958 if (this.listOpen == true) {
965 selectionList.prototype.deleteElement = function(elementName) {
966 existsPosition = this.elementExists(elementName);
967 if (existsPosition != -1) {
968 if (this.listOpen == true) {
971 var tempArray = new Array;
972 tempArray = tempArray.concat(this.elementsArray.slice(0,existsPosition),this.elementsArray.slice(existsPosition + 1,this.elementsArray.length));
973 this.elementsArray = tempArray;
974 if (this.activeSelection == existsPosition) {
975 this.selectedText.firstChild.nodeValue = this.elementsArray[this.activeSelection];
977 if (this.activeSelection > existsPosition) {
978 this.activeSelection -= 1;
980 if (this.listOpen == true) {
984 return existsPosition;
987 selectionList.prototype.addElementAtPosition = function(elementName,position) {
988 if (position > this.elementsArray.length) {
989 this.elementsArray.push(elementName);
990 position = this.elementsArray.length - 1;
993 var tempArray = new Array;
994 tempArray = tempArray.concat(this.elementsArray.slice(0,position),elementName,this.elementsArray.slice(position,this.elementsArray.length));
995 this.elementsArray = tempArray;
997 if (this.listOpen == true) {
1000 if (position <= this.activeSelection) {
1001 this.activeSelection += 1;
1003 if (this.listOpen == true) {
1009 selectionList.prototype.getCurrentSelectionElement = function() {
1010 return this.elementsArray[this.activeSelection];
1013 selectionList.prototype.getCurrentSelectionIndex = function() {
1014 return this.activeSelection;
1017 selectionList.prototype.removeSelectionList = function() {
1018 //remove all Elements of selectionList
1019 this.exists = false;
1020 while (this.parentGroup.hasChildNodes()) {
1021 this.parentGroup.removeChild(this.parentGroup.firstChild);
1025 selectionList.prototype.moveTo = function(moveX,moveY) {
1026 this.xOffset = moveX;
1027 this.yOffset = moveY;
1028 //reposition initial rectangle
1029 var initRect = document.getElementById("selRect_"+this.id);
1030 initRect.setAttributeNS(null,"x",this.xOffset);
1031 initRect.setAttributeNS(null,"y",this.yOffset);
1032 //reposition initial text
1033 this.selectedText.setAttributeNS(null,"x",this.xOffset + this.textPadding);
1034 this.selectedText.setAttributeNS(null,"y",this.yOffset + this.textPaddingVertical);
1035 //reposition small rectangle to the right
1036 var smallRectRight = document.getElementById("selPulldown_"+this.id);
1037 smallRectRight.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
1038 smallRectRight.setAttributeNS(null,"y",this.yOffset);
1039 //reposition triangle
1040 var triangle = document.getElementById("selTriangle_"+this.id);
1041 var myTrianglePath = "M"+(this.xOffset + this.width - 3 * this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - 2 * this.triangleFourth)+" "+(this.yOffset + 3 * this.triangleFourth)+" Z";
1042 triangle.setAttributeNS(null,"d",myTrianglePath);
1043 //change positions of rectbox below and above
1044 this.rectBelowBox.setAttributeNS(null,"x",this.xOffset);
1045 this.rectBelowBox.setAttributeNS(null,"y",this.yOffset + this.cellHeight);
1046 this.rectAboveBox.setAttributeNS(null,"x",this.xOffset);
1047 this.rectAboveBox.setAttributeNS(null,"y",this.yOffset + this.cellHeight);
1050 selectionList.prototype.resize = function(newWidth) {
1051 this.width = newWidth;
1052 //reposition initial rectangle
1053 var initRect = document.getElementById("selRect_"+this.id);
1054 initRect.setAttributeNS(null,"width",this.width);
1055 //reposition small rectangle to the right
1056 var smallRectRight = document.getElementById("selPulldown_"+this.id);
1057 smallRectRight.setAttributeNS(null,"x",this.xOffset + this.width - this.cellHeight);
1058 //reposition triangle
1059 var triangle = document.getElementById("selTriangle_"+this.id);
1060 var myTrianglePath = "M"+(this.xOffset + this.width - 3 * this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - this.triangleFourth)+" "+(this.yOffset + this.triangleFourth)+" L"+(this.xOffset + this.width - 2 * this.triangleFourth)+" "+(this.yOffset + 3 * this.triangleFourth)+" Z";
1061 triangle.setAttributeNS(null,"d",myTrianglePath);
1062 //change sizes of rectbox below and above
1063 this.rectBelowBox.setAttributeNS(null,"width",this.width - this.cellHeight);
1064 this.rectAboveBox.setAttributeNS(null,"width",this.width - this.cellHeight);