Select to view content in your preferred language

Triggering data actions from a custom widget

1820
12
Jump to solution
09-23-2022 07:15 AM
MaxDiebold
New Contributor

I want to enable a custom widget to consume the "View in Table" data action provided by the Table widget, but I don't want to use the DataActionDropDown component. How do I trigger the action programmatically? I don't see any methods to do so in the DataActionManager class.

0 Kudos
12 Replies
TimWestern
Frequent Contributor

I recently went through a similar issue, and realized that state and props, aren't always the same when the widgets load, this lead to me looking into the Redux part of the React implementation in Experience builder.  Did you find you had to use a similar solution?

0 Kudos
LucasAmarante
Emerging Contributor

Good morning dear,
How do I create a Data RecordSet (https://developers.arcgis.com/experience-builder/api-reference/jimu-core/DataRecordSet/) from a record that contains feature and dataSource to pass as a parameter to onExecute() ??

Can anybody help me? any example?

Thanks.

0 Kudos
TimWestern
Frequent Contributor

I know you were asking about this in march, but here's how I approached it:

In the action.ts file for the action (note whatever I named the action I'd name it like name-for-action.ts
(where named-for is a kebab case version of the action name.)

Here is a genericized version of an example of onExecute in  that class:

onExecute (message: Message, actionConfig?: any): boolean | Promise<boolean> {
    if (message.type === MessageType.DataRecordsSelectionChange) {
      const dataRecordSetChangeMessage = message as DataRecordSetChangeMessage
      const widgetId = dataRecordSetChangeMessage.widgetId

      console.log('Received data recordSetChangeMessage:', dataRecordSetChangeMessage)

      // @ts-expect-error
      const records = dataRecordSetChangeMessage.records || []
      if (records.length > 0) {
        const selectedRecord = records[0]
        const selectedFeature = selectedRecord?.feature

        if (selectedFeature) {
          const selectedAttributes = {
            attributes: selectedFeature.attributes,
			// (Note attribute_value_of_note in the system I was using could be lowercase or all upper case depending on whether it is a hosted layer or not)  It is a generic name and could be replaced with any field you'd find in the feature.attriubtes that are returned.
            attribute_value_of_note: selectedFeature.attributes.ATTRIBUTE_VALUE_OF_NOTE || selectedFeature.attributes.attribute_value_of_note,
            feature: selectedFeature,
            geometry: selectedFeature.geometry
          }
          console.log('Dispatching selected attribute_value_of_note attributes:', selectedAttributes)

          // Dispatch actions to the new Redux store extension
          getAppStore().dispatch(setSelectedAttributeOfValue(selectedAttributes.attribute_value_of_note))
          getAppStore().dispatch(setSelectedAttributes(selectedAttributes.attributes))

          // Convert geometry to JSON before dispatching
          getAppStore().dispatch(setSelectedGeometry(selectedFeature.geometry))
          getAppStore().dispatch(setSelectedFeature(selectedFeature))

          // If needed, you can still poll for state updates in the new store
          this.pollForSelectedAttributeOfValue(widgetId)
            .then(() => {
              console.log('selectedAttributeOfValue successfully stored in the Redux store.')
            })
            .catch((error) => {
              console.error(error.message)
            })
        } else {
          console.error('No feature found in the selected record.')
          return false
        }
      } else {
        console.error('No records found in the message.')
        return false
      }
    } else {
      console.error('Message type does not match.')
      return false
    }
    return true
  }


action-types.ts

// action-types.ts
export enum YourActionKeys {
  SetSelectedAttributeOfNote = 'SET_SELECTED_ATTRIBUTE_OF_NOTE',
  SetSelectedAttributes = 'SET_SELECTED_ATTRIBUTES',
  SetSelectedGeometry = 'SET_SELECTED_GEOMETRY',
  SetSelectedFeature = 'SET_SELECTED_FEATURE'
}


actions.ts

// actions.ts
import { YourActionKeys } from './action-types'

export const setSelectedAttributeOfNote = (attributeOfNote: string) => ({
  type: YourActionKeys.SetSelectedAttributeOfNote,
  attributeOfNote
})

export const setSelectedAttributes = (attributes: any) => ({
  type: YourActionKeys.SetSelectedAttributes,
  attributes
})

export const setSelectedGeometry = (geometry: __esri.Geometry) => {
  const geometryJSON = geometry.toJSON() // Convert to plain JSON object
  return {
    type: YourActionKeys.SetSelectedGeometry,
    geometry: geometryJSON
  }
}
export const setSelectedFeature = (feature: __esri.Geometry) => {
  return {
	  // Note instead of setting up an Enum you could actually just have the string like this)
    type: 'SET_SELECTED_FEATURE',
    payload: feature
  }
}


then in the store file for your widget. you need a getReducer method (but some of the interface and setup for the store can be defined here too:

// my-store.ts
import { type extensionSpec, type ImmutableObject, type IMState } from 'jimu-core'
import { YourActionKeys } from './action-types'

// interface MyState {
//   selectedAttributeOfNote: string | null
//   selectedAttributes: any
// }
// Note I used any because its often quicker than trying to speck out the deeply nested structures that might be returned.  If you want more type checking, definitely choose something other than any.
export interface YourWidgetStoreState {
  selectedAttributeOfNote: string | null
  selectedAttributes: any
  selectedGeometry: __esri.Geometry | null
  selectedFeature: any
}

export interface StateForYourWidgetStore extends IMState {
  YourWidgetStoreState: ImmutableObject<YourWidgetStoreState>
}

type IMYourWidgetStoreState = ImmutableObject<YourWidgetStoreState>

export default class YouWidgetStoreExtension implements extensionSpec.ReduxStoreExtension {
  id = 'your-results-store-extension'

  getActions () {
    return Object.keys(YourActionKeys).map(k => YourActionKeys[k])
  }

  getInitLocalState () {
    return {
      selectedAttributeOfNote: null,
      selectedAttributes: null,
      selectedFeature: null,
      selectedGeometry: null
    }
  }

  getReducer () {
    return (localState: IMYourWidgetStoreState, action: any, appState: IMState): IMYourWidgetStoreState => {
      switch (action.type) {
        case YourActionKeys.SetSelectedAttributeOfNote:
          return localState.set('selectedAttributeOfNote', action.attribute_of_note)
        case YourActionKeys.SetSelectedAttributes:
          return localState.set('selectedAttributes', action.attributes)
        case YourActionKeys.SetSelectedGeometry:
          return localState.set('selectedGeometry', action.geometry)
        case YourActionKeys.SetSelectedFeature:
          return localState.set('selectedFeature', action.feature)
        default:
          return localState
      }
    }
  }

  getStoreKey () {
    return 'YourWidgetStoreState'
  }
}

 

In theory you can generically then get access to what you stored when the action is triggered by just doing this:

    const store = getAppStore()
    const storeState = store.getState() as StateForYourWidgetStore

    // Access yourWidgetStoreState directly from the store
    const yourWidgetStoreState = storeState.yourWidgetStoreState
    console.log('storeState.yourWidgetStoreState: ', yourWidgetStoreState)

    if (yourWidgetStoreState && yourWidgetStoreState.selectedAttributeOfNote) {
      console.log(`Detected existing yourWidgetStoreState with attribute of note: ${yourWidgetStoreState.selectedAttributeOfNote}`)
    } else {
      console.log('No existing yourWidgetStoreState or attribute of note found.')
    }

 

In this example I was able to check this when the widget opened at construction, but in theory you could do it in whatever function in your widget would receive that message call.