ArcGIS JavaScript API: How to implement Drag’n’Dropping of pushpin
It happened that in some part of our web based project I had to use ArcGIS to communicate with world map. I have a good experience in working with GoogleEarth and Bing/VirtualEarth and would prefer to deal with them, but the usage of ArcGIS was a strong requirement of the customer (moreover it has to be an old 1.2 version). I’ve been faced with the fact that the implementation of a simple drag’n’dropping of pushpin (a small image) is not a trivial task if you use ArcGIS JavaScript API. I couldn’t find a working solution. A built-in dojox.gfx.Moveable object doesn’t meet our requirements as well, because it assumes that, at first, user selects pushpin by clicking on it, and only then he will be able to click on pushpin again and start dragging it. It’s inconvenient and intuitively incomprehensible. As the result I’ve implemented this functionality by my own. The cornerstone is an object DragableGraphic, which acts as wrapper for esri.Graphic object and provides a drag’n’drop ability leaning on the appropriate map events:
// represents wrapper for graphic object to provide drag'n'drop ability // map - instance of esri.Map // graphic - instance of esri.Graphic function DragableGraphic(map, graphic) { this._map = map; // parent map this._graphic = graphic; // moveable graphic object this._currentLocation = graphic.geometry; // current location of graphic object this._isInUse = false; // true if graphic object is in dragging this._onMouseDownHandler = null; // pointer to onMouseDown event handler this._onMouseUpHandler = null; // pointer to onMouseUp event handler this._onMouseDragHandler = null; // pointer to onMouseDrag event handler this._onMouseDragEndHandler = null; // pointer to onMouseDragEnd event handler // initializes object this._init = function() { // bind to onMouseDown that fires when graphic object is clicked on if (this._onMouseDownHandler == null) this._onMouseDownHandler = dojo.connect(this._map.graphics, "onMouseDown", this, this.onMouseDown); } // releases resources used by object this.Dispose = function() { // disconnect from onMouseDown if (this._onMouseDownHandler != null) { dojo.disconnect(this._onMouseDownHandler); this._onMouseDownHandler = null; } // if graphic object is in use, release it if (this._isInUse) { this._map.enableMapNavigation(); // enable default left button behavior this.unbindDragNDropEvents(); // disconnect from drag'n'drop events this._isInUse = false; // mark graphic object as not in use } } // binds to drag'n'drop events this.bindToDragNDropEvents = function() { // bind to onMouseDrag that fires when graphic object is being dragged if (this._onMouseDragHandler == null) this._onMouseDragHandler = dojo.connect(this._map, "onMouseDrag", this, this.onMouseDrag); // bind to onMouseDragEnd that fires when graphic object is dropped if (this._onMouseUpHandler == null) this._onMouseUpHandler = dojo.connect(this._map.graphics, "onMouseUp", this, this.onMouseUp); } // unbinds from drag'n'drop events this.unbindDragNDropEvents = function() { // disconnect from onMouseDrag if (this._onMouseDragHandler != null) { dojo.disconnect(this._onMouseDragHandler); this._onMouseDragHandler = null; } // disconnect from onMouseUp if (this._onMouseUpHandler != null) { dojo.disconnect(this._onMouseUpHandler); this._onMouseUpHandler = null; } } // fires when graphic object is clicked on this.onMouseDown = function(event) { if (event.button != 1) // ignore if clicked button is not a left one return; // ignore if clicked graphic object is not an object contained in the current DragableGraphic-object if (event.graphic != this._graphic) return; // if graphic object isn't in use yet, start using it if (!this._isInUse) { this._isInUse = true; // mark graphic object as in use this._map.disableMapNavigation(); // prevent default left button behavior this.bindToDragNDropEvents(); // connect to drag'n'drop events } } // fires when graphic object is dropped this.onMouseUp = function(event) { if (event.button != 1) // ignore if clicked button is not a left one return; // ignore if clicked graphic object is not an object contained in the current DragableGraphic-object if (event.graphic != this._graphic) return; // if graphic object is in use, release it if (this._isInUse) { this._currentLocation = event.mapPoint; // preserve end graphic object location this._graphic.setGeometry(event.mapPoint); // move graphic object to the end location this._map.enableMapNavigation(); // enable default left button behavior this.unbindDragNDropEvents(); // disconnect from drag'n'drop events this._isInUse = false; // mark graphic object as not in use } } // fires when graphic object is being dragged this.onMouseDrag = function(event) { if (this._isInUse) { this._currentLocation = event.mapPoint; // preserve a new graphic object location this._graphic.setGeometry(event.mapPoint); // move graphic object to the new location } } this._init(); // initialize object }
The next object represents a Pushpin: it creates esri.Graphic instance, adds it to the map and creates DragableGraphic inside itself:
// represents wrapper for DragableGraphic // map - instance of esri.Map // imgUrl - string-based image url // height - image height in pixels // height - image width in pixels // mapPoint - instance of esri.geometry.Point function Pushpin(map, imgUrl, height, width, mapPoint) { this._map = map; // parent map this._initialLocation = mapPoint; // initial location of pushpin this._height = height; // pushpin image height this._width = width; // pushpin image width this._imgUrl = imgUrl; // pushpin image url this._dragableGraphic = null; // reference to DragableGraphic object // initializes object this._init = function() { // create PictureMarkerSymbol var symbol = new esri.symbol.PictureMarkerSymbol(this._imgUrl, this._width, this._height); // create graphic object based on symbol var graphic = new esri.Graphic(this._initialLocation); graphic.setSymbol(symbol); // add graphic object to the map this._map.graphics.add(graphic); // create DragableGraphic object to provide drag'n'drop ability for graphic object this._dragableGraphic = new DragableGraphic(this._map, graphic); } // releases resources used by object this.Dispose = function() { if (this._dragableGraphic != null) { // remove graphic object from the map this._map.graphics.remove(this._dragableGraphic._graphic); // call Dispose of DragableGraphic this._dragableGraphic.Dispose(); } } // returns the current location of Pushpin on the map this.GetCurrentLocation = function() { if (this._dragableGraphic != null) return this._dragableGraphic._currentLocation; else return null; } this._init(); // initialize object }
The following piece of code demonstrates how to create Pushpin:
var mapPoint = new esri.geometry.Point(-13042885, 4033247, new esri.SpatialReference({ wkid: 102100 })); var pushpin = new Pushpin(map, "http://help.arcgis.com/en/webapi/javascript/arcgis/demos/images/play.png", 25, 25, mapPoint); var mapPoint2 = new esri.geometry.Point(-13042885, 2033244, new esri.SpatialReference({ wkid: 102100 })); var pushpin2 = new Pushpin(map, "http://help.arcgis.com/en/webapi/javascript/arcgis/demos/images/play.png", 25, 25, mapPoint2);
Below is an example of aspx-page that uses Pushpin:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ArcGISDragNDrop.aspx.cs" Inherits="ChoosePlaceOnMap.ArcGISDragNDrop" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <meta http-equiv="X-UA-Compatible" content="IE=7,IE=9" /> <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" /> <title>ArcGIS Drag'n'drop sample</title> <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.3/js/dojo/dijit/themes/claro/claro.css" /> <link rel="stylesheet" type="text/css" href= "http://serverapi.arcgisonline.com/jsapi/arcgis/1.2/js/dojo/dijit/themes/tundra/tundra.css" /> <script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis?v=1.2"></script> <script type="text/javascript"> // ... DragableGraphic declaration has to be here // ... Pushpin declaration has to be here var map = null; var pushpin = null; var pushpin2 = null; dojo.require("esri.map"); dojo.addOnLoad(function() { dojo.style(dojo.byId("map"), { width: dojo.contentBox("map").w + "px", height: dojo.contentBox("map").h + "px" }); esriConfig.defaults.map.slider = { right: "10px", top: "10px", width: null, height: "100px" }; map = new esri.Map("map", { wrapAround180: true, nav: false }); // pushpins will be added after map layers is loaded dojo.connect(map, "onLoad", function() { var mapPoint = new esri.geometry.Point(-13042885, 4033247, new esri.SpatialReference({ wkid: 102100 })); pushpin = new Pushpin(map, "http://help.arcgis.com/en/webapi/javascript/arcgis/demos/images/play.png", 25, 25, mapPoint); var mapPoint2 = new esri.geometry.Point(-13042885, 2033244, new esri.SpatialReference({ wkid: 102100 })); pushpin2 = new Pushpin(map, "http://help.arcgis.com/en/webapi/javascript/arcgis/demos/images/play.png", 25, 25, mapPoint2); }); var layer = new esri.layers.ArcGISTiledMapServiceLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"); map.addLayer(layer); //var tmpPushpinCurretnLocation = pushpin2.GetCurrentLocation(); //pushpin.Dispose(); //pushpin2.Dispose(); }); </script> </head> <body class="claro"> <form id="form1" runat="server"> <div id="map" style="position:absolute; left:400px; top:100px; width:1024px; height:512px;border: 1px solid #A8A8A8; background-color:#ffffff; " /> </form> </body> </html>
The full project of the example you can download from here.