javascriptangularjssmart-table

Filtering smart-table on transformed data


Apologies in advance, I am not a very experienced JS programmer, and even less so with AngularJS, but am trying to make some improvements on a legacy codebase that is using Angular 1.5.9 and Smart Table to display information from a database.

I've read all about st-search and st-safe-src vs. st-table, etc., but I am having trouble filtering on my table, since there are transformations happening on the underlying data before it gets displayed. The ng-repeat variable in my case is transaction, which has various fields to hold information for that transaction, such as payee, which holds a UUID pointing to another database document. In the app, we display the name of that payee using a function from another controller (dbCtrl.getPayeeName()), but the underlying data is the UUID. Thus, when attempting to filter with Smart Table, it does not filter on the displayed names, and only works when entering the UUID into the filter field.

A small example (with lots of the intervening bits removed, but hopefully enough to demonstrate my confusion):

<div class="account"
     st-table="displayedTransactions"
     st-safe-src="transactions"
     disable-ng-animate>
...
<div><input st-search="payee" placeholder="search for payee" class="input-sm form-control" type="search"/></div>
...
<div ng-repeat="transaction in displayedTransactions track by transaction.id"> 
...
  <div class="account__td" transaction-field-focus-name="payee">
    {{dbCtrl.getPayeeName(transaction.payee)}}
  </div>
...
</div>

Is there a relatively simple way to get the filtering to work for a situation like this where the displayed data is different than the underlying data? From what I'm reading in the documentation, it sounds like this might require some sort of custom plugin, which sounds like more work, but I could maybe figure out. I just wanted to see if I'm missing something obvious before heading down that route.


Solution

  • Circling back on this, I was able to accomplish what I needed using the st-set-filter attribute as described in the Strict mode filtering section of the documentation, as well as this helpful answer from laurent back in 2014.

    Essentially, I added st-set-filter="transactionFilters" to my table in my html template, as well as input tags with st-search="prop_to_search" attributes. Then in my applications module (I put this in one of the controllers, not sure if that's totally correct) I defined a filter such as below. expression gets passed into this code as an object with string values for whatever you typed in, so if you had three search fields, you'd get an object like:

    {
      "prop_to_search1": "value1",
      "prop_to_search2": "value2",
      "prop_to_search3": "value3"
    }
    

    In the filter function, I wrote an if block for each property that could come in, and then do my custom transformations and pattern matching there. This way, I have full control over the eventual matching, and rather than searching on the UUID, I can do something like $rootScope.dbCtrl.getPayeeName(uuidToSearch) instead. This is all acceptably performant in my use case, but I could probably cache those database lookups as a potential optimization.

    angular.module('myApp').filter('transactionFilters', function($rootScope, $filter){
      return function(array, expression){
        // console.log(`expression is: ${JSON.stringify(expression, null, 4)}`)
          return array.filter(function(val, index){
            // in this function's context, `expression` is an object with
            // the active filters entered in each field; `val` is the data
            // representation of each row of the table
    
            // if this function returns true, the row will match and smart-table
            // will show it, otherwise it will be hidden
    
            // define matches all at once, check them all, then return
            // a big logical AND on all of them
    
            let accountMatch = true;
            let payeeMatch = true;
            let categoryMatch = true;
    
            if (expression.account) {
              uuidToSearch = val.account  // this is the account UUID
              strToSearch = $rootScope.dbCtrl.getAccountName(uuidToSearch).toLowerCase();  // convert to an account name (we could memoize this to improve performance)
              if (strToSearch) {
                // if the account had a name (it always should, but catch in case)
                // then check if the row's account contains the text entered in the filter field
                accountMatch = strToSearch.includes(expression.account.toLowerCase());
              } else {
                accountMatch = false;
              }
            }
    
            if (expression.payee){
              if (val.payee) {
                uuidToSearch = val.payee
                strToSearch = $rootScope.dbCtrl.getPayeeName(uuidToSearch).toLowerCase();
              }
    
              if (strToSearch) {
                payeeMatch = strToSearch.includes(expression.payee.toLowerCase());
              } else {
                payeeMatch = false;
              }
            }
    
            if (expression.category) {
              if (val.category) {
                strToSearch = $rootScope.dbCtrl.getCategoryName(val.category, val.date).toLowerCase()
                categoryMatch = strToSearch.includes(expression.category.toLowerCase())
              } else {
                categoryMatch = false;
              }
            }
    
            return (
              accountMatch && 
              payeeMatch &&
              categoryMatch
            )
        })
      }
    });