Bind click event to selector with knockout - javascript

I have searched and I have tried different selectors but I can't figure this out. I am following a tutorial, but I am not getting a result.
The click event doesn't seem to be binding to the dynamically generated div section '.person-brief'? There is no click event associated with it. I tried .live() also, but that seems to have been deprecated.
Any idea what I am doing wrong?
person.js model
var gotoDetails = function (selectedPerson) {
if (selectedPerson && selectedPerson.id()) {
var url = '#/persondetail/' + selectedPerson.id();
router.navigateTo(url);
}
};
var viewAttached = function (view) {
bindEventToList(view, '.person-brief', gotoDetails);
};
var bindEventToList = function (rootSelector, selector, callback, eventName) {
var eName = eventName || 'click';
$(rootSelector).on(eName, selector, function () {
var ser = ko.dataFor(this);
callback(ser);
return false;
});
};
var vm = {
people: people,
title: 'people demo',
viewAttached: viewAttached
};
return vm;
person.html view
<section id="person-view" class="view">
<header>
<a class="btn btn-info btn-force-refresh pull-right"
data-bind="click: refresh" href="#"><i class="icon-refresh"></i>Refresh</a>
<h3 class="page-title" data-bind="text: title"></h3>
<div class="article-counter">
<address data-bind="text: people().length"></address>
<address>found what</address>
</div>
</header>
<section class="view-list" data-bind="foreach: people">
<article class="article-left-content">
<div class="person-brief" title="Go to person details">
<small data-bind="text: firstname" class="right"></small>
<small data-bind="text: lastname"></small>
</div>
</article>
</section>
</section>

With KnockoutJS you should use the click binding (or alternatively the event binding), not use jQuery to manually manipulate the DOM.
Something like this becomes your code:
var vm = {
people: people,
title: 'people demo',
viewAttached: viewAttached
};
vm.myHandler = function (person) {
goToDetails(person);
return false;
};
And since myHandler is so simple you might as well inline the goToDetails code, which has access to vm from its closure.
You bind in the view like this:
<div class="person-brief" data-bind="click: $root.myHandler">
...
</div>
A general tip: do a tutorial on either jQuery, or KnockoutJS. If you take the latter, try to use as little is possible jQuery (which is usually quite possible), most notably don't use jQuery to manipulate the DOM (except in custom binding handlers and after-render functions).

Related

Knockout error - Unable to process binding “foreach”

I'm having a bit of trouble seeing what's wrong with my code. More likely with the Knockout.js part... It's giving me the following error:
Message: Unable to process binding "attr: function (){return {href:website()} }"
HTML
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name"></div>
<div class="mdl-card__supporting-text" data-bind="text:location"></div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
JS
var favoriteSpotsList = [{
venueName: "name",
venueLocation: "address",
website: "url",
image: "<img src='img'",
}];
var favoriteSpot = function(data) {
this.name = ko.observable(data.venueName);
this.address = ko.observable(data.venueLocation);
this.website = ko.observable(data.website);
this.image = ko.observable(data.img);
};
var AppViewModel = function() {
var self = this;
/* Create array of hotspot locations. */
this.hotSpotList = ko.observableArray([]);
favoriteSpotsList.forEach(function(spot) {
self.hotSpotList.push(new favoriteSpot(spot));
});
};
ko.applyBindings(new AppViewModel());
As #saj and #haim770 mentioned in comment, there is no favoriteSpot property on the view-model. So, the data bind should loop the hotSpotList to get the website property in it. Like below.,
data-bind="foreach: hotSpotList"
There is an easy way to identify these kind of issues, specifically while performing bindings in view
You just need add a button with click binding, The Button should be placed before the exception line.
<button data-bind="click: function () { console.log($context); }"> Context Log </button>
The above code will log the entire context in the browser console(F12). As usual you will get the exception. And this code will not resolve the issue. But this will be very helpful to identify the issue.
The above code will log the entire context of the current operation. Which holds object, property with the value.
Below are common scenarios where as you can exactly find your binding object has exceptions.
1. Properties are present/missing due to the scope level problems?
2. Whether it has case sensitive problem?
3. Your object comes under where? Is it a parent, child / Alone?
4. Human error which makes exception while binding.
There are few other ways to find the object/data in view:
1. Logs the root:
<button data-bind="click: function () { console.log($root); }"> Root Log </button>
2. Logs the Current scope data:
<button data-bind="click: function () { console.log($data); }"> Current Data Log </button>
3. Logs the parent data: (specifically helpful when we do looping)
<button data-bind="click: function () { console.log($parent); }"> Parent Log </button>
4. Logs the list of parent data: (specifically helpful when we do looping with different types of parents)
<button data-bind="click: function () { console.log($parents); }"> Parents Log </button>
5. Logs the list of parent data: (specifically helpful when we do looping and access different types of parents)
<button data-bind="click: function () { console.log(objectName.propertyName); }">Property Log </button>
For Example in your case you can do like below:
<!-- Added this button before the exception -->
<button data-bind="click: function () { console.log(favoriteSpot); }">Log </button>
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name">
</div>
<div class="mdl-card__supporting-text" data-bind="text:location">
</div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
When you click the button, obviously the message will be logged as undefined in console.
Hope this helps.,

Knockout multiple click bindings do not work with IE8

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.

knockout.computed - access to element attribute

I have element in html, which have data-bind for css.
The field behind is ko.computed.
In the computed function, I need to access to the id of the element.
How can I get the element at the ko.computed function?
Code example:
<span id="ship" style="cursor:pointer" data-bind="click:changeItem, css:computeActive" >ship</span>
<span id="cat" style="cursor:pointer" data-bind="click:changeItam, css:computeActive" >cat</span>
<span id="dog" style="cursor:pointer" data-bind="click:changeItem, css:computeActive" >dog</span>
view model:
var vm = {
computeActive: function () {
return data.selectedItem()== (****here is the place where I want to use object id****)?
"activeText":"inActiveText" ;
},
changeItem: function (event, sender) {
data.selectedItem(sender.currentTarget.id);
}
};
I don't want:
1. to use knockout.bindingHandler
2. to write separated computed-function for each element
In the computed function, I need to access to the id of the element.
No, you don't.
Your view model never needs to know anything about your view. (In other words, if it does, you're doing something wrong.)
Better view model (thanks to #xdumaine assisting):
function VM() {
var self = this;
self.items = ko.observableArray(['ship', 'cat', 'dog']);
self.selectedItem = ko.observable();
self.computeActive = function (item) {
return self.selectedItem() === item ? "activeText" : "inActiveText";
};
self.selectItem = function (item) {
self.selectedItem(item);
}
};
ko.applyBindings(new VM());
and the view
<div data-bind="foreach: items">
<span data-bind="click: $root.selectItem, css: $root.computeActive($data), text: $data"></span>
</div>
See it in action: http://jsfiddle.net/6998N/5/
Passing $data (i.e. the item itself) like this css: $root.computeActive($data) is necessary in this case because it forces KnockOut to re-evaluate the css binding for every item individually, every time.
See http://jsfiddle.net/6998N/4/ for an alternative approach.
if you don't want to make custom binding handler then you can manually pass the id.
var vm = {
computeActive: function (id) {
return data.selectedItem()== id?"activeText":"inActiveText" ;
},
changeItem: function (event, sender) {
}
};
html:-
<span id="ship" style="cursor:pointer" data-bind="click:changeItem, css:computeActive('ship')" >ship</span>
<span id="cat" style="cursor:pointer" data-bind="click:changeItem, css:computeActive('cat')" >cat</span>
<span id="dog" style="cursor:pointer" data-bind="click:changeItem, css:computeActive(dog')" >dog</span>
Sample Fiddle

Read content from DOM element with KnockoutJS

The goal
Read content from DOM element with KnockoutJS.
The problem
I have a list of products in my HTML. The code is something like this:
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number"
placeholder="How much #Model["ProductName"] do you want?" />
<button>Add product</button>
</form>
</li>
When I click on <button>Add Product</button>, I want to send to KnockoutJS the text inside <h3></h3> of the element that was submitted.
The file to work with KnockoutJS is external and independent of HTML. It name is summary.js and the code is below:
function ProductReservation(id, name, unity, quantity) {
var self = this;
self.id = id;
self.name = name;
self.unity = unity;
self.quantity = quantity;
}
function SummaryViewModel() {
var self = this;
self.products = ko.observableArray([
new ProductReservation(1, "Testing", "kgs", 1)
]);
self.addToSummary = function () {
// Do Something
}
}
What I'm thinking about
HTML:
<li>
<div class="introduction">
<h3 data-bind="text: productName">#Model["ProductName"]</h3>
</div>
[...]
</li>
JS:
productName = ko.observable("text: productName");
But, of course, no success — this is not the correct context or syntax, was just to illustrate.
So I ask: what I need to do?
You're binding addToSummary via the submit binding. By default KO sends the form element to submit-bound functions.
So addToSummary should have a parameter -
self.addToSummary = function (formElement) {
// Do Something
}
You can pass additional parameters to this function (as described in KO's click binding documentation under 'Note 2'), or you could just add a hidden field to your form element and pull it from there.
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number" name="quantity"
placeholder="How much #Model["ProductName"] do you want?" />
<input type="hidden" name="productName" value="#Model["ProductName"]" />
<button>Add product</button>
</form>
</li>
Then in your knockout code you could use jQuery to process the form element to pull out the data -
self.addToSummary = function (formElement) {
var productName = $(formElement).children('[name="productName"]')[0].val();
var quantity= $(formElement).children('[name="quantity"]')[0].val();
// ...
self.products.push(new ProductReservation(?, productName, ?, quantity));
}
One strategy that has worked well in my experience is to implement a toJSON extension method that serializes your model (if you use a library like JSON.NET you can have a lot of control over what gets serialized and what does not).
Inside of the view where you initialize KnockoutJS, you could serialize your model and pass it into the KnockoutJS ViewModel you're creating (assuming :
Main view:
<script type="text/javascript">
var viewModel = new MyViewModel(#Model.ToJSON());
</script>
ViewModel:
function MyViewModel(options) {
this.productName = ko.observable(options.ProductName);
}

Handling observables in an array

Note: This is not a question about ObservableArrays.
Let's say I have the following viewmodel:
var viewmodel = {
arrayOfBooleans: [
ko.observable(false),
ko.observable(false),
ko.observable(false)
]
}
And a view like so:
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: ????">Set to true</button>
</div>
What can I do inside the foreach to get the <button> to set the observable to true when clicked? Using data-bind="click: someFunction", the first argument someFunction gets is the unwrapped values of observables in the array (not the observables themselves), and seemingly no way to get back at the observables or to pass custom arguments.
Hope it will give some idea .
var viewmodel = {
var self = this;
self.arrayOfBooleans = ko.observableArray([]);
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
function _newBoolean() {
self.one = ko.observable(false);
}
self.setToTrue = function(index){
self.arrayOfBooleans()[index].one(true);
};
}
If you want it as button
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: $root.setToTrue($parent.arrayOfBooleans.indexOf($data))">
Set to true
</button>
<input type=hidden data-bind="value:one" />
</div>
If you want it as radio button than it is much more simple
<div data-bind="foreach: arrayOfBooleans">
<span>Set To True</span><input type=radio data-bind="value:one" />
</div>
If you like this..Please Click uplink
*or*
If it solves your problem .. Mark this as answer

Categories

Resources