Knockout.js - Sum table, add row and fill the table with AJAX - javascript

Im using this table to add materials and using Knockoutjs-3.4.0.js to add row and to sum it. My problem is when i try to edit the code i want to populate the table with a AJAX request. The problem is that i don't know how to fill the table with the AJAX response.
If i use the code below i get this error:
ReferenceError: Unable to process binding "click: function (){return
addMaterial }" Message: Can't find variable: addMaterial
<table class="table table-bordered">
<thead>
<tr>
<th>Moment</th>
<th>Antal </th>
<th>Kostnad</th>
<th>Totalt</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: materials">
<tr>
<td><input data-bind="value: name" /></td>
<td><input data-bind="value: quantity" /></td>
<td><input data-bind="value: rate" /></td>
<td data-bind="text: formattedTotal"></td>
<td></td>
</tr>
<tfoot>
<tr>
<th colspan="2"><button class="fa fa-plus btn-success" data-bind="click: addMaterial, enable: materials().length < 20"> Lägg till rad</button></th>
<th class="text-right">Totalt</th>
<th class="text-center"><span data-bind="text: totalSurcharge().toFixed(0)"></span></th>
<th> </th>
</tr>
<tr id="momsRow" class="hidden">
<th colspan="3" class="text-right">Moms</th>
<th class="text-center"><span data-bind="text: totalVat().toFixed(1)"></span></th>
<th> </th>
</tr>
<tr id="byggmomsRow" class="hidden">
<th colspan="3" class="">Omvänd byggmoms</th>
<th class="text-center"></th>
<th> </th>
</tr>
<tr>
<th colspan="3" class="text-right">Totalt:</th>
<th class="text-center"><span data-bind="text: totalPlusVat().toFixed(2)"></span></th>
<th> </th>
</tr>
</tfoot>
</tbody>
</table>
The knockout.js code:
/*------------- Load rows ------------- */
function LoadRows() {
var self = this;
self.materials = ko.observableArray([]);
$.getJSON("/json/tender_offer_edit_moment_json.asp", function(data) {
self.materials(data);
})
}
//ko.applyBindings(new dealModel());
ko.applyBindings(new LoadRows());
/*------------- Sum table ------------- */
function addMaterial() {
this.name = ko.observable("");
this.quantity = ko.observable("");
this.rate = ko.observable(0);
this.formattedTotal = ko.computed(function() {
return this.rate() * this.quantity();
}, this);
}
function documentViewModel(){
var self = this;
//create a materials array
self.materials = ko.observableArray([
new addMaterial()
]);
// Computed data
self.totalSurcharge = ko.computed(function() {
var total = 0;
for (var i = 0; i < self.materials().length; i++)
total += self.materials()[i].formattedTotal();
return total;
});
// add VAT(moms 25%) data
self.totalVat = ko.computed(function() {
var totalWithVat = 0;
for (var i = 0; i < self.materials().length; i++)
totalWithVat += self.materials()[i].formattedTotal();
totalWithVat = totalWithVat*0.25;
return totalWithVat;
});
// Totalt with VAT(moms 25%) data
self.totalPlusVat = ko.computed(function() {
var totalWithVat = 0;
for (var i = 0; i < self.materials().length; i++)
totalWithVat += self.materials()[i].formattedTotal();
totalWithVat = totalWithVat*1.25;
return totalWithVat;
});
// Operations
self.addMaterial = function() {
self.materials.push(new addMaterial());
}
self.removeMaterial = function(material) { self.materials.remove(material) }
}
ko.applyBindings(new documentViewModel());
/*------------- Sum table END ------------- */
There is a correct json format on the AJAX request.
[{"name":"Moment 1","quantity":"1","rate":"10","formattedTotal":"10"},{"name":"Moment 2","quantity":"2","rate":"20","formattedTotal":"40"}]
$.ajax({
url: "/json/tender_offer_edit_moment_json.asp",
type: "GET",
dataType: "json",
success: function (data) {
console.log(data);
alert(data);
//new addMaterial(data);
new addMaterial(data);
}
});

JsFiddle
First of all, you call ko.applyBindings() twice and to whole page,
it is not suitable in your situation:
To load the initial data you can do smth like this:
var vm = new documentViewModel();
$.getJSON("/json/tender_offer_edit_moment_json.asp", function(data) {
vm.materials(data);
})
ko.applyBindings(vm);
and delete this lines:
function LoadRows() {
var self = this;
self.materials = ko.observableArray([]);
$.getJSON("/json/tender_offer_edit_moment_json.asp", function(data) {
self.materials(data);
})
}
//ko.applyBindings(new dealModel());
ko.applyBindings(new LoadRows());

Related

How to call a javascript method with knockout

I am using knockout for binding, and the issue I have is can't seem to know how to call the remove method. I have two class convocation, and vague.
class Convocation {
constructor(sessionId, description)
{
var self = this;
this.ConvocationID = ko.observable(sessionId);
this.ConvDesc = ko.observable(description);
this.Vagues = ko.observableArray();
addVague(start, end) {
this.Vagues.push(new Vague(start, end));
}
removeVague() {
self.Vagues.remove(this)
}
}
class Vague {
constructor(start, end) {
this.startDate = ko.observable(start);
this.endDate = ko.observable(end);
}
}
I initialize my knockout using this viewModel, witch works.
function ViewModel() {
var self = this;
this.Convocations = ko.observableArray();
// Get information
this.Initialize = function () {
$.ajax({
url: "/Convocations/GetConvocationList",
dataType: 'json',
//data: { id: id },
success: function (data) {
for (var i = 0; i < data.length; i++) {
self.Convocations.push(new Convocation(data[i].sessionCode, data[i].desc));
for (var j = 0; j < data[i].vagues.length; j++) {
self.Convocations()[i].addVague(data[i].vagues[j].start, data[i].vagues[j].end);
}
}
}
});
}
}
This is my jquery calling the viewModel once ready.
(function ($) {
// we can now rely on $ within the safety of our "bodyguard" function
$(document).ready(function () {
var vm = new ViewModel();
ko.applyBindings(vm);
vm.Initialize();
});
})(jQuery);
But when it comes to delete a vague, I can't seem to know how to call it this is a snip of my view
<tbody data-bind="foreach: Convocations">
<tr>
<td><Input data-bind="value: $data.ConvocationID"></td>
<td><Input data-bind="value: $data.ConvDesc"></td>
</tr>
<tr>
<td colspan="3">
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Start Date</th>
<th>End Date</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: $data.Vagues">
<tr>
<td><span type="date" data-bind="text: $data.startDate"></span></td>
<td><span type="date" data-bind="text: $data.endDate"></span></td>
<td><a href='#' data-bind='click: $parent.removeVague'>Delete</a></td>
</tr>
</tbody>
</table>
If i conver addRemove() to something like this, it use to work, but i can't define this in a class.
this.remove = function () {
self.Vagues.remove(this);
}
I suspect that there may be some confusion around what $data and $parent represents in the html bindings.
for the nested foreach knockout bindings like you have its generally a good idea to give each level its own object name rather than just using $data.
Personally I am still learning the javascript syntax around classes etc, so there is probably a better way of doing the javascript than what I have done.
var data = [{
sessionCode: 1,
desc: 'Convocation 1',
vagues: [{
start: '2020-07-01',
end: '2020-07-30'
}]
}, {
sessionCode: 2,
desc: 'Convocation 2',
vagues: [{
start: '2020-07-02',
end: '2020-07-29'
}]
}];
class Convocation {
constructor(sessionId, description) {
var self = this;
self.ConvocationID = ko.observable(sessionId);
self.ConvDesc = ko.observable(description);
self.Vagues = ko.observableArray();
self.addVague = function addVague(start, end) {
self.Vagues.push(new Vague(start, end));
}
self.removeVague = function removeVague(item) {
self.Vagues.remove(item);
}
}
}
class Vague {
constructor(start, end) {
this.startDate = ko.observable(start);
this.endDate = ko.observable(end);
}
}
function ViewModel() {
var self = this;
self.Convocations = ko.observableArray();
//helper function that mimics the success function of the ajax call to allow loading data
self.processData = function(data) {
for (var i = 0; i < data.length; i++) {
self.Convocations.push(new Convocation(data[i].sessionCode, data[i].desc));
for (var j = 0; j < data[i].vagues.length; j++) {
self.Convocations()[i].addVague(data[i].vagues[j].start, data[i].vagues[j].end);
}
}
}
// Get information
self.Initialize = function() {
$.ajax({
url: "/Convocations/GetConvocationList",
dataType: 'json',
//data: { id: id },
success: function(data) {
for (var i = 0; i < data.length; i++) {
self.Convocations.push(new Convocation(data[i].sessionCode, data[i].desc));
for (var j = 0; j < data[i].vagues.length; j++) {
self.Convocations()[i].addVague(data[i].vagues[j].start, data[i].vagues[j].end);
}
}
}
});
}
}
var vm = new ViewModel();
ko.applyBindings(vm);
//vm.Initialize();
vm.processData(data);
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table">
<tbody data-bind="foreach: {data: Convocations, as: 'convocation'}">
<tr>
<td>
<input data-bind="value: convocation.ConvocationID" />
</td>
<td>
<input data-bind="value: convocation.ConvDesc" />
</td>
</tr>
<tr>
<td colspan="3">
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Start Date</th>
<th>End Date</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach:{data: convocation.Vagues, as: 'vague'}">
<tr>
<td><span type="date" data-bind="text: vague.startDate"></span></td>
<td><span type="date" data-bind="text: vague.endDate"></span></td>
<td><a href='#' data-bind='click: convocation.removeVague'>Delete</a></td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</table>

How to show data from a database in my table from my website?

I have a database that I want to access to display my product data, in this case the ItemCode and ItemName.
the connections are all correct and the functional, my problems is the loop and show the data in the table that I have in the html file.
let ctrl = {
showDados: function(r) {
/*dados[3].ItemCode;*/
let dados = JSON.parse(r.responseText);
let cod = dados.ItemCode;
let name = dados.ItemName;
let text = "";
let i;
for (i = 0; i < cod.length; i++) {
text += cod[i] + "<br>";
}
document.getElementById("cod").innerHTML = text;
for (i = 0; i < name.length; i++) {
text += name[i] + "<br>";
}
document.getElementById("name").innerHTML = text;
},
init: function() {
util.ajax(settings.serviceAddress + "OITM", ctrl.showDados);
}
};
$(document).ready(function() {
ctrl.init();
});
<div class="container">
<table class="table table-hover">
<thead>
<tr>
<th>Código</th>
<th>Nome</th>
</tr>
</thead>
<tbody>
<tr>
<td id="cod">FSE0204</td>
<td id="name">25130101_Loc.Finaneira - BCP- CT 400105814</td>
</tr>
<tr>
<td>FSE0205</td>
<td>25130201_Loc.Finaneira - Totta- CT 195649</td>
</tr>
</tbody>
</table>
</div>

Calculate sum of merged rows and display in third column

I am trying to calculate the monthly expenses from my table.
Want to sum up all the amount month wise and display in total like for January month total will be $180, March will be $230 and May $200. The amount should reflect in the total column. I have created this table using ng-repeat of angular framework (dynamic table)
JSFIDDLE
I have tried below code which will sum up all the individual cols having only numeric values. This code is not working for merged rows.
for (col = 1; col < ncol + 1; col++) {
console.log("column: " + col);
sum = 0;
$("tr").each(function(rowindex) {
$(this).find("td:nth-child(" + col + ")").each(function(rowindex) {
newval = $(this).find("input").val();
console.log(newval);
if (isNaN(newval)) {
$(this).html(sum);
} else {
sum += parseInt(newval);
}
});
});
}
});
Any help on this will be really helpful.
I would add a data-month attribute to the cell displaying the amount, it is not visible to users, but super helpful for you. Have a look at the solution below.
function getMonth(month) {
var monthCells = $("td[data-month='" + month + "']"); // get all TDs with a data month attribute
var sum = 0;
for(var i = 0; i < monthCells.length; i++){ // iterate over the tds
var amountCell = monthCells[i]; // get a td for given iteration
var amountCellText = $(amountCell).text(); // get the text content
sum += parseInt(amountCellText.replace(/\D/, "")); // in amoutnCellText replace anything that's not a digit into an empty string
}
return sum;
}
console.log(getMonth("march"))
table, th, td {
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<th>Month</th>
<th>Savings</th>
<th>Total</th>
</tr>
<tr>
<td rowspan="2">January</td>
<td data-month="january">$100</td>
</tr>
<tr>
<td data-month="january">$80</td>
</tr>
<tr>
<td rowspan="2">March</td>
<td data-month="march">$200</td>
</tr>
<tr>
<td data-month="march">$30</td>
</tr>
<tr>
<td>May</td>
<td data-month="may">$200</td>
</tr>
</table>
How about something like this?
vm.data = [
{
month: 'January',
savings: [
{ amount: 100 },
{ amount: 200}
]
},
{
month: 'February',
savings: [
{ amount: 300 },
{ amount: 400 }
]
}
];
In html:
<table class="table table-bordered table-condensed">
<tr>
<th>Month</th>
<th>Savings</th>
<th>Total</th>
</tr>
<tr ng-repeat="row in vm.data">
<td>{{row.month}}</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="s in row.savings">
<td>${{s.amount}}</td>
</tr>
</table>
</td>
<td>${{vm.getTotal(row.savings)}}</td>
</tr>
</table>
In JS:
vm.getTotal = getTotal;
function getTotal(savings) {
var total = 0;
angular.forEach(savings,
function (row) {
total += row.amount;
});
return total;
}
The key here is data modeling so that you have a simple function in getting total. Hope it will help.
Sample

jquery .each loop odd behaviour

Hi I have the following code
html
<table id="tbPermission">
<tr>
<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>
<tr>
<td>3</td>
<td>Test3</td>
</tr>
</table>
script
var trArray = [];
var tdArray = [];
var reruiredObj = {"UserID":null,
"UserName":null
};
var first;
var second;
$('#tbPermission tr').each(function () {
$(this).find('td').each(function (index) {
//alert(index+'-'+ $(this).html());
//alert(index);
if(index == 0){
first = $(this).html();
}
else{
second = $(this).html();
}
//alert(JSON.stringify(reruiredObj));
});
alert(first+'-'+second)
reruiredObj['UserID'] = first;
reruiredObj['UserName'] = second;
trArray.push(reruiredObj);
});
alert(JSON.stringify(trArray));
Demo Here
My question why first and second in undefined in first alert, and why it is
[{"UserID":"3","UserName":"Test3"},{"UserID":"3","UserName":"Test3"},{"UserID":"3","UserName":"Test3"},{"UserID":"3","UserName":"Test3"}]
my desired output is
[{"UserID":"1","UserName":"Test1"},{"UserID":"2","UserName":"Test2"},{"UserID":"3","UserName":"Test3"}]
The scope of your reruiredObj object is incorrect which is why you get the same object three times. Try this instead:
var trArray = [];
var tdArray = [];
var first;
var second;
$('#tbPermission tr:gt(0)').each(function () {
var reruiredObj = {
"UserID": null,
"UserName": null
};
first = $(this).find('td').eq(0).html();
second = $(this).find('td').eq(1).html();
reruiredObj['UserID'] = first;
reruiredObj['UserName'] = second;
trArray.push(reruiredObj);
});
console.log(JSON.stringify(trArray));
jsFiddle example
And the undefined values come from iterating over the first row which you don't want, and can ignore with tr:gt(0)
The first alert gives undefined because the first row of the table does not contain any td element.
To exclude the first row from the loop:
$('#tbPermission tr').each(function (i) {
if (i != 0) {
// execute ..
}
});
As for the array, try this in each loop:
var reruiredObj = { "UserID": first , "UserName":second };
Check the DEMO
Below works fine for me.
Since your first tr doesnt have td it gives undefined error. Try below one
<table id="tbPermission">
<thead>
<tr>
<th>User ID</th>
<th>User Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Test1</td>
</tr>
<tr>
<td>2</td>
<td>Test2</td>
</tr>
<tr>
<td>3</td>
<td>Test3</td>
</tr>
</tbody>
</table>
<script>
$(function () {
var trArray = [];
var tdArray = [];
var reruiredObj = {"UserID":null,
"UserName":null
};
jsonObj = [];
var first;
var second;
$('#tbPermission tbody tr').each(function () {
$(this).find('td').each(function (index) {
//alert(index+'-'+ $(this).html());
//alert(index);
if(index == 0){
first = $(this).html();
}
else{
second = $(this).html();
}
//alert(JSON.stringify(reruiredObj));
});
alert(first+'-'+second)
item = {}
item ["UserID"] = first;
item ["UserName"] = second;
jsonObj.push(item);
});
console.log(jsonObj);
});
</script>
alert jsonObj. This gives the required result.

Click table row and get value of all cells

I don't know JQuery, so I'm hoping there is a way to do this in pure Javascript.
I need to click on a table row and get the value of each cell in that row. Here is the format of my table:
<table class='list'>
<tr>
<th class='tech'>OCB</th>
<th class='area'>Area</th>
<th class='name'>Name</th>
<th class='cell'>Cell #</th>
<th class='nick'>Nickname</th>
</tr>
<tr onclick="somefunction()">
<td>275</td>
<td>Layton Installation</td>
<td>Benjamin Lloyd</td>
<td>(801) 123-456</td>
<td>Ben</td>
</tr>
</table>
Is there anyway short of putting a unique ID to each cell?
There is no need to add ids or add multiple event handlers to the table. One click event is all that is needed. Also you should use thead and tbody for your tables to separate the heading from the content.
var table = document.getElementsByTagName("table")[0];
var tbody = table.getElementsByTagName("tbody")[0];
tbody.onclick = function (e) {
e = e || window.event;
var data = [];
var target = e.srcElement || e.target;
while (target && target.nodeName !== "TR") {
target = target.parentNode;
}
if (target) {
var cells = target.getElementsByTagName("td");
for (var i = 0; i < cells.length; i++) {
data.push(cells[i].innerHTML);
}
}
alert(data);
};
<table class='list'>
<thead>
<tr>
<th class='tech'>OCB</th>
<th class='area'>Area</th>
<th class='name'>Name</th>
<th class='cell'>Cell #</th>
<th class='nick'>Nickname</th>
</tr>
</thead>
<tbody>
<tr>
<td>275</td>
<td>Layton Installation</td>
<td>Benjamin Lloyd</td>
<td>(801) 123-456</td>
<td>Ben</td>
</tr>
</tbody>
</table>
Example:
http://jsfiddle.net/ZpCWD/
Check this fiddle link
HTML:
<table id="rowCtr" class='list'>
<thead>
<tr>
<th class='tech'>OCB</th>
<th class='area'>Area</th>
<th class='name'>Name</th>
<th class='cell'>Cell #</th>
<th class='nick'>Nickname</th>
</tr>
</thead>
<tbody>
<tr>
<td>275</td>
<td>Layton Installation</td>
<td>Benjamin Lloyd</td>
<td>(801) 123-456</td>
<td>Ben</td>
</tr>
</tbody>
</table>
JAVASCRIPT:
init();
function init(){
addRowHandlers('rowCtr');
}
function addRowHandlers(tableId) {
if(document.getElementById(tableId)!=null){
var table = document.getElementById(tableId);
var rows = table.getElementsByTagName('tr');
var ocb = '';
var area = '';
var name = '';
var cell = '';
var nick = '';
for ( var i = 1; i < rows.length; i++) {
rows[i].i = i;
rows[i].onclick = function() {
ocb = table.rows[this.i].cells[0].innerHTML;
area = table.rows[this.i].cells[1].innerHTML;
name = table.rows[this.i].cells[2].innerHTML;
cell = table.rows[this.i].cells[3].innerHTML;
nick = table.rows[this.i].cells[4].innerHTML;
alert('ocb: '+ocb+' area: '+area+' name: '+name+' cell: '+cell+' nick: '+nick);
};
}
}
}
var elements = document.getElementsByTagName('td');
for (var i =0; i < elements.length; i++) {
var cell_id = 'id' + i;
elements[i].setAttribute('id', cell_id);
}
Maybe put something like this in function your onclick links to from the tr?
$("tr").click(function () {
var rowItems = $(this).children('td').map(function () {
return this.innerHTML;
}).toArray();
});
This shows the row's first cell which is clicked according to dataTr.querySelectorAll("td")[0].innerText;
document.querySelector("#myTable").addEventListener("click",event => {
let dataTr = event.target.parentNode;
let dataRes = dataTr.querySelectorAll("td")[0].innerText;
console.log(dataRes);
});

Categories

Resources