Issue with Knockout not binding to model - javascript

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>

Related

Knockout JS How to Access a Value from Second View Model / Binding

I am new to Knockout JS and think it is great. The documentation is great but I cannot seem to use it to solve my current problem.
The Summary of my Code
I have two viewmodels represented by two js scripts. They are unified in a parent js file. The save button's event is outside
both foreach binders. I can save all data in the details foreach.
My Problem
I need to be able to include the value from the contacts foreach binder with the values from the details foreach binder.
What I have tried
I have tried accessig the data from both viewmodels from the parent viewmodel and sending the POST request to the controller from there but the observeableArrays show undefined.
Create.CSHTML (Using MVC5 no razor)
<div id="container1" data-bind="foreach: contacts">
<input type="text" data-bind="value: contactname" />
</div>
<div data-bind="foreach: details" class="card-body">
<input type="text" data-bind="value: itemValue" />
</div>
The save is outside of both foreach binders
<div class="card-footer">
<button type="button" data-bind="click: $root.save" class="btn
btn-success">Send Notification</button>
</div>
<script src="~/Scripts/ViewScripts/ParentVM.js" type="text/javascript"></script>
<script src="~/Scripts/ViewScripts/ViewModel1.js" type="text/javascript"></script>
<script src="~/Scripts/ViewScripts/ViewModel2.js" type="text/javascript"></script>
ViewModel1
var ViewModel1 = function (contacts) {
var self = this;
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return {
contactName: contact.contactName
};
}));
}
ViewModel2
var ViewModel2 = function (details) {
var self = this;
self.details = ko.observableArray(ko.utils.arrayMap(details, function (detail) {
return {
itemNumber: detail.itemValue
};
}));
}
self.save = function () {
$.ajax({
url: baseURI,
type: "POST",
data: ko.toJSON(self.details),
dataType: "json",
contentType: "application/json",
success: function (data) {
console.log(data);
window.location.href = "/Home/Create/";
},
error: function (error) {
console.log(error);
window.location.href = "/Homel/Create/";
}
});
};
ParentViewModel
var VM1;
var VM2;
var initialContactInfo = [
{
contactPhone: ""
}
]
var initialForm = [
{
itemValue: ""
]
}
$(document).ready(function () {
if ($.isEmptyObject(VM1)) {
ArnMasterData = new ViewModel1(initialContactInfo);
ko.applyBindings(VM1, document.getElementById("container1"));
}
if ($.isEmptyObject(VM2)) {
VM2 = new ViewModel2(initialForm);
ko.applyBindings(VM2, document.getElementById("container2"));
}
});

knockout observableArray not updating table

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.

controller to populate the data into the datatable using angular js

I want to populate the data into the datatable but no data is getting populated into the table.
Error I'm getting on debugging is:
Uncaught TypeError: Cannot read property 'getContext' of null
html:
<table class="display table table-bordered table-striped" id="example">
<thead>
<tr>
<th>User Name</th>
<th>Email Id</th>
<th>Group Name</th>
<th>Created Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{item.user}}</td>
<td>{{item.email}}</td>
<td>{{item.groupName}}</td>
<td>{{item.createdAt}}</td>
</tr>
</tbody>
</table>
controller:
(function () {
"use strict";
angular.module('app').controller('superAdminController', function ($scope, AuthenticationService, $timeout, $location, $http, myConfig) {
AuthenticationService.loadSuperAdmin(function (response) {
if (response.data.success) {
$scope.populateTable(response.data);
console.log(response.data);
} else {
$scope.items = [];
}
});
$scope.populateTable = function (data) {
$scope.items = data.loadSuperAdminData;
$timeout(function () {
$("#example").dataTable();
}, 200)
};
});
}());
In controller, you can populate your server response like this.
$.post(MY_CONSTANT.url + '/api_name',{
access_token: accessToken
}).then(
function(data) {
var dataArray = [];
data = JSON.parse(data);
var lst = data.list;
lst.forEach(function (column){
var d = {user: "", email: "", group: "", created: ""};
d.user = column.user;
d.email = column.email;
d.groupName = column.group;
d.createdAt = column.created;
dataArray.push(d);
});
$scope.$apply(function () {
$scope.list = dataArray;
// Define global instance we'll use to destroy later
var dtInstance;
$timeout(function () {
if (!$.fn.dataTable) return;
dtInstance = $('#datatable4').dataTable({
'paging': true, // Table pagination
'ordering': true, // Column ordering
'info': true, // Bottom left status text
// Text translation options
// Note the required keywords between underscores (e.g _MENU_)
oLanguage: {
sSearch: 'Search all columns:',
sLengthMenu: '_MENU_ records per page',
info: 'Showing page _PAGE_ of _PAGES_',
zeroRecords: 'Nothing found - sorry',
infoEmpty: 'No records available',
infoFiltered: '(filtered from _MAX_ total records)'
}
});
var inputSearchClass = 'datatable_input_col_search';
var columnInputs = $('tfoot .' + inputSearchClass);
// On input keyup trigger filtering
columnInputs
.keyup(function () {
dtInstance.fnFilter(this.value, columnInputs.index(this));
});
});
// When scope is destroyed we unload all DT instances
// Also ColVis requires special attention since it attaches
// elements to body and will not be removed after unload DT
$scope.$on('$destroy', function () {
dtInstance.fnDestroy();
$('[class*=ColVis]').remove();
});
});
});

Knockout: Binding to a select-observable returns nothing

While following example 3 in this Knockout-example: http://knockoutjs.com/documentation/options-binding.html
I have a lists of sport-objects I select via a dropdown. Each sport-object has a list of categories which I iterate over based on which sport is selected. This currently works.
Now, I need to get the name of the sport currently selected so I can use it in CRUD-operations. In the example below I try to store it in a hidden field which does not work as intended, leaving the value-attribute in the field empty.
So, how can I get the name (or any other observable in the model) from the selected value in the dropdown?
The code below shows the models and the html I use:
<select id="categorySelector" class="form-control" data-bind="options: Sports, optionsText: 'Name', value: SelectedSport"></select>
<table data-bind="foreach: SelectedSport">
<tbody data-bind="foreach: Categories">
<tr data-bind="attr: { 'data-name': Name }">
<td data-bind="text: Name"></td>
</tr>
</tbody>
</table>
<input type="hidden" data-bind="value: $root.SelectedSport.Name" />
<span data-bind="text: SelectedSport.Name"></span>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.1.0.js"></script>
<script>
var CategoryModel = function (name) {
var self = this;
self.Name = ko.observable(name);
};
var SportModel = function (name) {
var self = this;
self.Name = ko.observable(name);
};
var ViewModel = function () {
var self = this;
self.Sports = ko.observableArray();
self.SelectedSport = ko.observable();
self.GetSports = function () {
$.getJSON('data.json', function (data) {
$.each(data, function (sportIndex, sportValue) {
if (sportValue.hasOwnProperty('name')) {
var newSport = new SportModel(sportValue.name);
console.log(sportValue.name);
$.each(sportValue.categories, function (categoryIndex, categoryValue) {
if (categoryValue.hasOwnProperty('name')) {
newSport.Categories.push(new CategoryModel(categoryValue.name));
console.log('--' + categoryValue.name);
}
});
self.Sports.push(newSport);
}
});
})
.fail(function () {
console.log("Failed to load and/or parse bikeData.json");
});
};
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.GetSports();
</script>
The Json file:
[
{
"name": "Archery",
"categories": [
{ "name": "Världsmästerskap" },
{ "name": "Vintermästerskap" }
]
},
{
"name": "Bike",
"categories": [
{ "name": "Världsmästerskap" },
{ "name": "Europamästerskap" }
]
}
]
I found the answer: you need to check if there really is a value selected and if there isn't, insert a default, like so:
<input type="hidden" data-bind="value: SelectedSport() ? SelectedSport().Name : 'unknown'" />
The example on the Knockout-site showed this.

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