submit login information
search button
Advanced Search
NAVTEQ Logo_Click to go back to homepage     Store Map Reporter Developers Merchants     About Us
Buy a
Map
Update
Report Map
Changes
NAVTEQ
Network for
Developers
Put your Brand
on our Map
 
 
  • Most Read
  • Top Searches
  • Top Rated
  • My Profile

      If you're a member, please login to see this information.

      If you're not a member, then become one now

  • My Favorites

      If you're a member, please login to see this information.

      If you're not a member, then become one now

  • Recently Viewed

      If you're a member, please login to see this information.

      If you're not a member, then become one now

  • Share & Save
blogspot facebook twitter linkedin youtube flickr

Score:
Login to rate page
MapTP AJAX API 2.3

Draggable Route

This tutorial describes the differences between a draggable and a normal route and some operation tips for using this draggable route.

How to Create a Draggable Route

Draggable route is supported since AJAX API version 2.2. It extends a normal route by allowing users to change the course of a route interactively: A user can drag any point of the route with the mouse to another position.

You can create a draggable route by customizing a normal route object: For that you have to create a handler which takes over control for displaying and event handling of this route object. The handler is referenced as “MAP24_ROUTE_DIGITIZER”. Additionally, you must set some customer-defined properties in the handler.

While the following example gives you a recap of how to create a normal route, the second example shows how to create a draggable route.

  • Normal route:
//defines the MRC command for creating a normal route
DeclareMap24RouteObject: new Map24.WebServices.DeclareMap24RouteObject({
mrcCommands[mrcCommands.length++] = new Map24.WebServices.XMLCommandWrapper({
MapObjectID: "nmroute",
Map24RouteID: route.RouteID 
}) })
  • Draggable route:

//Defines the MRC command for creating a draggable route
DeclareMap24RouteObject: new Map24.WebServices.DeclareMap24RouteObject({
  MapObjectID: "szroute",
  Map24RouteID: route.RouteID,
  Customize: new Map24.WebServices.MapObjectCustomSettings({
       Handler: "MAP24_ROUTE_DIGITIZER",
       // On submit a JavaScript function can be called.
       Properties: [
         new Map24.WebServices.Property({Key:"CALLBACK_URL",Value: "javascript:showRouteDescription({
         EVENTID: '', ROUTEID: ''
        })"}),
           new Map24.WebServices.Property({Key:"CALLBACK_TARGET", Value:"_self"}),
          new Map24.WebServices.Property({Key:"routeColor", Value:"0,0,255,100"}),
          new Map24.WebServices.Property({Key:"routeDragColor", Value:"0,255,0,100"}),
         new Map24.WebServices.Property({Key:"startLogoURL", Value: "route_start.png#hotspot=1,24"}),
        new Map24.WebServices.Property({Key:"destLogoURL", Value: "route_dest.png#hotspot=1,24"}),
        new Map24.WebServices.Property({Key:"viaLogoURL", Value: "route_via.png#hotspot=1,24"})
     ]
  })
})

 

These two steps are sufficient for declaring a basic draggable route. However, this draggable route does not provide all functions for a convenient usage. In particular, it is not possible to delete an existing waypoint from the route. To be able to do that, we have to add some code that handles the mouse events that appear on the route object.

 

How to Handle Mouse Events on a Draggable Route

To allow for more interactivity, the mapping application should handle mouse clicks from users and provide a context menu that allows users to remove existing waypoints. The events of mouse clicks on map could be received by adding following code:

map.addListener( "Map24.Event.MapClick", mapClickHandler );

This code adds a listener which listens to map clicks from users and assigns a callback function mapClickHandler which handles the MapClick events. The function mapClickHandler filters all the mapclick events it receives and handles only those right mouse button events that happen on a route object. Since the user should be able to perform different actions, depending on the point on the route that has been clicked, we have to distinguish four different cases in the mapClickHandler function:

  1. Clicks on start point of the route.
  2. Clicks on destination point of the route.
  3. Clicks on one of the way points of the route.
  4. Clicks on the route (neither on a start, destination, or way point)

This is the code of the mapClickHandler function which handles each of the four events listed above (see the comments highlighted in bold):

function mapClickHandler(e) {
  //Don't stop processing the callback stack
   if( e.TopDown ) return true;
  hideContextMenu();

  //if the right mouse button has been clicked
  if(e.Button == e.RIGHT_BUTTON){
   var objStackFirst = e.ObjectStack[0];

   //if the target is a route object
   if(map.Session.MapObjects[objStackFirst.TargetId] instanceof Map24.Session.Route){
     var waypointIndex = null;
     var routeDesc = null;
     var routeWaypoints = null;
      var routeId = null;
     var content = "";
     var targetMapArray = null;

     //get the route (object) id from the event
     routeId = objStackFirst.TargetId;

     //split the target map
     //if there is just a number in the TargetMap, we will get an array containing one entry
     targetMapArray = objStackFirst.TargetMap.toString().split("-");
     //if a route waypoint has been clicked

     if(objStackFirst.TargetType.toLowerCase() == "routewaypoint"){
      //get the route description
      routeDesc = map.Session.MapObjects[objStackFirst.TargetId].getDescription();
      //get the route waypoints
      routeWaypoints = routeDesc.Waypoints;

      //if this is a route waypoint, we will have its index in the first entry of the array
      waypointIndex = parseInt(targetMapArray[0]); 

      //Handle first event case: Mouse click happens on the start point of the route
      if(waypointIndex == 0){
       //if the route contains more than 2 waypoints, provide the option to remove the start point
       if(routeWaypoints.length > 2){
         content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
         "Context Menu for <strong>start</strong> point<br/><br/>" +
         "<a href=\"javascript: map.Session.MapObjects['" + routeId +
         "'].removeWaypoint(0);hideContextMenu();\" target='_self'>" +
         "Remove the start point</a>" +
         ""</div>";
       }
       //if the route contains less than 2 waypoints, disable the option to remove the start point
       else{
         content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
         "Context Menu for <strong>start</strong> point<br/><br/>" +
         "<span style='color:#999999;'>Remove the start point</span>" +
         "</div>";
       }
       showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
     }

     //Handle second event case: Mouse click happens on the destination point of the route
     else if(waypointIndex == (routeWaypoints.length-1)){
       //if the route contains more than 2 waypoints, provide the option to take out the destination point
       if(routeWaypoints.length > 2){
         content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
         "Context Menu for <strong>destination</strong> point<br/><br/>" +
         "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].removeWaypoint(" + waypointIndex +
         ");hideContextMenu();\" target='_self'>Remove the destination point</a>" +
         "</div>";
       }
       //if the route contains less than 2 waypoints, disable the option to remove the destination point
       else{
         content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
        "Context Menu for <strong>destination</strong> point<br/><br/>" +
        "<span style='color:#999999;'>Remove the destination point</span>" +
        "</div>";
       }
       showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
     }

     //Handle third event case: Mouse click happens on one of the way points of the route
     else{
       content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
       "Context Menu for Waypoint <strong>" + waypointIndex + "</strong><br/><br/>" +
       "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].removeWaypoint(" + waypointIndex +
       ");hideContextMenu();\" target='_self'>Remove the waypoint</a>" +
       "</div>";
       showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
     }
   }
   //Handle forth event case: Mouse click happens anywhere on the route (not on a waypoint)
   //and if the targetMapArray contains two entrys (previous and next waypoint)
   else if(objStackFirst.TargetType == "Route" && targetMapArray.length == 2){
     content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
     "Context Menu for the route object<br/><br/>" +
     "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].insertWaypoint(" + targetMapArray[1] + ", " +
     e.Coordinate.Longitude + ", " + e.Coordinate.Latitude +
     ");hideContextMenu();\" target='_self'>Add a waypoint</a>" +
     "</div>";
     showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
    }
   }
  }
}

            

Further Information     

Learn how to create a context menu in the Context Menu tutorial.

For information on how to fetch and display the route description see the Routing Basics tutorial.

 

View example


Executable code:

                         

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
   <title>Draggable Route</title>
   <script type="text/javascript" language="javascript" src="http://api.maptp.map24.com/ajax?appkey=Your AJAX application key goes here"></script>
   <script type="text/javascript" language="javascript">

    //Declare global variables
    var map = null;
    var remconn = null;
    var LocalConn = null;
    var LonValueSt = 0;
    var LatValueSt = 0;
    var LonValueDe = 0;
    var LatValueDe = 0;
    var RouteID = "route";
    var StReady = false;
    var DeReady = false;
    var language = "en";

    function goMap24(){
     Map24.loadApi( ["core_api"] , map24ApiLoaded );
    }

    //Callback function called when the API is loaded. The map can now be shown.
    function map24ApiLoaded(){
     //Initialize mapping client and show map.
     map = new Map24.Map();
     map.addCanvas( new Map24.Canvas( { NodeName: "maparea" } ), "c" );
     map.addMapClient( new Map24.MapClient.Static(), "St" );
     map.show( "St", "c" );

     //Handler for mouse clicks on map, it is used for adding and eliminating via points
     //this listner can also be omitted, and the route is still draggable but in this case
     //via points will not be able to be eliminated by mouse
     map.addListener( "Map24.Event.MapClick", mapClickHandler );
    }

     //geocode start and destination of the route.
     function showRoute(){

      //Open a local connection.
      LocalConn = map.Local.openConnection();
      //Open a connection to the Web services.
      remconn = map.WebServices.openConnection();

      var start = document.getElementById("start_address").value;
      var destination = document.getElementById("dest_address").value;

      //submit the geocode request for start of the route.
      remconn.searchFree(
       new Map24.WebServices.Message.searchFreeRequest({
        MapSearchFreeRequest: new Map24.WebServices.MapSearchFreeRequest({
         SearchText: start,
         MaxNoOfAlternatives: 1
        })
       }),
       //callback function
       this.addRouteOnSuccessSt
     );
     //submit the geocode request for destination of the route.
     remconn.searchFree(
      new Map24.WebServices.Message.searchFreeRequest({
       MapSearchFreeRequest: new Map24.WebServices.MapSearchFreeRequest({
        SearchText: destination,
        MaxNoOfAlternatives: 1
       })
      }),
      //callback function
      this.addRouteOnSuccessDe
     );
    }

     //Get the coordinates of the route's start point
     function addRouteOnSuccessSt() {
       var ResponseMapSearchFree = this.Response.getProperty("MapSearchResponse");

       //use the first value
       var geocodedAddress = ResponseMapSearchFree.getProperty("Alternatives")[0];

       //Get longitude/latitude values
       LonValueSt = geocodedAddress.getProperty("Coordinate").getProperty("Longitude"); 
       LatValueSt = geocodedAddress.getProperty("Coordinate").getProperty("Latitude");

       StReady = true;
       //push the route if both start and destination of the route is geocoded.
       if(StReady&&DeReady)
          pushRoute();

     }


     //Get the coordinates of the route's destination point
     function addRouteOnSuccessDe() {
       var ResponseMapSearchFree = this.Response.getProperty("MapSearchResponse");

       //use the first value
       var geocodedAddress = ResponseMapSearchFree.getProperty("Alternatives")[0];

       //Get longitude/latitude values
       LonValueDe = geocodedAddress.getProperty("Coordinate").getProperty("Longitude"); 
       LatValueDe = geocodedAddress.getProperty("Coordinate").getProperty("Latitude");

       DeReady = true;
       //push the route if both start and destination of the route is geocoded.
       if(StReady&&DeReady)
          pushRoute();

     }
   

     function pushRoute(){

      StReady = false;
      DeReady = false;

      var remoteConn = map.WebServices.openConnection();

      //callback function of this webservice connection
      //this function is called if the webservice connection is opened successfully
      remoteConn.onSuccess = function(){
       route = this.Response.getProperty("CalculateRouteResponse").getProperty("Route");

       var addDragging = "";
       var mrcCommands = [];

       //these are the settings for the draggable route:
       addDragging = new Map24.WebServices.MapObjectCustomSettings({
        Handler: "MAP24_ROUTE_DIGITIZER",
        // On submit a javascript function can be called.
        Properties: [
         new Map24.WebServices.Property({
          Key:"CALLBACK_URL",Value: "javascript:showRouteDescription({EVENTID: '', ROUTEID: ''})"
         }),
         new Map24.WebServices.Property({Key:"CALLBACK_TARGET", Value:"_self"}),
         new Map24.WebServices.Property({Key:"routeColor", Value:"0,0,255,100"}),
         new Map24.WebServices.Property({Key:"routeDragColor", Value:"0,255,0,100"}),
         new Map24.WebServices.Property({Key:"startLogoURL", Value: "./route_start.png#hotspot=1,24"}),
         new Map24.WebServices.Property({Key:"destLogoURL", Value: "./route_dest.png#hotspot=1,24"}),
         new Map24.WebServices.Property({Key:"viaLogoURL", Value: "./route_via.png#hotspot=1,24"})
        ]
       })
       //defines the MRC command that declares the route object
       mrcCommands[mrcCommands.length++] = new Map24.WebServices.XMLCommandWrapper({
        DeclareMap24RouteObject: new Map24.WebServices.DeclareMap24RouteObject({
         MapObjectID: "szroute",
         Map24RouteID: route.RouteID,
         Customize: addDragging
        })
       })
       //defines the MRC command that sets the route object visible
       mrcCommands[mrcCommands.length++] = new Map24.WebServices.XMLCommandWrapper({
        ControlMapObject: new Map24.WebServices.ControlMapObject({
         MapObjectIDs: ["szroute"],
         Control: "ENABLE"
        })
       })
       //defines the MRC command which resets the map view
       mrcCommands[mrcCommands.length++] = new Map24.WebServices.XMLCommandWrapper({
        SetMapView: new Map24.WebServices.SetMapView({
         MapObjectIDs: ["szroute"],
         ClippingWidth: new Map24.WebServices.SetMapViewClippingWidth({
          ViewPercentage: 100
         })
        })
       })
       //submit the MRC commands by local connection
       LocalConn.mapletRemoteControl(
        new Map24.WebServices.Message.mapletRemoteControlRequest({
         MapletRemoteControlRequest: new Map24.WebServices.MapletRemoteControlRequest({
          Map24MRC: new Map24.WebServices.Map24MRC({
           Commands:
          mrcCommands
         })
        })
       })
      );
      //calls the function to show route descriptions
      showRouteDescription({"ROUTEID": route.RouteID});
     };
     //submit the request for calculating route
     remoteConn.calculateRoute(new Map24.WebServices.Message.calculateRouteRequest({
      CalculateRouteRequest: new Map24.WebServices.CalculateRouteRequest({
       Start: new Map24.WebServices.CoordinateAndAddress({
        Coordinate: new Map24.WebServices.Coordinate({
         Longitude: LonValueSt,
         Latitude: LatValueSt
        })
       }),
       Destination: new Map24.WebServices.CoordinateAndAddress({
        Coordinate: new Map24.WebServices.Coordinate({
         Longitude: LonValueDe,
         Latitude: LatValueDe
        })
       }),
       IgnoreWaypoints: false,
       IgnoreDescription: false
      })
     }));
    }

    //the event handler which will be triggerd by each map click
    //without this event handler, it will not be possible to handle
    //the via points with mouse clicks
    function mapClickHandler(e) {

     //don't stop processing the callback stack
     if( e.TopDown ) return true;

     hideContextMenu();

     //if the right mouse button has been clicked
     if(e.Button == e.RIGHT_BUTTON){

      var objStackFirst = e.ObjectStack[0];
      //if the target is a route object
      if(map.Session.MapObjects[objStackFirst.TargetId] instanceof Map24.Session.Route){

      var waypointIndex = null;
      var routeDesc = null;
      var routeWaypoints = null;
      var routeId = null;
      var content = "";
      var targetMapArray = null;

      //get the route (object) id from the event
      routeId = objStackFirst.TargetId;
      //split the target map
      //if there is just a number in the TargetMap, we will get an array containing one entry
      targetMapArray = objStackFirst.TargetMap.toString().split("-");

           //if a route waypoint has been clicked
           if(objStackFirst.TargetType.toLowerCase() == "routewaypoint"){
            //get the route description
            routeDesc = map.Session.MapObjects[objStackFirst.TargetId].getDescription();
            //get the route waypoints
            routeWaypoints = routeDesc.Waypoints;

            //if this is a route waypoint, we will have its index in the first entry of the array
            waypointIndex = parseInt(targetMapArray[0]);

            //if the clicked object is the start point of the route
            if(waypointIndex == 0){
             //if the route contains more than 2 waypoints, we could let the user taking out the start point
             if(routeWaypoints.length > 2){
              content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
              "Context Menu for <strong>start</strong> point<br/><br/>" +
              "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].removeWaypoint(0);hideContextMenu();\" target='_self'>Remove the start point</a>" +
              "</div>";
             }
             //otherwise we do not allowed the user to take out the start point
             else{
              content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
              "Context Menu for <strong>start</strong> point<br/><br/>" +
              "<span style='color:#999999;'>Remove the start point</span>" +
              "</div>";
             }
             showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
            }
            //if the clicked object is the destination point of the route
            else if(waypointIndex == (routeWaypoints.length-1)){
             //if the route contains more than 2 waypoints, we could let the user taking out the destination point
             if(routeWaypoints.length > 2){
              content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
              "Context Menu for <strong>destination</strong> point<br/><br/>" +
              "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].removeWaypoint(" + waypointIndex + ");hideContextMenu();\"               target='_self'>Remove the destination point</a>" +
              "</div>";
             }
             //otherwise we do not allowed the user to take out the destination point
              else{
               content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
               "Context Menu for <strong>destination</strong> point<br/><br/>" +
               "<span style='color:#999999;'>Remove the destination point</span>" +
               "</div>";
              }
              showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
             }
             //if the clicked object is an other waypoint of the route
             else{
              content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
              "Context Menu for Waypoint <strong>" + waypointIndex + "</strong><br/><br/>" +
              "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].removeWaypoint(" + waypointIndex + ");hideContextMenu();\"     target='_self'>Remove the waypoint</a>" +
              "</div>";
              showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
            }
           }
           //if the click happened on anywhere on the route (not on a waypoint).
           //and if the targetMapArray contains two entrys (previous and next waypoint)
           else if(objStackFirst.TargetType == "Route" && targetMapArray.length == 2){
            content = "<div style='border: 0px; text-align: left; background-color: #f0f0ea'>" +
            "Context Menu for the route object<br/><br/>" +
            "<a href=\"javascript: map.Session.MapObjects['" + routeId + "'].insertWaypoint(" + targetMapArray[1] + ", " + e.Coordinate.Longitude + ", " + e.Coordinate.Latitude + ");hideContextMenu();\" target='_self'>Add a waypoint</a>" +
            "</div>";
            showContextMenu(e.Coordinate.Longitude, e.Coordinate.Latitude, content);
           }
          }
         }
       }


      //hide the context menu
      function hideContextMenu() {
        //if the context menu isn't visible, don't hide it
        if(contextMenuVisibility == false){
          return;
        }
        var commands = [
          new Map24.WebServices.XMLCommandWrapper({
            ControlMapObject: new Map24.WebServices.ControlMapObject({
              Control: "DISABLE",
              MapObjectIDs: [
                 "_ctxMenu"
              ]
            })
          })
        ];
        LocalConn.mapletRemoteControl(new Map24.WebServices.Message.mapletRemoteControlRequest({
          MapletRemoteControlRequest: new Map24.WebServices.MapletRemoteControlRequest({
             Map24MRC: new Map24.WebServices.Map24MRC({
               Commands: commands
             })
           })
         }));
         contextMenuVisibility = false;
       }

       //retrieve a calculated Route from the Routing service
       //and display on web page if the route is a final result
       function showRouteDescription(args) {
         if (args['ROUTEID'] && RouteID != args['ROUTEID'])
            //this is the original or final route
            RouteID = args['ROUTEID'];
         else
           //one route description is displayed on web page and language is selected
           if (args['language'] && RouteID != "route")
             language = args['language'];
           else
             return;

           var remoteConn = map.WebServices.openConnection();

           //the request retrieves previously calculated Route from the route service
           remoteConn.fetchRoute( new Map24.WebServices.Message.fetchRouteRequest({
             FetchRouteRequest: new Map24.WebServices.FetchRouteRequest({
               RouteID: RouteID,
               IgnoreWaypoints: false,
               IgnoreDescription: false,
               DescriptionLanguage: language
             })
           }))

          //if the connection is opened successfully, start
          //this function to extract route description from FetchRouteResponse
          remoteConn.onSuccess = function(){
             var route = this.Response.getProperty("FetchRouteResponse").getProperty("Route");
             route.update(true);

             //we store the route descriptions in this variable route_table
             var route_table = [];

             route_table[route_table.length++] = '<ul style="padding-left: 20px;list-style-position:outside;list-style-type:decimal;">';

             for(var i = 0; i < route.Segments.length; i++) {
               route_table[route_table.length++] = '<li style="padding-bottom:3px;padding-top:3px;border-top:1px solid #D4D4D4;">';

               //replace map24 tags
               var formatted_text = route.Segments[i].Descriptions[0].Text;
               formatted_text = formatted_text.replace(/\[M24_STREET\]/g, '<b>' );
               formatted_text = formatted_text.replace(/\[\/M24_STREET\]/g, '</b>' );

               formatted_text = formatted_text.replace(/\[M24_LENGTH\]/g, '<b style="white-space:nowrap;">' );
               formatted_text = formatted_text.replace(/\[\/M24_LENGTH\]/g, '</b>' );

               formatted_text = formatted_text.replace(/(\[|\[\/)[0-9A-Z_]+\]/g, '' );

               route_table[route_table.length++] = formatted_text;
               route_table[route_table.length++] = '</li>';
             }
             route_table[route_table.length++] = '</ul>';
             route_table = route_table.join("");
             document.getElementById("routeDescriptionText").innerHTML = route_table;
          }
        }

   </script>
  </head>

 <body onload="goMap24();">
   <div id="maparea" style="width:700px;height:500px;background-color:#E0E0E0;"></div>
   <br />
   <input type='text' name='start_address' id='start_address' size='25' value='Street, City, ...' /><br/>
   <input type='text' name='dest_address' id='dest_address' size='25' value='Street, City, ...' />

   <input type="button" value="Route" onclick="showRoute();" />
   <img src="./flag_gb.gif" alt="English" style="cursor: pointer;" onclick="showRouteDescription({language:'en'});" />
   <img src="./flag_de.gif" alt="German" style="cursor: pointer;" onclick="showRouteDescription({language:'de'});" />
   <img src="./flag_es.gif" alt="Spanish" style="cursor: pointer;" onclick="showRouteDescription({language:'es'});" />
   <img src="./flag_fr.gif" alt="French" style="cursor: pointer;" onclick="showRouteDescription({language:'fr'});" />
   <br />
   <table>
     <tr style="margin-top: 5px;">
        <td colspan="2" id="routeDescriptionText">
           ...
         </td>
      </tr>
  </table>
</body>
</html>