When I click this button, an object is created and added to the array which is reflected in UI. Next when I click this button again I get an error "Cannot read property 'push' of undefined". What could possibly be the issue? Since the context seems fine otherwise it wouldn't have called the appropriate function in Rule class.
html code
<div data-bind="with: formatterVM">
<div data-bind="with: currentRule">
<ul data-bind="foreach: conditions">
<li data-bind="html: x"></li>
<li data-bind="html: y"></li>
</ul>
<button data-bind="click: addCondition">Click</button>
</div>
</div>
JS/Knockout code
function ReportsVM() {
self = this
self.formatterVM = new FormatterVM()
}
function FormatterVM(){
self = this
self.rules = ko.observableArray()
self.currentRule = ko.observable(new Rule())
}
function Rule(){
self = this
self.conditions = ko.observableArray()
self.addCondition = function(){
self.conditions.push(new Condition(2,3))
}
}
function Condition(x,y){
self = this
self.x = ko.observable(x)
self.y = ko.observable(y)
}
// Activates knockout.js
ko.applyBindings(new ReportsVM());
Working JSFiddle
https://jsfiddle.net/etppe937/
Update
It was an issue with the scope of the self variable. We need to use var keyword in order to not let the variable have a global scope. JSFiddle has been updated.
Related
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>
So I'm trying to add content to an observable array, but it doesn't update. The problem is not the first level content, but the sub array. It's a small comments section.
Basically I've this function to declare the comments
function comment(id, name, date, comment) {
var self = this;
self.id = id;
self.name = ko.observable(name);
self.date = ko.observable(date);
self.comment = ko.observable(comment);
self.subcomments = ko.observable([]);
}
I've a function to retrieve the object by the id field
function getCommentByID(id) {
var comment = ko.utils.arrayFirst(self.comments(), function (comment) {
return comment.id === id;
});
return comment;
}
This is where I display my comments
<ul style="padding-left: 0px;" data-bind="foreach: comments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
<div style="margin-left:40px;">
<ul data-bind="foreach: subcomments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
</li>
</ul>
<textarea class="comment" placeholder="comment..." data-bind="event: {keypress: $parent.onEnterSubComment}, attr: {'data-id': id }"></textarea>
</div>
</li>
</ul>
And onEnterSubComment is the problematic event form
self.onEnterSubComment = function (data, event) {
if (event.keyCode === 13) {
var id = event.target.getAttribute("data-id");
var obj = getCommentByID(parseInt(id));
var newSubComment = new comment(0, self.currentUser, new Date(), event.target.value);
obj.subcomments().push(newSubComment);
event.target.value = "";
}
return true;
};
It's interesting, because when I try the same operation during initialization(outside of any function) it works fine
var subcomment = new comment(self.commentID, "name1", new Date(), "subcomment goes in here");
self.comments.push(new comment(self.commentID, "name2", new Date(), "some comment goes here"));
obj = getCommentByID(self.commentID);
obj.subcomments().push(subcomment);
If anyone can help me with this, cause I'm kind of stuck :(
You need to make two changes:
1st, you have to declare an observable array:
self.subcomments = ko.observableArray([]);
2nd, you have to use the observable array methods, instead of the array methods. I.e. if you do so:
obj.subcomments().push(subcomment);
If subcomments were declared as array, you'd be using the .push method of Array. But, what you must do so that the observable array detect changes is to use the observableArray methods. I.e, do it like this:
obj.subcomments.push(subcomment);
Please, see this part of observableArray documentation: Manipulating an observableArray:
observableArray exposes a familiar set of functions for modifying the contents of the array and notifying listeners.
All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change
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
I have a simple webpage with a large list of products (20,000+). When you can click on a product, it will load (via AJAX) a list of colors and display them inline. Html...
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul
</div>
Shop view model:
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
product.colors(data);
}
}
Product view Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.colors = ko.observableArray([]);
}
When I have 20,000+ products, it uses a lot of memory. Each product has a colors array, which is always empty/null, until the user clicks on it, but it still uses a lot of memory.
Ideally, I'd like to remove the colors observableArray and somehow create it dynamically when user clicks on the product. Or separate it into a new viewModel.
I want to eliminate the empty observableArrays to minimise memory, but can't figure out how it do it.
I would use one of Knockout's control-flow bindings (if, with) to only bind the colors:foreach when there is actually a colors property on the productModel().
HTML:
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<div data-bind="if: hasColors">
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul>
</div>
</div>
Product View Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.hasColors = ko.observable(false);
self.colors = null;
}
Shop View Model
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
if(product.colors == null) {
product.colors = ko.observableArray(data);
product.hasColors(true);
} else {
product.colors(data);
}
}
}
You don't have to store an empty observable array: you can default to undefined and Knockout will treat it as an empty array in a foreach binding.
Here's a demonstration: http://jsfiddle.net/zm62T/
In WinJS can I bind a property getter in a listView? Say I have an object defined like this:
var MyLib = MyLib || {};
MyLib.ToDoItem = function() {
this.name = '';
this.description = '';
Object.defineProperty(this, "completed", {
get : function() {
return false;
}
});
}
MyLib.ToDoList = [];
//MyLib.ToDoList.push....add todo items
I am declaring a WinJS.Binding.Template where all of the properties are binding except the one that is defined with a property getter:
<div id="myItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="titleTile">
<h4 class="item-title" data-win-bind="textContent: name"></h4>
<p data-win-bind="textContent: description"></p>
<div data-win-bind="textContent: completed"></div> <-- Renders as undefined
</div>
</div>
The "completed" property renders as undefined. If I put a breakpoint in the javascript console where I am loading the data, I can get to the completed property, but the databinding doesn't seem to like it...any ideas?
You missed one line after your getter.
get : function() {
return false;
}
, enumerable: true
By setting enumerable to true, you can make data binding works on this property.