I am new to knockout.js and am having a problem with binding within a foreach section. I am receiving the error:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: hideSearchElements is not defined;
Bindings value: click: hideSearchElements
Here is an exert of the html:
<div id="searchResults" data-bind="visible: searchIsVisible">
<label id = "lblSearchResults">select a template:</label>
<div data-bind="foreach: titles">
<div data-bind="text: Title"></div>
<div data-bind="click: hideSearchElements">hide</div>
</div>
And an exert from the viewModel:
var viewModel = function () {
this.searchIsVisible = ko.observable(true);
this.showSearchElements = function () {
this.searchIsVisible(true);
};
this.hideSearchElements = function (
this.searchIsVisible(false); }
}
return new viewModel();
I have both showSearchElements and hideSearchElements working fine outside of the foreach block but when inside it, I get the error.
If I add $parent.hideSearchElements I can bind but then get an error saying:
Uncaught TypeError: Object # has no method 'searchIsVisible'
.
I have probably have two distinct issues but thought the detail may help :)
I'm keen to understand what's going on here? Can anyone help please?
A link to the relevant page in the documentation would also be very helpful - I'm reading through that now.
Thanks
You was right when use $parent.hideSearchElements because hideSearchElements function is in a parent context. You got exception because when knockout calls your function this has another context. You have to use closure to store this pointer. Update your view model as follow:
var viewModel = function () {
var self = this;
self.searchIsVisible = ko.observable(true);
self.showSearchElements = function () {
self.searchIsVisible(true);
};
self.hideSearchElements = function (
self.searchIsVisible(false); }
}
Related
I am not very good with angular, but i know some basics. Now, I have access points and want to assign them to a building. I can select this building with a <select>. I have written a simple controller, but it wont work. What am I making wrong, i cant find a solution.
Edit 1:
I can see the option fields (they are 3). But after I select one of these, my browser console throws the exception
Edit 2: Plunkr -> https://plnkr.co/edit/EIPs8yVlTSaYQ0EuZLTb (i hope, this url works) .. When you click on "Neuer Access-Point" the error will occur, after you select something on "Gebäude"
Select field
<select ng-model="$ctrl.input.building">
<option ng-repeat="building in $ctrl.buildings" ng-value="building.id" ng-bind="building.name"></option>
</select>
Controller
(function () {
function createController(Building) {
var ctrl = this;
ctrl.buildings = null;
ctrl.input = {
host: '',
desc: '',
web: '',
building: ''
};
ctrl.$onInit = function () {
Building.getAll().then(function (res) {
if (res.status >= 200 && res.status < 300) {
ctrl.buildings = res.data;
}
});
};
}
angular.module('app').controller('CreateController', createController)
})();
Error
angular.js:14791 Error: [$rootScope:inprog] http://errors.angularjs.org/1.6.8/$rootScope/inprog?p0=%24apply
at angular.js:88
at p (angular.js:18897)
at m.$digest (angular.js:18319)
at m.$apply (angular.js:18640)
at Object.$$debounceViewValueCommit (angular.js:29394)
at Object.$setViewValue (angular.js:29372)
at angular.js:33596
at m.$eval (angular.js:18533)
at m.$apply (angular.js:18632)
at HTMLSelectElement.<anonymous> (angular.js:33595)
Here is the issue with your code.
<div id="wrapper" ng-app="accessPoints" ng-controller="RootController **as $root**">
Change it to simply
ng-controller="RootController as anythingButNot$root"
or even just ng-controller="RootController"
Declaring any controller as $root is creating mess for you. $root is the root level controller of your application. Inside your html if you declare any controller as $root, it tries to overwrite $root, creating trouble with digest cycle, hence you are getting the error.
Link to updated plunk => https://plnkr.co/edit/UcJHVmekMqWMXJBnv2Cu?p=preview
I have a View model, which has a loaddata function. It has no constructor. I want it to call the loadData method IF the ID field has a value.
That field is obtained via:
self.TemplateId = ko.observable($("#InputTemplateId").val());
Then, at the end of my ViewModel, I have a bit of code that checks that, and calls my load function:
if (!self.CreateMode()) {
self.loadData();
}
My load method makes a call to my .Net WebAPI method, which returns a slighly complex structure. The structure is a class, with a few fields, and an Array/List. The items in that list, are a few basic fields, and another List/Array. And then THAT object just has a few fields. So, it's 3 levels. An object, with a List of objects, and those objects each have another list of objects...
My WebAPI call is working. I've debugged it, and the data is coming back perfectly.
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
});
}
I am trying to load the contents of this call, into an observable object called 'Data'. It was declared earlier:
self.Data = ko.observable();
TO load it, and keep everything observable, I am using the Knockout mapping plugin.
self.Data(ko.mapping.fromJS(data));
When I breakpoint on that, I am seeing what I expect in both data (the result of the API call), and self.Data()
self.Data seems to be an observable version of the data that I loaded. All data is there, and it all seems to be right.
I am able to alert the value of one of the fields in the root of the data object:
alert(self.Data().Description());
I'm also able to see a field within the first item in the list.
alert(self.Data().PlateTemplateGroups()[0].Description());
This indicates to me that Data is an observable and contains the data. I think I will later be able to post self.Data back to my API to save/update.
Now, the problems start.
On my View, I am trying to show a field which resides in the root class of my complex item. Something I alerted just above.
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Data.Description">
I get no error. Yet, the text box is empty.
If I change the code for the input box to be:
data-bind="value: Data().Description()"
Data is displayed. However, I am sitting with an error in the console:
Uncaught TypeError: Unable to process binding "value: function
(){return Data().Description() }" Message: Cannot read property
'Description' of undefined
I think it's due to the view loading, before the data is loaded from the WebAPI call, and therefore, because I am using ko.mapping - the view has no idea what Data().Description() is... and it dies.
Is there a way around this so that I can achieve what I am trying to do? Below is the full ViewModel.
function PlateTemplateViewModel() {
var self = this;
self.TemplateId = ko.observable($("#InputTemplateId").val());
self.CreateMode = ko.observable(!!self.TemplateId() == false);
self.IsComponentEditMode = ko.observable(false);
self.IsDisplayMode = ko.observable(true);
self.CurrentComponent = ko.observable();
self.Data = ko.observable();
self.EditComponent = function (data) {
self.IsComponentEditMode(true);
self.IsDisplayMode(false);
self.CurrentComponent(data);
}
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
});
}
self.cancel = function () {
window.location.href = "/PlateTemplate/";
};
self.save = function () {
var data = ko.mapping.toJS(self.Data);
$.post("/api/PlateTemplate/Save", data).done(function (result) {
alert(result);
});
};
if (!self.CreateMode()) {
self.loadData();
}
}
$(document).ready(function () {
ko.applyBindings(new PlateTemplateViewModel(), $("#plateTemplate")[0]);
});
Maybe the answer is to do the load inside the ready() function, and pass in data as a parameter? Not sure what happens when I want to create a New item, but I can get to that.
Additionally, when I try save, I notice that even though I might change a field in the view (Update Description, for example), the data in the observed view model (self.Data) doesn't change.
Your input field could be this:
<div data-bind="with: Data">
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Description">
</div>
I prefer using with as its cleaner and should stop the confusion and issues you were having.
The reason that error is there is because the html is already bound before the data is loaded. So either don't apply bindings until the data is loaded:
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
ko.applyBindings(self, document.getElementById("container"));
});
Or wrap the template with an if, therefore it won't give you this error as Data is undefined originally.
self.Data = ko.observable(); // undefined
<!-- ko if: Data -->
<div data-bind="with: Data">
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Description">
</div>
<!-- /ko -->
Also if you know what the data model is gonna be, you could default data to this.
self.Data = ko.observable(new Data());
Apply Bindings Method:
var viewModel = null;
$(document).ready(function () {
viewModel = new PlateTemplateViewModel();
viewModel.loadData();
});
The problem:
Multiple click binding do not work in IE8.
The code:
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart.books.push(/* new book */);
};
self.showAddCD = function() {
self.cart.cds.push(/* new cd */);
};
};
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
Background:
Apologies in advance. I don't have access to jsFiddle at work.
I have a deadline to get this piece of work complete which is why I am using knockout with jQuery. Would love to use Angular but can't because we have to support IE8. Would love to use Durandal but I have no experience of it and don't have the time just yet to learn it and finish this piece of work.
A user can create a new book or a new cd and add it to a collection. Not real-world example but reflects the problem I am solving.
A user can click on an Add button, this launches a jQuery dialog which captures some information about a book. This is then saved to the observable array on the model, and the list of books gets updated.
Question:
Why does IE8 only seem to bind the first click and not the second? If I click to add a book the dialog is shown. If I click to add a cd, nothing. I have debugged and the function does not get called.
TIA
As far as I can tell, neither of them should work, and not on any browser (rather than just not working on IE8), because both functions have the same problem: They don't unwrap cart:
self.cart.books.push(/* new book */);
// ^^^^^^
cart is an observable, so you need:
self.cart().books.push(/* new book */);
// ^^
...and similarly for the CDs stuff.
If you fix that, it works (even on IE8):
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart().books.push({name: "New book " + (+new Date())});
};
self.showAddCD = function() {
self.cart().cds.push({name: "New CD " + (+new Date())});
};
};
ko.applyBindings(new TheModel(), document.body);
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Apologies for slightly incorrect example but I had missed one level of nesting. My example is not a true reflection of my complex model and implementation but I worked out the cause of the problem.
My Cart in this example has a selectedItem property (an observable) of type object that has the array of books (observable array) and CDs (observable array).
var Items = function () {
this.books = ko.observableArray();
this.cds = ko.observableArray();
}
var Cart = function() {
this.selectedItem = ko.observable(new Items());
}
var Model = function () {
this.cart = new Cart();
}
I was using the knockout 'with' binding and setting the context to cart.selectedItem
<div data-bind="with: cart.selectedItem"> ... </div>
With this approach I noticed that only the first click (add book) was working. Clicking on Add CD was doing nothing.
I changed the context from cart.selectedItem to cart and set the foreach binding (that displays the list of books and cds) to selectedItem().books and selectedItem().cds and that worked in IE8 and other browser.
If I change the context using the knockout 'with' binding back to cart.selectedItem then only the first click works.
Hope that helps anyone else who encounters this problem.
I have an ASPX page with a dropdown select bound with Knockout.JS. On the page load I check the url for a parameter and update the view if their is a parameter which you can see in my API. I've changed the API to leave out unnecessary code because it returns the value needed. My problem is that I cannot get my observable SelectedView to update to "Notes". Any advice?
ASPX:
<asp:DropDownList runat="server" data-bind="value: SelectedView" id="viewselect">
<asp:ListItem>Select A View</asp:ListItem>
<asp:ListItem>Notes</asp:ListItem>
<asp:ListItem>Credit Manager</asp:ListItem>
</asp:DropDownList>
View Model:
function CustomerViewModel() {
this.self = this;
self.SelectedCustomer = ko.observable();
self.SelectedView = ko.observable();
}
API:
$(document).ready(function () {
var custnmbr = "123456";
if (custnmbr != "") {
var notes = "Notes";
self.SelectedView(notes);
}
});
I guess if you look in console you 'll get error:
Uncaught TypeError: Object [object global] has no method 'SelectedView'
Because in your $(document).ready you are using the object self which is only defined inside CustomerViewModel().
To solve this, you need to call .SelectedView(notes); on the object instance you are passing to ko.applyBindings,
UPDATE
for example:
function CustomerViewModel() {
this.self = this;
self.SelectedCustomer = ko.observable();
self.SelectedView = ko.observable();
}
var customerObj=new CustomerViewModel();
ko.applyBindings(customerObj);
// later in your code.
customerObj.SelectedView(notes);
I'm following John Papa's jumpstart course about SPA's and trying to display a list of customers loaded via ASP.NET Web API the knockout foreach binding is not working. The Web API is working fine, I've tested it on it's own and it is returning the correct JSON, because of that I won't post the code for it. The get method simply returns one array of objects, each with properties Name and Email. Although not a good practice, knockout is exposed globaly as ko by loading it before durandal.
I've coded the customers.js view model as follows
define(['services/dataservice'], function(ds) {
var initialized = false;
var customers = ko.observableArray();
var refresh = function() {
return dataservice.getCustomers(customers);
};
var activate = function() {
if (initialized) return;
initialized = true;
return refresh();
};
var customersVM = {
customers: customers,
activate: activate,
refresh: refresh
};
return customersVM;
});
The dataservice module I've coded as follows (I've not wrote bellow the function queryFailed because I know it's not being used)
define(['../model'], function (model) {
var getCustomers = function (customersObservable) {
customersObservable([]);
var options = {url: '/api/customers', type: 'GET', dataType: 'json'};
return $.ajax(options).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var customers = [];
data.forEach(function (item) {
var c = new model.Customer(item);
customers.push(c);
});
customersObservable(customers);
}
};
return {
getCustomers: getCustomers
};
});
Finaly the model module was built as follows:
define(function () {
var Customer = function (dto) {
return mapToObservable(dto);
};
var model = {
Customer: Customer
};
return model;
function mapToObservable(dto) {
var mapped = {};
for (prop in dto)
{
if (dto.hasOwnProperty(prop))
{
mapped[prop] = ko.observable(dto[prop]);
}
}
return mapped;
}
});
The view is then simply a list, it is simply:
<ul data-bind="foreach: customers">
<li data-bind="text: Name"></li>
</ul>
But this doesn't work. Any other binding works, and I've looked on the console window, and it seems the observable array is being filled correctly. The only problem is that this piece of code doesn't show anything on screen. I've reviewed many times the files but I can't seem to find the problem. What's wrong with this?
You can use the knockout.js context debugger chrome extension to help you debug your issue
https://chrome.google.com/webstore/detail/knockoutjs-context-debugg/oddcpmchholgcjgjdnfjmildmlielhof
Well, I just spent a lot of time on an local issue to realize that the ko HTML comment format, if used, should be like this:
<!-- ko foreach: arrecadacoes -->
and NOT like this:
<!-- ko: foreach: arrecadacoes -->
: is NOT used after ko...
I know this question is a little old but I thought I'd add my response in case someone else runs into the same issue I did.
I was using Knockout JS version 2.1.0 and it seems the only way I can get the data to display in a foreach loop was to use:
$data.property
so in the case of your example it would be
$data.Name
Hope this helps
I don't see anywhere in your code that you've called ko.applyBindings on your ViewModel.
KO has a known issue while using foreach in a non-container element like the one above <ul> so you have to use containerless control flow syntax.
e.g.
<ul>
<!-- ko foreach: customers-->
<li data-bind="text: Name"></li>
<!-- /ko -->
</ul>
Ref: http://knockoutjs.com/documentation/foreach-binding.html