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>
0 comments:
Post a Comment