JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://rawgit.com/kamranayub/65399fa247a6c182bc65/raw/b5d83a75b60b42019277ab0b738d474bef3e628a/ko.bindingReport.js"></script>
<h3>
Introduction
</h3>
<p>
This widget is running faster in this demo (outside my application), but still slowly.
</p>
<p>
I want to make it faster. To see the problem, uncheck and re-check "Show meal dates" below, or re-run the demo.
</p>
<p>
In my app the list of users is this size, and is filterable, so I need to re-render when items become visible.
</p>
<h3>
Demo
</h3>
<input type="checkbox" data-bind="checked:showMeals" value="1"> Show meal dates<br>
<table>
<tbody data-bind="foreach: guests">
<tr>
<td data-bind="text: firstname"></td>
<td data-bind="text: lastname">Data</td>
<td>
<meals params="guest: $data, show: $root.showMeals"></meals>
</td>
</tr>
</tbody>
</table>
<template id='meals-template'>
<!-- ko if: show -->
<div data-bind="click: function(){}, clickBubble: false">
<!-- Once the page has loaded, the meals array never changes -->
<!-- ko foreach: meals -->
<div class="dates">
<span class="dates__label" data-bind="text: name"></span>
<span data-bind="html: $root.createHtml($data)"></span>
<!-- commented ko foreach: dates
<span class="dates__date" data-bind="css: {
is_setup: is_setup,
is_needed: $component.guest.meals().hasOwnProperty(id),
is_assigned: $component.guest.meals().hasOwnProperty(id) && $component.guest.meals()[id].diningRoom != null,
is_selected: $component.guest.meals().hasOwnProperty(id) ? $component.guest.meals()[id].selected : false
...
CSS
.rowSelected td { color: black; background: yellow; }
.dates {
font-size: 10px;
}
.dates__label {
width: 50px;
display: inline-block;
}
.dates__date {
font-size: 10px;
display: inline-block;
padding: 1px;
text-align: center;
width: 16px;
color: #ccc;
margin: 0 0 2px;
}
.is_setup {
background-color: #f6f6f6;
}
.is_needed {
background-color: orange;
color: #666;
cursor: pointer;
}
.is_assigned {
background-color: greenyellow;
color: #666;
}
.is_selected {
background: deeppink;
}
JavaScript
function MealsViewModel(params) {
// 'params' is an object whose key/value pairs are the parameters
// passed from the component binding or custom element.
this.meals = params.meals || meals;
this.guest = params.guest;
this.show = params.show;
// Process these params into the data structure we need to render
}
ko.components.register('meals', {
viewModel: MealsViewModel,
template: { element: 'meals-template' }
});
viewModel = function(guests) {
var self = this;
self.showMeals = ko.observable(true);
self.guests = ko.observableArray(guests);
self.toggleMealDateSelected = function(data) {
data.selected(!data.selected());
};
self.createHtml = function(meal){
var parts = [];
for (var i = 0; i < meal.dates.length; i++) {
parts.push("<span");
parts.push("class=\"");
if(meal.dates[i].is_setup ) parts.push("is_setup,");
if(meal.dates[i].is_needed ) parts.push("is_needed,");
if(meal.dates[i].is_assigned ) parts.push("is_assigned,");
if(meal.dates[i].is_selected ) parts.push("is_selected,");
parts.push("\"");
parts.push("data-diningroom-id=\"");
parts.push("123");
parts.push("\">");
parts.push(new Date(meal.dates[i].date).getDate());
parts.push("</span>");
}
return parts.join(" ");
}
};
/////////////////////////////////////////////////////////
// Data below. Initialization at bottom
/////////////////////////////////////////////////////////
var guest = {
"id": 2235,
"firstname": "John",
"lastname": "Doe",
"catered": true,
"dietaryRequirements": false,
"meals": {
"333": {
"date": "2016-09-06",
"name": "Breakfast",
"diningRoom": null,
"id": 55,
"selected": false
},
"334": {
"date": "2016-09-07",
"name": "Breakfast",
"diningRoom": null,
"id": 56,
"selected": false
},
"335": {
"date": "2016-09-08",
...