Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a wrapper function for default knockout bindings

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?

like image 448
Đinh Carabus Avatar asked May 16 '17 08:05

Đinh Carabus


People also ask

What does Ko unwrap do?

unwrapObservable(item) Looking at the code, that call basically checks to see if item is an observable. If it is, return the value(), if it's not, just return the value.

What are the types of binding supported by knockout JS?

Binding Values The binding value can be a single value, literal, a variable or can be a JavaScript expression.

What is Ko applyBindings?

For example, ko. applyBindings(myViewModel, document. getElementById('someElementId')) . This restricts the activation to the element with ID someElementId and its descendants, which is useful if you want to have multiple view models and associate each with a different region of the page.

What is knockout library?

Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model.


1 Answers

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>
like image 192
user3297291 Avatar answered Oct 22 '22 00:10

user3297291