Knockout.js Pre-seed ObservableArray - javascript

I'm not sure if I'm trying to do too much here, but here is the scenario. I have an asp.net mvc page that, on the first time loading, returns a table of data in a view using the standard foreach mechanisms in the mvc framework. If the user has javascript enabled, I want to use knockout to update the table going forward. Is there a way to have knockout read the data from the dom table and use that data as the initial observable collection. From then on out, I would use knockout and ajax to add, edit, or delete data.
In a nutshell, I need to parse an html table into a knockout observable collection.

I've had a go at coding this up:
Here's the basic markup:
<table id="table" data-bind="template: { name: 'table-template' }">
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody>
<tr>
<td>Richard</td>
<td>Willis</td>
</tr>
<tr>
<td>John</td>
<td>Smith</td>
</tr>
</tbody>
</table>
<!-- Here is the template we'll use for re-building the table -->
<script type="text/html" id="table-template">
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: surname"></td>
</tr>
</tbody>
</script>
Javascript:
(function() {
function getTableData() {
// http://johndyer.name/html-table-to-json/
var table = document.getElementById('table');
var data = [];
var headers = [];
for (var i = 0; i < table.rows[0].cells.length; i++) { 
headers[i] = table.rows[0].cells[i].innerHTML.toLowerCase().replace(/ /gi, '');
}
// go through cells
for (var i = 1; i < table.rows.length; i++) {
var tableRow = table.rows[i];
var rowData = {};
for (var j = 0; j < tableRow.cells.length; j++) {
rowData[headers[j]] = tableRow.cells[j].innerHTML;
}
data.push(rowData);
}
return data; 
}
var Vm = function () {
this.data = ko.observableArray(getTableData());
};
ko.applyBindings(new Vm(), document.getElementById('table'));
})();
You can extend this concept using the mapping plugin to create observables for each row: http://knockoutjs.com/documentation/plugins-mapping.html
View a demo here: http://jsfiddle.net/CShqK/1/
EDIT: I'm not saying this is the best approach, as it can be costly to traverse a large table to get the data. I would probably just output the JSON in the page as suggested by others in this thread.

How about just feeding your observable array with the data instead of parsing the html table
myArray: ko.observableArray(#Html.Raw(Json.Encode(Model.myTableData)))
If you really need to go the parsing the html way, you can use the following code
var tableData = $('#myTable tr').map(function(){
return [
$('td,th',this).map(function(){
return $(this).text();
}).get()
];
}).get();
$(document).ready(function() {
var myData = JSON.stringify(tableData);
alert(myData)
});
Here is a fiddle showing the code in action:
http://jsfiddle.net/FWCXH/

Related

Getting a checkbox from a table field

Good afternoon, below is the code in it I am getting fields from my table. The 0 field contains a checkbox, how can I find out if it is checked or not(true or false)? You need to change this line: console.log (td.item (f));
var table = document.getElementsByClassName("table-sm");
for (var i = 0; i < table.length; i++) {
var tr = table.item(i).getElementsByTagName("tr");
for (var j = 0; j < tr.length; j++) {
var trr = tr.item(j);
var td = tr.item(j).getElementsByTagName("td");
for (var f = 0; f < td.length; f++) {
if (f === 0) console.log(td.item(f));
console.log(td.item(f).innerText);
}
}
}
Firstly, please learn about JavaScript Table API is much better than just making complex for-loops.
Next time please add full code (HTML/JavaScript) so people can help you.
Now let's fix your code.
Suppose we have this table
<table class="table-sm" id="myTable">
<thead>
<tr>
<th>Checkbox</th>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" name="selected[]" checked /></td>
<td>1</td>
<td>Mohammed</td>
</tr>
<tr>
<td><input type="checkbox" name="selected[]" /></td>
<td>2</td>
<td>Ali</td>
</tr>
</tbody>
</table>
and we want to get the first item of each rows, and check whether the checkbox is checked or not.
We can do it easly using JS Table APIs.
Get the table by it's ID.
var table = document.getElementById("myTable");
Get the table rows
I use Array.from() to convert HTMLCollection to normal array, so I can use Array APIs (Like .forEach).
var rows = Array.from(table.rows);
Loop into table rows, and get cells of each row.
rows.map((row) => {
var cells = Array.from(row.cells);
// TODO add logic here.
});
Get the firstChild of first cell.
rows.map((row) => {
var cells = Array.from(row.cells);
if (Array.isArray(cells) && cells.length > 0) {
var firstChild = cells[0].firstChild;
console.log(firstChild.checked);
}
});
Live Example

localstorage saves an empty object

<table id="products" class="table table-dark table-hover" style="text-align: center;width:45%;margin: 4px">
<tr>
<th>Car</th>
<th>Model</th>
<th>Price</th>
<th>Basket</th>
</tr>
<tr onclick="buyProduct(this)">
<td>Mercedes_Benz</td>
</tr>
<tr onclick="buyProduct(this)">
<td>BMW</td>
</tr>
<tr onclick="buyProduct(this)">
<td>Toyota</td>
</tr>
</table>
<table id="basket" class="table table-dark table-hover" style="text-align: center;width:45%;margin: 4px"></table>
i have a problem with storing data to localstorage , i want to save clicked to localstorage and onload get it but on local storage it shows empty object, this is code,, Thank you in advance
var row = document.getElementsByTagName('tr');
var prod = document.getElementById('products');
var bas = document.getElementById('basket');
var k = 0;
var arr = [];
function buyProduct(x) {
arr[k] = x;
localStorage.setItem("sold"+k, JSON.stringify(arr))
x.remove();
x.removeAttribute('onclick')
bas.append(x);
k++;
}
window.onload = function() {
var stored;
if(localStorage) {
for(var i = 0; i < localStorage.length; i++) {
stored = localStorage.getItem("sold"+i);
console.log(JSON.parse(stored))
}
}
}
When you invoke onclick="buyProduct(this)" you're saving the DOM <tr> elements in the array, not their associated values.
The JSON.stringify() function finds no enumerable properties on those elements, and so saves an empty object.
If you actually want the contents of the <td> that's within the row you could use this:
arr.push(this.querySelector('td').textContent);
NB: note use of push to add to the end of the array instead of the error-prone practice of maintaining your own variable tracking the array's length.

change first row tds width with pure javascript

i am trying to update first row width with JavaScript,
I have multiple tables
<table page="1">
<tbody>
<tr><td>aaaa</td><td>bbbbb</td></tr>
<tr><td>cccc</td><td>ddddd</td></tr>
....
</tbody>
</table>
<table page="2">
<tbody>
<tr><td>eeee</td><td>fffff</td></tr>
<tr><td>gggg</td><td>hhhhh</td></tr>
....
</tbody>
</table>
and an array
var cw = ["100","500"];
i am trying to create a function that will change only the tds from first row inside each table, so the end result should be
<table page="1">
<tbody>
<tr><td style="width:100px;">aaaa</td><td style="width:500px;">bbbbb</td></tr>
<tr><td>cccc</td><td>ddddd</td></tr>
....
</tbody>
</table>
<table page="2">
<tbody>
<tr><td style="width:100px;">eeee</td><td style="width:500px;">fffff</td></tr>
<tr><td>gggg</td><td>hhhhh</td></tr>
....
</tbody>
</table>
Since i only started using JavaScript i don't know best way to do things, yet, so i put this jsfiddle together from different examples, the problem is that i will have a lot of tables in one page (around 300) and i would like to know if this is the best way to do it:
var cw = ["100","500"];
var i,j,col;
var tables = document.getElementsByTagName("table");
for(i = 0;i<tables.length;i++) {
for ( j = 0, col; col = tables[i].rows[0].cells[j]; j++) {
col.style.width = cw[j]+'px';
}
}
i am afraid that 2 loops will make the browser unresponsive or slow, so can this be done safer with JavaScript or jquery?
Also i need this to work on IE, Chrome and Firefox
Thanks
Here is an example of how it should work
var size = ["100px", "500px"];
var tr = document.querySelectorAll('tbody tr:first-child');
for (i = 0; i < tr.length; i++) {
tr[i].firstElementChild.style.width = size[0];
tr[i].querySelector('td:nth-child(2)').style.width = size[1];
}
td {
background: yellow;
}
// Background for showing purpose only!
<table page="1">
<tbody>
<tr><td>aaaa</td><td>bbbbb</td></tr>
<tr><td>cccc</td><td>ddddd</td></tr>
</tbody>
</table>
<table page="2">
<tbody>
<tr><td>eeee</td><td>fffff</td></tr>
<tr><td>gggg</td><td>hhhhh</td></tr>
</tbody>
</table>
Enjoy!
I'm not sure you'll be able to get away from using two loops. One to go through each table and find the first row and the second to apply the widths to each td. Here's my jQuery version:
function assignWidths(arr){
$('table').each(function(){
var firstRowTds = $(this).find('tr').eq(0).children('td');
firstRowTds.each(function(i){
$(this).css('width', arr[i] + 'px');
});
});
}

Table headers of second table not correct with queryAllSelector

I am using a JavaScript snippet to show a responsive table, setting the headers on mobile via attributes. This works, but, if I use a second table with the same class, it goes all wrong on mobile (please resize your screen to see this); the headers of.
What am I doing wrong here and how can I fix this?
This is the HTML:
<table class="test">
<thead>
<tr>
<th>Bla</th>
<th>Bla</th>
<th>Bla</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bla</td>
<td>Blabla</td>
<td>Blablabla</td>
</tr>
</tbody>
</table>
<table class="test">
<thead>
<tr>
<th>Not</th>
<th>Not</th>
</tr>
</thead>
<tbody>
<tr>
<td>Twatwa</td>
<td>Twatwa</td>
</tr>
</tbody>
</table>
http://codepen.io/anon/pen/QbJqVv
Edit: after the new answer, it does show table headers on the second table now, but not the correct ones. It just puts the table headers of the first table, into the second.
As I wrote in the comments, you need to handle each table separately. For .querySelectorAll('.test th') will simply give you all th elements, irregardless of which table they belong to.
Here's a quick example of how this could be done.
// for each .test
[].forEach.call(document.querySelectorAll('.test'), function (table) {
// get header contents
var headers = [].map.call(table.querySelectorAll('th'), function (header) {
return header.textContent.replace(/\r?\n|\r/, '');
});
// for each row in tbody
[].forEach.call(table.querySelectorAll('tbody tr'), function (row) {
// for each cell
[].forEach.call(row.cells, function (cell, headerIndex) {
// apply the attribute
cell.setAttribute('data-th', headers[headerIndex]);
});
});
});
demo: http://codepen.io/anon/pen/NqEXqe
First of all, your HTML is invalid, as you are not closing any of your elements (<tr><td></td></tr> etc) - but that's another issue. Please practice good HTML standards.
You are not using querySelectorAll when selecting your table bodies, so you're only setting the attribute in the first one found.
This revised snippet should achieve what you are trying to do.
var headertext = [],
headers = document.querySelectorAll(".test th"),
tablerows = document.querySelectorAll(".test th"),
tablebody = document.querySelectorAll(".test tbody");
for(var i = 0; i < headers.length; i++) {
var current = headers[i];
headertext.push(current.textContent.replace(/\r?\n|\r/,""));
}
for (var tb = 0; tb < tablebody.length; tb++) {
for (var i = 0, row; row = tablebody[tb].rows[i]; i++) {
for (var j = 0, col; col = row.cells[j]; j++) {
col.setAttribute("data-th", headertext[j]);
}
}
}

Create an array of objects by iterating through table rows

I have an HTML table and I want to iterate through its rows and create a collection or lets say an "array of objects".
For example:
<table id="tbPermission">
<tr>
<th>User ID</th>
<th>User Name</th>
</tr>
<tr>
<td>1</td>
<td>Test1</td>
</tr>
</table>
I want to create a collection as below:
var trArray = [];
$('#tbPermission tr').each(function () {
var tdArray = [];
$(this).find('td').each(function () {
// I want to create the array of objects here …
tdArray.push();
});
// Final array
trArray.push(tdArray);
});
The arrays may be like below:
tdArray : {'UserID' : '1', 'UserName' : 'Test1'};
and:
trArray : [
{'UserID' : '1', 'UserName' : 'Test1'},
{'UserID' : '2', 'UserName' : 'Test2'}
]
Try this code
var trArray = [];
$('#tbPermission tr').each(function () {
var tr =$(this).text(); //get current tr's text
var tdArray = [];
$(this).find('td').each(function () {
var td = $(this).text(); //get current td's text
var items = {}; //create an empty object
items[tr] = td; // add elements to object
tdArray.push(items); //push the object to array
});
});
Here, I just created an empty object, filled object with references of tr and td, the added that object to the final array.
adding a working jsfiddle
This solution relies on adding thead and tbody elements which is a good idea anyways since it indicates to the browser that the table actually is a "data" table and not presentational.
jQuery has a .map() function. map is a basic function where you take an array and then replace the values with a the return value of a callback function - which results in a new array.
$([1,4,9]).map(function(){ return Math.sqrt(this) });
// [1, 2, 3]
.toArray converts the array like jQuery object we get into a "true array".
jQuery(function(){
var $table = $("#tbPermission");
var headers = $table.find('thead th').map(function(){
return $(this).text().replace(' ', '');
});
var rows = $table.find('tbody tr').map(function(){
var result = {};
var values = $(this).find('>td').map(function(){
return $(this).text();
});
// use the headers for keys and td values for values
for (var i = 0; i < headers.length; i++) {
result[headers[i]] = values[i];
}
// If you are using Underscore/Lodash you could replace this with
// return _.object(headers, values);
return result;
}).toArray();
// just for demo purposes
$('#test').text(JSON.stringify(rows));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="tbPermission">
<thead>
<tr>
<th>User ID</th>
<th>User Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Test1</td>
</tr>
</tbody>
</table>
<textarea id="test"></textarea>
If you for whatever reason cannot change the HTML you could use the index of the rows to differentiate between headers and rows of data:
var headers = $table.find('tr:eq(0) th').map(function(){
return $(this).text().replace(' ', '');
});
var rows = $table.find('tr:gt(0)').map(function(){
// ...
});
I would suggest changing your html slightly.
<table id="tbPermission">
<tr>
<th>User ID</th>
<th>User Name</th>
</tr>
<tr>
<td class="userid">1</td>
<td class="username">Test1</td>
</tr>
</table>
Then in your javascript when you want to get all the elements as an array you could do.
var userIdArray = $('#tbPermission .userid').map(function(userid){ return $(userid).html(); }).toArray();
This will find all elements with a class userid on the table, collect just the values, and .toArray() that result to get a basic javascript array. You can then take that and manipulate it into whatever json structure you want, or you could possibly create your json object inside that map function.
Check the console, you will get an array with the desired objects
var arr = [];
$('#tbPermission tr:not(.header)').each(function() {
var that = $(this);
var id = that.find('td').eq(0).text();
var name = that.find('td').eq(1).text();
var obj = { 'userId': id , 'userName': name };
arr.push(obj);
});
console.log(arr);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id="tbPermission">
<tr class="header">
<th>User ID</th>
<th>User Name</th>
</tr>
<tr>
<td>1</td>
<td>Test1</td>
</tr>
<tr>
<td>2</td>
<td>Test2</td>
</tr>
</table>
It's a bit tricky based on the given structure. You may have to modify the HTML a bit to map cells to headers, like below.
var myArray = [];
$("#tbPermission").find("td").each(function() {
var $this = $(this), obj = {};
obj[$this.data("column")] = $this.text();
myArray.push(obj);
});
alert ( JSON.stringify(myArray) );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="tbPermission">
<tr>
<th>User ID</th>
<th>User Name</th>
</tr>
<tr>
<td data-column="User ID">1</td>
<td data-column="User Name">Test1</td>
</tr>
</table>
Please give in some time to learn about Array.push() and Objects in Javascript. Hope that helps.

Categories

Resources