The Property Mapper is our most popular web mapping application. It allows users to search for properties and get information for tax records. People might use this website when searching for a new house, or working on a tax assessment appeal (to reduce their property taxes).
A few years ago, we migrated this site from the Esri Flex Viewer platform to Web AppBuilder. Whereas Flex Viewer used Flash, Web AppBuilder was based on HTML5 and JavaScript. It was Esri’s modern solution to building easily configurable (and cutsomizable) web mapping applications.
While being able to serve our site to more browsers/devices was a big plus, there was one drawback. The Flex Viewer supported printing the map with the contents from the feature pop-up. However, Web AppBuilder does not support this. Esri has an article discussing this issue. They even provide a sample Python script to create your own printing web service to support pop-up information.
We took a different approach. Our solution was to create a custom widget that would call the browser’s window.print() method. And we used a print media query to pull the contents from the pop-up window DOM element and put it on the print out. Developing this widget was a user experience improvement. Earlier, we had created the print media query. But we simply instructed users to run the Ctrl + P command on their keyboards to open up the browsers print dialog window. This wasn’t intuitive, so creating a widget with a “Print Map” button seemed like an improvement.

I wanted to share some of the sample code for this solution. And I recognize this may not be the best solution. But as our goal when migrating platforms was not to lose any functionality, this solution has worked well. At some point in the future, we may create a custom print service that supports printing information from the pop-up. I have always wanted to learn more about Esri’s print services.
Below is the JavaScript code for the custom widget:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// see https://developers.arcgis.com/web-appbuilder/sample-code/create-custom-in-panel-widget.htm for more info on how to create a custom widget for Esri Web AppBuilder | |
define(['dojo/_base/declare', 'jimu/BaseWidget'], | |
function(declare, BaseWidget) { | |
//To create a widget, you need to derive from BaseWidget. | |
return declare([BaseWidget], { | |
// Custom widget code goes here | |
baseClass: 'jimu-widget-printmapdialogwindow', | |
//this property is set by the framework when widget is loaded. | |
name: 'Print Map Dialog Window', | |
//methods to communication with app container: | |
// postCreate: function() { | |
// this.inherited(arguments); | |
// console.log('postCreate'); | |
// }, | |
//startup: function() { | |
// this.inherited(arguments); | |
// this.mapIdNode.innerHTML = 'map id:' + this.map.id; | |
// console.log('startup'); | |
// }, | |
onOpen: function(){ | |
// web app object; need access to it to manipulate the map object | |
var wab = this; | |
// print map button – users click on this element to print the map | |
var printMapEl = document.getElementById('printMapButton'); | |
// add 'click' event listener to print map button | |
printMapEl.addEventListener('click', function() { | |
// display the selected feature as higlighted | |
// this helps remediate some quircks with the selected feature not always appearing "selected," even though it is selected | |
wab.map.infoWindow.showHighlight(); | |
// the selected feature on the map; we will print it's popup information | |
var feat = wab.map.infoWindow.getSelectedFeature(); | |
// check if a feature is selected; if true, execture some special code; if false, just print the map | |
if (feat) { | |
// run a second conditional statement to test against the selected feature being the result of | |
// a geocode service search | |
// without this, if you try to print when the selected feature was the result of a geocoding service search, you get an error | |
if (typeof feat._layer.name !== 'undefined') { | |
// set map extent to selected feature | |
// adjust value in expand() to fit around features; this adds padding around the selected feature | |
wab.map.setExtent(feat.geometry.getExtent().expand(2.5)); | |
// center map on selected featured | |
wab.map.centerAt(feat.geometry.getCentroid()); | |
} | |
// show print loading message | |
var printLoadingMessage = document.getElementById('print-prep-msg'); | |
printLoadingMessage.style.display = 'block'; | |
// use timeout function to give map time to refresh tiles | |
// this helps ensure the map has time to redraw so the map print-out looks correct and nice | |
window.setTimeout(function() { | |
// open browser print dialog window | |
window.print(); | |
// hide print loading message | |
printLoadingMessage.style.display = 'none'; | |
}, 4000); | |
} else { | |
// open browser print dialog window | |
// no need to do fancy stuff if no features are selected | |
window.print(); | |
} | |
}); | |
} | |
// onClose: function(){ | |
// console.log('onClose'); | |
// }, | |
// onMinimize: function(){ | |
// console.log('onMinimize'); | |
// }, | |
// onMaximize: function(){ | |
// console.log('onMaximize'); | |
// }, | |
// onSignIn: function(credential){ | |
// /* jshint unused:false*/ | |
// console.log('onSignIn'); | |
// }, | |
// onSignOut: function(){ | |
// console.log('onSignOut'); | |
// } | |
// onPositionChange: function(){ | |
// console.log('onPositionChange'); | |
// }, | |
// resize: function(){ | |
// console.log('resize'); | |
// } | |
//methods to communication between widgets: | |
}); | |
}); |
Here is the HTML file for the widget:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<p>Click or Press the button below to print the map. Depending upon which web browser you are using, you may get a preview of the page. You may also get the option to save the map as a .pdf file.</p> | |
<p>If a property is selected, the map will print on the first page, and the property record will print on the second page.</p> | |
<div id="print-prep-msg"> | |
<hr /> | |
<p>The map print out is being generated…</p> | |
<img alt="spinning circle representing a computer command processing" src="images/loading.gif" /> | |
<hr /> | |
</div> | |
<input id="printMapButton" type="button" title="Print Map" value="Print Map" /> | |
</div> |
While developing this solution, we discovered a quirk with Web AppBuilder. The first time a user would search for an address, the property would appear selected. But every other time a user would search for property, the property would not appear selected. But if you were to open the print widget and click the “Print Map” button, the pop-up information would still appear. The property was selected, but the graphic representing it being selected was not shown. If users were to manually select a property, and then open the print widget, the property would always appear selected.
After some back and forth with Esri technical support, we came up with a workaround. As most users would search for a property when using this site, we added a line of code in the Search Widget to open up the Print Widget [openWidgetById(‘the id for the widget’)]. And to make the feature appear selected when opening the Print Widget, we called the map.infoWindow.showHighlight() method.
I wanted users to be able to print the map whether or not a property was selected. I used a conditional statement to execute code if a feature was selected, or just call the window.print() method if not feature was selected.
One issue I ran into was that if someone was searching for an address against our geocodig service instead of the tax parcels feature layer, an error would occur when running the Print Widget. Therefore, I added a second conditional to only run the code if the result of the Search Widget was a tax parcel (property).

Before creating this widget, users would never know quite where the map was going to print. So in addition to providing them with a logical “Print Map” button, I wanted to center the map on the selected feature (in this case, a property). I called the setExtent() method on the map object. You pass an Extent object into this method. I passed in feat.geometry.getExtent() to set the extent based upon the selected feature.
To ensure there was some padding around the selected feature, I called the expand() method. It took some experimentation to find a expand factor that worked for most instances. An improvement would be creating a function that gets the geometry of the selected feature, and then sets the expand factor based on that value. Lastly, I centered the map on the selected feature by calling the map.centerAt() method and passing in feat.geometry.getCentroid().
After centering the map on the selected feature, I call the window.print() method within a setTimeout() function. This is to help ensure the map refreshes for the print out. While the timeout is running, I add text and a graphic to inform the user that the map print-out is being generated. I chose to do this so the user is aware that something is happening. One improvement I would like to make to this solution is the save the print-out to a pdf. I’m not sure how I would approach that, so if you have any tips, please leave a comment.
One final thing to mention is that we are using the Jewelry Box Theme for the Property Mapper. Therefore, many of the styles in the sample style-sheet below apply to that template. If you want to use this print widget solution for a different template, you’ll need to do the heavy lifting of figuring out what styles need modified to get a similar result.
Stylesheet for our widget:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.jimu-widget-printmapdialogwindow p { | |
margin-bottom: 15px; | |
color: #000; | |
font-size: 1.15em; | |
} | |
.jimu-widget-printmapdialogwindow input { | |
display: block; | |
margin: 0 auto; | |
margin-top: 25px; | |
padding: 10px; | |
text-align: center; | |
font-size: 1.5em; | |
color: #fff; | |
background-color: #005fa2; | |
border-radius: 5px; | |
border-color: #005fa2; | |
-webkit-transition: background-color 0.3s ease-in-out; | |
-moz-transition: background-color 0.3s ease-in-out; | |
-ms-transition: background-color 0.3s ease-in-out; | |
transition: background-color 0.3s ease-in-out; | |
} | |
.jimu-widget-printmapdialogwindow input:hover, | |
.jimu-widget-printmapdialogwindow input:focus { | |
background-color: #000; | |
} | |
.jimu-widget-printmapdialogwindow #print-prep-msg { | |
display: none; | |
font-size: 1.5em; | |
font-weight: bold; | |
} | |
.jimu-widget-printmapdialogwindow #print-prep-msg img { | |
display: block; | |
margin: 0 auto; | |
height: 75px; | |
width: 75px; | |
} | |
/*** Print ***/ | |
/* Allow pop-up to display when printing using ctrl + p method */ | |
@media print { | |
@page { | |
margin: 0.5in 0.25in; | |
} | |
body { | |
height: auto; | |
max-width:1024px; | |
} | |
div#jimu-layout-manager { | |
position: relative !important; | |
} | |
div#map { | |
position: relative !important; | |
height: 100%; | |
width: 200% !important; | |
top: 0px !important; | |
left: 0px !important; | |
right: 360px !important; | |
margin-left: -360px !important; | |
} | |
div#map_root { | |
width: 100% !important; | |
height: 100% !important; | |
} | |
div.map div.esriMapContainer { | |
position: relative !important; | |
} | |
div#map_container { | |
position: relative !important; | |
height: 720px; | |
} | |
div.jimu-widget-scalebar { | |
margin-left: 360px !important; | |
} | |
div.jimu-widget-header-controller, | |
div.jimu-widget-onscreen-icon, | |
div.jimu-widget-zoomslider, | |
div.jimu-widget-homebutton, | |
div.jimu-widget-search, | |
div.jimu-widget-mylocation, | |
div.jimu-widget-coordinate, | |
/*div.jimu-widget-scalebar,*/ | |
div.jimu-widget-fullScreen, | |
div.jimu-widget-extent-navigate, | |
div.esriControlsBR, | |
div.popupheader, | |
div.esriPopup, | |
div.jimu-on-screen-widget-panel, | |
div.jimu-widget-attributetable, | |
div.jimu-widget-attributetable-move, | |
div.jimu-widget-attributetable-switch, | |
div.jimu-widget-attributetable div.dijitToolbar, | |
div.esriPopup div.actionsPane, | |
div.jimu-panel { | |
display: none !important; | |
} | |
div.jimu-widget-attributetable-main { | |
overflow: auto !important; | |
} | |
div.jimu-widget-attributetable-main .dijitTabContainer { | |
height: auto !important; | |
} | |
div.jimu-widget-attributetable-main .dijitTabListContainer-top, | |
div.jimu-widget-attributetable-main .dijitTabContainerTop-container, | |
div.jimu-widget-attributetable-main .dijiTabSpacer { | |
position: relative !important; | |
} | |
div.jimu-widget-attributetable-main .dgrid { | |
height: auto !important; | |
} | |
div.jimu-widget-attributetable-main .dgrid-header-row { | |
position: relative !important; | |
} | |
div.jimu-widget-attributetable-main .dgrid-scroller { | |
position: relative !important; | |
overflow-y: auto !important; | |
} | |
div.jimu-widget-attributetable-main .dgrid-content { | |
width: auto !important; | |
} | |
div.jimu-panel.jimu-ldockable-panel { | |
display: block !important; | |
position: relative !important; | |
page-break-before: always; | |
left:auto !important; | |
top:auto !important; | |
border-width: 0; | |
box-shadow: none; | |
} | |
.claro .dijitContentPane, | |
.jimu-widget-frame.jimu-container{ | |
padding-top: 0 !important; | |
} | |
} |