Hello All,
Problem: Widget works fine in builder, but "fails to load" in preview.
This is my first time creating a custom widget. My goal was to create a widget where a user can select points on a map and send them to another layer that is connected to the map. The widget will also have a field where they can give those selected points a name. All attributes will be copied from the selected points and inserted into the other layer. Settings.tsx
/** @jsx jsx */
import { React, jsx, DataSourceTypes, Immutable } from 'jimu-core'
import type { AllWidgetSettingProps } from 'jimu-for-builder'
import type { UseDataSource } from 'jimu-core'
import { DataSourceSelector } from 'jimu-ui/advanced/data-source-selector'
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components'
import { useState } from 'react'
export default function Setting(props: AllWidgetSettingProps<any>) {
const [statusMessage, setStatusMessage] = useState('')
// Current target layer (DataSource)
const targetUseDs: UseDataSource[] = props.config.targetDataSource
? [props.config.targetDataSource]
: []
// Toggle target layer enabled
const onToggleTargetEnabled = (enabled: boolean) => {
props.onSettingChange({
id: props.id,
config: props.config.set('useTargetDsEnabled', enabled)
})
}
// Update target DataSource
const onTargetDsChange = (useDataSources: UseDataSource[]) => {
if (!useDataSources || useDataSources.length === 0) {
setStatusMessage('Please select a target layer')
props.onSettingChange({
id: props.id,
config: props.config.set('targetDataSource', null)
})
return
}
setStatusMessage('')
props.onSettingChange({
id: props.id,
config: props.config.set('targetDataSource', useDataSources[0])
})
}
return (
<div className="widget-setting">
<h5>Source Map Widget</h5>
<MapWidgetSelector
useMapWidgetIds={Immutable(props.useMapWidgetIds ?? [])}
onSelect={(ids) => {
props.onSettingChange({ id: props.id, useMapWidgetIds: ids })
}}
/>
<h5 className="mt-3">Target Layer</h5>
<DataSourceSelector
widgetId={props.id}
types={Immutable([DataSourceTypes.FeatureLayer])}
useDataSources={Immutable(targetUseDs)}
useDataSourcesEnabled={props.config.useTargetDsEnabled ?? true}
onChange={onTargetDsChange}
onToggleUseDataEnabled={onToggleTargetEnabled}
/>
{statusMessage && (
<div className="status-message" style={{ marginTop: 8, color: 'red' }}>
{statusMessage}
</div>
)}
</div>
)
}
widget.tsx
/** @jsx jsx */
import { React, jsx, DataSourceManager, } from 'jimu-core'
import type { FeatureLayerDataSource, DataRecord } from 'jimu-core'
import { useState } from 'react'
import { Button, TextInput, Alert } from 'jimu-ui'
import Graphic from '@arcgis/core/Graphic'
export default function MoveToStrikeTeamWidget(props) {
const [strikeTeamName, setStrikeTeamName] = useState('')
const [loading, setLoading] = useState(false)
const [status, setStatus] = useState<{ type: 'success' | 'error' | 'warning'; message: string } | null>(null)
const showStatus = (type: 'success' | 'error' | 'warning', message: string) => {
setStatus({ type, message })
setTimeout(() => { setStatus(null) }, 5000)
}
// Render fallback if configuration is missing
if (!props.useMapWidgetIds?.length || !props.config?.targetDataSource) {
return <div style={{ color: '#666' }}>Configure the source map widget and target layer in settings.</div>
}
const moveSelectedFeatures = async () => {
if (!strikeTeamName.trim()) {
showStatus('warning', 'Please enter a Strike Team Name.')
return
}
setLoading(true)
try {
// Get all data sources as array
const allDsArray = Object.values(DataSourceManager.getInstance().getDataSources())
// Filter only FeatureLayerDataSources
const sourceDsList = allDsArray.filter(
ds => ds.type === 'FeatureLayer' && typeof ds.getSelectedRecords === 'function'
) as FeatureLayerDataSource[]
if (!sourceDsList.length) {
showStatus('error', 'No source FeatureLayer found in the selected map widget.')
setLoading(false)
return
}
// Collect selected records
const selectedRecords: DataRecord[] = sourceDsList.flatMap(ds => ds.getSelectedRecords())
if (!selectedRecords.length) {
showStatus('warning', 'No features selected in the source map.')
setLoading(false)
return
}
// Get target DataSource
const targetUseDs = props.config.targetDataSource
const targetDs = DataSourceManager.getInstance().getDataSource(targetUseDs.dataSourceId) as FeatureLayerDataSource
if (!targetDs || !targetDs.layer) {
showStatus('error', 'Target data source not ready.')
setLoading(false)
return
}
const targetLayer = targetDs.layer
await targetLayer.when() // ensure layer is fully loaded
// Prepare graphics for target
const addFeatures: Graphic[] = selectedRecords.map(r => {
const json = r.toJson()
return new Graphic({
geometry: json.geometry,
attributes: {
...json.attributes,
StrikeTeamName: strikeTeamName
}
})
})
// Apply edits to target layer
const addResults = await targetLayer.applyEdits({ addFeatures })
if (!addResults.addFeatureResults || addResults.addFeatureResults.length === 0) {
showStatus('error', 'Failed to add features to target layer.')
setLoading(false)
return
}
// Delete features from source layers
for (const ds of sourceDsList) {
const recordsToDelete = ds.getSelectedRecords()
const sourceLayer = ds.layer
if (recordsToDelete.length && sourceLayer) {
await sourceLayer.when()
const deleteFeatures = recordsToDelete.map(r => ({ objectId: Number(r.getId()) }))
await sourceLayer.applyEdits({ deleteFeatures })
}
}
showStatus('success', `Successfully moved ${addResults.addFeatureResults.length} feature(s) to StrikeTeamGroup.`)
setStrikeTeamName('')
} catch (err) {
console.error('Runtime error in MoveToStrikeTeamWidget:', err)
showStatus('error', 'Widget failed to load or move features. Check console.')
} finally {
setLoading(false)
}
}
return (
<div className="p-2">
<h4>Move to Strike Team</h4>
<TextInput
placeholder="Enter Strike Team Name"
value={strikeTeamName}
onChange={e => { setStrikeTeamName(e.target.value) }}
className="mb-2"
/>
<Button
type="primary"
onClick={moveSelectedFeatures}
disabled={loading}
block
>
{loading ? 'Moving...' : 'Move Selected Features'}
</Button>
{status && (
<Alert type={status.type} text={status.message} className="mt-2" />
)}
</div>
)
}
Thanks in advance.
-Matthew
Solved! Go to Solution.
I found the solution in another post HERE.
Basically added "dependency": "jimu-arcgis" to manifest.json
I found the solution in another post HERE.
Basically added "dependency": "jimu-arcgis" to manifest.json