Tuesday, May 30, 2017

How to create a wrapper function for default knockout bindings

Leave a Comment

I am displaying a huge tabular structure with knockout. The user has the option to remove rows by clicking a checkbox on the row:

data-bind="checked: row.removed" 

The problem is that the table has to be re-rendered on click, which on slow computers/browsers takes up to one or two seconds - the checkbox changes its state after the table has been rendered so the UI feels unresponsive. I would like to create a wrapper function that does the same thing as the default checked-binding but additionally displays a loader symbol - then hides it again after the checked binding did its job. Something like:

ko.bindingHandlers.checkedWithLoader = {     update: function(element, valueAccessor, allBindings) {         loader.show();         // call knockout's default checked binding here         loader.hide();     } }; 

Is something like that possible? Is there a better alternative?

1 Answers

Answers 1

How to access other bindings in a custom binding

You can use ko.applyBindingsToNode:

ko.applyBindingsToNode(element, { checked: valueAccessor() }) 

Knockout's source actively exposes this method (here) and references it in an example on its own documentation page (here).

It probably won't solve your issue with handling slow renders though...

Handling slow updates

You could also create an extra layer in your viewmodel to build in the loading feature:

this.checked = ko.observable(false);  this.isLoading = ko.observable(false); this.showLargeAndSlowTable = ko.observable(false);  this.checked.subscribe(function(isChecked) {   this.isLoading(true);   this.showLargeAndSlowTable(isChecked);   this.isLoading(false); }, this); 

You'll need an if or with binding bound to showLargeAndSlowTable, and bind the checkbox value to checked.

In some cases, you might need to force a repaint between setting the loading observable and injecting the large data set. Otherwise, knockout and the browser can bundle these updates in to one frame.

You can achieve this by putting the showLargeAndSlowTable and isLoading(false) in a setTimeout, or by using a delayed/throttled additional observable that triggers the work after isLoading's change has been given time to render:

function AppViewModel() {      var self = this;            // The checkbox value that triggers the slow UI update      this.showLargeTable = ko.observable(false);            // Checkbox change triggers things      this.showLargeTable.subscribe(updateUI)            // Indicates when we're loading:      this.working = ko.observable(false);      this.delayedWorking = ko.pureComputed(function() {        return self.working();      }).extend({ throttle: 10 });            // Instead of directly subscribing to `working`, we      // subscribe to the delayed copy      this.delayedWorking.subscribe(function(needsWork) {        if (needsWork) {          doWork();          self.working(false);        }      });            function updateUI(showTable) {        if (showTable) {          self.working(true); // Triggers a slightly delayed update        } else {          self.data([]);        }      }            // Some data from doc. page to work with      function doWork() {        // (code only serves to mimic a slow render)        for (var i = 0; i < 1499; i++) {            self.data([]);            self.data(data.reverse());        }      };            var data = [          { name: 'Alfred', position: 'Butler', location: 'London' },          { name: 'Bruce', position: 'Chairman', location: 'New York' }      ];            // Some data to render      this.data = ko.observableArray([]);        }      ko.applyBindings(new AppViewModel());
.is-loading {    height: 100px;    background: red;    display: flex;    align-items: center;    justify-content: center;  }    .is-loading::after {    content: "LOADING";    color: white;      }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>    <label>    <input type="checkbox" data-bind="checked: showLargeTable, disable: working">Show slowly rendered table  </label>    <table data-bind="css: { 'is-loading': working }">    <tbody data-bind="foreach: data">      <tr>        <td data-bind="text: name"></td>        <td data-bind="text: position"></td>        <td data-bind="text: location"></td>      </tr>    </tbody>  </table>    <em>Example based on the <a href="http://knockoutjs.com/documentation/deferred-updates.html">knockout docs</a></em>

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment