Hide Table of Contents
Tutorials
About the API
Work with the API
Graphics and feature layers
Popups and Info Windows
Geoprocessor
Operations Dashboard
Create extensions
Mobile
ArcGIS Server Services
References
What's New archive
Legacy: This topic discusses extending operation views created using the Windows version Operations Dashboard for ArcGIS. This version of the application has been moved to Mature Support status. To learn more about the latest web-based version, see Operations Dashboard for ArcGIS.
A map tool in an operation view allows users to interact with a map widget. For example, the measure tool allows users to find the length or area of a shape that they define on a map widget. In this tutorial, you'll develop, style, and test a buffer map tool. The map tool calculates and displays buffer rings around a point drawn on the map.

Before you begin, you must set up your development environment for Windows Operations Dashboard and prepare it to run in developer mode.
In the extensions folder that you created in the Set up a development environment tutorial, create a folder called bufferMapTool, and create the following files in that folder:
Note: You can assign different names to the above files.
Open the buffer.json file, or the manifest file, in your IDE and add the following text:
{
"type": "Map Tool",
"title": "Buffer Map Tool",
"description": "A map tool that creates buffers from a clicked point",
"usePortalServices": true,
"runtime": {
"iconPath": "icon.png",
"path": "buffer.html",
"viewType": "toolbar"
},
"credits": "By Esri (http://www.esri.com)"
}
Note:
Note: For this map tool to work, this property must be set to true.
To learn more about the manifest file properties, see Manifest file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
</body>
</html>
<body> tags, create a <div> and add a data-dojo-type
attribute with the value extension/BufferMapTool. This attribute is used to instantiate an
extension/BufferMapTool object that comes from the buffer map tool dijit you'll create in the
BufferMapTool.js file.
<body>
<div data-dojo-type="extension/BufferMapTool"></div>
</body>
<div class="table">
<div class="table-cell labelContainer">
Click on the map to create three buffers for the clicked location.
</div>
<div class="table-cell buttonContainer">
<button data-dojo-attach-event="onClick:deactivateMapTool">Done</button>
</div>
</div>
<script> block before the closing
<body> tag. The script block contains the dojoConfig object that will load the buffer tool
dijit. To learn more about dojoConfig, see Dojo Toolkit documentation.
<script>
var dojoConfig = {
async: true,
paths: {
"extension": location.pathname.replace(/\/[^/]+$/, '')
}
};
</script>
<div> element in this
file.
<script> block from the previous step, add another <script> block
referencing the ArcGIS API for JavaScript. You must load the dojoConfig before loading the ArcGIS API for
JavaScript, otherwise the dojoConfig will be ignored.
<script src="//js.arcgis.com/3.26/"></script>
<script> block, add the Dojo parser:
<script>
require(["dojo/parser","dojo/domReady!"]
, function(parser){
parser.parse();
});
</script>
define function. This will be used to define the buffer
map tool dijit. This function loads the dependent modules and passes the modules as parameters in a callback function.
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"esri/opsdashboard/MapToolProxy",
"esri/tasks/BufferParameters",
"esri/tasks/GeometryService",
"esri/Color",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/PictureMarkerSymbol",
"esri/graphic",
"dojo/text!./bufferMapToolTemplate.html"
], function (declare, lang, _WidgetBase, _TemplatedMixin,
MapToolProxy, BufferParameters, GeometryService, Color,
SimpleLineSymbol, SimpleFillSymbol, PictureMarkerSymbol, Graphic, templateString
) {
});
...
"dojo/text!extension/bufferMapToolTemplate.html"
], function (declare, lang, _WidgetBase, _TemplatedMixin,
MapToolProxy, BufferParameters, GeometryService, Color, SimpleLineSymbol, SimpleFillSymbol, PictureMarkerSymbol, Graphic, templateString
) {
return declare("BufferMapTool", [_WidgetBase, _TemplatedMixin, MapToolProxy], {
});
});
return declare("BufferMapTool", [_WidgetBase, _TemplatedMixin, MapToolProxy], {
templateString: templateString,
});
constructor: function () {
// The buffer parameters
this.bufferParams = new BufferParameters();
this.bufferParams.unit = GeometryService.UNIT_METER;
this.bufferParams.distances = [500, 1000, 2000];
// Create the graphic for the push pin
var iconPath = location.href.replace(/\/[^/]+$/, '/');
var symbol = new PictureMarkerSymbol(iconPath + "pushpin.png", 15, 30);
symbol.yoffset = 10;
this.pushPinGraphic = new Graphic(null, symbol);
// Create the buffer graphics
var outlineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color("#000000"), 1);
var bufferSymbol = new SimpleFillSymbol(SimpleLineSymbol.STYLE_SOLID, outlineSymbol, null);
this.bufferGraphics = [];
for (var i = 0; i < 3; i++) {
this.bufferGraphics.push(new Graphic(null, bufferSymbol));
}
},
hostReady: function () {
//Set up the UI of the map tool and create the graphics layer
//when the host (Operations Dashboard) is ready
// Retrieve the geometry service specified for the organization
// Note: The buffer.json manifest file must have the "usePortalServices" set to true
// in order for the geometry service (and any other helper services) to be retrieved
if (!this.portalHelperServices || !this.portalHelperServices.geometry) {
alert("Cannot get the geometry service required for creating buffers.");
this.deactivateMapTool();
return;
}
// Update the buffer params with the target map widget spatial reference
this.bufferParams.outSpatialReference = this.mapWidgetProxy.spatialReference;
// Setup a geometry service
this.geometryService = new GeometryService(this.portalHelperServices.geometry.url);
// Update the size of the user experience
this.setDisplaySize({
width: Math.min(this.availableDisplaySize.width / 2, 400),
height: 40
});
// Creates two graphics layers to control the order of draw buffers below the pushpin.
return this.mapWidgetProxy.createGraphicsLayerProxy().then(lang.hitch(this, function (graphicsLayerProxy) {
this.bufferGraphicsLayerProxy = graphicsLayerProxy;
return this.mapWidgetProxy.createGraphicsLayerProxy().then(lang.hitch(this, function (graphicsLayerProxy) {
this.pushPinGraphicsLayerProxy = graphicsLayerProxy;
// Activate the drawing activity when the graphics layer is ready
this.activateMapDrawing({geometryType: "point"});
}));
}));
},
availableDisplaySizeChanged: function (availableSize) {
// Update the size of the user experience
this.setDisplaySize({
width: Math.min(availableSize.width / 2, 400),
height: 40
});
},
mapDrawComplete: function (geometry) {
// When the drawing activity has been performed by the user, use the resulting geometry
// to calculate the buffer rings and display them on the map
if (!geometry)
return;
// Clear the graphics layer.
this.bufferGraphicsLayerProxy.clear();
this.pushPinGraphicsLayerProxy.clear();
// Immediately show a feedback for the user
this.showPushPin(geometry);
// Starts the buffering process
this.showBuffers(geometry);
},
showPushPin: function (geometry) {
// Update the position of the push pin graphic
this.pushPinGraphic.setGeometry(geometry);
// Update the host graphics layer
this.pushPinGraphicsLayerProxy.addOrUpdateGraphic(this.pushPinGraphic);
},
showBuffers: function (geometry) {
// Use the geometry service to calculate 3 buffer rings around the clicked point
// Update the buffer params
this.bufferParams.geometries = [geometry];
// When the buffer rings have been calculated, call this.onBufferResult to update the graphics
this.geometryService.buffer(this.bufferParams, lang.hitch(this, function (geometries) {
if (!geometries || geometries.length === 0)
return;
// For each of the buffer geometries, update the buffer graphics
for (var i = 0; i < geometries.length; i++) {
this.bufferGraphics[i].setGeometry(geometries[i]);
}
// Update the host graphics layer
this.bufferGraphicsLayerProxy.addOrUpdateGraphics(this.bufferGraphics);
}));
},
deactivateMapTool: function () {
// Deactivate the map tool when the Done button is clicked
// Clean up then deactivating
this.deactivateMapDrawing();
this.mapWidgetProxy.destroyGraphicsLayerProxy(this.bufferGraphicsLayerProxy);
this.mapWidgetProxy.destroyGraphicsLayerProxy(this.pushPinGraphicsLayerProxy);
// Call the base function
this.inherited(arguments, []);
}
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"esri/opsdashboard/MapToolProxy",
"esri/tasks/BufferParameters",
"esri/tasks/GeometryService",
"esri/Color",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/PictureMarkerSymbol",
"esri/graphic",
"dojo/text!./bufferMapToolTemplate.html"
], function (declare, lang, _WidgetBase, _TemplatedMixin, MapToolProxy, BufferParameters,
GeometryService, Color, SimpleLineSymbol, SimpleFillSymbol, PictureMark-erSymbol,
Graphic, templateString) {
return declare("BufferMapTool", [_WidgetBase, _TemplatedMixin, MapToolProxy], {
templateString: templateString,
constructor: function () {
// The buffer parameters
this.bufferParams = new BufferParameters();
this.bufferParams.unit = GeometryService.UNIT_METER;
this.bufferParams.distances = [500, 1000, 2000];
// Create the graphic for the push pin
var iconPath = location.href.replace(/\/[^/]+$/, '/');
var symbol = new PictureMarkerSymbol(iconPath + "pushpin.png", 15, 30);
symbol.yoffset = 10;
this.pushPinGraphic = new Graphic(null, symbol);
// Create the buffer graphics
var outlineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color("#000000"), 1);
var bufferSymbol = new SimpleFillSymbol(SimpleLineSymbol.STYLE_SOLID, outlineSymbol, null);
this.bufferGraphics = [];
for (var i = 0; i < 3; i++) {
this.bufferGraphics.push(new Graphic(null, bufferSymbol));
}
},
hostReady: function () {
//Set up the UI of the map tool and create the graphics layer
//when the host (Operations Dashboard) is ready
// Retrieve the geometry service specified for the organization
// Note: The buffer.json manifest file must have the "usePortalServices" set to true
// in order for the geometry service (and any other helper services) to be retrieved
if (!this.portalHelperServices || !this.portalHelperServices.geometry) {
alert("Cannot get the geometry service required for creating buffers.");
this.deactivateMapTool();
return;
}
// Update the buffer params with the target map widget spatial reference
this.bufferParams.outSpatialReference = this.mapWidgetProxy.spatialReference;
// Set up a geometry service
this.geometryService = new GeometryService(this.portalHelperServices.geometry.url);
// Update the size of the user experience
this.setDisplaySize({
width: Math.min(this.availableDisplaySize.width / 2, 400),
height: 40
});
// Creates two graphics layers to control the order of draw buffers below the push-pin.
return this.mapWidgetProxy.createGraphicsLayerProxy().then(lang.hitch(this, function (graphicsLayerProxy) {
this.bufferGraphicsLayerProxy = graphicsLayerProxy;
return this.mapWidgetProxy.createGraphicsLayerProxy().then(lang.hitch(this, function (graphicsLayerProxy) {
this.pushPinGraphicsLayerProxy = graphicsLayerProxy;
// Activate the drawing activity when the graphics layer is ready
this.activateMapDrawing({geometryType: "point"});
}));
}));
},
availableDisplaySizeChanged: function (availableSize) {
// Update the size of the user experience
this.setDisplaySize({
width: Math.min(availableSize.width / 2, 400),
height: 40
});
},
mapDrawComplete: function (geometry) {
// When the drawing activity have been performed by the user, use the resulting geometry
// to calculate the buffer rings and display them on the map
if (!geometry)
return;
// Clear the graphics layer.
this.bufferGraphicsLayerProxy.clear();
this.pushPinGraphicsLayerProxy.clear();
// Immediately show a feedback for the user
this.showPushPin(geometry);
// Starts the buffering process
this.showBuffers(geometry);
},
showPushPin: function (geometry) {
// Update the position of the push pin graphic
this.pushPinGraphic.setGeometry(geometry);
// Update the host graphics layer
this.pushPinGraphicsLayerProxy.addOrUpdateGraphic(this.pushPinGraphic);
},
showBuffers: function (geometry) {
// Use the geometry service to calculate 3 buffer rings around the clicked point
// Update the buffer params
this.bufferParams.geometries = [geometry];
// When the buffer rings have been calculated, call this.onBufferResult to update the graphics
this.geometryService.buffer(this.bufferParams, lang.hitch(this, function (geometries) {
if (!geometries || geometries.length === 0)
return;
// For each of the buffer geometries, update the buffer graphics
for (var i = 0; i < geometries.length; i++) {
this.bufferGraphics[i].setGeometry(geometries[i]);
}
// Update the host graphics layer
this.bufferGraphicsLayerProxy.addOrUpdateGraphics(this.bufferGraphics);
}));
},
deactivateMapTool: function () {
// Deactivate the map tool when the Done button is clicked
// Clean up then deactivating
this.deactivateMapDrawing();
this.mapWidgetProxy.destroyGraphicsLayerProxy(this.bufferGraphicsLayerProxy);
this.mapWidgetProxy.destroyGraphicsLayerProxy(this.pushPinGraphicsLayerProxy);
// Call the base function
this.inherited(arguments, []);
}
});
});
With the map tool's UI and logic in place, add the following text to the style.css file to customize the look and feel of the extension:
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.table {
display: table;
width: 100%;
height: 100%;
}
.table-cell {
display: table-cell;
vertical-align: middle;
padding: 5px;
}
.labelContainer {
font-family: Arial;
font-size: 12px;
width: 100%;
}
.buttonContainer {
width: 20px;
}
Your extension is now ready for testing in Operations Dashboard.
Note: To learn more about creating an operation view, see Create an operation view in the PDF of the operation view help.


The map tool is now included in the map toolbar.
If you need to debug your extension, you can use the Operations Dashboard extension debugger.

Tip: You can also click Dev Mode in the upper right corner of the app, and click Open Debug Tool to launch the debugger.
Note: The URL displayed at the top of the debugger window starts from localhost:<port number> because the extension is running from a local server.

Tip: If you make a change to the HTML or JavaScript files during debugging, you can click the
Refresh button
at the upper left corner of the debugger to see the update.
However, if you make any changes to the manifest, you will need to close and reopen the operation view.
To debug your extension using the web browser, complete the following steps:

The operation view will open in your default browser.
Note: Since you're hosting your extension in the local server, you need to keep the Windows app and the local server running.