jimu-ui FieldSelector componenet with multiple data sources

688
2
05-08-2023 11:31 AM
AlexGilvarry1
New Contributor III

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:

  1. Whenever I select a field in a data source that is not the first in the list it will pop back to the initial selection. Logging props.useDataSources show that the select was made despite this.
  2. When I try to switch to a data source other than the initial after having made a selection it triggers the onChange parameter, which will wipe out my selected fields because it registers as nothing being selected.
  3. In addition to #2 usually when I try to switch the data source it won't actually change despite the above behavor.

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>
}

 

0 Kudos
2 Replies
RoderickPerendy
New Contributor III

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;

 

RoderickPerendy
New Contributor III

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. 

0 Kudos