Select to view content in your preferred language

Accessing Map Widget (WebMap) Reference in a Custom Widget in Experience Builder

275
12
Jump to solution
Monday
MirzaMuhammadAhsanAli
Occasional Contributor

Hello,

I am working on experience builder, migrating a web app of Web App Builder into it from scratch. In WAB we have a map reference in this.map so that we can apply definationexpressions. Now in Experience builder how can i get the reference of the map in custom widget. I have used map widget and attached webmap on it.

@Sage 
Can anybody help me to get the reference of the map in custom widget?

0 Kudos
1 Solution

Accepted Solutions
JeffreyThompson2
MVP Frequent Contributor

JeffreyThompson2_1-1750871845184.png

Your file structure is incorrect. The setting folder must be directly under the src folder. Experience Builder is highly opinionated and will not find files or folders if they are not placed in the correct place with the correct name.

I have not reviewed your code closely, so there could be other issues as well.

GIS Developer
City of Arlington, Texas

View solution in original post

12 Replies
MirzaMuhammadAhsanAli
Occasional Contributor

@JeffreyThompson2 , can you please help.

0 Kudos
TimWestern
MVP

I'll ask a few clarifying questions until @JeffreyThompson2 can chime in.

You mention in a custom widget you want to reference maps.  The map URLs used in WepAppBuilder should reside at the same place as they did before, you just need to add them as a map or datasource in the editor part of ExperienceBuilder.

I beleive Jeffrey has a post over here that may be related: https://community.esri.com/t5/arcgis-experience-builder-questions/how-to-get-the-data-source-for-add... 

I'm not sure how you had WAB deployed, if its in portal itself, or on a standalone server.

if its a stand alone server you might look in the folder where ArcGISWebAppBuilder\server\apps\{number for the app}|\config.json for something that looks like this:

"map": {
"3D": false,
"2D": true,
"position": {
"left": 0,
"top": 0,
"right": 0,
"bottom": 0
},
"itemId": "6a749c0dd65b4592ae3814df0500db7b",
"mapOptions": {},
"id": "map",
"portalUrl": "https://exampleurl.com",
"mapRefreshInterval": {
"useWebMapRefreshInterval": true
}

the itemId would trace to what item in the portal it is.  from there you might be able to find the full url EXB needs to connect and use the map as a data source.  (I think there is another variant where the url is fully there under an "operationalLayers" key, but I don't have a local example I can refer to.


So I think i read somewhere you could then take the id: https://www.arcgis.com/home/webmap/viewer.html?webmap=6a749c0dd65b4592ae3814df0500db7b

whatever that ID is might be the reference you are looking for?

IN theory if this is in your AGOL or AGE Portal you could search for it there, and open the page for it and find the information


So for example that would show up here:
TimWestern_0-1750688703035.png

For other data sources, like Feature layers there will be a box on the right hand side with a URL you can open or copy paste to verify its the same service for a given data source.

Those can then in theory be added to the EXB through the Add Sources panel.

You can look for this icon on the left side  of EXB when editing:

 

TimWestern_0-1750689135365.png

editor part of Experience Builder.

and then either add Data from there:
TimWestern_4-1750688912684.png



or

from select map on the right (after clicking on the map you need to add:

TimWestern_3-1750688891747.png

 

If you don't have one already added you click add Data which takes you to a dialog that has this at the top:

TimWestern_6-1750688966094.png


You can then browse under My Content, My Groups, My Organization, etc. (Which maps with what you'd see in Portal under Content), and find the map in question, and it adds it automatically

 

 

TimWestern_7-1750689032515.png


Let us know if this does not fully answer the question, as it may be there is more to what you are asking than just connecting an existing widget.



For a custom widget, you may want to read up on how to create a settings section for your widget



Have you already tried the starter widget?
https://developers.arcgis.com/experience-builder/guide/create-a-starter-widget/
And
https://developers.arcgis.com/experience-builder/guide/get-map-coordinates/ (this has notes about the settings panel)

(There is a lot to learn with custom widgets, but it may be you need to look at how to setup a widget settings section, to learn how to setup your own control to select the Map from what is available on the given portal)



0 Kudos
MirzaMuhammadAhsanAli
Occasional Contributor

@TimWestern , Thank you for response.

Let me explain the scenario in detail.

I have create a app in experience builder , added a built in  Map Widget in application then added data and added a webmap for ArcGIS Enterprises portal. Then I added a custom widget in that application , let suppose i query the feature layers and get some points in result. Now i want to show these points on map. In custom widget how can i told the map to change the extent to show only result points?  like in Web App builder, this.map has the reference to map and we can use this map object to change the extent and zoom on a map. I want to do same thing in Experience Builder


0 Kudos
TimWestern
MVP

There are a couple of things you could do, however, given you are talking about zoom which I know some widgets have actions for things like pan and zoom, this might require a custom action when the event on the Custom Widget fires:
https://doc.arcgis.com/en/experience-builder/latest/configure-widgets/action-triggers.htm

Would be where I would start.

The only thing I'm not sure about is whether you've already setup the data for the custom widget by creating a settings section to get the references to the layers you are working with.

But based on what You've said so far, the widget needs to fire an action after the extent/selection is done (it might need to fire more than one if extent is going to change, I've not encountered doing this in my work yet though)  But if firing an existing action on the map is not enough, you could make  custom action as described here.

0 Kudos
MirzaMuhammadAhsanAli
Occasional Contributor

@TimWestern , Thank You for your assistence.

Can I got the map object in my custom widget, so that I can do as I had done in webAppBuilder, like find the layer from the map object , get the current defination expression and change the defination expression with respect to filters on frontend?

Currently , I have not setup setting panel for my custom widget, now i am trying by creating setting panel to. Please help me to resolve this issue.

0 Kudos
MirzaMuhammadAhsanAli
Occasional Contributor

@TimWestern ,

MirzaMuhammadAhsanAli_0-1750850459381.png


I have followed this => https://developers.arcgis.com/experience-builder/guide/get-map-coordinates/ (this has notes about the settings panel)

but there is no map option inside the right panel of the custom widget, i have restarted the client as well. 

My code Hirerchy

MirzaMuhammadAhsanAli_3-1750850644307.png

 

widget.jsx

/* eslint-disable @typescript-eslint/quotes */
/* eslint-disable @typescript-eslint/consistent-type-imports */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { React, type AllWidgetProps } from 'jimu-core';
import { type IMConfig } from '../config';
import DashboardTiles, { TileConfig } from '../components/DashboardCard';
import Query from '@arcgis/core/rest/support/Query';
import * as query from '@arcgis/core/rest/query';
import { useState, useEffect } from 'react';
// import { JimuMapView, JimuMapViewComponent } from 'jimu-arcgis';
import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';

const Widget = (props: AllWidgetProps<IMConfig>) => {
  const [tilesCountData, setTilesCountData] = useState<TileConfig[]>([]);
  const [jimuMapView, setJimuMapView] = useState<JimuMapView | null>(null);

  // Debug: Log useMapWidgetIds
  useEffect(() => {
    console.log('Map widget IDs:', props.useMapWidgetIds);
  }, [props.useMapWidgetIds]);

  // Debug: Log jimuMapView changes
  useEffect(() => {
    console.log('Jimu Map:', jimuMapView);
  }, [jimuMapView]);

  const onActiveMapViewChange = (jimuMapView: JimuMapView | null) => {
    console.log('onActiveMapViewChange called, jimuMapView:', jimuMapView);
    setJimuMapView(jimuMapView);
  };

  // Fetch tile counts
  useEffect(() => {
    const tilesData: TileConfig[] = props.config?.jsonData?.asMutable({ deep: true }) ?? [];
    const fetchCounts = async () => {
      const updatedTiles = await Promise.all(
        tilesData.map(async (tile) => {
          const queryParam = new Query({
            where: tile.whereClause,
            returnGeometry: false,
            outFields: ['*'],
          });

          try {
            const result = await query.executeQueryJSON(tile.layerUrl, queryParam);
            return {
              ...tile,
              count: result.features?.length ?? 0,
            };
          } catch (err) {
            console.error('Error fetching tile count:', err);
            return {
              ...tile,
              count: 0,
            };
          }
        })
      );
      setTilesCountData(updatedTiles);
    };
    fetchCounts();
  }, [props.config?.jsonData]);

  // Handle tile click and apply query to map
  // const handleTileClick = async (tile: TileConfig) => {
  //   console.log('Tile clicked:', tile);

  //   // Apply query to map
  //   if (jimuMapView && jimuMapView.view) {
  //     try {
  //       // Find the layer in the map by URL
  //       const layer = jimuMapView.view.map.layers.find((layer) => layer.url === tile.layerUrl);
  //       if (layer) {
  //         // Apply query as a definition expression
  //         layer.definitionExpression = tile.whereClause;
  //         console.log(`Applied query to layer: ${tile.whereClause}`);

  //         // Optional: Zoom to features
  //         const queryParam = new Query({
  //           where: tile.whereClause,
  //           returnGeometry: true,
  //           outFields: ['*'],
  //         });
  //         const result = await query.executeQueryJSON(tile.layerUrl, queryParam);
  //         if (result.features.length > 0) {
  //           await jimuMapView.view.goTo(result.features.map((f) => f.geometry));
  //           console.log('Zoomed to features');
  //         }
  //       } else {
  //         console.warn(`Layer with URL ${tile.layerUrl} not found in map`);
  //         console.log('Available layers:', jimuMapView.view.map.layers.toArray());
  //       }
  //     } catch (error) {
  //       console.error('Error applying query to map:', error);
  //     }
  //   } else {
  //     console.warn('Map view is not available, jimuMapView:', jimuMapView);
  //   }

  //   // Existing REST query
  //   const queryParam = new Query({
  //     where: tile.whereClause,
  //     returnGeometry: true,
  //     outFields: ['*'],
  //   });

  //   try {
  //     const result = await query.executeQueryJSON(tile.layerUrl, queryParam);
  //     console.log('Query result:', result?.features);
  //   } catch (error) {
  //     console.error('Query error:', error);
  //   }
  // };

  const handleTileClick = async (tile: TileConfig) => {
    console.log("Clicked on Tile");
  }

  return (
    <div className="widget-starter jimu-widget">
      {props.useMapWidgetIds && props.useMapWidgetIds.length === 1 && (
    <JimuMapViewComponent useMapWidgetId={props.useMapWidgetIds?.[0]} />
  )}
      <DashboardTiles tilesConfig={tilesCountData} onTileClick={handleTileClick} />
    </div>
  );
};

export default Widget;
 
setting.jsx 
// runtime/setting/settings.tsx
import { React } from 'jimu-core';
import { AllWidgetSettingProps } from 'jimu-for-builder';
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components';

const Settings = (props: AllWidgetSettingProps<any>) => {
  const onMapWidgetSelected = (useMapWidgetIds: string[]) => {
    props.onSettingChange({
      id: props.id,
      useMapWidgetIds: useMapWidgetIds
    });
  };

  return (
    <div className="widget-setting-demo">
      <MapWidgetSelector useMapWidgetIds={props.useMapWidgetIds} onSelect={onMapWidgetSelected} />
    </div>
  );
};

export default Settings;
 
widget menifest.json 
{
  "name": "dashboardTiles",
  "label": "Dashboard Tiles",
  "type": "widget",
  "version": "1.16.0",
  "exbVersion": "1.16.0",
  "dependency": ["jimu-arcgis"],
  "author": "Muhammad Ahsan Ali",
  "description": "Displays dashboard tiles",
  "properties": {
    "hasMap": true,
    "supportMapWidget": true,
    "useMapWidget": true,
    "useMapWidgetIds": {
      "type": "array"
    }
  },
  "translatedLocales": [
    "en"
  ],
  "defaultSize": {
    "width": 800,
    "height": 500
  }
}
 
What i am missing, why it is not working for me?
 
0 Kudos
JeffreyThompson2
MVP Frequent Contributor

JeffreyThompson2_1-1750871845184.png

Your file structure is incorrect. The setting folder must be directly under the src folder. Experience Builder is highly opinionated and will not find files or folders if they are not placed in the correct place with the correct name.

I have not reviewed your code closely, so there could be other issues as well.

GIS Developer
City of Arlington, Texas
MirzaMuhammadAhsanAli
Occasional Contributor

@JeffreyThompson2 , @TimWestern 

Thank you both for help.

MirzaMuhammadAhsanAli_0-1750919263147.png


Finally, Map option appeared, there are two options , none and map and i have selected Map, and now i have the map object in jimuMapView (state variable).

0 Kudos
MirzaMuhammadAhsanAli
Occasional Contributor

@TimWestern , @JeffreyThompson2 

Thank you both for help.

Finally , Map option appeared in setting panel of the custom widget, and now I have map object in jimuMapView (state variable). 

These guides also helped me as I am beginner to experience builder.
1. https://developers.arcgis.com/experience-builder/guide/create-a-starter-widget/
2. https://developers.arcgis.com/experience-builder/guide/get-map-coordinates/

Again, Thank You @JeffreyThompson2 and @TimWestern for assistance.