jump to navigation

Source code for Zoom Maps project (Openlaszlo) March 13, 2007

Posted by julien in : RIA , trackback

Several developers asked me the code for this product presentation written in Openlaszlo.

You will find it below.

Forewords

This program left from a really simple prototype, and I tried to keep it as clear as possible while it was growing up.
As I am not an Openlaszlo expert, the code can be improved in many ways I am sure. So please think this prototype as an alpha version, and feel free to comment if you wish.

LZX calls two xml files :
- zoomdata.xml which is the first file to be loaded. this file has the informations about the zooms (ratio, picture size, …) and the path to the tile url
- product.xml has the product description and the POI location and texts.

And now the code :

Code (javascript)
  1. <?xml version="1.0" encoding="iso-8859-1"?>
  2.  
  3. <canvas width="515" height="614" bgcolor="#eaeaea">
  4.  
  5. <splash/>
  6.  
  7. <script>
  8. <![CDATA[
  9.         var productName, productDescription;
  10.         var nbTilesX;
  11.         var nbTilesY;
  12.         var zoomLevel;
  13.         var ratio;
  14.         var currentZoomNumber;
  15.         var picWidth = 0;
  16.         var basePath, poiPath;
  17.         var oldPicWidth = 0;
  18.         var picHeight = 0;
  19.         var nb_pois = 0;
  20.         var nb_otherPics = 0;
  21.         var oldPicHeight = 0;
  22.         var tileSize;
  23.         var poiLoaded = false;
  24.         var serie = "A";       
  25.         function replace(s, t, u) {
  26.                 // function to replace into s the substring t by the substring u
  27.               i = s.indexOf(t);
  28.                 r = "";
  29.               if (i == -1) return s;
  30.               r += s.substring(0,i) + u;
  31.               if ( i + t.length < s.length)
  32.             r += replace(s.substring(i + t.length, s.length), t, u);
  33.               return r;
  34.         }
  35.         // xmlPath is passed by the querystring of the swf file.
  36.         // it gives the place where to find the zoomdata.xml file (see below)
  37.         xmlPath = LzBrowser.getInitArg("xmlPath");
  38.         if (typeof(xmlPath) == ‘undefined’) {
  39.               // if no param is passed, use this default path
  40.               xmlPath = "http://localhost/bagages/d18182/zoomdata.xml";
  41.         }     
  42. ]]>
  43. </script>
  44.  
  45. <!– productData is the file where we will find informations about product :
  46. Point of interest (POI), description, other views, …
  47. –>
  48. <dataset name="productData" type="http" request="false" />
  49. <handler name="ondata" reference="productData">
  50. <![CDATA[
  51.         var dp = productData.getPointer();
  52.         // grab number of POI into nb_pois variable
  53.         var qs = dp.xpathQuery(‘/product/pois/poi’);
  54.         nb_pois = qs.length;   
  55.         poiLoaded = true;
  56.         // grab various product infos : name, description, …
  57.         productName = dp.xpathQuery(‘/product/@name’)
  58.         titleText.setText(productName);
  59.         productDescription = dp.xpathQuery(‘/product/description/text()’);     
  60.         infoText.setText(productDescription);   
  61.         // grap other views of the product (right side) and create thumbnail
  62.         thumbsView.buildThumbs();
  63.         draggableView.refreshThis();
  64. ]]>
  65. </handler>
  66.  
  67. <!– first file that is loaded. This file contains informations
  68. on the zoom view (the one with tiles) –>
  69.  
  70. <dataset name="zoomData" request="true" type="http" src="${xmlPath}" />
  71. <handler name="ondata" reference="zoomData">
  72.         var dp = zoomData.getPointer();
  73.         basePath = dp.xpathQuery(‘/view/@basePath’);
  74.         // load productData xml file
  75.         poiPath = basePath + ‘/product.xml’;
  76.         productData.setSrc(poiPath);
  77.         productData.doRequest()
  78.         // grab tileSize and number of zooms for this file
  79.         tileSize = parseInt(dp.xpathQuery(‘/view/@tileSize’));
  80.         var qs = dp.xpathQuery(‘/view/zoom’);   
  81.         nb_zooms = qs.length;
  82.         // first view is the last zoom, i.e the "fit to window" zoom
  83.         currentZoomNumber = nb_zooms;      
  84. </handler>
  85.  
  86. <!–
  87. This class is the class for the thumbnailed view on the right side of the window
  88. –>
  89. <class name="otherPicThumb" extends="view" height="70" width="56">
  90. <attribute type="text" name="masterOf" />
  91. <handler name="onmouseover">
  92. <![CDATA[
  93.         // masterOf if the name of the view this thumbnail commands when making a rollover
  94.         // ie the real size view that arrives from the left and disappears when rolling out
  95.         var myvar = globalCanvas.searchSubviews("name", this.masterOf);
  96.         if (myvar.animCome.started) {
  97.                 myvar.animCome.stop();
  98.                 myvar.setX(-500);
  99.         }       
  100.         myvar.animCome.doStart();              
  101.         myvar.setVisible(true);
  102.         // hide buttons when the full size view appears
  103.         btnView.fadeOut.doStart();     
  104. ]]>
  105. </handler>
  106. <handler name="onmouseout">
  107. <![CDATA[
  108.         // hides the real size view that just arrived from the left, and show the buttons back
  109.         var myvar = globalCanvas.searchSubviews("name", this.masterOf);
  110.         myvar.setVisible(false);
  111.         btnView.fadeIn.doStart();
  112. ]]>
  113. </handler>
  114. </class>
  115.  
  116. <!– class for the real size picture which arrive from the left, when rolling on a otherPicThumb instance –>
  117. <class name="otherPic" extends="view">
  118. <animator name="animCome" attribute="x" from="-500" to="15" duration="750" start="false" motion="easein" />
  119. </class>
  120.  
  121. <!– poi (point of interest) class, basically a magnifier that is diplayed on some particular
  122. points of the zoom view –>
  123. <class name="poi" extends="multistatebutton" resource="multi_resource" overResourceNumber="6" downResourceNumber="6" normalResourceNumber="5">
  124. <attribute type="text" name="commentText" />
  125. <attribute name="targetX"/>
  126. <attribute name="targetY"/>
  127. <attribute name="whichZoom"/>
  128. <method event="onclick">
  129. <![CDATA[
  130. isNewZoom = false;
  131. if (currentZoomNumber!=whichZoom) {
  132.         // if zoom linked to that point of interest (whichZoom attribute) is different from the current zoom
  133.         // we will have to change the zoom
  134.         isNewZoom = true;
  135.         currentZoomNumber = whichZoom;
  136.         // hide all tiles
  137.         draggableView.hideAll();
  138.         // we use two series of tiles, to allow later effects like fading etc …
  139.         if (serie=="A") { serie = "B" } else { serie = "A" };
  140.         // refresh the zoom view at the new zoom level
  141.         draggableView.refreshThis();    
  142. }
  143. // we have to seek the view center on the point this poi shows (targetX, targetY)
  144. // we compute the new coordinates and sends the view to this place
  145. newX = this.targetX * -1 + globalFrame.width / 2;                                                 
  146. if (newX<draggableView.dragging.heldArgs.drag_min_x) { newX = draggableView.dragging.heldArgs.drag_min_x; }
  147. if (newX>0) { newX = 0; }
  148. draggableView.setAttribute("x",newX) ;
  149. newY = this.targetY * -1 + globalFrame.height / 2;
  150. if (newY<draggableView.dragging.heldArgs.drag_min_y) { newY = draggableView.dragging.heldArgs.drag_min_y; }
  151. if (newY>0) { newY = 0; }
  152. draggableView.setAttribute("y",newY);
  153. // if we had to change to a new zoom level, we have to reload the tiles. we have to do this after positionning the
  154. // zoom view, in order to load only necessary (ie visible) tiles.
  155. if (isNewZoom) { draggableView.loadOrNotTile(); }
  156. ]]>
  157. </method>
  158. <method event="onmouseover">
  159.         // show text linked to this poi at the bottom of the window
  160.         infoText.setText(this.commentText);
  161. </method>
  162. <method event="onmouseout">
  163.         // show back product description when rolling out
  164.         infoText.setText(productDescription);
  165. </method>
  166. </class>
  167.  
  168. <!– the base class for a tile of picture –>
  169. <class name="tile" extends="view">
  170. <attribute type="text" name="imgResource" />
  171. <attribute type="boolean" name="loaded" />
  172. <method event="oninit"
  173.         loaded = false
  174. </method>
  175. <animator name="anim" attribute="opacity" from="0" to="1" duration="1500" start="false" motion="easein"/>
  176. <method event="onload">
  177.         // when picture has finished loading, start the fade in animation "anim"
  178.         if (this.loaded) {this.anim.doStart();}
  179. </method>
  180. <method name="loadOrNotThisTile">
  181. <![CDATA[       
  182.         // this method determines whether a tile must be loaded or not
  183.         // tile must be loaded if and only if it is visible by the user
  184.         if (! loaded) {  
  185.                 parentx = draggableView.x;
  186.                 parenty = draggableView.y;
  187.                 parentwidth = globalFrame.width;
  188.                 parentheight = globalFrame.height;
  189.                 realx = this.x + parentx;
  190.                 realy = this.y + parenty;              
  191.                 isVisible = (realx<parentwidth) || (realy<parentheight);
  192.                 if (isVisible) {
  193.                         isVisible = ((realx+tileSize)>=0) || ((realy+tileSize)>=0);
  194.                 }
  195.                 if (isVisible) {
  196.                         // if that tile must be loaded, sets his source to basePath + this.imgResource
  197.                         // basePath is loaded from zoomdatafile
  198.                         this.setSource(basePath + this.imgResource,"clientonly");              
  199.                         this.stretchResource()
  200.                         this.loaded = true;
  201.                         }
  202.                 }              
  203. ]]>
  204. </method>
  205. </class>
  206.  
  207. <simplelayout axis="y" />
  208.  
  209. <resource name="multi_resource">
  210.         <frame src="resources/zoomin.swf"/>
  211.         <frame src="resources/zoomin_over.swf"/>
  212.         <frame src="resources/zoomout.swf"/>
  213.         <frame src="resources/zoomout_over.swf"/>
  214.         <frame src="resources/poi.swf"/>
  215.         <frame src="resources/poi_over.swf"/>
  216.         <frame src="resources/home.swf"/>
  217.         <frame src="resources/home_over.swf"/>
  218. </resource>       
  219.  
  220. <simplelayout axis="x" />
  221.  
  222. <handler name="onclick" reference="btnHome">
  223. <![CDATA[
  224. // when the home button is clicked, go back to default zoom, ie the "fit window" view
  225. if (currentZoomNumber!=nb_zooms) {
  226.         currentZoomNumber = nb_zooms;
  227.         draggableView.hideAll();
  228.         if (serie=="A") { serie = "B" } else { serie = "A" };
  229.         draggableView.refreshThis();    
  230.         draggableView.setAttribute("x",0) ;
  231.         draggableView.setAttribute("y",0);                                   
  232.         draggableView.loadOrNotTile();
  233.         }
  234. ]]>
  235. </handler>
  236.  
  237. <handler name="onclick" reference="btnZoomIn">
  238. <![CDATA[
  239. // when button Zoom in is clicked, go to previous zoom, if applicable
  240. if (currentZoomNumber>1) {
  241.         currentZoomNumber–;
  242.         // hide zoom view and change serie
  243.         draggableView.hideAll();
  244.         if (serie=="A") { serie = "B" } else { serie = "A" };
  245.         // refresh zoom view (= create new tiles)
  246.         draggableView.refreshThis();    
  247.         if (oldPicWidth!=0) {   
  248.                 // compute new position of the zoom view, so that previous zoom and new zoom are centered
  249.                 // on the same point … that works approximatively
  250.                 newX = draggableView.x - (picWidth-oldPicWidth)/2;                                         
  251.                 if (newX<draggableView.dragging.heldArgs.drag_min_x) { newX = draggableView.dragging.heldArgs.drag_min_x; }
  252.                 if (newX>0) { newX = 0; }
  253.                 draggableView.setAttribute("x",newX) ;
  254.                 newY = draggableView.y - (picHeight-oldPicHeight)/2;
  255.                 if (newY<draggableView.dragging.heldArgs.drag_min_y) { newY = draggableView.dragging.heldArgs.drag_min_y; }
  256.                 if (newY>0) { newY = 0; }              
  257.                 draggableView.setAttribute("y",newY);      
  258.         }       
  259.         // refresh zoom view by creating tiles   
  260.         draggableView.loadOrNotTile();
  261.         }
  262. ]]>
  263. </handler>
  264. <handler name="onclick" reference="btnZoomOut">
  265. <![CDATA[
  266. // for explanations, please refer to zoom in
  267. if (currentZoomNumber<nb_zooms) {
  268.         currentZoomNumber++;
  269.         draggableView.hideAll();
  270.         if (serie=="A") { serie = "B" } else { serie = "A" };
  271.         draggableView.refreshThis();    
  272.         if (oldPicWidth!=0) {                  
  273.                 newX = draggableView.x + (picWidth-oldPicWidth)/2;
  274.                 if (newX<draggableView.dragging.heldArgs.drag_min_x) { newX = draggableView.dragging.heldArgs.drag_min_x; }
  275.                 if (newX>0) { newX = 0; }
  276.                 draggableView.setAttribute("x",newX) ;
  277.                 newY = draggableView.y + (picHeight-oldPicHeight)/2;
  278.                 if (newY<draggableView.dragging.heldArgs.drag_min_y) { newY = draggableView.dragging.heldArgs.drag_min_y; }          
  279.                 if (newY>0) { newY = 0; }
  280.                 draggableView.setAttribute("y",newY);      
  281.         }
  282.         draggableView.loadOrNotTile()
  283.         }
  284. ]]>
  285. </handler>
  286.  
  287. <view id="globalCanvas" width="${parent.width}" height="${parent.height}" resource="resources/fond_bagages.jpg">
  288.  
  289. <view id="globalFrame" width="374" height="499" x="15" y="5" clip="true">
  290.  
  291. <!– draggableView is the core of the application, it is the "zoom view" that can be dragged and that is the
  292. container for tiles –>
  293. <view id="draggableView" x="0" y="0" clickable="true" onmousedown="dragging.apply()">
  294. <method event="onmouseup">
  295.         // when user stops dragging the view, checks which tiles are now visible and need to be loaded
  296.         dragging.remove();
  297.         stop()
  298.         this.loadOrNotTile();   
  299. </method>
  300. <method name="hideAll">
  301. <![CDATA[
  302. // hide all tiles from the zoom view
  303. if (nbTilesX==null) { return; }
  304. for (var i=0;i<nbTilesX+1;i++) {               
  305.         for (var j=0;j<nbTilesY+1;j++) {               
  306.                 var myvar = draggableView.searchSubviews("name", "tile_A_" + i + "_" + j);                 
  307.                 if (myvar != null) { myvar.destroy(); };
  308.                 var myvar = draggableView.searchSubviews("name", "tile_B_" + i + "_" + j);                 
  309.                 if (myvar != null) { myvar.destroy(); };                                    
  310.         }
  311. }
  312. ]]>     
  313. </method>
  314. <method name="refreshThis">
  315. <![CDATA[       
  316.         // after a zoom change, this method is called to load the tiles back
  317.         this.hideAll()
  318.         pleaseWaitView.fadeIn.doStart();
  319.         // grab different datas from the zoomdata file : informations about the new zoom :
  320.         // number of tiles horizontaly and verticaly, global pic and height of the picture
  321.         // and ratio. Ratio is relative to the original image, that means that for a max zoom
  322.         // ratio is equal to 1
  323.         var dp = zoomData.getPointer();    
  324.         zoomLevel = parseInt(dp.xpathQuery(‘/view/zoom[’ + currentZoomNumber + ‘]/@level’));
  325.         nbTilesX = parseInt(dp.xpathQuery(‘/view/zoom[’ + currentZoomNumber + ‘]/@x’))
  326.         nbTilesY = parseInt(dp.xpathQuery(‘/view/zoom[’ + currentZoomNumber + ‘]/@y’));  
  327.         oldPicHeight = picHeight;
  328.         oldPicWidth = picWidth;
  329.         picWidth = parseInt(dp.xpathQuery(‘/view/zoom[’ + currentZoomNumber + ‘]/@width’)