Knockout computed does not auto update - javascript

The debug console output shows that an option change does change the knockout data and the result of the change can be seen by forcing the computed to run by pressing a button on the last line on the html page but for some reason a ko.computed that is displayed on the second last line of the html does not auto update as I would expect.
Any help would be appreciated!
Code
JSFiddle Reference
function CustomBindingViewModel() {
var self = this;
self.range = [1, 2, 3, 4, 5];
self.opts = [
"Functionality, compatibility, pricing - all that boring stuff",
"How often it is mentioned on Hacker News",
"Number of gradients/dropshadows on project homepage",
"Totally believable testimonials on project homepage"
];
self.optionRange = 5;
self.options = ko.observableArray();
self.opts.forEach(function(option) {
self.options.push({
option: option,
importance: 2
});
});
self.points = function() {
var sum = 0;
var sumStr = "";
self.options().forEach(function(option) {
sum += option.importance;
sumStr += option.importance;
});
console.log(sumStr);
return 10 - sum;
};
}
ko.applyBindings(new CustomBindingViewModel());
.points {
font-weight: bold;
}
.select {
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
<h1>Knockout Custom Bindings</h1>
<h2>Which factors affect your technology choices?</h2>
<p>Please distribute <span class="points">10</span> points between the following options.</p>
<table>
<thead>
<tr>
<td>Option</td>
<td>Importance</td>
</tr>
</thead>
<tbody data-bind="foreach:options">
<tr>
<td data-bind="text:option"></td>
<td class="select">
<select data-bind="options:$root.range, value:importance"></select>
</td>
</tr>
</tbody>
</table>
<p>You've got <span class="points" data-bind="text:points()"></span> points left to use.</p>
<button data-bind="click:$root.points()">Finished</button>

While the array is an observable array the values in the array are not observable.
Make the importance property of the items in the array observable as well since that is what you want to watch.
//...other code removed for brevity
self.options = ko.observableArray();
self.opts.forEach(function(option) {
self.options.push({option:option, importance:ko.observable(2)}); //<-- NOTE THIS HERE
});
//...other code removed for brevity
so that the event fires when importance values change.
You would then need to update how the points are calculated.
//...other code removed for brevity
self.points = function() {
var sum = 0;
var sumStr = "";
self.options().forEach(function (option) {
sum += option.importance(); //<-- NOTE THE BRACES
sumStr += option.importance(); //<-- NOTE THE BRACES
});
console.log(sumStr);
return 10 - sum;
};
//...other code removed for brevity
And Here is the completed js fiddle

Related

How do I simplify this code? Is there a better way of doing this?

I just got this code working after hours of stress. I am new to Javascript so I am not sure if I did this the most efficient way possible. I am using an API that is provided by IEX. The goal of this code is to output out news when there is some. This isn't completely working as you can tell, but I did get the headline to work. So if I am doing anything wrong let me know please.
<html>
<head>
<style>
/* Outter Table <Tbody> Settings*/
.outtertable tbody {
vertical-align: top;
}
/* Innertable Table data settings */
.innertable tr > td {
vertical-align: top;
}
/* Div Article Holder Settings */
.divBorder {
margin-bottom: 10px;
border: solid;
border-color: #c4ef8b;
border-width: 4px 0px 0px 0px;
}
/* Article Image settings */
.articleImg {
height:50px;
width: 50px;
}
/* DivBorder Mouse Hover */
.divBorder:hover {
cursor: pointer;
background-color: #f3ffe5;
}
</style>
</head>
<body>
<table class="outterTable" id="newsContent"></table>
</body>
<script>
var request = new XMLHttpRequest();
request.open ('GET', 'https://api.iextrading.com/1.0/stock/spy/news')
//on request load
request.onload = function() {
//VARIABLES
var newsContainer = document.getElementById("newsContent");
var JSONData = JSON.parse(request.responseText);
var articleAmount = JSONData.length;
var rowAmount = articleAmount / 3;
var rowAmountRoundDown= Math.trunc(rowAmount);
var rowAmountRoundUp = (Math.trunc(rowAmount) + 1);
var remainder = (rowAmount - Math.floor(rowAmount)).toFixed(2); //.00, .67, or .33;
//=== TABLE CREATOR =============================================
//Create an "<tbody>" element
let tbodyHTML = document.createElement('tbody');
//"Assembler" inside is "createTable()"
tbodyHTML.innerHTML = createTable();
//FUNCTION Create Table
function createTable() {
var tData = '';
var index = 0;
//========= First Table Part Row Loop ===========================================================
for (var i = 1; i <= rowAmountRoundDown; i++) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c = 1 + index; c < 4 + index; c++) {
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//Row End
tData = tData + `
</tr>
`;
index = index + 3;
}
//========= Second table part =====================================================================
//If remainder is .67 create 2 <td>
if (remainder == 0.67) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c2 = (1 + index); c2 < (3 + index); c2++){
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c2}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c2}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//row End
tData = tData + `
</tr>
`;
//If remainder is .33 create 1 <Td>
} else if (remainder == 0.33) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c = (1 + index); c < (2 + index); c++){
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//row End
tData = tData + `
</tr>
`;
//Anything else dont do anything
} else {
tData = tData;
}
return tData;
}
//Inject into the HTML
newsContainer.appendChild(tbodyHTML);
//===============================================================
var red = (JSONData.length + 1)
console.log(red);
//Output data to html
for (var l = 1; l < red; l++){
console.log("l: " + l);
spyOutputToHTML(JSONData, l);
}
};
function spyOutputToHTML(data, i) {
//get current variables in this HTML page x3
var offset = i - 1;
var headline = document.getElementById(`headline${i}`);
//Get Content From the JSON file ex: ".latestPrice"
var JSONHeadline = data[offset].headline;
//Inject data into HTML
headline.innerHTML = JSONHeadline;
}
request.send()
</script>
First of all, great job! This is impressive work for a newbie in javascript
Few things could definitely be improved, but I don't see you're doing anything wrong. Maybe, only the logic with remainder is too confusing. I bet there should be an easier way
Readability
Your code would be undoubtedly easier to read and understand if you had the view (templating) logic, the request logic, and the data "massaging" logic separated
The view logic
Generally, constructing HTML structures "by hand" (createElement, appendChild) takes more effort and is, arguably, more confusing as opposed to rendering a string with a template function (kind of like you did) and injecting the result where you need it. Mixing these approaches is even more error-prone than doing everything "by hand". So, I would suggest you have one view/ template function that would take data and return a string
function renderTable(data) {
var result = '<div>';
// build the result string...
result += '</div>';
return result;
}
// and then...
targetEl.innerHTML = renderTable(data);
You may also want to leverage micro-templating. One or another kind of templating would be a must-have for a larger application. Make yourself familiar with templating engines. For your project, building strings with javascript is fine. Although, there is a more advanced technique you can consider
The data "massaging" logic
Well, that comes down to having your template function not being "smart" about its context (basic separation of concerns), and only consuming the data. Not "cooking" it, only eating it :)
So, instead of doing this
function renderTable(data) {
var result = '<div>';
var something = data.abc * data.xyz;
// do something with something here
result += '</div>';
return result;
}
targetEl.innerHTML = renderTable(data);
... you do this
function adaptResponsePayloadData(data) {
var result = { ...data };
result.something = result.abc * result.xyz;
return result;
}
function renderTable(data) {
// ...
}
targetEl.innerHTML = renderTable(adaptResponsePayloadData(data));
This is an example of the so-called Adapter design pattern. This is the right case for using it. There are many other design patterns and I strongly recommend to dedicate time to make yourself familiar with them
The request logic
Another concern separation here. You can have the request logic in a separate function, similarly how we separated "massaging" from the view above
const API_ENDPOINT_URL = 'https://api.iextrading.com/1.0/stock/spy/news';
function fetchData(callback) {
var request = new XMLHttpRequest();
request.open('GET', API_ENDPOINT_URL);
request.onLoad(() => {
var data = JSON.parse(request.responseText);
callback(data);
});
request.send();
}
// ... and then
fetchData(data => {
// ...
targetEl.innerHTML = renderTable(adaptResponsePayloadData(data));
});
Note on execution order
There is the rule of thumb to make it clear when your code is run. This is totally a nit-pick, but it is possible to separate what from when on the code level
function goOn() { // there are many conventional names for this, like `main`
fetchData(data => {
document.body.innerHTML = renderTable(adaptResponsePayloadData(data));
});
}
window.onload = goOn;
Notes on HTML and CSS
You don't really need <tbody>. It is not needed unless you want to highlight something with CSS
Avoid using inline styles, like <td style="width: 33.33%; padding: 0px 25px">. You can express that with CSS
You don't need the divBorder class. Add padding to the parent td and border to the child table
Other minor notes
Conventionally, names with the first capital letter are object constructors or classes. Simply, make regular var names lowerCamelCase, like jsonHeadline
JSON is a term for notation we know. When we parse a string of that notation, it simply becomes data or contextData, you got it... Then, what's inside that data becomes simply headline
Do your best at naming variables so that you don't have to write comments to understand what you meant. Find other good tips here
Make sure your production code has no console.log statements
let keyword is safer than var. You'd never pollute the global scope with it
Please mind that there is Code Review on StackExchange where you can learn many other aspects of how to write great code
You did really great. Good luck to you on your journey! :)

How to sort list by priority in JavaScript?

How to sort list items by priority? This is a to-do list. User can input an item, choose a priority, and add to list.
This is my HTML form:
<input id="task" type="text"/>
<select id="priority">
<option id="Normal">Normal</option>
<option id="Urgent">Urgent</option>
<option id="Critical">Critical</option>
<option id="If You Can">If You Can</option>
</select>
<button onclick="amitFunction()">Add</button>
<hr/>
<table>
<tr>
<th id="result"></th>
<th id="priorit"></th>
</tr>
<table>
This is my JS:
function amitFunction() {
/* Define vars and values */
var lia = document.createElement("p");
var lib = document.createElement("p");
var item = document.getElementById('task').value;
var pro = document.getElementById('priority').value;
var pro_array = ['Urgent','Critical','Normal'];
var item_list = document.createTextNode(item);
var item_pro = document.createTextNode(pro);
lia.appendChild(item_list);
lib.appendChild(item_pro);
/* Check if text is less than 6 chars or more than 42 chars */
if (item.length<6) {
alert('Your text must have a least 6 chars');
} else if (item.length>42) {
alert('Your text must have less than 42 chars');
} else {
document.getElementById("result").appendChild(lia);
document.getElementById("priorit").appendChild(lib);
document.getElementById('task').value='';
}
/* Change text color base on priority */
if (pro==pro_array[0]) {
$("p:last-child").css('color','red');
}
if (pro==pro_array[1]) {
$("p:last-child").css('color','orange');
}
if (pro==pro_array[2]) {
$("p:last-child").css('color','green');
}
/* Delete text when user clicks on it */
$([lia,lib]).click(function(){
$([lia,lib]).css('color','gray');
$([lia,lib]).css("text-decoration", "line-through");
});
}
What I need is, when user adds a new item, it will sort by priority order.
first : Urgent
second : Critical
third : Normal
fourth : If You Can
Each new item that user adds, should be sorted like that. How can I do that?
This is the complete script (JSBin) to understand what I need.
First of all I would suggest you create a new table row every time you create a TODO task, however I decided to keep as much of your code as I could and implement what you asked for. I will admit that it is not the best decision and could be optimized a lot, however I am leaving it as it is simply because there might be many interesting cases in the code that might teach you something new. The sorting is implemented. I hope this helps :)
Your html, left as it was:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<input id="task" type="text"/>
<select id="priority">
<option id="Normal">Normal</option>
<option id="Urgent">Urgent</option>
<option id="Critical">Critical</option>
<option id="If You Can">If You Can</option>
</select>
<button onclick="amitFunction()">Add</button>
<hr/>
<table>
<tr>
<th id="result"></th>
<th id="priorit"></th>
</tr>
<table>
</body>
</html>
and the edited JS code:
//creating a global collection to hold your todo list in memory
var todo_list = [];
function amitFunction() {
var item = document.getElementById('task').value;
/* Check if text is less than 6 chars or more than 42 chars
and return if validation is not passed */
if(item.length<6){
alert('Your text must have a least 6 chars');
return;
}else if(item.length>42){
alert('Your text must have less than 42 chars');
return;
}
var pro = document.getElementById('priority').value;
//keep this for colors
var pro_array = ['Urgent','Critical','Normal'];
//map string priorities to numeric values
var priorities =
{
'Urgent' : 0,
'Critical' : 1,
'Normal' : 2,
'If You Can' : 3
}
//push each new task in the todo list
todo_list.push(
{
priority : pro,
task : item
}
);
//Now this here is perhaps the most important part,
//this is where you sort your todo list based on the
//mapped to numeric values priorities
todo_list.sort(function (task1, task2) {
return priorities[task1.priority] - priorities[task2.priority];
});
//clear the containers holding your list
var resultNode = document.getElementById("result");
while (resultNode.firstChild) {
resultNode.removeChild(resultNode.firstChild);
}
var priorityNode = document.getElementById("priorit");
while (priorityNode.firstChild) {
priorityNode.removeChild(priorityNode.firstChild);
}
//recreate the DOM based on the todo_list collection
for(var i =0; i < todo_list.length; i++)
{
var lia = document.createElement("p");
var lib = document.createElement("p");
var item_list = document.createTextNode(todo_list[i].task);
var item_pro = document.createTextNode(todo_list[i].priority);
lia.appendChild(item_list);
lib.appendChild(item_pro);
document.getElementById("result").appendChild(lia);
document.getElementById("priorit").appendChild(lib);
document.getElementById('task').value='';
/* Change text color base on priority */
if(todo_list[i].priority == pro_array[0]){
$("p:last-child").css('color','red');
}
if(todo_list[i].priority == pro_array[1]){
$("p:last-child").css('color','orange');
}
if(todo_list[i].priority == pro_array[2]){
$("p:last-child").css('color','green');
}
}
//reinitialize the click handlers
var resultNode = document.getElementById("result");
var priorityNode = document.getElementById("priorit");
for(var i =0; i< resultNode.childNodes.length; i++) (function(i){
resultNode.childNodes[i].onclick = function() {
$([resultNode.childNodes[i],priorityNode.childNodes[i]]).css('color','gray');
$([resultNode.childNodes[i],priorityNode.childNodes[i]]).css("text-decoration", "line-through");
}
priorityNode.childNodes[i].onclick = function() {
$([resultNode.childNodes[i],priorityNode.childNodes[i]]).css('color','gray');
$([resultNode.childNodes[i],priorityNode.childNodes[i]]).css("text-decoration", "line-through");
}
})(i);
}
And a working example here:
https://jsbin.com/kudipacexi/edit?html,js,output
In fact there are plenty of approaches, another approach would be to not keep a global collection for your list, instead do the sorting directly using the DOM elements, however you will still have to keep some kind of numeric representation of your priorities in order to sort them by priority. It might also be a good idea to subscribe each of the elements to a single click handler function, then add the line-through style based on the caller of the function. Another thing I'd suggest is, if you are involving jQuery and not focusing on just Vanilla JS, try and use jQuery for the majority of the DOM manipulation.
Because you have a table I suggest to use that as the output.
You can rearrange your select in order to add for each option a priority number and a color attribute like:
<option value="2" color="green">Normal</option>
The table can contain a first column as the current row priority. This column will be hidden.
Each time a new row must be added a sorting process is executed on table rows.
The snippet:
$('button').on('click', function (e) {
var priorityValue = $('#priority option:selected').val();
var priorityText = $('#priority option:selected').text();
var colorVal = $('#priority option:selected').attr('color');
var taskValue = $('#task').val();
if (taskValue.length < 6) {
$('#errMsg').text('Your text must have a least 6 chars');
return;
} else if (taskValue.length > 42) {
$('#errMsg').text('Your text must have less than 42 chars');
return;
}
$('#errMsg').text('');
//
// create the new table row...
//
var newRow = $('<tr/>', {style: 'color:' + colorVal})
.append($('<td/>', {style: "display: none", text: priorityValue}))
.append($('<td/>', {text: taskValue}))
.append($('<td/>', {text: priorityText}));
//
// enlarge current table rows with the current one and sort elements
//
var tableRowsSorted = $('#result tbody').append(newRow).find('tr').get().sort(function(a, b) {
var p1 = +$(a).find('td:first').text();
var p2 = +$(b).find('td:first').text();
return p1 - p2;
});
//
// append/replace the taable body
//
$('#result tbody').append(tableRowsSorted);
//
// reset input text
//
$('#task').val('');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="task" type="text"/><span id="errMsg" style="color: red;"></span>
<select id="priority">
<option value="2" color="green">Normal</option>
<option value="0" color="red">Urgent</option>
<option value="1" color="orange">Critical</option>
<option value="3" color="black">If You Can</option>
</select>
<button>Add</button>
<hr/>
<table id="result">
<thead>
<tr>
<th style="display: none">priority</th>
<th>result</th>
<th>priority</th>
</tr>
</thead>
<tbody>
</tbody>
</table>

How to multiply 2 td in Javascript?

I have a table where one td gets 1 if a checkbox is checked and I would like to multiple this td with another and display it in a third one.
See the html here:
<div>
<input type="checkbox" id="fut1">check</input>
</div>
<table border="1" cellpadding="10" id="countit">
<tr>
<td id="td1"></td>
<td id="td2">5000</td>
<td id="td3"></td>
</tr>
</table>
And here is the js:
$('#fut1').change(function () {
if ($(this).is(":checked")) {
$('#td1').text('1');
} else {
$('#td1').text('0');
}
});
$('#td1').change(function () {
var me = $('#td1').value;
var ar = $('#td2').value;
var sum = me * ar;
$('#td3').text(sum);
});
$('#td1').change(function () { // <--- td elements don't have a change event listener/handler
var me = $('#td1').value; // <--- td elements don't have a value
var ar = $('#td2').value; // <--- td elements don't have a value
var sum = me * ar;
$('#td3').text(sum);
});
If you want to do it this way:
$('#fut1').change(function () {
if ($(this).is(":checked")) {
$('#td1').text('1');
} else {
$('#td1').text('0');
}
callTdChange();
});
function callTdChange() {
var me = parseInt($('#td1').text());
var ar = parseInt($('#td2').text());
var sum = me * ar;
$('#td3').text(sum);
}
Of course, the better way should be to use form elements (inputs) in the case you want to save your data to a server, or use change behaviors.
#td1 doesn't support the change event, because that's only meant for interactive elements (like input, select, or textarea).
You can either do the calculation in your first event listener in #fut1, or declare an input element inside #td1.

Knockout.js two-way binding for checkbox not working

I'm new to knockout.js and I have some trouble with two-way binding of a checkbox.
I have a table that's bound to a list of "companies".
<table>
<thead>
<tr>
<th>...</th>
<th>...</th>
<th>...</th>
</tr>
</thead>
<tbody data-bind="foreach: companies">
<tr>
<td>...</td>
<td><input type="checkbox" data-bind="checked:isCompanyForCommunication, click:$root.checkedNewCompanyForCommunication" /></td>
<td>...</td>
</tr>
</tbody>
</table>
But there should only be 1 company with isCompanyForCommunication = true in the table. So when another checkbox is checked, I have to set all other companies to isCompanyForCommunication = false. Therefore I created a method in the viewModel to uncheck all other companies.
// Definition of Company
var Company = function (crmAccountObject, contactId, companyForCommunicationId) {
this.isCompanyForCommunication = ko.observable(false);
}
// Definition of the ViewModel
var ViewModel = function () {
var self = this;
// ...
// Lists
this.companies = null; // This observableArray is set somewhere else
// Events
this.checkedNewCompanyForCommunication = function (company, event) {
// Set all companies to False
for (var i = 0; i < self.companies().length; i++) {
self.companies()[i].isCompanyForCommunication = ko.observable(false);
}
// Set the "checked" company to TRUE
company.isCompanyForCommunication = ko.observable(true);
return true;
}
}
However, the changes are not reflected in the HTML page. All other checkboxes remain checked.
I created a simplyfied example in jsfiddle to show what exactly I want to achieve with the checkbox https://jsfiddle.net/htZfX/59/
Someone has any idea what I'm doing wrong? Thanks in advance!
You are not setting the value of the isCompanyForCommunication but overriding it with a new observable.
Observables are functions and to update them you need to call them with the new value as the argument:
this.checkedNewCompanyForCommunication = function (company, event) {
// Set all companies to False
for (var i = 0; i < self.companies().length; i++) {
self.companies()[i].isCompanyForCommunication(false);
}
// Set the "checked" company to TRUE
company.isCompanyForCommunication(true);
return true;
}
I've also updated your sample code: JSFiddle.

Create grid with knockoutjs

Let's say we have an observable array of N+1 elements. What i would like to achive is to build 3 by X grid with buttons from these elements. Like phone keypad or something like this.
What i have done so far is created a table with foreach and if in it:
<table>
<tbody data-bind="foreach: tableList">
<!-- ko if: isEof($index()) -->
<tr>
<!-- /ko -->
<td><button data-bind="text: name"></button></td>
<!-- ko if: isEof($index()) -->
</tr>
<!-- /ko -->
</tbody>
</table>
isEof() function should determine by list index wether we have already 3 elements rendered. If yes, then it will render tags. Also if index is 0, then it also renders elements.
This is the code of the function:
function TableItem(data){
this.id=ko.observable(data.id);
this.name=ko.observable(data.name);
this.isEof = function(index){
if(index ==0){
return true;
}else{
if((index+3) % 3 === 0){
return true;
}else{
return false;
}
}
}
}
But i'm facing two problems with this setup.
1) With these if blocks enabled, button name binding does not work. When i remove ko if blocks, it will be rendered correctly.
2) ko if statements does not seem to work correctly. It will render out only those lines, where is also allowed to render.
I've made JSFiddle sample of my solution: http://jsfiddle.net/kpihus/3Lw7xjae/2/
I would create a ko.computed that converts your list of table items into an array of arrays:
var TableItem = function TableItem(data) {
this.id = ko.observable(data.id);
this.name = ko.observable(data.name);
};
var Vm = function Vm() {
this.tableItems = ko.observableArray([]);
this.columnCount = ko.observable(3);
this.columns = ko.computed(function() {
var columns = [],
itemCount = this.tableItems().length,
begin = 0;
// we use begin + 1 to compare to length, because slice
// uses zero-based index parameters
while (begin + 1 < itemCount) {
columns.push( this.tableItems.slice(begin, begin + this.columnCount()) );
begin += this.columnCount();
}
return columns;
// don't forget to set `this` inside the computed to our Vm
}, this);
};
vm = new Vm();
ko.applyBindings(vm);
for (var i = 1; i < 15; i++) {
vm.tableItems.push(new TableItem({
id: i,
name: "name: " + i
}));
}
This way, you can display your table by nesting two foreach bindings:
<table>
<tbody data-bind="foreach: { data: columns, as: 'column' }">
<tr data-bind="foreach: column">
<td>
<button data-bind="text: name">A</button>
</td>
</tr>
</tbody>
</table>
JSFiddle
If you haven't worked with ko.computed before, they track whatever observables are accessed inside of them — in this case, this.tableItems and this.columnCount —, and whenever one of them changes, they run again and produce a new result.
this.columns takes our array of table items
[TableItem, TableItem, TableItem, TableItem, TableItem, TableItem]
and groups them by this.columnCount into
[[TableItem, TableItem, TableItem], [TableItem, TableItem, TableItem]]
We can achieve this by simply passing $root which will have tableList and do the looping in simple way .
View Model:
function TableItem(data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.pickarray = ko.observableArray();
self.isEof = function (index, root) {
if (index === 0) {
self.pickarray(root.tableList().slice(index, index + 3));
return true;
} else if ((index + 3) % 3 === 0) {
self.pickarray(root.tableList().slice(index, index + 3));
return true;
} else {
return false;
}
}
}
var vm = function () {
this.tableList = ko.observableArray();
for (var i = 1; i < 15; i++) {
this.tableList.push(new TableItem({
id: i,
name: "name: " + i
}));
}
}
ko.applyBindings(new vm());
View :
<table data-bind="foreach:tableList">
<tr data-bind="if:isEof($index(),$root)">
<!-- ko foreach: pickarray -->
<td>
<button data-bind="text: id"></button>
</td>
<!-- /ko -->
</tr>
</table>
Trick here is very simple we just need to conditionally fill pickarray which does the job for us .
Working Fiddle here

Categories

Resources