FeatureTable with related records

This sample demonstrates support for displaying and editing related records within the FeatureTable widget. Support for related records in the FeatureTable was added at version 4.30. Any related information within the parent, i.e. origin, layer will display as a column, or columns, at the end of the table. This column displays a link to any related records corresponding to that feature.

In order for a relationship column to display in the table:

  • The relatedRecordsEnabled property must be true.
  • The table's layer must have an established relationship.
  • Ensure that any layers or tables that the table needs to link to are also loaded in the map.

In addition to demonstrating related record support, this sample also showcases the ability to configure the table with an ActionColumn. This column provides the ability to configure a custom action to perform specific functionality. In this use case, the action zooms to the row's feature. The table now supports setting its title through custom functions. This allows the title to dynamically update based on specific behavior written into the function. In this sample, the title accesses the count of selected features and formats and displays this in comparison to the total amount of features.

This sample also demonstrates how to filter and have the table display only the associated features that are currently displayed within the map. It does this by listening for when the view's extent updates. Whenever a view's extent changes, the FeatureTable's filterGeometry is set to this new extent and only those features that fall within this area display.

Lastly, this sample explorers how to sync up the selection state of features within the table to features selected in the view. As long as the table's view property is set, a selected row will automatically highlight its corresponding feature in the view. In order to get the reverse behavior, the object ID of the clicked feature is passed into the FeatureTable's highlightIds. This automatically selects that feature within the table. In addition to having the table and view's selections synced, the table also filters down the display to only show those selected features. By default, all features display within the table. In this example, we remove all extraneous rows from the table and only focus on displaying those features that are selected. This is handled by setting the table's filterBySelectionEnabled property.

How it works

Create the FeatureTable by passing a FeatureLayer and field column templates to display. Take note of the additional properties such as editingEnabled and relatedRecordsEnabled are also set to allow that functionality.

The title is set to work with a custom function and an ActionColumn is also configured to provide the ability to zoom to each feature.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const featureTable = new FeatureTable({
  title: () => {
    if (!featureTable) {
      return;
    }
    const state = featureTable.state;
    switch (state) {
      case "loading":
        return "Loading layer data...";
      case "loaded":
        const title = featureTable.layer?.title;
        const rowCount = featureTable.size;
        const selectedCount = featureTable.highlightIds?.length ?? 0;
        return `${title} (${selectedCount} rows selected out of ${rowCount} total)`;
      case "error":
        return "Error loading layer.";
      default:
        return;
      }
  },
  description: "Hydrants are related to inspections, inspections are related to violations",
  actionColumnConfig: {
    label: "Go to feature",
    icon: "zoom-to-object",
    callback: (params) => {
      view.goTo(params.feature);
    },
  },
  view,
  editingEnabled: true, // required to enable editing
  relatedRecordsEnabled: true, // required to enable related records
  layer: featureLayer,
  tableTemplate: { // TableTemplate is autocastable
    columnTemplates: [
    {
      type: "field", // FieldColumnTemplate is autocastable, 'type' must be set
      fieldName: "FACILITYID",
      label: "Facility ID",
      icon: "key", // added icon to indicate relate key field
      autoWidth: true, // set this to have the columns automatically adjust width
    },
    {
      type: "field",
      fieldName: "MANUFACTURER",
      label: "Manufacturer",
      autoWidth: true
    },
    {
      type: "field",
      fieldName: "OPERABLE",
      label: "Operable",
      autoWidth: true
    },
    {
      type: "field",
      fieldName: "LASTSERVICE",
      label: "Date of last service",
      autoWidth: true
     },
     {
       type: "field",
       fieldName: "FLOW",
       label: "Flow rate",
       autoWidth: true
    }],
  },
  container: document.getElementById("tableDiv"),
});

The table also applies a filterGeometry to only display the features that fall within the extent of the view.

Next, listen for the view's immediate-click event. It performs a hitTest on the point location and, if applicable, selects the corresponding feature's row in the table. It does this by adding its associated ObjectId into the FeatureTable's highlightIds collection.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
view.on("click", async (event) => {
  const response = await view.hitTest(event);
  const candidate = response.results.find(
    (result) =>
    result.graphic && result.graphic.layer === featureLayer);

  if (candidate) {
    const objectId = candidate.graphic.attributes.OBJECTID;
    const index = featureTable.highlightIds.indexOf(objectId);

    // If there are objectIds in the highlightIds, remove the clicked feature
    // from the array. If there are no more objectIds, remove the filter
    // to show only selected records

    if (index > -1) {
      featureTable.highlightIds.splice(index, 1);
      if (featureTable.highlightIds.length === 0) {
        featureTable.filterBySelectionEnabled = false;
      }
    } else {
      // Add the objectId of the clicked feature into the highlightIds.
      // This selects the feature in the table and sets a filter to only display
      // the selected rows
      featureTable.highlightIds.push(objectId);
      featureTable.filterBySelectionEnabled = true;
    }
  }
});

Finally, the sample also watches for any changes to highlightIds' length. If showing a selection of features, it listens for any updates, (i.e. removing/adding), and updates the selection filter as needed.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
featureTable.highlightIds.on("change", async (event) => {
  event.removed.forEach((item) => {
    const data = features.find((data) => {
      return data === item;
    });
    if (data) {
      features.splice(features.indexOf(data), 1);
    }
    // Check if there are no more selected rows in the table,
    // Once everything is unchecked, remove the filter for selected records
    if (featureTable.highlightIds.length === 0) {
      featureTable.filterBySelectionEnabled = false;
    }
  });

  // If the selection is added, push all added selections to array
  event.added.forEach((item) => {
    features.push(item);
  });
});

Known Limitations

For a comprehensive list of limitations, please refer to the widget's API Reference documentation.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.