knockout observableArray not updating table - javascript

I wanted to get values from form fields and save them as an object into an observableArray. And show them in the table. So, every time i hit 'add' button the table should be updated but its not working.
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="$data.id"></td>
<td data-bind="$data.name"></td>
<td data-bind="$data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
this is my html. 'gradeList' is also an observableArray but its working and i'm getting nice dropdown menu. On last 'p' element, text gets updated with every 'add' button click with [Object object] text but table never gets updated.
var newModel = function () {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function () {
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
};
self.addMark = function () {
// console.log("button clicked");
self.mark({ "id": self.selectedGrade().Id, "name": self.selectedGrade().Name, "mark": self.komark() });
console.log(self.mark());
self.allMarks.push(self.mark());
console.log(self.allMarks());
};
self.loadAllGrades();
}
this is my javasript. The value of 'mark' and 'allMarks' gets updated in console but TABLE never gets updated.

<td data-bind="$data.id"></td> doesn't do anything, you haven't specified a binding. You probably wanted:
<td data-bind="text: $data.id"></td>
<!-- ----------^^^^^^ -->
...and similar for name, mark.
Working example:
var newModel = function() {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function() {
/*
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
*/
self.gradeList.push(
{Id: 1, Name: "Grade1"},
{Id: 2, Name: "Grade2"},
{Id: 3, Name: "Grade3"}
);
};
self.addMark = function() {
// console.log("button clicked");
self.mark({
"id": self.selectedGrade().Id,
"name": self.selectedGrade().Name,
"mark": self.komark()
});
//console.log(self.mark());
self.allMarks.push(self.mark());
//console.log(self.allMarks());
};
self.loadAllGrades();
}
ko.applyBindings(new newModel(), document.body);
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Side note: $data.id is a long way to write id. :-)
Side note 2: The [object Object] you're seeing for allMarks is because you're applying the text binding to an array of objects, so you get the default toString behavior of the object. You probably want a foreach for allMarks as well.

Related

Uncaught Error: Unable to process binding "foreach" (knockout js)

Error :
"Uncaught Error: Unable to process binding "foreach: function() {return phones }"
Message: Invalid object that looks like an observable; possibly from another Knockout instance"
html :
<h3>Contacts</h3>
<div >
<table >
<thead>
<tr>
<th>first name</th>
<th>last name</th>
<th>number</th>
</tr>
</thead>
<tbody data-bind="foreach: contacts">
<tr>
<td>
<input data-bind="value :firstName" /><br />
delete contact
</td>
<td>
<input data-bind="value : lastName" />
</td>
<td>
<table>
<tbody data-bind="foreach : phones">
<tr>
<td><select data-bind="options : contactType , value : contactTypeValue"></select></td>
<td><input data-bind="value : number" /></td>
<td>delete number</td>
</tr>
</tbody>
</table>
add number
</td>
</tr>
</tbody>
</table>
<br /><br />
<button data-bind="click : addContact"> add contact</button>
</div>
javascript :
var contactsModel = function ()
{
var self = this;
this.contacts = ko.observableArray([]);
this.addContact = function ()
{
this.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray()
});
};
this.deleteContact = function (contact)
{
this.contacts.remove(contact);
};
this.addNumber = function (contact)
{
contact.phones.push({
type: "",
number: ""
});
};
this.deleteNumber= function (phone)
{
$.each(this.contacts(), function () { this.phones.remove(phone) })
};
}
ko.applyBindings(new contactsModel());
I am new to Knockout JS so can someone please give solution to above error.
I will note 2 things.
First of all, you declare the observableArray without an empty array in it, this might look like a simple case, yet knockout has an issue regarding array declarations, at least it had, up to some version.
So change to this:
this.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray([]) // here
});
Also there is a reason that you define self=this;, yet you never really use it.
Re-write your code like so:
var contactsModel = function ()
{
var self = this;
self.contacts = ko.observableArray([]);
self.addContact = function ()
{
self.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray([])
});
};
self.deleteContact = function (contact)
{
this.contacts.remove(contact);
};
self.addNumber = function (contact)
{
contact.phones.push({
type: "",
number: ""
});
};
self.deleteNumber= function (phone)
{
$.each(self.contacts(), function () { self.phones.remove(phone) })
};
}

Add and remove Items on client side with Knockoutjs

I've been struggling to make an interactive form, in which a viewmodel has a collection of items. I want to dynamically add/remove items from that collection.
I've found it difficult to find examples that go to this depth and most of them usually stay on a more straight forward implementation, however I've come across This post which pretty much explains what i'm doing with this brilliant jsfiddle, in which a json is pulled using the knockout mapping pluggin and then mapped.
var company;
function PersonViewModel(data) {
var personMapping = {
'ignore': ['twitter', 'webpage'],
'copy': ['age'],
'lastName': {
'create': function (options) {
return ko.observable(options.data.toUpperCase());
}
}
};
ko.mapping.fromJS(data, personMapping, this);
this.fullName = ko.computed(function () {
return this.firstName() + ' ' + this.lastName();
}, this);
}
function CompanyViewModel(data) {
var companyMapping = {
'ignore': ['address', 'website'],
'name': {
'create': function (options) {
return ko.observable(options.data.toUpperCase());
}
},
'employees': {
key: function (data) {
return ko.utils.unwrapObservable(data.personId);
},
create: function (options) {
return new PersonViewModel(options.data);
}
}
};
ko.mapping.fromJS(data, companyMapping, this);
}
What i don't know how to achieve is how and where exactly to add the 'addEmployee' and 'removeEmployee' functions? and how to bind them to a button?.
Thank you in advance!
The logical place to add these would be to your CompanyViewModel. For example, something like this:
function CompanyViewModel(data) {
var self = this;
var companyMapping = {
// ...as before
};
self.addEmployee = function () {
// as an example, we are just adding a static new employee
self.employees.push(new PersonViewModel({
lastName: "new",
firstName: "employee",
age: 10
}));
}
// important, with how we are binding the function, we expect the
// argument, e, to be the employee to remove
self.removeEmployee = function (e) {
self.employees.remove(e);
}
ko.mapping.fromJS(data, companyMapping, this);
}
Add to bind, you can do something like this:
<div id="company">
<h1 data-bind="text: name"></h1>
<h2>Employees</h2>
<input type="button" value="add" data-bind="click: addEmployee" />
<table>
<thead>
<tr>
<th>Full name</th>
<th>Last name</th>
<th>First name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-bind="foreach: employees">
<tr>
<td data-bind="text: fullName"></td>
<td data-bind="text: lastName"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: age"></td>
<td>
<input type="button" value="x" data-bind="click: $parent.removeEmployee" />
</td>
</tr>
</tbody>
</table>
</div>
Which will add an add button as well as a remove x button to each employee which calls the removeEmployee function on the parent CompanyViewModel passing in the current employee.
Here's an updated fiddle
You would add those functions to your CompanyViewModel.
CompanyViewModel
...
this.addEmployee = function () {
this.employees.push(new PersonViewModel({
firstName: 'New',
lastName: 'Employee',
age: 777,
}));
};
this.removeEmployee = function () {
this.employees.pop();
};
HTML
....
<div data-bind="click: addEmployee">Add Employee</div>
<div data-bind="click: removeEmployee">Remove Employee</div>
...
http://jsfiddle.net/HBKYP/198/

Issue with Knockout not binding to model

I have an issue with Knockout binding to a model here is my code. The code fires and returns a JSON object but the table is empty. Any suggestions would be appreciated.
HTML
<table style="border: double">
<thead>
<tr>
<td>jobId</td>
</tr>
</thead>
<!--Iterate through an observableArray using foreach-->
<tbody data-bind="foreach: Jobs">
<tr style="border: solid" data-bind="click: $root.getselectedjob" id="updtr">
<td><span data-bind="text: $data.jobId "></span></td>
</tr>
</tbody>
</table>
Javascript
var JobViewModel = function () {
//Make the self as 'this' reference
var self = this;
//Declare observable which will be bind with UI
self.jobId = ko.observable("");
self.name = ko.observable("");
self.description = ko.observable("");
//The Object which stored data entered in the observables
var jobData = {
jobId: self.jobId,
name: self.name,
description: self.description
};
//Declare an ObservableArray for Storing the JSON Response
self.Jobs = ko.observableArray([]);
GetJobs(); //Call the Function which gets all records using ajax call
//Function to Read All Employees
function GetJobs() {
//Ajax Call Get All Job Records
$.ajax({
type: "GET",
url: "/Client/GetJobs",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
debugger;
self.Jobs(data); //Put the response in ObservableArray
},
error: function (error) {
alert(error.status + "<--and--> " + error.statusText);
}
});
//Ends Here
}
//Function to Display record to be updated. This will be
//executed when record is selected from the table
self.getselectedjob = function (job) {
self.jobId(job.jobId),
self.name(job.name),
self.description(job.description)
//,
//self.DeptName(employee.DeptName),
//self.Designation(employee.Designation)
};
};
ko.applyBindings(new JobViewModel());
C# Method to get jobs
public ActionResult GetJobs(string AccountIDstr)
{
//parse this as parameter
int AccountID = Convert.ToInt32(AccountIDstr);
AccountID = 1;
var jobs = (from c in db.jobs
select c).OrderByDescending(m => m.jobId).ToList();
//"Business logic" method that filter jobs by the account id
var jobsFilter = (from e in jobs
where (AccountID == null || e.accountId == AccountID)
select e).ToList();
var jobsresult = from jobrows in jobsFilter
select new
{
jobId = jobrows.jobId.ToString(),
name = jobrows.name,
description = jobrows.description
};
return Json(new
{
Jobs = jobsresult
},
JsonRequestBehavior.AllowGet);
}
JSON Object
{"Jobs":[{"jobId":"5","name":"Job 5 ","description":"Job 5 description"},{"jobId":"1","name":"Job 1 ","description":"Job 1 description"}]}
Your Jobs is an observableArray, but the data is wrapped in an object. When you set the value in GetJobs, you should be doing
self.Jobs(data.Jobs);
Here's a runnable snippet that works. You should be able to run this using your ajax function to populate data. If it doesn't work, examine what you're getting back.
var JobViewModel = function() {
//Make the self as 'this' reference
var self = this;
//Declare observable which will be bind with UI
self.jobId = ko.observable("");
self.name = ko.observable("");
self.description = ko.observable("");
//The Object which stored data entered in the observables
var jobData = {
jobId: self.jobId,
name: self.name,
description: self.description
};
//Declare an ObservableArray for Storing the JSON Response
self.Jobs = ko.observableArray([]);
GetJobs(); //Call the Function which gets all records using ajax call
//Function to Read All Employees
function GetJobs() {
//Ajax Call Get All Job Records
var data = {
"Jobs": [{
"jobId": "5",
"name": "Job 5 ",
"description": "Job 5 description"
}, {
"jobId": "1",
"name": "Job 1 ",
"description": "Job 1 description"
}]
};
setTimeout(function() {
self.Jobs(data.Jobs);
}, 500);
}
//Function to Display record to be updated. This will be
//executed when record is selected from the table
self.getselectedjob = function(job) {
self.jobId(job.jobId),
self.name(job.name),
self.description(job.description)
//,
//self.DeptName(employee.DeptName),
//self.Designation(employee.Designation)
};
};
ko.applyBindings(new JobViewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.1.0/knockout-min.js"></script>
<table style="border: double">
<thead>
<tr>
<td>jobId</td>
</tr>
</thead>
<!--Iterate through an observableArray using foreach-->
<tbody data-bind="foreach: Jobs">
<tr style="border: solid" data-bind="click: $root.getselectedjob" id="updtr">
<td><span data-bind="text: $data.jobId "></span>
</td>
</tr>
</tbody>
</table>

knockout dynamic grid, add column and rows

I am trying to add rows and columns to a knockout grid on the client side after .NET MVC has displayed the html grid
I could find a way to dynamically add rows but am not able to get the grid to add columns and rows. Does anyone know how to add both rows and columns dynamically with knockout and then have the data in the grid (including the new row and column headers) to be available for saving to the server?
<form action='/controller/save'>
<p>There are <span data-bind='text: series().length'> </span> series set</p>
<table data-bind='visible: series().length > 0'>
<thead>
<tr>
<th>---</th>
<!-- ko foreach: cols -->
<th data-bind='text:title'></th>
<!-- /ko -->
<th><button> Add New Column</button></th>
</tr>
</thead>
<tbody data-bind='foreach: series'>
<tr>
<td data-bind='text:name'></td>
<!-- ko foreach: cols -->
<td><input class='required' /></td>
<!-- /ko -->
<td><a href='#' data-bind='click: $root.removeSeries'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: addNewSeries'>Add New Series</button>
<button data-bind='enable: series().length > 0' type='submit'>Save</button>
and the js
var ItemModel = function (series) {
var self = this;
self.series = ko.observableArray(series);
self.addNewSeries = function () {
self.series.push({
name: "",
value: ""
});
};
self.removeSeries = function (series) {
self.series.remove(series);
};
self.save = function (form) {
ko.utils.postJson($("form")[0], self.items);
};
};
var viewModel = new ItemModel([
{ name: "2012/13", value: "122345" },
{ name: "2013/14", value: "564543" }
]);
ko.applyBindings(viewModel);
var ColumnModel = function (cols) {
var self = this;
self.cols = ko.observableArray(cols);
var Col = function (title) {
var self = this;
self.title = ko.observable(title);
self.addNewCol = function () {
self.cols.push(new Col('new'));
};
};
};
var columnModel = new ColumnModel([
{ title: "col1" },
{ title: "col2" }
]);
ko.applyBindings(columnModel);
I have created a fiddle to show
you made a lot of mistakes there. you need to use the chrome\explorer\firefox tools to debug your stuff. i got it yo work though
<form action='/controller/save'>
<p>There are <span data-bind='text: series().length'> </span> series set</p>
<div data-bind='visible: series().length > 0'>
<table>
<thead>
<tr data-bind="foreach: cols">
<th data-bind='text:$data.title'></th>
</tr>
<button data-bind="click:addNewCol"> Add New Column</button>
</thead>
<tbody data-bind='foreach: series'>
<tr>
<td data-bind='text:name'></td>
<!-- ko foreach :$root.cols -->
<!-- ko if: $index() != 0 -->
<td><input class='required' /></td>
<!-- /ko -->
<!--/ko-->
<td><a href='#' data-bind='click: $root.removeSeries'>Delete</a></td>
</tr>
</tbody>
</table>
</div>
<button data-bind='click: addNewSeries'>Add New Series</button>
<input type="text" data-bind="value: title"/>
<button data-bind='enable: series().length > 0' type='submit'>Save</button>
</form>
$(document).ready(function(){
var ItemModel = function (series) {
var self = this;
self.series = ko.observableArray(series);
self.addNewSeries = function () {
self.series.push({
name: self.title(),
value: ""
});
};
self.title=ko.observable();
self.removeSeries = function () {
//do something with series
self.series.remove(this);
};
self.save = function (form) {
ko.utils.postJson($("form")[0], self.items);
};
self.cols = ko.observableArray([{ title: "col1" },
{ title: "col2" }]);
function Col (title) {
this.title = ko.observable(title);
};
self.addNewCol = function () {
self.cols.push(new Col('new'));
};
};
var viewModel = new ItemModel([
{ name: "2012/13", value: "122345" },
{ name: "2013/14", value: "564543" }
]);
ko.applyBindings(viewModel);
/* var ColumnModel = function (cols) {
};
var columnModel = new ColumnModel([
{ title: "col1" },
{ title: "col2" }
]);
ko.applyBindings(columnModel);
*/
// Activate jQuery Validation
// $("form").validate({ submitHandler: viewModel.save });
});
http://jsfiddle.net/dCfYP/23/

UI does not update when Updating ObservableArray

When a user clicks on a row from a list in the UI (html table tblAllCert), I have a click event that fires to populate another observable array that should populate a second html table (tblCertDetails). My click event fires, I can see data come back, and i can see data in the observable array, but my view does not update.
Here is an overview of the sequence in the code-
1. user clicks on row from html table tblAllCert, which fires selectThing method.
2. In viewmodel code, selectThing passes the row data from the row selected to the GetCertificateDetails(row) method.
3. GetCertificateDetails(row) method calls getCertDetails(CertificateDetails, source) function on my data service (the data service gets data from a web api). getCertDetails(CertificateDetails, source) function returns an observable array.
4. once the data is returned to selectThing, CertificateDetails observable array is populated. It does get data to this point.
Step 5 should be updating the UI (specifically tblCertDetails html table), however the UI does not get updated.
Here is my view-
<table id="tblAllCert" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: allCertificates">
<tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
<td>
<ul style="width: 100%">
<b><span data-bind=" text: clientName"></span> (<span data-bind=" text: clientNumber"></span>) <span data-bind=" text: borrowBaseCount"></span> Loan(s) </b>
<br />
Collateral Analyst: <span data-bind=" text: userName"></span>
<br />
Certificate: <span data-bind="text: lwCertID"></span> Request Date: <span data-bind=" text: moment(requestDate).format('DD/MMM/YYYY')"></span>
</td>
</tr>
</tbody>
</table>
<table id="tblCertDetails" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: CertificateDetails">
<tr id="Tr1" style="cursor: pointer">
<td>
<ul style="width: 100%">
<b>Loan: <span data-bind=" text: loanNum"></span>
</td>
</tr>
</tbody>
</table>
My viewmodel-
define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
var allCertificates = ko.observableArray([]);
var myCertificates = ko.observableArray([]);
var isSelected = ko.observable();
var serverSelectedOptionID = ko.observable();
var CertificateDetails = ko.observableArray([]);
var serverOptions = [
{ id: 1, name: 'Certificate', OptionText: 'lwCertID' },
{ id: 2, name: 'Client Name', OptionText: 'clientName' },
{ id: 3, name: 'Client Number', OptionText: 'clientNumber' },
{ id: 4, name: 'Request Date', OptionText: 'requestDate' },
{ id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
];
var activate = function () {
// go get local data, if we have it
return SelectAllCerts(), SelectMyCerts();
};
var vm = {
activate: activate,
allCertificates: allCertificates,
myCertificates: myCertificates,
CertificateDetails: CertificateDetails,
title: 'Certificate Approvals',
SelectMyCerts: SelectMyCerts,
SelectAllCerts: SelectAllCerts,
theOptionId: ko.observable(1),
serverOptions: serverOptions,
serverSelectedOptionID: serverSelectedOptionID,
SortUpDownAllCerts: SortUpDownAllCerts,
isSelected: isSelected,
selectThing: function (row, event) {
CertificateDetails = GetCertificateDetails(row);
isSelected(row.lwCertID);
}
};
serverSelectedOptionID.subscribe(function () {
var sortCriteriaID = serverSelectedOptionID();
allCertificates.sort(function (a, b) {
var fieldname = serverOptions[sortCriteriaID - 1].OptionText;
if (a[fieldname] == b[fieldname]) {
return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
}
return a[fieldname] > b[fieldname] ? 1 : -1;
});
});
return vm;
function GetCertificateDetails(row) {
var source = {
'lwCertID': row.lwCertID,
'certType': row.certType,
}
return CertificateDataService.getCertDetails(CertificateDetails, source);
}
function SortUpDownAllCerts() {
allCertificates.sort();
}
function SelectAllCerts() {
return CertificateDataService.getallCertificates(allCertificates);
}
function SelectMyCerts() {
return CertificateDataService.getMyCertificates(myCertificates);
}
});
And releveant excerpt from my data service-
var getCertDetails = function (certificateDetailsObservable, source) {
var dataObservableArray = ko.observableArray([]);
$.ajax({
type: "POST",
dataType: "json",
url: "/api/caapproval/CertDtlsByID/",
data: source,
async: false,
success: function (dataIn) {
ko.mapping.fromJSON(dataIn, {}, dataObservableArray);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
return dataObservableArray;
}
Ideas of why the UI is not updating?
When you're using Knockout arrays, you should be changing the contents of the array rather than the reference to the array itself. In selectThing, when you set
CertificateDetails = GetCertificateDetails(row);
the new observableArray that's been assigned to CertificateDetails isn't the same observableArray that KO bound to your table.
You're very close to a solution, though. In your AJAX success callback, instead of creating a new dataObservableArray and mapping into that, you can perform the mapping directly into parameter certificateDetailsObservable. ko.mapping.fromJSON should replace the contents of the table-bound array (you would also get rid of the assignment in selectThing).

Categories