Select to view content in your preferred language

Add/Remove Layers or Show/Hide Layer with javascript (Experience Builder Developer)

2980
8
Jump to solution
11-06-2023 06:42 AM
JasonBOCQUET
Frequent Contributor

Hello community.

I'm looking to use some javascript script to improve my experience builder application.

 

I want to create a button to Add layers from one of my Feature Layers service and it works :

const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

const formSubmit = (evt) => {
    evt.preventDefault()
    const layer = new FeatureLayer({
      url: 'MYURL'
    })
    jimuMapView.view.map.add(layer)
  }

return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>

 

I repeat the "formSubmit" const each time I need to create multiple button depending of which layer I want to add on a Map.

But now, I want to allow my users to REMOVE the layers that it was added to the map.

So I try this :

const formSubmit3 = (evt) => {
    evt.preventDefault()

    jimuMapView.view.map.remove(layer)
  }

with the same code on the return part to create my button "Remove". But it doesn't work.

I think i don't get one thing of how to use the remove function. I need to use another function to call my previous layers ?

 

And my second question is : Rather than add/remove Feature Layers, which function can I use to Show/Hide layers from a list of layers ?

 

Thanks if anyone can help me !

0 Kudos
1 Solution

Accepted Solutions
JeffreyThompson2
MVP Regular Contributor

As a rule, I try to not to actually write code for other people, so they can better see how to fix their own problems in the future, but it will be difficult to explain this without coding it all out.

You actually have two problems. The issue with State mentioned above and also your references to layer are scope-bound to the functions they are defined in. (Google "scope javascript" if that didn't make sense, scope is a very important general programming concept.)

The following modifications should create an on and off button for your layer.

import { React, AllWidgetProps } from 'jimu-core'
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis'
import FeatureLayer from 'esri/layers/FeatureLayer'

const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()
  // Creates a reference to layer saved to State
  const [layer, setLayer] = useState(null)

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

  const formSubmit = (evt) => {
    evt.preventDefault()
    //Renamed constant to prevent any naming conflict
    const item = new FeatureLayer({
      url: 'URL1',
      //If you want this layer to not appear in the Layer List on load, add this. (Notice: comma above.)
      listMode: 'hide'
    })
    //Adds layer to the map.
    jimuMapView.view.map.add(item)
    //Saves the reference to the layer to State.
    setLayer(item)
  }
  
const formSubmit3 = (evt) => {
    evt.preventDefault()
    //Removes the layer from the map.
    jimuMapView.view.map.remove(layer)
    //Cleans out the reference from State so widget can be reused.
    setLayer(null)
  }
  
  
  return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>
	 
	   <form onSubmit={formSubmit3}>
        <div>
          <button>Delete</button>
        </div>
      </form> 
	  
    </div>
  )
}

export default Widget

You can modify the layer properties on load as in the code above. Or change the properties later with layer.listMode = 'hide'.

GIS Developer
City of Arlington, Texas

View solution in original post

8 Replies
JeffreyThompson2
MVP Regular Contributor

I have been thinking about writing up a primer on React for Experience Builder and if your problem is what I think it is, it would make a really great example. Could you please share your entire code as is and do you mind if I use it in an instructional post on the Tips and Tricks Board?

In React, everytime a component re-renders, it will forget everything that is not explicitly saved by storing it in State or a Ref. I think your .remove() is not working because the component has lost the reference to layer, so that variable is now undefined. You will need to use a useState() function to store the layer reference so that you can retrieve it later and call .remove().

The layer class has a property called .listMode. https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-Layer.html#listMode Layers can be hidden from the layer list with layer.listMode = 'hide' and shown with layer.listMode = 'show'.

GIS Developer
City of Arlington, Texas
0 Kudos
JasonBOCQUET
Frequent Contributor

No problem if you want to use my case to made an instructional post 😉

 

Here my complete code : 

import { React, AllWidgetProps } from 'jimu-core'
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis'
import FeatureLayer from 'esri/layers/FeatureLayer'


const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

  const formSubmit = (evt) => {
    evt.preventDefault()
    const layer = new FeatureLayer({
      url: 'URL1'
    })
    jimuMapView.view.map.add(layer)
  }

const formSubmit2 = (evt) => {
    evt.preventDefault()

    const layer = new FeatureLayer({
      url: 'URL2'
    })

    jimuMapView.view.map.add(layer)
  }
  
const formSubmit3 = (evt) => {
    evt.preventDefault()

    jimuMapView.view.map.remove(layer)
  }
  
  
  return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>
	  
	  <form onSubmit={formSubmit2}>
        <div>
          <button>Add Layer2</button>
        </div>
      </form>
	   <form onSubmit={formSubmit3}>
        <div>
          <button>Delete</button>
        </div>
      </form> 
	  
    </div>
  )
}

export default Widget

 

for the .listMode property, it work as the same way than add/remove layers ? Or it need to use another library ? I don't understand how can i know which library it's used when I want to call a function. For me I've only to use "import Layer" right ?

0 Kudos
JeffreyThompson2
MVP Regular Contributor

As a rule, I try to not to actually write code for other people, so they can better see how to fix their own problems in the future, but it will be difficult to explain this without coding it all out.

You actually have two problems. The issue with State mentioned above and also your references to layer are scope-bound to the functions they are defined in. (Google "scope javascript" if that didn't make sense, scope is a very important general programming concept.)

The following modifications should create an on and off button for your layer.

import { React, AllWidgetProps } from 'jimu-core'
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis'
import FeatureLayer from 'esri/layers/FeatureLayer'

const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()
  // Creates a reference to layer saved to State
  const [layer, setLayer] = useState(null)

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

  const formSubmit = (evt) => {
    evt.preventDefault()
    //Renamed constant to prevent any naming conflict
    const item = new FeatureLayer({
      url: 'URL1',
      //If you want this layer to not appear in the Layer List on load, add this. (Notice: comma above.)
      listMode: 'hide'
    })
    //Adds layer to the map.
    jimuMapView.view.map.add(item)
    //Saves the reference to the layer to State.
    setLayer(item)
  }
  
const formSubmit3 = (evt) => {
    evt.preventDefault()
    //Removes the layer from the map.
    jimuMapView.view.map.remove(layer)
    //Cleans out the reference from State so widget can be reused.
    setLayer(null)
  }
  
  
  return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>
	 
	   <form onSubmit={formSubmit3}>
        <div>
          <button>Delete</button>
        </div>
      </form> 
	  
    </div>
  )
}

export default Widget

You can modify the layer properties on load as in the code above. Or change the properties later with layer.listMode = 'hide'.

GIS Developer
City of Arlington, Texas
JasonBOCQUET
Frequent Contributor

Thanks ! It works ! Sorry for making you break your own rule.

So If I have correctly understand, without using the following line :

const [layer, setLayer] = useState(null)

 It's impossible tu re-use a variable in another function ?

 

And I think I misspoke about the functionnality of show/hide. I want to create my own script to separate in several Features List all layers of my WebMap. By this, I need to know what is the correct library to create the check-box to allow the user to show/hide one of the feature layer. (If it possible...)

With the ListMode it make it able or not to show the layer in the list. 

0 Kudos
JeffreyThompson2
MVP Regular Contributor

Not exactly. The purpose of useState() is to preserve values through React re-renders. React components are re-rendering all the time because something higher up the React tree is updating and anytime a component calls setState(), it forces a re-render of that component. Add console.log('re-render') on the line under const Widget and see how often it actually happens.

If you want to use multiple Map Layers widgets that can all be customized, that functionality has now been added to the ArcGIS Online version. It will be available in Developer Edition 1.13 which will be released in November or December, so I suggest just waiting for the update to be released. https://www.esri.com/arcgis-blog/products/experience-builder/announcements/whats-new-in-arcgis-exper...

The listMode property does allow layers to be hidden/un-hidden in the Map Layers widget if you need to change this property at runtime.

GIS Developer
City of Arlington, Texas
0 Kudos
JasonBOCQUET
Frequent Contributor

Ok ! I think I have understand. I need to practice more to be better on javascript but I have a short time to make my application. It's hard ^^

 

For the update on the Map Layer widget. It seems to be an upgrade of the "group" layer and it's only in one pop-up box ? I try to see in ArcGOL but I don't get the difference, it's the same option that I have in Exp Builder 

JasonBOCQUET_0-1699291062614.png

With this fonctionnality in 1.12 I can choice which layer have to be show/hide. In 1.13 it's possible to do this but a multiple time ?

 

0 Kudos
JeffreyThompson2
MVP Regular Contributor

In 1.12, you can customize your Map Layer widget so you can decide which layers to show, but if you try to add a second Map Layer widget, you will see an error saying it cannot be customized.

In 1.13, you can add as many Map Layer widgets as you like and customize them all.

GIS Developer
City of Arlington, Texas
0 Kudos
JasonBOCQUET
Frequent Contributor

All right ! It will be good for my project to create many buttons with certain layers of functionality grouped by theme.

 

Let's go learn Javascript as fast as possible and wait for the 1.13 update in dev edition.

 

Thanks 🙂

 

0 Kudos