Monday, May 9, 2016

rateLimit'ing ojInputText using a Read-only Observable (Debouncing the rawValue)

In my post Filtering Oracle JET ArrayTableDataSource I showed how to use an ojInputText to filter tabular data. In that post I mentioned using the knockout rateLimit extender, but shied away from it because rateLimit'ing an observable attached to an ojInputText rawValue throws the error, "rawValue option cannot be set" (even though rateLimit'ing in this manner works). It is true that rawValue is a read only field. For some reason, rateLimit'ing an observable causes a write to the component property bound to the observable. This makes sense because that is the point of an observable: update something after the observable value changes. The problem is that rawValue is read-only. I raised this issue with my colleagues and Jeanne Waldman shared this idea:

Create a pureComputed based off the rawValue observable and rateLimit the pureComputed.

Interesting. Jeanne's idea essentially creates a read-only observable that we can then rateLimit, effectively isolating rawValue from a write attempt. Here are the JavaScript changes necessary to implement Jeanne's idea:


// ... other code above here
ko.pureComputed(self.nameSearch)
  .extend({
    rateLimit: {
      timeout: 250,
      method: "notifyWhenChangesStop"
    }
  })
  .subscribe(function(newValue) {
    self.datasource.reset(
      filteredEmployees(newValue)
    );
  });

// ... other code below here

Notice that we are wrapping the previously used nameSearch observable in a ko.pureComputed, extending the ko.pureComputed, and then subscribing to the rate limited observable. Here is the jsFiddle:

Success! Our filter code now only runs 250 milliseconds after user input pauses. Considering our search algorithm, this will definitely perform better than invoking the algorithm for each key press. But is it as good as it could be? I'm not sure. We are now rate limiting a second observable rather than the primary observable. Will the primary observable continue to emit events with each key press? Did we trade an innocuous error message for some CPU/memory overhead? Does it matter? Normally, given two working solutions, I would choose performance, but I really don't want to see error messages cluttering my console.

2 comments:

  1. Hi Jim, First off thank you very much for this post. It is exactly what i'm looking for. I'm having trouble getting it to work when fetching my data using $.getJSON. My data comes in just fine and i see the collection being filtered when i print to the console, but by datasource.reset is not filtering the results. When i hardcode the array, it works just fine. Would you have time to help me on this issue? The only difference between your example and mine is my JSON call:

    var apptJSON = "http://host:port/appointments.json";
    $.getJSON(apptJSON, function (data) {
    $.each(data, function (i, item) {
    self.appointments.push({
    appointmentid: item.AppointmentId,
    scheduled: item.Scheduled,
    shipto: item.ShipTo,
    shiptoaddress: item.ShipToAddress,
    droptrailerorblank: item.DropTrailerOrBlank
    });

    });

    });

    ReplyDelete
    Replies
    1. Do you want to fork the Fiddle and update it with your changes? Then we can work on it together.

      Delete