/* Scripts to create interactive selectionLists/Dropdown boxes in SVG using ECMA script Copyright (C) <2003> Version 0.99, 2003-09-21 neumann@karto.baug.ethz.ch http://www.carto.net/ http://www.karto.ethz.ch/neumann/ Initial code was taken from Michel Hirtzler --> pilat.free.fr (thanks!) Thanks also to many people of svgdevelopers@yahoogroups.com This ECMA script library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library (lesser_gpl.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- original document site: http://www.carto.net/papers/svg/gui/selectionlist/ Please contact the author in case you want to use code or ideas commercially. If you use this code, please include this copyright header, the included full LGPL 2.1 text and read the terms provided in the LGPL 2.1 license (http://www.gnu.org/copyleft/lesser.txt) Design/concept: * all variables, function names and ids in this script start with "sL" --> to avoid name-collisions * unfortunately I am not a programmer and don't know about OO design and avoiding global variables, if you know better, please tell me! Prerequisites/Assumptions/Usage: Please study the source code of the example, specifically the files "index.svg", "init.js" and "selectionList.js" Necessary modifications/additions in "index.svg" (the svg that uses the selectionLists): ---- ---- Use an internal coordinate system (viewBox="0 0 1024 768" is a good starting point), leave width and height at 100% or omit it. Use "onload", "onzoom" and "onresize" event to initialize selectionList and to reset factors on resize and zoom. Please note that you have to specify the viewBox, with blanks as delimiters, since the application uses these values in order to determine zoomFactors and width/height ratios. ---- ---- reference the script in the svg-file that contains all functions concerning the selectionList ---- ---- Create an empty group to which the selectionList graphics will be attached. The group name needs to be "sLselectionBox" followed immediately by the index of an associative ECMAScript array that contains the selectionList data. In the above example, we create an empty group for the selectionList "Roses". Necessary initialization function: ---- var svgdoc; //global doc variable var sLselections = new Array(); sLselections["Roses"] = new Array("Butterscotch","Ci Peace","Impatient","Lady Hillingdon","Lavaglut","Mission Bells","Sexy Rexy","Souvenir de Pierre Notting","Sunflare","Whisky Mac","Whisper Floribunda"); ---- You need to have a global "svgdoc" variable that holds a reference to the SVG document. You need to declare a new empty associative array, with the name "sLselections". Please note that you should not change this name, unless you want to edit my script library. You need to create a new array, within the associative array, holding the data for your selectionList, in our example above with the index "roses". ---- function initMap(evt) { //initialize svg references svgdoc=evt.target.ownerDocument; //to reset scaleFactor for dragging scroller of selectionList sLresetFactors(evt); //usage: createSelectionList(IndexToStringArray,width,xOffset,yOffset,height(nrOfELements),nrToPreSelect(zero-based!),functionName); //create an empty group width Id "selection+indexNr" sLcreateSelectionList("Roses",150,50,100,4,0,"showRoses"); //layer list } ---- In an initialization function called "onload" you need to initialize the svgdoc variable, reset scale and zoom factors with the function call "sLresetFactors(evt);" and create the selectionList. The creation function call takes 7 arguments: * the index to the associative array, in our case "roses" * the width of the selectionList, in our case 150 units * the x coordinate of the upper left corner, in our case 50 * the y coordinate of the upper left corner, in our case 100 * the height of the selectionList in nr of elements, in our case 4 * a number to preselect, in our case 0, which means, we preselect the first element in the array * functionName to call, when a user selects an element, in our case "showRoses" ---- function showRoses(roseNr,listName) { var roseName = sLselections[listName][roseNr]; svgdoc.getElementById("rosename").getFirstChild().setData("Rosename: "+roseName); roseName = "roses/" + roseName.toLowerCase().replace(/\s/g,"_") + ".jpg"; svgdoc.getElementById("roseimage").setAttribute("xlink:href",roseName); } ---- Finally you need a function that is called, when the user select an element. This function always gets 2 arguments: a index to the selected element (in our case roseNr), and the index of the list that holds our selectionList data (in our case listName ["Roses"]) If you wish, you can modify parameters (such as "look and feel") in the file "selectionList.js". You can adapt colors, fonts, cellpaddings, etc. ------------------------------- Please report bugs and send improvements to neumann@karto.baug.ethz.ch If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/selectionlist/) somewhere in the source-code-comment or the "about" of your project and give credits, thanks! */ //global variables and associative arrays var sLscaleFactor; var sLpanY; var sLoffsetY; var sLactiveSelection = new Array(); var sLselectionVisible = new Array(); var sLfuncName = new Array(); var sLnrHeight = new Array(); //this is to add scrollbar for longer lists var sLcurLowerIndex = new Array(); //this is to hold current lower index for longer lists var sLscrollStatus = new Array(); //this is to hald status when scrolling var sLpanStatus = new Array(); //to hold mouse status when moving scrollbar var sLpanEvtY = new Array(); //to hold previous y-coordinate //define look and feel here (colors, fonts and stroke-width, cellHeight, etc.) var sLtextLook = "fill:darkcyan;font:Helvetica;font-size:11;"; var sLselectBoxfillStroke = "stroke:darkcyan;fill:white;"; var sLscrollBarfillStroke = "stroke:darkcyan;fill:whitesmoke;"; var sLsmallRectLook = "stroke:darkcyan;fill:lightGray;"; var sLhighLightColor = "fill:darkcyan;stroke:none;fill-opacity:0.3;"; var sLhighLightColorUnsel = "fill:white;stroke:none;"; var sLtriangleLook = "stroke:none;fill:darkcyan;"; var sLcellHeight = 16; //outer Height var sLtriangleFourth = Math.round(sLcellHeight / 4); var sLtextPaddingHorizontal = 3; //this is relative to the left of the cell var sLtextPaddingVertical = 12; //this is relative to the top of the cell var scrollerMinHeight = 20; //minimal height of the scroller rect //this is the first function called to initialize the selectionList function sLcreateSelectionList(sLnrSelectList,sLwidth,sLxOffset,sLyOffset,sLHeight,nrToSelect,functionName) { sLactiveSelection[sLnrSelectList]=nrToSelect; sLfuncName[sLnrSelectList] = functionName; sLselectionVisible[sLnrSelectList]=false; sLnrHeight[sLnrSelectList] = sLHeight; sLcurLowerIndex[sLnrSelectList] = nrToSelect; //this value is adapted if the user moves scrollbar sLmySelectionBox = svgdoc.getElementById("sLselectionBox"+sLnrSelectList); //group node to hold dummy rectangle to catch mouse-events to prevent routing events to other elements //and to allow closing of the list after losing focus sLnode=svgdoc.createElement("g"); sLnode.setAttribute("id","sLselectionGroupRectPlaceholder"+sLnrSelectList); sLmySelectionBox.appendChild(sLnode); //initial Rect, visible at the beginning sLnode=svgdoc.createElement("rect"); sLnode.setAttribute("x",sLxOffset); sLnode.setAttribute("y",sLyOffset); sLnode.setAttribute("width",sLwidth); sLnode.setAttribute("height",sLcellHeight); sLnode.setAttribute("style",sLselectBoxfillStroke); sLnode.addEventListener("click", new sLBSeListener(sLnrSelectList,sLxOffset,sLyOffset,sLwidth), false); sLnode.addEventListener("keyup", new sLBSeListener(sLnrSelectList,sLxOffset,sLyOffset,sLwidth), false); sLmySelectionBox.appendChild(sLnode); //initial text sLnode=svgdoc.createElement("text"); sLnode.setAttribute("x",sLxOffset+sLtextPaddingHorizontal); sLnode.setAttribute("y",sLyOffset+sLtextPaddingVertical); sLnode.setAttribute("style",sLtextLook); sLnode.setAttribute("pointer-events","none"); sLselectionText=svgdoc.createTextNode(sLselections[sLnrSelectList][nrToSelect]); sLnode.appendChild(sLselectionText); sLnode.setAttribute("id","sLactualSelectionText"+sLnrSelectList); sLmySelectionBox.appendChild(sLnode); //small Rectangle to the right, onclick unfolds the selectionList sLnode=svgdoc.createElement("rect"); sLnode.setAttribute("x",sLxOffset+sLwidth-sLcellHeight); sLnode.setAttribute("y",sLyOffset); sLnode.setAttribute("width",sLcellHeight); sLnode.setAttribute("height",sLcellHeight); sLnode.setAttribute("style",sLsmallRectLook); sLnode.addEventListener("click", new sLBSeListener(sLnrSelectList,sLxOffset,sLyOffset,sLwidth), false); sLmySelectionBox.appendChild(sLnode); //triangle sLnode=svgdoc.createElement("path"); sLmyPath = "M"+(sLxOffset + sLwidth - 3 * sLtriangleFourth)+" "+(sLyOffset + sLtriangleFourth)+" L"+(sLxOffset + sLwidth - sLtriangleFourth)+" "+(sLyOffset + sLtriangleFourth)+" L"+(sLxOffset+sLwidth - 2 * sLtriangleFourth)+" "+(sLyOffset + 3 * sLtriangleFourth)+" Z"; sLnode.setAttribute("d",sLmyPath); sLnode.setAttribute("style",sLtriangleLook); sLnode.setAttribute("pointer-events","none"); sLmySelectionBox.appendChild(sLnode); //rectangle below unfolded selectBox sLnode=svgdoc.createElement("rect"); sLnode.setAttribute("x",sLxOffset); sLnode.setAttribute("y",sLyOffset+sLcellHeight); sLnode.setAttribute("width",sLwidth-sLcellHeight); sLnode.setAttribute("height",0); sLnode.setAttribute("style",sLselectBoxfillStroke+"visibility:hidden;"); sLnode.setAttribute("id","sLbelowSelection"+sLnrSelectList); sLmySelectionBox.appendChild(sLnode); //group node to hold dynamic text elements and highLight-Rects sLnode=svgdoc.createElement("g"); sLnode.setAttribute("id","sLselectionGroup"+sLnrSelectList); sLmySelectionBox.appendChild(sLnode); } //this function is called when selectionList is unfolded/showed function sLshowSelectionList(evt,sLnrSelectList,sLxOffset,sLyOffset,sLwidth) { sLmyNrSelectionsOrig = sLselections[sLnrSelectList].length; if (sLnrHeight[sLnrSelectList] < sLmyNrSelectionsOrig) { sLmyNrSelections=sLnrHeight[sLnrSelectList]; } else { sLmyNrSelections=sLmyNrSelectionsOrig; } sLselectionHeight=sLcellHeight*sLmyNrSelections; sLbelowSelectionObj=svgdoc.getElementById("sLbelowSelection"+sLnrSelectList); sLbelowSelectionObj.setAttribute("height",sLselectionHeight); sLbelowSelectionObj.getStyle().setProperty("visibility","visible"); //build textElements from array sLmySelectionGroup = svgdoc.getElementById("sLselectionGroup"+sLnrSelectList); //hold currentSelection Index to unroll list at new offset if ((sLmyNrSelectionsOrig - sLactiveSelection[sLnrSelectList]) >= sLmyNrSelections) { sLcurLowerIndex[sLnrSelectList] = sLactiveSelection[sLnrSelectList]; } else { sLcurLowerIndex[sLnrSelectList] = sLmyNrSelectionsOrig - sLmyNrSelections; } for (var i=0;i (sLnrElements - sLcurLowerIndex[this.sLnrSelectList])) { sLscrollNr = sLnrElements - sLcurLowerIndex[this.sLnrSelectList]; } sLcurLowerIndex[this.sLnrSelectList] = sLcurLowerIndex[this.sLnrSelectList] + sLscrollNr; var sLactiveSelectionOld = sLactiveSelection[this.sLnrSelectList]; sLactiveSelection[this.sLnrSelectList] = sLcurLowerIndex[this.sLnrSelectList]; sLshowSelectionList(evt,this.sLnrSelectList,this.sLxOffset,this.sLyOffset,this.sLwidth) sLactiveSelection[this.sLnrSelectList] = sLactiveSelectionOld; } } } //event listener data for Highlighter element function sLHLeListener(sLnr,sLnrSelectList,sLxOffset,sLyOffset,sLwidth) { this.sLnr = sLnr; this.sLnrSelectList = sLnrSelectList; this.sLxOffset = sLxOffset; this.sLyOffset = sLyOffset; this.sLwidth = sLwidth; } //event listener for Highlighter element sLHLeListener.prototype.handleEvent = function(evt) { sLmyEl = evt.getTarget(); if (evt.type == "mouseover") { sLmyEl.setAttribute("style",sLhighLightColor); } if (evt.type == "mouseout") { sLmyEl.setAttribute("style",sLhighLightColorUnsel); } if (evt.type == "click") { sLactiveSelection[this.sLnrSelectList]=this.sLnr; sLhideSelectionList(evt,this.sLnrSelectList); sLcurLowerIndex[this.sLnrSelectList]=this.sLnr; sLobject=svgdoc.getElementById("sLactualSelectionText"+this.sLnrSelectList); sLchild=sLobject.getFirstChild(); sLchild.setData(sLselections[this.sLnrSelectList][sLactiveSelection[this.sLnrSelectList]]); sLselectionVisible[this.sLnrSelectList]=false; var sLMyFunctionString = sLfuncName[this.sLnrSelectList]+"("+sLactiveSelection[this.sLnrSelectList]+",'"+this.sLnrSelectList+"')" setTimeout("eval("+sLMyFunctionString+")",300); } if (evt.type == "keyup") { var sLkey = String.fromCharCode(evt.charCode).toLowerCase(); for (var i=0;i this.sLscrollStep) { sLscrollNr = Math.abs(Math.round(sLpanDiffY / this.sLscrollStep)); if (sLscrollNr > sLnrHeight[this.sLnrSelectList]) { sLscrollNr = sLnrHeight[this.sLnrSelectList]; } if (sLpanDiffY > 0) { sLpanDiffY = this.sLscrollStep * sLscrollNr; } else { sLpanDiffY = this.sLscrollStep * sLscrollNr * -1; } } var sLnewY = parseFloat(sLscroller.getAttribute("y")) + sLpanDiffY; if ((sLnewY >= this.sLyOffset + sLcellHeight * 2) && (sLnewY <= this.sLyOffset + parseFloat(sLscrollBar.getAttribute("height")) - parseFloat(sLscroller.getAttribute("height")))) { sLscroller.setAttribute("y",sLnewY); if ((sLpanNewEvtY > sLpanEvtY[this.sLnrSelectList]) && (sLnewY - (this.sLyOffset+sLcellHeight*2+this.sLscrollStep*sLcurLowerIndex[this.sLnrSelectList])) > (this.sLscrollStep - this.sLscrollStep*0.6)) { sLscrollIt(this.sLnrSelectList,'down',this.sLscrollStep,sLscrollNr,this.sLxOffset,this.sLyOffset,this.sLwidth,"no"); } if ((sLpanNewEvtY < sLpanEvtY[this.sLnrSelectList]) && (sLnewY - (this.sLyOffset+sLcellHeight*2+this.sLscrollStep*sLcurLowerIndex[this.sLnrSelectList])) < (this.sLscrollStep - this.sLscrollStep*0.6)) { sLscrollIt(this.sLnrSelectList,'up',this.sLscrollStep,sLscrollNr,this.sLxOffset,this.sLyOffset,this.sLwidth,"no"); } } sLpanEvtY[this.sLnrSelectList] = sLpanNewEvtY; } } } //event listener data for scroll element function sLSCreListener(sLnrSelectList,sLxOffset,sLyOffset,sLscrollStep,sLwidth,direction) { this.sLnrSelectList = sLnrSelectList; this.sLxOffset = sLxOffset; this.sLyOffset = sLyOffset; this.sLscrollStep = sLscrollStep; this.sLwidth = sLwidth; this.direction = direction; } //event listener for scroll element sLSCreListener.prototype.handleEvent = function(evt) { if (evt.type == "mousedown") { sLscrollStatus[this.sLnrSelectList] = 1; window.setTimeout("scroll('"+this.sLnrSelectList+"','"+this.direction+"',"+this.sLscrollStep+",1,"+this.sLxOffset+","+this.sLyOffset+","+this.sLwidth+",'yes')",350); } if (evt.type == "mouseup") { sLscrollStatus[this.sLnrSelectList] = 0; } } function scroll(sLnrSelectList,sLdirection,sLscrollStep,sLscrollNr,sLxOffset,sLyOffset,sLwidth,sLstepBool) { sLscrollIt(sLnrSelectList,sLdirection,sLscrollStep,sLscrollNr,sLxOffset,sLyOffset,sLwidth,sLstepBool); if(sLscrollStatus[sLnrSelectList] == 1) { window.setTimeout("scroll('"+sLnrSelectList+"','"+sLdirection+"',"+sLscrollStep+","+sLscrollNr+","+sLxOffset+","+sLyOffset+","+sLwidth+",'yes')",50); } } //this function is called to actually scroll down a selectionList function sLscrollIt(sLnrSelectList,sLdirection,sLscrollStep,sLscrollNr,sLxOffset,sLyOffset,sLwidth,sLstepBool) { sLmyNrSelections = sLselections[sLnrSelectList].length; sLobject=svgdoc.getElementById("sLscroller_"+sLnrSelectList); sLmySelectionGroup = svgdoc.getElementById("sLselectionGroup"+sLnrSelectList); if ((sLcurLowerIndex[sLnrSelectList] > 0) && (sLdirection == "up")) { if (sLscrollNr > sLcurLowerIndex[sLnrSelectList]) { sLscrollNr = sLcurLowerIndex[sLnrSelectList]; } //decrement current index sLcurLowerIndex[sLnrSelectList] = sLcurLowerIndex[sLnrSelectList] - sLscrollNr; sLscrollStep = sLscrollStep * -1; //move scroller if (sLstepBool == "yes") { sLobject.setAttribute("y",parseFloat(sLobject.getAttribute("y"))+sLscrollStep); } //add upper rect elements for (var i=0;i sLmyRatio) { //case window is more wide than myRatio sLscaleFactor = sLmyViewBoxArray[3] / window.innerHeight; } else { //case window is more tall than myRatio sLscaleFactor = sLmyViewBoxArray[2] / window.innerWidth; } var sLzoomFact = evt.target.currentScale; sLscaleFactor = sLscaleFactor / sLzoomFact; sLoffsetY = (window.innerHeight - 768 * 1 / sLscaleFactor) / 2; //sLoffsetY = (window.innerHeight - 768 * 1 / sLscaleFactor) / 2; sLpanY = evt.target.currentTranslate.y; }