How do I set selectedItem and bind to it with knockout - javascript

I have a list as follows:
<ul id="blogList" data-bind="foreach: Data">
<li>
<span data-bind="text: Title"> </span>
View
</li>
</ul>
And knockout view model as below:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
this.currentSelected = ko.observable();
self.viewEntry = function () {
currentSelected = this;
}
};
Setting the currentSelected to this doesn't seem to work because when I try to bind to currentSelected somewhere else I get nothing happening:
<h2 data-bind="text: currentSelected.Title"></h2>
Is that the correct way to bind to currentSelected? The list is working fine but setting the currentSelected and binding to it isn't working.

First let's have a look at your viewEntry method.
self.viewEntry = function () {
currentSelected = this;
}
currentSelected is undefined it should be self.currentSelected additionally since this is an observable you should not set this as var with equal sign but rather treat this as function so it should be self.currentSelected(this)
the other thing is binding inside this part:
<h2 data-bind="text: currentSelected.Title"></h2>
again this is an object so to access its properties you need to use currentSelected as function so it should be
<h2 data-bind="text: currentSelected().Title"></h2>
Here you can find a working sample: https://jsfiddle.net/jz9t5vbo/

Related

Function bound to click action in foreach not being called

I'm having trouble binding my click action to the view model function to remove an item from an array (inside a foreach binding)
I've got the following view model
var FileGroupViewModel = function () {
var self = this;
self.files = ko.observableArray();
self.removeFile = function (item) {
self.files.remove(item);
}
self.fileUpload = function (data, e) {
var file = e.target.files[0];
self.files.push(file);
};
}
var ViewModel = function () {
var self = this;
self.FileGroup = ko.observableArray();
self.FileGroup1 = new FileGroupViewModel();
self.FileGroup2 = new FileGroupViewModel();
self.FileGroup3 = new FileGroupViewModel();
self.uploadFiles = function () {
alert("Uploading");
}
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
And my view, which basically lists 3 "groups" of buttons, where a user can select files to upload
Everything below is working as expected, except $parent.removeFile isn't removing the file:
<div class="row files">
<h2>Files 1</h2>
<span class="btn btn-default btn-file">
Browse <input data-bind="event: {change: FileGroup1.fileUpload}" type="file" />
</span>
<br />
<div class="fileList" data-bind="foreach: FileGroup1.files">
<span data-bind="text: name"></span>
Remove
<br />
</div>
</div>
Fiddle at https://jsfiddle.net/alexjamesbrown/aw0798p7/
Am I wrong to do $parent.removeFile - it seems this doesn't get called on click?
This is a cut down working example, not the finished product!
You're misunderstanding $parent. It takes you out one context level. Your foreach uses FileGroup1.files as its index, so you might think that the $parent level would be Filegroup1, but it's not. It's the top-level viewmodel, because that is the context outside the foreach.
So your click binding should be
click: $parent.FileGroup1.removeFile

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.

Why is my $parent binding context empty inside a Knockout.js foreach template?

I'm calling the following template through Knockout:
<script type="text/html" id="uploaded-files-template">
<li data-bind="text: original_name"></li>
<pre data-bind="text: ko.toJSON($parent, null, 2)"></pre>
</script>
which is being called in a foreach loop from this:
<ul class="files-list" data-bind="template: { name: 'uploaded-files-template', foreach: uploadedFiles, as: 'uploadedFile' }"></ul>
I'm trying to access the $parent binding context from my template, but I'm being told it is undefined, and when I try and output it JSON-formatted (as I am doing above), it is empty.
$uploadedFiles is a ko.observableArray():
self.uploadedFiles = ko.observableArray([]);
Which is populated via AJAX like so:
and then pushed on:
$.each(message.objects, function(index, object) {
self.uploadedFiles.push(object);
});
If I replace $parent with $data, I can see the current iteration being outputted. Any idea why this $parent does not show anything?
There is nothing wrong with your templates and bindings, see working fiddle.
var SimpleListModel = function(items) {
this.uploadedFiles = ko.observableArray(items);
};
var model = new SimpleListModel([
{ original_name: "One" },
{ original_name: "Two" },
{ original_name: "Three" }]
)
ko.applyBindings(model);
It has to be mistake in your viewModel or viewModel population.
Err... well, this is embarrassing. My $parent context was empty simply because I was not instantiating my ViewModel. I was doing this:
$(document).ready(function() {
ko.applyBindings(uploadViewModel);
});
When I should have been doing this:
$(document).ready(function() {
ko.applyBindings(new uploadViewModel());
});
Thanks to Max Brodin & supercool for helping out, however.

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

binding knockout observable arrays to multiple <ul> elements

For a navigation menu, I have two groups of links, each group and link showing up or not dependent on a user's role. The roles are looked up when the link structure is being built and the list of links is built accordingly. The returned JSON gets parsed, put into observable arrays with no problem, but when I actually try and apply the bindings, the binding fails because the observables are blank. Here is the HTML...
<ul id="user-menu" class="menu" data-bind="foreach: areas">
<li>
<a data-bind="attr: { href: areaLink }">
<img data-bind="attr: { src: iconUri }" />
<span data-bind="text: areaName"></span>
</a>
</li>
</ul>
<ul id="admin-menu" class="menu" data-bind="foreach: adminAreas">
<li>
<a data-bind="attr: { href: areaLink }">
<img data-bind="attr: { src: iconUri }" />
<span data-bind="text: areaName"></span>
</a>
</li>
</ul>
Knockout view model in the background...
var navigation = (function() {
function Area() {
var self = this;
self.areaName = ko.observable();
self.areaLink = ko.observable();
self.iconUri = ko.observable();
self.sequenceNo = ko.observable();
self.isAdmin = ko.observable();
self.loadFromVM = function (vm) {
self.areaName(vm.name || '');
self.areaLink(vm.link || '');
self.iconUri(vm.iconUri || '');
self.sequenceNo(vm.sequenceNo || '');
self.isAdmin(vm.isAdmin);
}
}
function viewModel() {
var self = this;
self.areas = ko.observableArray([]);
self.adminAreas = ko.observableArray([]);
self.setup = function () {
var data = {}; // population with basic session data
$.getJSON('....php', { JSON.stringify(data) }, function (results) {
for (var i = 0; i < results.length; i++) {
var area = new Area();
area.loadFromVM(results[i]);
if (area.isAdmin()) {
self.adminAreas().push(area);
} else {
self.areas().push(area);
}
}
});
};
}
var vmInstance;
return {
setup: function () {
vmInstance = new viewModel();
vmInstance.setup();
ko.applyBindings(vmInstance, $('#user-menu')[0]);
ko.applyBindings(vmInstance, $('#admin-menu')[0]);
}
};
})();
And then I bring it together with this in the navigation view file...
navigation.setup();
So after I get my JSON back, parse it, and organize it when I loop through the success function of the $.getJSON method, putting a watch on self.areas() and self.adminAreas() does show that the arrays have the exact information I want them to. But by the time they have to be applied, calling vmInstance.areas().length or vmInstance.adminAreas().length returns zero. Even more oddly, putting in an alert with the length of the arrays right after the $.getJSON call but within the setup() function will cause the alert to fire first, show zeroes, then goes through the get, populates the array, then fires zeroes again.
Not exactly sure what's going on here, but I can't remember seeing this kind of behavior in another project so I'm not quite sure what I'm doing wrong here. Any ideas or advice would be greatly appreciated.
EDIT: Nevermind on the Fiddle. It doesn't really capture my actual error.
adminarea object is not initialized.you made the adminArea variable but instead of this you have used same area variable to set values.
var adminArea = new Area();
adminArea.areaName('test admin area');
adminArea.areaLink('#');
adminArea.iconUri('http://evernote.com/media/img/getting_started/skitch/windows8/win8-checkmark_icon.png');
adminArea.sequenceNo(1);
adminArea.isAdmin(true);
Fiddle Demo

Categories

Resources