Table not searching tags

Ok here comes the longer description of my solution.

I have mainTable2 set up with an option list for editableCol. Those values are integers, but with string labels.

In the bottom right you see a filter-button. That's actually not a button but a "copy" of mainTable2, here called filterTableDummy.

With "copy" I mean it takes the first 100 rows of mainTable2, enough to infer data types properly, but not affecting performance.
filterTableDummy has minimal size and grey background, and a Filter-toolbar in the top. This makes the whole table look like a single button.
There's also en event handler which calls a script called filterTableDummyChangeFilterHandler on "Change filter"

Moreover, filterTableDummy also has a Format-script for its dynamic columns (both mainTable2 and filterTableDummy are dynamic so that they can be populated with different source tables' data depending on which source table user clicks in a sidebar). The format script ensures "Option list"-columns are formatted as "tag", and not "decimal" which they are otherwise inferred as.

So now to the fun stuff. As mentioned above, once user changes the filter the script filterTableDummyChangeFilterHandler is triggered.
That script takes filterTableDummy._columnOptionList and uses the filters (filterTableDummy.filterStack) on filterTableDummy._columnOptionList.[column_in_question].labelByIndex to find any label matching a filter. It then picks up the corresponding value (filterTableDummy._columnOptionList.[column_in_question].valueByIndex). Using this info it assemles a new filterStack and sets it on mainTable2. All DONE!

So why using filterTableDummy rather than a filter toolbar on mainTable2? Because when we set the manipulated filterStack the filters user sees will have the numerical value rather than the label user just set. Which is confusing (and it also creates "infinite loops" one would have to handle). Same issue happens with the stand-alone Filter component (which also doesn't handle dynamic columns).

A gif of how it's functioning:
Recording 2024-10-31 114000

Here's the filterTableDummyChangeFilterHandler script in full:


// Function to update the filterStack with string-based operator logic
function updateFilterStack(filterStack, _columnKey, _columnOptionList) {
  if (!filterStack) return null;
  const updatedFilters = [];

  filterStack.filters.forEach(filter => {
    const { columnId, operator, value } = filter;

    // Skip matching if the filter's value is empty
    if (value === undefined || value === null || value === "") {
      return;
    }

    // Prepare the filter value for comparison (convert to lowercase for case-insensitive matching)
    const normalizedValue = value.toString().toLowerCase();

    // Find the corresponding columnKey
    const columnKey = Object.keys(_columnKey).find(
      key => _columnKey[key] === columnId
    );

    if (columnKey && _columnOptionList[columnKey]) {
      // Find matching validation_ids
      let matchingIds = [];

      for (let index in _columnOptionList[columnKey].labelByIndex) {
        let val = _columnOptionList[columnKey].labelByIndex[index];
        const validationId = _columnOptionList[columnKey].valueByIndex[index];

        // Treat undefined or null as an empty string for comparison
        if (val === undefined || val === null) {
          val = "";
        }

        // Prepare the entry value for comparison (convert to lowercase)
        const normalizedEntryValue = val.toString().toLowerCase();

        switch (operator) {
          // Operators applicable to all strings
          case "isEmpty":
            if (normalizedEntryValue === "") {
              matchingIds.push(validationId);
            }
            break;
          case "isNotEmpty":
            if (normalizedEntryValue !== "") {
              matchingIds.push(validationId);
            }
            break;
          case "is":
            if (normalizedEntryValue === normalizedValue) {
              matchingIds.push(validationId);
            }
            break;
          case "isNot":
            if (normalizedEntryValue !== normalizedValue) {
              matchingIds.push(validationId);
            }
            break;
          case "includes":
            if (normalizedEntryValue.includes(normalizedValue)) {
              matchingIds.push(validationId);
            }
            break;
          case "doesNotInclude":
            if (!normalizedEntryValue.includes(normalizedValue)) {
              matchingIds.push(validationId);
            }
            break;
          default:
            // Handle other operators if needed
            break;
        }
      }

      // Update filters based on matchingIds
      if (matchingIds.length > 0) {
        if (matchingIds.length === 1) {
          // Single match
          updatedFilters.push({
            id: uuid.v4(),
            columnId: columnId,
            operator: "=",
            value: matchingIds[0].toString()
          });
        } else {
          // Multiple matches: create sub-filters combined with "or"
          const subFilters = matchingIds.map(id => ({
            id: uuid.v4(),
            columnId: columnId,
            operator: "is",
            value: id.toString()
          }));

          updatedFilters.push({
            id: uuid.v4(),
            operator: "or",
            filters: subFilters
          });
        }
      } else {
        // No matches found: add an impossible value filter
        updatedFilters.push({
            id: uuid.v4(),
          columnId: columnId,
          operator: "=",
          value: "hf983f9)37f6&#$?" // some value that will never match
        });
      }
    } else {
      // No columnKey or mappedData found: keep the filter unchanged
      updatedFilters.push(filter);
    }
  });

  // Update the filterStack with the new filters
  filterStack.filters = updatedFilters;

  return filterStack;
}





// Use the function
const updatedFilterStack = updateFilterStack(filterTableDummy.filterStack, mainTable2._columnKey, mainTable2._columnOptionList);

if (updatedFilterStack) {
  mainTable2.setFilterStack({filters: updatedFilterStack.filters, operator: updatedFilterStack.operator})
}
else {
  mainTable2.clearFilterStack();
}


2 Likes