A few months ago, I wrote about a Public Notification Application I was working on and a weird, mostly functional workaround for making printing work the way my users wanted it. Well, I'm going to talk about that application again and another weird, mostly functional workaround for making printing work the way my users wanted it.
That application I made back in November fell short of the design requirements in three ways:
- The user interface was clunky.
- With multiple maps and an always on Near Me listener, it just was not a good user experience.
- The users needed to be able to select multiple parcels or zoning areas at once to use as their selection areas.
- The users needed to make a print out with a second overview map.
- In my original post, I mentioned an ArcGIS Pro Add-In I am competing with and that I could not possibly meet it's full capabilities. This was what I was talking about. I had no idea how to make this happen.
Thanks to some recent events, I am revisiting this application and I have a path for addressing all of these issues. I also mentioned a personal goal that I would not make any Widgets for this application and it would have been great if I had worded it in that very specific way because I could say I meet my goal. If I bend my words like this...
- I would not make any Widgets for this application.
- I made @Brian_McLeer make Widgets for this application.
- A big part of what kicked off this new attempt at this application was when Brian added shapefile exports and extracting map features to the Draw Widget.
- I encouraged him to make it possible to extract multiple features at once and optionally merge them together.
- I also encouraged him to make Draw features sendable to his Mailing Labels Widget and my upcoming rebuild of my Identify Widget.
- I would not make any Widgets for this application.
- As mentioned above, I am finalizing work on a new version of my Identify Widget. I'm really proud of it. I think you'll like what it can do.
- I would not make any Widgets for this application.
- The actual subject of this post isn't exactly a Widget, at least it's not like any Widget I've made before. It's a separate webpage inside, but also outside, of the Experience Builder framework.
...And with that annoyingly, long introduction, let's get to the actual subject of this post: You can make dynamic webpages within Experience Builder.
What do I mean by a dynamic webpage? It's a page built on-demand to highlight some arbitrary content. Go to Generic Shopping Website Of Your Choice and click on one of the items. Now, click another item. See how those pages look similar. They have different names, prices, pictures and so on, but the layout is the same. All the stuff is in the same place. Think about how many things Generic Shopping Website Of Your Choice has for sale. They didn't pay somebody to make a new page for each of those things. They have a database of items and when you click on one of them the site uses that data on a template to make the item page.
Experience Builder tries to sell itself as a competitor with Wordpress and Squarespace. A full featured website builder that just happens to be more map/data focused. My biggest complaint with that characterization and what I think keeps Experience Builder from being a true full featured framework is the lack of support for dynamic webpages. You can't build Generic Shopping Website Of Your Choice with Experience Builder.
Are we supposed to be talking about printing with an overview map? What does any of this have to do with that? Back at DevSummit, I was having a chat with Joey Harig (other people were also involved in the conversionation, but the idea came from Joey) about this issue and he suggested using a HTML page to load this second overview map.
So that's how this Widget works. The Widget part of this Widget is little more than a link to the HTML page where the JavaScript on that page does most of the work. And I do mean JavaScript, this page isn't TypeScript, TSX or JSX, or React. None of this is complied. In my src/runtime/assets folder, based on this sample widget, I have a set HTML, JS and CSS files just liked you'd see in baby's first website. Other than Joey and the author of the Use Assets Sample Widget, I also have to give a lot of credit to Microsoft Copilot.
Recently, I have been going to generative AI early in the development process for things I know must be solved problems, but I personally don't know how to do. So, I gave Copilot the task of opening a link in a new tab, sending it a data package, and reading it on the other side. It took several iterations, I improved the error handling, and this Widget still isn't flawless. Sometimes the HTML page will not receive the screenshot of the primary map and you will need to close the page and press the button again. I assume it's some sort of race condition problem, but I called it good enough for my purposes and didn't investigate too thoroughly. If you manage to find the problem, please let us know.
The data sending function in widget.tsx looks something like this:
const sendData = async () => {
const child = window.open(`${props.context.folderUrl}dist/runtime/assets/child.html`, "_blank")
if (!child) return
const payload = {
data
}
// One-time handshake
const channel = new MessageChannel()
channel.port1.onmessage = (e) => {
if (e.data?.type === "ACK") channel.port1.close()
}
// Send to child (same-origin recommended; validate on receiver)
child.postMessage(payload, location.origin, [channel.port2])
}
With the receiving function in the JS file looking something like this:
const ac = new AbortController()
let attempts = 0
const receiveData = (ev) => {
//error handling
attempts++
if (attempts > 5) {
failFunction()
ac.abort()
}
if (ev.origin !== expectedOrigin) return
if (ev.source !== window.opener) return
//If data received
ev.ports?.[0]?.postMessage?.({ type: "ACK" })
const data = ev.data.data
processData(data)
ac.abort()
}
window.addEventListener('message', receiveData, {signal: ac.signal})
With the processData() function adding some text to the page and loading another Map Component, adding an extent indicator to it, taking a screenshot of it and removing itself. Feel free to look at the actual code for the details.
In an earlier draft, I attempted to send the entire MapView object to the HTML page with the intent that I would attach it to a Map Component in the child page. In order for the data package to be sent between tabs, it must be serialized into JSON and the MapView object is just too complicated for that to work. I also tried to get Copilot to chop the MapView into serializable portions and re-assemble it on the other side, but whatever Copilot made just didn't work and I abandoned the concept for a simpler screenshot transmission. I'm pointing this out so you know what kind of problem you would need to solve if you wanted to transmit a map in some other Widget or modify this Print Widget for more complicated map surrounds like a Legend or Scale Bar.
Somewhere around here, I realized what I had just done. I made a dynamic webpage. I could make Generic Shopping Website Of Your Choice like this. Imagine a Custom List Widget and clicking on a item would open an entire page of data related to that thing. You could do that. And this blank sheet of HTML, isn't bound by any of the restrictions of the Experience Builder framework. (It also doesn't have any of the benefits of the framework.) Want to bring in a map from another Portal? Don't see any reason it wouldn't work. Hack a feature from a new version of the JavaScript SDK into an old Enterprise Portal? Should be able to do that, too. The only limit is your imagination (and what objects you can serialize).
Ok, so we've talked about how this Widget works. Let's talk about the Widget itself. As I have been alluding to, this isn't exactly a full featured replacement to the standard Print Widget. (That's part of why it's on this blog and not over in the Custom Widgets Group. I see it more as a teaching demo than something you will actually use.) It's designed to make one fairly simple page layout that just happens to include an overview map. If you want to use this yourself, you'll probably need to make some modifications in the code base to make it work the way you want it.
If you still want to use this, you'll need to go to your Portal and create a Webmap to use as the Overview Map. You'll also need to make OAuth 2.0 Credentials. (Remember: This HTML page doesn't know you're using Experience Builder, so the Portal doesn't know who you are.) I suggest restricting this token to just your Overview Map to limit potential security risks. You'll also need a logo at some URL your page can access. Get the Item ID of the Overview Map, the Client ID of your OAuth Credentials, the URL of your logo and some disclaimer text and put it in the Builder.

The end user will add a Title, their name, and a map description in the Widget interface and press a button.

And they should be rewarded with a print out that looks something like this:

I hope you all find lots of cool weird stuff to do with this trick. And remember the proper way to fix a printer.

print-with-overview-custom.zip