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?
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.
Binding Values The binding value can be a single value, literal, a variable or can be a JavaScript expression.
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.
Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model.
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...
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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With