A few months ago, I asked for submissions to this blog stating that I was almost out of ideas. Well, this is it. This is my last idea. So, if you've got something, please help out. There is a reason this is the last post. It kind of doesn't belong here. It's really belongs on the JavaScript API Tips and Tricks Board, but that doesn't exist...yet. (Don't look at me. I'm not starting it. But if someone wants to do it, I'll play along.) But as I often say Experience Builder is mostly just a skin over the JavaScript API and this tip can be accomplished either with or without code.
And, this tip is not just for Developer Edition. There is at least some Enterprise users that may also find this useful. For many functions in Experience Builder, you will need to use Feature Layers, but for many reasons you may want to use Group Layers or Map Image Layers. In some versions of Enterprise, there is a bug that prevents you from accessing Feature Layers in various settings of Experience Builder. If you have encountered this issue, you may find it useful to hybridize your layers, shadow loading a Feature Layer to use for these Build Mode setting while showing your end users the Group or Map Image Layer. If you are on a recent version of Enterprise or Online Edition, you can stop reading now. This tip is not for you.
A quick primer for those who may not be familiar with the difference between Feature Layers and Map Image Layers. Feature Layers render client-side and transmit all their data to the end users computer which allows for Experience Builder to do stuff with it. Map Image Layer render server-side. The power of server-side rendering makes Map Image Layers faster in most circumstances. It also allows for more complex symbology and labels. The server-side labeling engine will also dynamically reposition labels as the end user moves around the map which is super useful for big polygons and long polylines. If you're not sure what kind of layer you have, a quick test is to look at the end of the URL. If you see MapServer, it's probably a Map Image Layer. If it ends in a number, it's probably a Feature Layer.
Map Image Layers can also contain sublayers and those sublayers can be Feature Layers. The way Experience Builder is designed to work, you first build a webmap preloaded with all the layers you want to use which you then pull into Experience Builder to use as data sources. And if that's how you load your data, this is your stop. You can get off the bus now. You are already using hybridized layers. But if you're a bad boy who doesn't always play by the rules and you like loading your data at runtime, say through a fancy custom widget, keep reading, the introduction is finally over.
Who's still on the Vangabus? We're going to Hybrid Layer Town!
To the five people still reading this, you're cool. Let's do this.
There are at least four different ways to mix together Map Image Layers and Feature Layers. Each of these is useful in some way.
Map Image Layer With A Hidden Feature Layer - No Code
If you're one of those Enterprise people mentioned earlier, this is the method for you.
- In the Web MapViewer, add a Map Image Layer and a Feature Layer for whatever sublayer you need a data function for.
- Using the options in the Web Map Viewer, turn off Visibility, Show In Map Legend, and Enable Pop-ups.
- To also hide the Feature Layer from the Map Layers List, use the Map Layers Widget, not the Layers Tool, as the Map Layers Widget offers the option to hide layers.
Not everything will function exactly the same using this method as if you only had a single unified layer. The most troubling thing here being if you have say a Filter Widget , the user may see data in some other Widget that should be filtered out. But Actions like Zoom To, should still behave in a reasonable manner and this is kind of the best you can do in Enterprise.
Enterprise people please get off the bus. Save yourself. Only true madness and coding lies ahead.
Feature Layer With Map Image Layer Labeling
In my project, I have a road layer that I know users will almost never turn off. I need the advanced labeling of a Map Image Layer, but I also need to dynamically load and unload a Feature Layer. This is where I use this flavor of hybrid layer.
- In the Web MapViewer, add a Map Image Layer containing the layer you want to label.
- If there are any other sublayers in the Map Image Layer, remove them.
- With the sublayer you will get your labels from, turn off Show In Map Legend and Enable Pop-ups and make sure Enable Labels is turned on.
- In the Properties Tab, go to Appearance and find the Transparency slider. Slide that thing all the way to 100% transparent. At this point, your Map Image Layer should only be labels for the specific sublayer you want to label.

- In Experience Builder, use the options in the Map Layers Widget to hide this labeling layer.
- Dynamically load a Feature Layer of the same data.
So, you could stop there and the only code you would need to write is adding a Feature Layer to a Map Widget. But...That won't work very well. You will get too many labels, as there will be labels from both the Map Image Layer and the Feature Layer and when the user turns the labeling or visibility of the Feature Layer, the labels from the Map Image Layer will still be there. But, we can fix these issues with some code. Let's address the over-labeling problem first.
A Feature Layer contains a boolean labelsVisible property, so when loading the Feature Layer set it to false and done. Nope, that won't work. At least, it won't if you give your end user a Map Layers Widget. This will turn off the double labels when it loads, but the user can just turn them back on again and with what we are going to do a little later, it will throw the Hide Labels Button out of sync. We need to alter the labels themselves. Feature Layers get their labeling info from a property appropriately called labelingInfo, this is an array of objects to support multiple label classes in a single Feature Layer. So, we can set this to an empty array... and it's not that simple either. Experience Builder rather sensibly interprets this empty array as the layer not having any labels and takes away the Hide Labels Button. We need to trick Experience Builder into thinking the Feature Layer is labeled when it actually isn't. We can do that with some code that looks like this...
featureLayer.labelingInfo = new LabelClass([
{
labelExpressionInfo: {
expression: ' '
},
symbol: {
type: 'text',
color: 'black',
font: {
family: 'Noto Sans',
size: 7,
weight: 'normal'
}
}
}
])
(I'm not certain if the symbol property is strictly necessary. I'm also not sure if the labelExpressionInfo can be '' without the space inbetween. If anyone tests this out let me know how it goes.)
With a empty space in the labelExpressionInfo, Experience Builder says, "Ah yes, it is very important I label this layer with empty spaces." and you get your Hide Label Button.
Now, we need to address how we hide the labels from the Map Image Layer. For this, we turn to my good buddy, the reactiveUtils.
const imageLayer = jimuMapView.view.map.findLayerById('idOfMapImageLayer')
//Watch for when the Map Image Layer loads and then add reactiveUtils to the Feature Layer
reactiveUtils.watch(() => imageLayer?.loadStatus === 'loaded', () => {
if (imageLayer) {
const featureLayer = jimuMapView.view.map.findLayerById('idOfFeatureLayer')
//Watch the labeling and visibility properties of the Feature Layer and hide the Map Image Layer if either are set to false.
reactiveUtils.watch(() => [featureLayer?.visible, featureLayer?.labelsVisible], ([visible, labels]) => {
if (visible && labels) {
imageLayer.visible = true
} else {
imageLayer.visible = false
}
})
}
},
{
initial: true
})
Because the only thing in the whole Map Image Layer is just the labels of a single sublayer, we can just make it invisible if the Feature Layer is invisible or unlabeled. I've used other variants of this method with multiple sublayers in my Map Image Layer, which makes it slightly more complicated, but I'll leave that for you to work out for yourself. Also, if I was being thorough, I would add a reactiveUtils to monitor the definitionExpression of the Feature Layer and copy it to the sublayer on the Map Image Layer. This would hide the filtered out labels, if the user did any filtering.
Nothing in the Webmap
In this scenario, you want the fancy labeling of a Map Image Layer, but this layer won't be used very often, so you don't want it bogging down your map when it's not in use. Now you need to make a labels-only Map Image Layer using nothing but code. This code should work...
imageLayer.listMode = 'hide'
imageLayer.sublayers = [
{
//The id here is the sublayer number you wish to bring in as a label. This is the number at the end of the Feature Layer URL.
id: 6,
legendEnabled: false,
listMode: 'hide',
opacity: 0
}
]
If you use the sublayers property of a Map Image Layer, any sublayer not listed by id will be excluded, so you can use that to filter out the unwanted sublayers while altering the properties of the one you want to keep. Set up your Feature Layer and reactiveUtils as above and you're good to go.
Ginger Rogers Style: A Map Image Layer, But Works Like A Feature Layer
Now, we have come to the true dark arts. Use these powers carefully, as you will be taxing both your server and the user's machine. There are also lag issues where the "feature" won't respond properly because the Map Image Layer has loaded, but the Feature Layer is still processing. This setup will also break the legend in the Map Layers Widget, but not the legend in the Legend Widget.
So, why would you want to do this to yourself... Map Image Layers support more complex symbology than Feature Layers and you may find complicated geometries draw significantly faster in Map Image form.
We will build an arrangement where the Map Image Layer is displayed unaltered in the map, but the buttons in the Map Layers List are actually from the Feature Layers. Yes, you heard that s correctly. This time I'm going to assume you are adding all the sublayers in the Map Image Layer as Feature Layers. We don't really need to alter the Map Image Layer this time, but we should give it listMode = 'hide'.
So, how do we go about making an invisible Feature Layer? You might think we can just set the layer's visible property to false, but this will disable many map functions, like pop-ups. However, you can alter the layer's renderer, so that it renders invisibly while still being functional. Here's what that looks like for a polygon layer...
featureLayer.renderer: {
type: 'simple',
symbol: {
type: 'simple-fill',
color: 'rgba(0,0,0,0)',
outline: null
}
}
We should also set the legendDisabled property to true and use the labelingInfo trick from above. And with that, you have a Feature Layer that has no visible presence, but will still trigger a pop-up or be found with a Zoom To.
Next, we make the buttons work by setting up some reactiveUtils.
const imageLayer = jimuMapView.view.map.findLayerById('idOfImageLayer')
reactiveUtils.watch(() => imageLayer?.loadStatus === 'loaded', () => {
if (imageLayer) {
const sublayers = imageLayer.allSublayers
sublayers.forEach(sublayer => {
const idNum = sublayer.id
const featureLayer = jimuMapView.view.map.findLayerById(`idBaseOn${idNum}`)
reactiveUtils.watch(() => featureLayer.visible, (visible => {
if (visible) {
sublayer.visible = true
} else {
sublayer.visible = false
}
}))
reactiveUtils.watch(() => featureLayer.labelsVisible, (labels => {
if (labels) {
sublayer.labelsVisible = true
} else {
sublayer.labelsVisible = false
}
}))
reactiveUtils.watch(() => featureLayer.opacity, (opacity => {
sublayer.opacity = opacity
}))
})
}
},
{
initial: true
})
This should loop through the sublayers in a Map Image Layer and create reactiveUtils monitoring the visibility, opacity and labeling of each of them.
Backwards and in high heels
Hey look, you made it to the end. Good for you. I hope some of this is actually useful to you.