Knockout binding with a handle to templated element - javascript

Knockout is just great but I'm a little bit confused on how to deal with DOM elements after they are generated. For example I have a collection of users. Each user has an Id:
var user = {
id : 123,
name : 'testUser',
age: 45
};
Using Knockout I bind my collection of described above data structure with the following html template:
<div data-bind="foreach: users">
<div class='user-wrapper'>
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
and now I want to change background color on user click:
$(".user-wrapper").click(function (e) {
//doesn't work - toggelClass is not a function
e.target.toggleClass("user-selected");
});
Once I hit a user target could be different (span or div), I need to make sure that I'm getting the right div. Moreover e.target doesn't work with "not a fucntion" error.
How can I access calling element to toggle the class?
How can I get a user id of that element to access other controls related to that id?

You should use the click binding in conjunction with the css binding:
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
Note that if you're ever tempted to use jQuery to manipulate DOM while you're using KnockoutJS (or client side MVVM libraries in general): don't. If you absolutely must, you probably need a custom binding handler, much like you'd use a directive for DOM manipulation in "that other" mvvm framework.
Here's a demo:
var user = {
id : 123,
name : 'testUser',
age: 45
};
var UserVm = function(data) {
var self = this;
self.name = data.name;
self.age = data.age;
self.isSelected = ko.observable(false);
self.toggleSelected = function() {
self.isSelected(!self.isSelected());
}
};
ko.applyBindings({ users: [new UserVm(user)] });
.user-selected { background-color: red; }
.user-wrapper:hover { cursor: pointer; background-color: pink; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>

Related

KnockoutJS Hover on UL doesn't trigger, works with CSS:hover?

So I've got a organizational system for a geocaching site I'm working on, and for some reason I cannot for the life of me get a onmouseover to fire on this UL.
<ul data-bind="foreach: markers, event { onmouseover: highlightLocation }" class="locationList">
<h3 class="resultTITLE"> <span data-bind="text: title"></span></h3>
<p class="resultLATLONG">LAT <span data-bind="text: lat"></span>, LONG <span data-bind="text: lng"></span></p>
</ul>
If I do a simple CSS :hover pseudo the UL styles will change, but if I try to use it through KnockoutJS, I can't get anything to fire. As a reference with the other code removed, here's what my viewModel looks like:
var viewModel = {
//other code inbetween, linted
highlightLocation : function() {
console.log("hovering!");
},
};
ko.applyBindings(viewModel);
I've been staring at this for the past few hours and I feel like I'm msissing something simple that's staring me right in the face that I'm missing. Any thoughts?
Not really familiar with knockout but noticed a few things and got it to work.
You are missing a colon after event and the event is actually called mouseover, not onmouseover.
var viewModel = {
title: "title",
lat: 5,
lng: 5,
highlightLocation: function() {
console.log("hovering!");
},
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="event: { mouseover: highlightLocation }" class="locationList">
<h3 class="resultTITLE"> <span data-bind="text: title"></span></h3>
<p class="resultLATLONG">LAT <span data-bind="text: lat"></span>, LONG <span data-bind="text: lng"></span></p>
</ul>
(fiddle)

Not getting all values in View when binding children properties of observable

In my knockout bound view I am not getting all values.
This is my script file:
var ViewModel = function () {
var self = this;
self.games = ko.observableArray();
self.error = ko.observable();
self.detail = ko.observable();
var gamesUri = '/api/games/';
self.getGameDetail = function (item) {
ajaxHelper(gamesUri + item.Id, 'GET').done(function (data) {
self.detail(data);
});
console.log(self.detail);
};
function ajaxHelper(uri, method, data) {
self.error('');
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
function getAllGames() {
ajaxHelper(gamesUri, 'GET').done(function (data) {
self.games(data);
});
}
getAllGames();
};
ko.applyBindings(new ViewModel());
This is my view:
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Games</h2>
</div>
<div class="panel-body">
<ul class="list-unstyled" data-bind="foreach: games">
<li>
<strong><span data-bind="text: DeveloperName"></span>:<span data-bind="text: Title"></span></strong>
<small>Details</small>
</li>
</ul>
</div>
</div>
<div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading"><h2 class="panel-title">Details</h2></div>
</div>
<table class="table">
<tr><td>Developer</td><td data-bind="text: detail().DeveloperName"></td></tr> //Only this value is displayed
<tr><td>Title</td><td data-bind="text: detail().Title"></td></tr>
<tr><td>Price</td><td data-bind="text: detail().Price"></td></tr>
<tr><td>Genre</td><td data-bind="text: detail().Genre"></td></tr>
<tr><td>Year</td><td data-bind="text: detail().Year"></td></tr>
</table>
</div>
<div class="col-md-4">
</div>
</div>
The problem is it only displays DeveloperName in the view. Title, Price, Genre and Year are not dispayed in the view. I tried many things but I don't know where the error is.
There are two approaches.
The easiest one is to use the with or template binding. The technique is similar, but I'll show an example with the with binding:
<table class="table" data-bind="with: details">
<tr><td>Developer</td><td data-bind="text: DeveloperName"></td></tr>
<tr><td>Title</td><td data-bind="text: Title"></td></tr>
<tr><td>Price</td><td data-bind="text: Price"></td></tr>
<tr><td>Genre</td><td data-bind="text: Genre"></td></tr>
<tr><td>Year</td><td data-bind="text: Year"></td></tr>
</table>
With this technique, whenever you change the object inside the details observable, the new values are applied to the children elements inside the elment which has the with binding. In this case all the elements inside the table element. Besides, the syntax is shorter and more clear. NOTE: you must can use $parent, $parents[] or $root if you need to bind something outside the object bound with with.
The hardest one, which is only neccessary if your viewmodel is more complex and has, for example, computed observables, you need to create an object whose properties are also observables. In this case you bind this object once, and, on the next occasions, you update the innser obervable properties, instead of changing the object itself.
For your example, you must create an object which has all its properties, like DeveloperName, Title, Price, etc. defined as observables. Then you must map the values recovered by AJAX to this properties, which you can do by hand, doing details().DeveloperName(newDetails.DeveloperName), and so on, or using the ko.mapping plugin.
Important note: if you use this technique you must keep the original details bound object, and update its properties. If you substitute the details object itself with a new one, the binding will be lost, and will stop working as desired.
Another note: you cannot use cleanNodes for what you think. Please, see this Q&A.

ObservableArray not binding to GUI

I'm new with knockout.js and trying to fix data binding on a site that is build on Laravel and is using knockout.js.
Observable array works well and items can be pushed and popped without issues. The problem is with the binding to GUI. When items are pushed to array those are added to GUI, but nothing else works, like removing items, and also when adding more items later on those are added on the top of the GUI element list, not added after existing items on the GUI. The observable array is having correct items after push/pop/removeall, its just not reflecting to GUI.
I guess that the problem is that observable array is not binded to GUI, but I cannot figure out what could be wrong.
Stripped code:
Chat.init = function(){
Chat.viewModel = new Chat.ViewModel;
ko.applyBindings(Chat.viewModel, $('#msg_canvas').get(0));
};
Chat.ViewModel = function(){
self.messages = ko.observableArray();
self.setMessages = function(msgs){
_.each(msgs, function(msg){
self.messages.push(msg);
});
};
self.clearMessages = function(data, e){
self.messages.removeAll();
}
}
clearMessages is called via onclick: data-bind="click: $parent.clearMessages
The HTML is this:
<div id="msg_canvas" class="msg-wrap col-md-12"
style="height:274px;overflow-y:scroll;" data-bind="foreach: messages">
<div class="media msg">
<div class="media-body">
<span data-bind="text: sent_at"></span>
<small class="col-lg-10" data-bind="text: message"></small>
</div>
</div>
Any help or pointer to what could be causing the problem would be highly appreciated.
UPDATE: added inner HTML which was not included to post before
You need to have a control inside the div to hold your messages, like a <span> or <p>. Otherwise, you're simply doing the foreach without outputting the values. So your div should look something like this, using $data to access the value:
<div id="msg_canvas" data-bind="foreach: messages">
<p data-bind="text: $data"></p>
</div>
Here's a working snippet based on your code (setMessages slightly modified / hard coded with values):
ViewModel = function(){
self.messages = ko.observableArray([]);
self.setMessages = function(){
var msgs = ['message','message','message'];
_.each(msgs, function(msg){
self.messages.push(msg + ' ' + self.messages().length);
});
};
self.clearMessages = function(data, e){
self.messages.removeAll();
}
self.removeMessage = function(item){
self.messages.remove(item);
}
};
ko.applyBindings(new ViewModel());
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="msg_canvas" class="msg-wrap col-md-12"
style="height:274px;overflow-y:scroll;border: black solid 1px" data-bind="foreach: messages">
<p data-bind="text: $data"></p>
<input type="button" data-bind="click: removeMessage" value="Remove Item" />
</div>
<input type="button" data-bind="click: setMessages" value="Add Message" />
<input type="button" data-bind="click: clearMessages" value="Remove All" />

KnockoutJS - foreach not working with a single entry in viewmodel

I have the following code that, on a successful AJAX return, displays a popup window with a list of addresses. The knockout version is 2.3.0.
If there is more than 1 address then the html correctly renders with a 'display' string.
The problem is that if there is ONLY 1 address the html list renders but without any text in the span.
In both cases the view model is correctly being populated with data so it looks to me like a problem updating the html.
I have tried pushing the data again and although I can use jQuery to update the html but this doesn't help me understand the problem.
HTML
<div id="reverseGeocodingResults">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Local Addresses</h4>
</div>
<div>
<ul data-bind="foreach: vm.reverseGeocodingViewModel.AddressList" class="locationList">
<li class="locationListItem" data-bind="click: SubmitAddressRequest">
<div>
<span data-bind="text: Display"></span>
</div>
</li>
</ul>
</div>
</div>
</div>
Javascript (from a separate file so only taken what I think is relevant)
var vm;
var masterViewModel = function () {
this.viewModel = { LocationList: ko.observableArray([]), SubQuery: ko.observable() };
this.reverseGeocodingViewModel = { AddressList: ko.observableArray([]) };
};
function SubmitReverseGeocodingRequest(easting, northing, projectId, mouseLocation) {
$.post('url?action=ReverseGeocodingLookup', {
easting: easting,
northing: northing,
pid: projectId
})
.done(function (data) {
spinner.stop();
if (parseInt(data.NumberOfAddressesFound) > 0) {
if (data.AddressList.length == 1) {
alert('just 1 address');
}
// remove all array items before adding new
// Not pretty but gets around an issue the UI seems to have displaying the updated list
if (vm.reverseGeocodingViewModel.AddressList().length > 0) {
vm.reverseGeocodingViewModel.AddressList.splice(0, vm.reverseGeocodingViewModel.AddressList().length);
}
vm.reverseGeocodingViewModel.AddressList(data.AddressList);
}
});
)
$(document).ready(function () {
vm = new masterViewModel();
ko.applyBindings(vm);
})
HTML Result - Multiple Results
<ul class="locationList" data-bind="foreach: vm.reverseGeocodingViewModel.AddressList" style="height: 265px;">
<li data-bind="click: SubmitAddressRequest" class="locationListItem">
<div>
<span data-bind="text: Display">Yates Wine Lodge, SWINDON</span>
</div>
</li>
<li data-bind="click: SubmitAddressRequest" class="locationListItem">
<div>
<span data-bind="text: Display">The Brunel Centre, SWINDON</span>
</div>
</li>
</ul>
HTML Result - Single Result
<ul class="locationList" data-bind="foreach: vm.reverseGeocodingViewModel.AddressList" style="height: 265px;">
<li class="locationListItem" data-bind="click: SubmitAddressRequest">
<div>
<span data-bind="text: Display"></span>
</div>
</li>
</ul>
I have looked at various previous questions on this topic and couldn't come up with an answer hence the question, but please point me to another post if I have missed one.
to remove all before add new use reverseGeocodingViewModel.AddressList.removeAll()
Maybe didn't find it but you have to parse data, $.parseJSON I use for this.
Try to use
$.each($.parsejSON(data), function(i, el){
reverseGeocodingViewModel.AddressList.push(el);
})
I hope it will help you
I've noticed following issues:
function SubmitReverseGeocodingRequest should be closed with } not )
foreach: vm.reverseGeocodingViewModel.AddressList should be foreach: reverseGeocodingViewModel.AddressList, without vm because vm is already binded
vm = new masterViewModel();
ko.applyBindings(vm);
To clean AddressList use vm.reverseGeocodingViewModel.AddressList([]) instead vm.reverseGeocodingViewModel.AddressList.splice(0, vm.reverseGeocodingViewModel.AddressList().length);
Note: Here's possible mistake
<li class="locationListItem" data-bind="click: SubmitAddressRequest">
so AddressList item have to have the SubmitAddressRequest function. I don't think that you get data with the function
I've created test sample based on your code, take a look here

How can a data-bind to an element within a Kendo-Knockout listview?

I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.

Categories

Resources