Knockout does not update view with the observable object - javascript

Could you please help with this error? I have spent a lot of time on it, but no progress so for. Thanks!!
Error:
VM4705 knockout-debug.js:3326 Uncaught ReferenceError: Unable to process binding "with: function (){return currentCat }"
Message: Unable to process binding "text: function (){return clickCount }"
Message: clickCount is not defined
Expected behavior:
The program should list the cat names in the div with id "catNames" and update the div that has id "cat" with the information of the first cat from the "data" observable array. Next, when you click on different cat names from the names list, it should set the value of "currentCat" to the cat that is clicked on, which in turn should update the div with id "cat".
Link to JsFiddle
Below is my JS code:
var cats = [
{name:'cat1', photo: 'https://s20.postimg.org/owgnoq5c9/cat_1.jpg', clicks: 0 },
{name:'cat2', photo: 'https://s20.postimg.org/f9d5f0ccp/cat_2.jpg', clicks: 0 },
{name:'cat3', photo: 'https://s20.postimg.org/su3xe4s5l/cat_3.jpg', clicks: 0 },
{name:'cat4', photo: 'https://s20.postimg.org/xdg5zna15/cat_4.jpg', clicks: 0 },
{name:'cat5', photo: 'https://s20.postimg.org/78yuqivex/cat_5.jpg', clicks: 0 }
];
function CatRecord(cat){
var self = this;
self.catName = ko.observable(cat.name);
self.imgSrc = ko.observable(cat.photo);
self.clickCount= ko.observable(cat.clicks);
};
var ViewModel = function(){
var self = this;
self.data = ko.observableArray([]);
// data
cats.forEach(function(cat){
self.data.push(new CatRecord(cat));
}); // -- end of for Each
// view
self.currentCat = ko.observable(self.data()[0]);
self.setCurrentCat = function(catIndex){
self.currentCat(self.data()[catIndex]);
};
// actions
self.incrementClicks = function(){
var clickCount = self.currentCat().clickCount();
self.currentCat().clickCount(clickCount + 1);
};
};
ko.applyBindings(new ViewModel());
and the html:
<body>
<div id="catNames">
<ul id="catList" data-bind="foreach: data">
<li data-bind="text: catName, click:$parents[0].currentCat($index()) "></li>
</ul>
</div>
<div id="cat" data-bind="with: currentCat">
<h2 id="clicks" data-bind="text: clickCount"></h2>
<img id="photo" src="" alt="cat photo" data-bind=" click: $parent.incrementClicks,
attr: {src: imgSrc}">
<h4 id="name" data-bind="text: catName"></h4>
<button type="submit">Admin</button>
</div>
</body>

The issue is actually with the click binding inside foreach. It should be:
<ul id="catList" data-bind="foreach: data">
<li data-bind="text: catName, click:$parent.setCurrentCat"></li>
</ul>
The first parameter of setCurrentCat is the cat object which triggered the event. So you don't need the index. You can simply do this:
self.setCurrentCat = function(cat){
self.currentCat(cat);
};
I'm still not sure why you're getting the error for with binding.
Updated fiddle

Related

Knockout.js -Getting error - Uncaught ReferenceError: Unable to process binding "with: function"

I am working on a neighborhood map project and I am stuck! I am new to knockout.js. I am trying to use data-bind getting this error -
knockout-3.4.1.js:72 Uncaught ReferenceError: Unable to process binding "with: function (){return filteredItems }"
The snippet of HTML source -
section class="main">
<form class="search" method="post" action="index.html" >
<input type="text" data-bind="textInput: filter" placeholder="Click here/Type the name of the place">
<ul data-bind="with: filteredItems">
<li><span data-bind="text: title, click: $parent.showInfoWindow"></span></li>
</ul>
</form>
</section>
and this is my viewModel -
function viewModel(markers) {
var self = this;
self.filter = ko.observable(''); // this is for the search box, takes value in it and searches for it in the array
self.items = ko.observableArray(locations); // we have made the array of locations into a ko.observableArray
// attributed to - http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html , filtering through array
self.filteredItems = ko.computed(function() {
var filter = self.filter().toLowerCase();
if (!filter) {
return self.items();
} else {
return ko.utils.arrayFilter(self.items(), function(id) {
return stringStartsWith(id.name.toLowerCase(), self.filter);
});
}
});
var stringStartsWith = function (string, startsWith) {
string = string || "";
if (startsWith.length > string.length)
return false;
return string.substring(0, startsWith.length) === startsWith;
};
// populateInfoWindow(self.filteredItems,)
// this.showInfoWindow = function(place) { // this should show the infowindow if any place on the list is clicked
// google.maps.event.trigger(place.marker, 'click');
// };
}
Some lines are commented because I am still working on it. To see the whole project- https://github.com/Krishna-D-Sahoo/frontend-nanodegree-neighborhood-map
The with binding creates a new binding context with the provided element. The error is thrown because of a reference to title within the <span> element, but filteredItems does not have a title property.
If you want to display a <li> element for each element in the filteredItems array, you can use a foreach binding, like this:
<ul data-bind="foreach: filteredItems">
<li><span data-bind="text: title, click: $parent.showInfoWindow"></span></li>
</ul>

There is any way like append more data in jquery?

I'm using knockoutjs to bind data, and I want to append data bind in one element.
Here is my code :
html:
<div id="wrapper">
<div data-bind="foreach: people">
<h3 data-bind="text: name"></h3>
<p>Credits: <span data-bind="text: credits"></span></p>
</div>
Javascript code:
function getData(pageNumber)
{
//code get data
//binding
ko.applyBindings({ peopla: obj }, document.getElementById('wrapper'));
}
In the first time the pageNumber is 1, then I call getData(1), and I want show more data in page 2 I will call getData(2), and in page 2 data will be show more in wrapper element like append in jquery.
If I use normal jquery I can call some like that
$("#wrapper").append(getData(2));
So I don't know how to use knockout bind more data in one elemnt
Try the following script, this sort of simulates how you can append data to your array by replacing existing data or adding on to it. Hope it helps
function myModel() {
var self = this;
self.people = ko.observableArray([{
name: 'Page 1 data',
credits: 'credits for page 1'
}]);
var i = 2;
self.getData = function () {
var returnedData = [{
name: 'Page ' + i + ' Name',
credits: 'credits for page ' + i
}];
self.people(returnedData);
i++;
}
self.getData2 = function () {
var returnedData = {
name: 'Page ' + i + ' Name',
credits: 'credits for page ' + i
};
self.people.push(returnedData);
i++;
}
}
ko.applyBindings(new myModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="wrapper">
<div data-bind="foreach: people">
<h3 data-bind="text: name"></h3>
<p>Credits: <span data-bind="text: credits"></span>
</p>
</div>
<button data-bind="click: getData">Simulate get data (Replaces current data)</button>
<button data-bind="click: getData2">Append to the same array (Adds to existing array)</button>

Knockout: Can't get specific value from observableArray

I created array with data about some music albums in viewmodel. I can use this array in foreach loop (for example) but I can't display only one value from this array.
My viewmodel:
function CD(data) {
this.Author = ko.observable(data.Author);
this.Title = ko.observable(data.Title);
this.Price = ko.observable(data.Price);
this.Label = ko.observable(data.Label);
this.Id = ko.observable(data.Id);
}
function NewsListViewModel() {
var self = this;
self.newsList = ko.observableArray([]);
$.getJSON("/music/news", function (allData) {
var mapped = $.map(allData, function (item) { return new CD(item) });
self.newsList(mapped);
});
self.oneObject = ko.computed(function () {
return self.newsList()[0].Title;
});
}
$(document).ready(function () {
ko.applyBindings(new NewsListViewModel());
});
And how I used it in html:
<ul data-bind="foreach: newsList">
<li data-bind="text: Title"></li>
<li data-bind="text: Author"></li>
<li data-bind="text: Price"></li>
<li data-bind="text: Label"></li>
</ul>
This works perfectly. But if I trying additionally display one value:
<ul data-bind="foreach: newsList">
<li data-bind="text: Title"></li>
<li data-bind="text: Author"></li>
<li data-bind="text: Price"></li>
<li data-bind="text: Label"></li>
</ul>
...
<p data-bind="text: oneObject"</p>
it dosen't working, firebug thrown error:
Type error: self.newsList()[0].Title is undefined
Why I cannot get one specific value but I can display whole list?
PS. Code <p data-bind="text: newsList()[0].Title"</p> didn't works too.
You get the error because $.getJSON is asynchronous.
So when your computed is declared your items are not necessary ready in the newsList so there is no first element what you could access.
So you need to check in your computed that your newsList is not empty (anyway your server could also return an empty list so it is always good to have this check) and only then access the first element:
self.oneObject = ko.computed(function () {
if (self.newsList().length > 0)
return self.newsList()[0].Title();
return 'no title yet';
});
Note: you need also write .Title() to get its value because Title is an observable.
I can see a few problems with your code:
$.getJSON is asynchronous so self.newsList is empty when it's accessed by Knockout for the first time. Which in turn means that self.newsList[0] is undefined.
return self.newsList()[0].Title; in the computed observable doesn't return the value you want it to return. It should be return self.newsList()[0].Title();
You don't have to reinvent the wheel, there is the mapping plugin which you can use to map the data: http://knockoutjs.com/documentation/plugins-mapping.html

why does this knockout method receive a form element instead of the object its nested in?

I have this HTML:
<ul class="chat_list" data-bind="foreach: chats">
<li>
<div class="chat_response" data-bind="visible: CommentList().length == 0">
<form data-bind="submit: $root.addComment">
<input class="comment_field" placeholder="Comment…"
data-bind="value: NewCommentText"
/>
</form>
</div>
</li>
</ul>
and this JavaScript:
function ChatListViewModel(chats) {
// var self = this;
self.chats = ko.observableArray(ko.utils.arrayMap(chats, function (chat) {
return { CourseItemDescription: chat.CourseItemDescription,
CommentList: ko.observableArray(chat.CommentList),
CourseItemID: chat.CourseItemID,
UserName: chat.UserName,
ChatGroupNumber: chat.ChatGroupNumber,
ChatCount: chat.ChatCount,
NewCommentText: ko.observable("")
};
}));
self.newChatText = ko.observable();
self.addComment = function (chat) {
var newComment = { CourseItemDescription: chat.NewCommentText(),
ParentCourseItemID: chat.CourseItemID,
CourseID: $.CourseLogic.dataitem.CourseID,
AccountID: $.CourseLogic.dataitem.AccountID,
SystemObjectID: $.CourseLogic.dataitem.CommentSystemObjectID,
SystemObjectName: "Comments",
UserName: chat.UserName
};
chat.CommentList.push(newComment);
chat.NewCommentText("");
};
}
ko.applyBindings(new ChatListViewModel(initialData));
When I go into the debugger it shows that the chat parameter of the addComment() function is a form element instead of a chat object.
Why is this happening?
Because of KO behavior. To pass chat variable to submit handler you may use this:
<ul class="chat_list" data-bind="foreach: chats">
<li>
<div class="chat_response" data-bind="visible: CommentList().length == 0">
<form data-bind="submit: function(form){$root.addComment($data, form)}">
<input class="comment_field" placeholder="Comment…" data-bind="value: NewCommentText" />
</form>
</div>
</li>
</ul>
This is by design. From the Knockout.js docs:
As illustrated in this example, KO passes the form element as a
parameter to your submit handler function. You can ignore that
parameter if you want, but for an example of when it’s useful to have
a reference to that element, see the docs for the ko.postJson utility.
As noted by Serjio you can use currying to pass additional parameters into the function, or you can make use of Knockout's Unobtrusive Event Handling, which allows you to get the entire context associated with the form element.
self.addComment = function (form) {
var context = ko.contextFor(form);
var chat = context.$data;
//rest of your method here
};

How to modify object on a click

I'm trying to modify an object on a click. Here's what I have.
<form>
<ul class="tabs" data-tabs="tabs" data-bind="template: 'lineTemplate'"></ul>
<div class="pill-content" data-bind="template: 'lineDivTemplate'" ></div>
</form>
<script id="lineTemplate" type="text/html">
{{each(i, line) lines()}}
<li><a data-bind="click: function() { viewModel.setActive(line) }, attr : { href : '#line' + id() }"><span style="font-size: 15px;" data-bind="text : model"/></a></li>
{{/each}}
</script>
var viewModel = {
lines: ko.observableArray([]),
setActive : function(line) {
**//I need to modify this object**
line.activeTab = true;
}
};
$.getJSON("/json/all/lines", { customer_id : customer_id } , function(data) {
ko.mapping.fromJS(data, null, viewModel.lines);
});
ko.applyBindings(viewModel);
Basically when the user clicks the tab I need it to update the model(and eventually the database) that it is the currently active tab. The first way I had was the delete the object modify it and then push it back to the array, but pushing adds it to the end of the array, which I don't want. Thanks for any help.
Typically, you would mantain something like a "selectedTab" or "activeTab" observable.
var viewModel = {
lines: ko.observableArray([]),
activeTab: ko.observable(),
};
viewModel.setActive = function(line) {
this.activeTab(line);
}.bind(viewModel);
Then, you can do any binding that you want against activeTab. In KO 1.3, you could do:
<div data-bind="with: activeTab">
...add some bindings here
</div>
Prior to that you could do:
<script id="activeTmpl">
...add your bindings here
</script>

Categories