I'm trying to extend the Use a feature layer (function) sample to use more than one Feature Layer, but I can't seem to make the FieldSelector component cooperate. It seems like it working part of the way, but I have these issues:
I'm not sure if these issues are something I'm missing or if there is an issue in the API itself but the documentation doesn't give any example of using FieldSelector with more than one data source.
I haven't made many changes to the code, but here is my setting.tsx file:
import { React, Immutable, IMFieldSchema, UseDataSource, AllDataSourceTypes, DataSource, ImmutableObject } from 'jimu-core'
import { AllWidgetSettingProps } from 'jimu-for-builder'
import { DataSourceSelector, FieldSelector } from 'jimu-ui/advanced/data-source-selector'
export default function Setting (props: AllWidgetSettingProps<{}>) {
const onFieldChange = (allSelectedFields: IMFieldSchema[], ds: DataSource, isSelectedFromRepeatedDataSourceContext: boolean) => {
props.onSettingChange({
id: props.id,
useDataSources: [...props.useDataSources].map(item => item.dataSourceId === ds.id ? { ...item, ...{ fields: allSelectedFields.map(f => f.jimuName) } } : item)
})
}
const onToggleUseDataEnabled = (useDataSourcesEnabled: boolean) => {
props.onSettingChange({
id: props.id,
useDataSourcesEnabled
})
}
const selectedFields = () => {
let newFields = Immutable({}) as ImmutableObject<{ [dataSourceId: string]: string[] }>
props.useDataSources.forEach(ds => {
newFields = newFields.set(ds.dataSourceId, ds.fields ? ds.fields : [])
})
return newFields
}
const onDataSourceChange = (useDataSources: UseDataSource[]) => {
props.onSettingChange({
id: props.id,
useDataSources: useDataSources
})
}
return <div className="use-feature-layer-setting p-2">
<DataSourceSelector
types={Immutable([AllDataSourceTypes.FeatureLayer])}
useDataSources={props.useDataSources}
useDataSourcesEnabled={props.useDataSourcesEnabled}
onToggleUseDataEnabled={onToggleUseDataEnabled}
onChange={onDataSourceChange}
widgetId={props.id}
isMultiple={true}
/>
{
props.useDataSources && props.useDataSources.length > 0 &&
<FieldSelector
useDataSources={props.useDataSources}
onChange={onFieldChange}
selectedFields={selectedFields() || Immutable([])}
/>
}
</div>
}
Hey Alex,
I know this is somewhat late to your question but this is how I was able to use a multiple datasource and be able to select the fields for several fields in my first datasource and just one from the second. Hopefully this can help others!
import { React, jsx, Immutable, DataSourceTypes, UseDataSource, IMFieldSchema, DataSource } from 'jimu-core';
import { AllWidgetSettingProps } from 'jimu-for-builder';
import { IMConfig } from '../config';
import {
SettingSection,
SettingRow,
} from 'jimu-ui/advanced/setting-components';
import {
AllDataSourceTypes,
DataSourceSelector,
FieldSelector,
} from 'jimu-ui/advanced/data-source-selector';
export class Setting extends React.PureComponent<AllWidgetSettingProps<IMConfig>, any> {
constructor(props) {
super(props);
this.state = {
selectedFields: {} // This will be in the form: { [dataSourceId]: { fieldName: field } }
}
}
onDataSourceChange = (useDataSources: UseDataSource[]) => {
// Update the widget's settings to reflect the selected data sources
this.props.onSettingChange({
id: this.props.id,
useDataSources: useDataSources,
});
};
onFieldChange = (attributeName, allSelectedFields: IMFieldSchema[], ds: DataSource) => {
const fieldName = allSelectedFields[0].jimuName;
// Update the local state with the selected field for the given attribute
this.setState(prevState => {
const updatedFieldsForDataSource = {
...prevState.selectedFields[ds.id],
[attributeName]: fieldName
};
return {
selectedFields: {
...prevState.selectedFields,
[ds.id]: updatedFieldsForDataSource
}
};
}, () => {
// Update the widget's settings to reflect the new selected fields
this.props.onSettingChange({
id: this.props.id,
useDataSources: [...this.props.useDataSources].map(item =>
item.dataSourceId === ds.id
? { ...item, ...{ fields: allSelectedFields.map(f => f.jimuName) } }
: item)
});
});
};
renderFieldSelectors(dataSource, fieldCount) {
const selectors = [];
const dataSourceFields = this.state.selectedFields[dataSource.dataSourceId] || {};
for (let i = 0; i < fieldCount; i++) {
const fieldKey = `${dataSource.dataSourceId}_field_${i + 1}`;
const selectedField = dataSourceFields[fieldKey];
selectors.push(
<FieldSelector
key={i}
useDataSources={Immutable.from([dataSource.asMutable({deep: true})])}
isMultiple={false}
isDataSourceDropDownHidden={true}
isSearchInputHidden={false}
useDropdown={true}
placeholder={`Select Field ${i + 1} for ${dataSource.dataSourceId}`}
useDefault={false}
onChange={this.onFieldChange.bind(this, `${dataSource.dataSourceId}_field_${i + 1}`)}
selectedFields={selectedField ? Immutable.from([selectedField]) : Immutable([])}
/>
);
}
return selectors;
}
render() {
return (
<div>
<div className='widget-setting-br-buffer-tool'>
<SettingSection className='node-selector-section' title='Select Datasource for Activity Nodes'>
<SettingRow>
<DataSourceSelector
mustUseDataSource
types={Immutable([AllDataSourceTypes.FeatureLayer] || [AllDataSourceTypes.FeatureService])}
useDataSources={this.props.useDataSources}
onChange={this.onDataSourceChange}
widgetId={this.props.id}
isMultiple={true}
/>
</SettingRow>
</SettingSection>
{this.props.useDataSources && this.props.useDataSources.length > 0 && (
<React.Fragment>
{this.props.useDataSources.map((dataSource, index) => (
<SettingSection key={index} className='node-selector-section' title={`Fields for ${dataSource.dataSourceId}`}>
{index === 0 ? this.renderFieldSelectors(dataSource, 7) : this.renderFieldSelectors(dataSource, 1)}
</SettingSection>
))}
</React.Fragment>
)}
</div>
</div>
);
}
}
export default Setting;
Though my version correctly shows different selections, I should add that it does change the 'Fields' property of useDataSources to the latest field I selected. This obviously wasn't my case as I need to use what the author mapped in my custom widget. That being said, the 'selectedFields' does return the entire list of selected fields and their datasource. Pushing this to config.ts should work.
A great example is to look at the "List" widget and see how it uses Datasource instead of useDataSources and in regard to selectionFields.
Alter the code allowed the selection of all the fields I used 6 in each for my case
import { React, Immutable, type IMFieldSchema, type UseDataSource, AllDataSourceTypes } from 'jimu-core'
import { type AllWidgetSettingProps } from 'jimu-for-builder'
import { DataSourceSelector, FieldSelector } from 'jimu-ui/advanced/data-source-selector'
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components'
export default function Setting(props: AllWidgetSettingProps<unknown>) {
const onFieldChange = (allSelectedFields: IMFieldSchema[]) => {
props.onSettingChange({
id: props.id,
useDataSources: [{ ...props.useDataSources[0], ...{ fields: allSelectedFields.map(f => f.jimuName) } }]
})
}
const onToggleUseDataEnabled = (useDataSourcesEnabled: boolean) => {
props.onSettingChange({
id: props.id,
useDataSourcesEnabled
})
}
const onDataSourceChange = (useDataSources: UseDataSource[]) => {
props.onSettingChange({
id: props.id,
useDataSources: useDataSources
})
}
const onMapSelected = (useMapWidgetIds: string[]) => {
props.onSettingChange({
id: props.id,
useMapWidgetIds: useMapWidgetIds
})
}
return <div className="use-feature-layer-setting p-2">
<MapWidgetSelector
onSelect={onMapSelected}
useMapWidgetIds={props.useMapWidgetIds}
/>
<DataSourceSelector
types={Immutable([AllDataSourceTypes.FeatureLayer])}
useDataSources={props.useDataSources}
useDataSourcesEnabled={props.useDataSourcesEnabled}
onToggleUseDataEnabled={onToggleUseDataEnabled}
onChange={onDataSourceChange}
widgetId={props.id}
/>
{
props.useDataSources && props.useDataSources.length > 0 &&
<FieldSelector
useDataSources={props.useDataSources}
onChange={onFieldChange}
selectedFields={props.useDataSources[0].fields || Immutable([])}
isMultiple={true}
/>
}
</div>
}
Disregard the previous, code. I copied from the wrong settings. This was the altered code.
import { React, jsx, Immutable, DataSourceTypes, UseDataSource, IMFieldSchema, DataSource } from 'jimu-core';
import { AllWidgetSettingProps } from 'jimu-for-builder';
import { IMConfig } from '../config';
import {
SettingSection,
SettingRow,
} from 'jimu-ui/advanced/setting-components';
import {
AllDataSourceTypes,
DataSourceSelector,
FieldSelector,
} from 'jimu-ui/advanced/data-source-selector';
export class Setting extends React.PureComponent<AllWidgetSettingProps<IMConfig>, any> {
constructor(props) {
super(props);
this.state = {
selectedFields: {} // This will be in the form: { [dataSourceId]: { fieldName: field } }
}
}
onDataSourceChange = (useDataSources: UseDataSource[]) => {
// Update the widget's settings to reflect the selected data sources
this.props.onSettingChange({
id: this.props.id,
useDataSources: useDataSources,
});
};
onFieldChange = (attributeName, allSelectedFields: IMFieldSchema[], ds: DataSource) => {
const fieldName = allSelectedFields[0].jimuName;
// Update the local state with the selected field for the given attribute
this.setState(prevState => {
const updatedFieldsForDataSource = {
...prevState.selectedFields[ds.id],
[attributeName]: fieldName
};
return {
selectedFields: {
...prevState.selectedFields,
[ds.id]: updatedFieldsForDataSource
}
};
}, () => {
const existingFields = this.props.useDataSources.find(item => item.dataSourceId === ds.id).fields || [];
const newFields = Array.from(new Set([...existingFields, fieldName]));
// Update the widget's settings to reflect the new selected fields
this.props.onSettingChange({
id: this.props.id,
useDataSources: [...this.props.useDataSources].map(item =>
item.dataSourceId === ds.id
? { ...item, ...{ fields: newFields } }
: item)
});
});
};
renderFieldSelectors(dataSource, fieldCount) {
const selectors = [];
const dataSourceFields = this.state.selectedFields[dataSource.dataSourceId] || {};
for (let i = 0; i < fieldCount; i++) {
const fieldKey = `${dataSource.dataSourceId}_field_${i + 1}`;
const selectedField = dataSourceFields[fieldKey];
selectors.push(
<FieldSelector
key={i}
useDataSources={Immutable.from([dataSource.asMutable({ deep: true })])}
isMultiple={false}
isDataSourceDropDownHidden={true}
isSearchInputHidden={false}
useDropdown={true}
placeholder={`Select Field ${i + 1} for ${dataSource.dataSourceId}`}
useDefault={false}
onChange={this.onFieldChange.bind(this, `${dataSource.dataSourceId}_field_${i + 1}`)}
selectedFields={selectedField ? Immutable.from([selectedField]) : Immutable([])}
/>
);
}
return selectors;
}
render() {
return (
<div>
<div className='widget-setting-br-buffer-tool'>
<SettingSection className='node-selector-section' title='Select Datasource for Activity Nodes'>
<SettingRow>
<DataSourceSelector
mustUseDataSource
types={Immutable([AllDataSourceTypes.FeatureLayer]) || Immutable([AllDataSourceTypes.FeatureService])}
useDataSources={this.props.useDataSources}
onChange={this.onDataSourceChange}
widgetId={this.props.id}
isMultiple={true}
/>
</SettingRow>
</SettingSection>
{this.props.useDataSources && this.props.useDataSources.length > 0 && (
<React.Fragment>
{this.props.useDataSources.map((dataSource, index) => (
<SettingSection key={index} className='node-selector-section' title={`Fields for ${dataSource.dataSourceId}`}>
{index === 0 ? this.renderFieldSelectors(dataSource, 6) : this.renderFieldSelectors(dataSource, 6)}
</SettingSection>
))}
</React.Fragment>
)}
</div>
</div>
);
}
}
export default Setting;