Below are two knockout templates plugin and plugin2. Both are dependent upon ko.computed observables and both observables have their deferEvaluation property set to true. Plugin does not render, but plugin2 does render.
What needs to be changed to make plugin render (keeping deferEvaluation==true)?
Fiddle here http://jsfiddle.net/jeljeljel/YKLGM/
HTML
<div id="plugin" data-bind="template: { name: 'template1', data: $data }" ></div>
<div id="plugin2" data-bind="template: { name: 'template2', data: $data }" ></div>
<script type="text/html" id="template1">
<div data-bind="foreach: columns()">
<span data-bind="text: displayText"></span>
</div>
</script>
<script type="text/html" id="template2">
<div data-bind="text: dataItem" ></div>
</script>
Javascript
var gridData = {
columns: [{
displayText: 'Name'
}, {
displayText: 'Last Login Date'
}, {
displayText: 'Email'
}]
};
function DataModel() {
var self = this;
self.columns = ko.observableArray([]);
self.loadGrid = ko.computed({
read: function () {
self.columns(gridData.columns);
},
owner: this,
deferEvaluation: true
});
self.id = ko.observable(1);
self.dataItem = ko.computed({
read: function () {
return self.id() * 3;
},
owner: this,
deferEvaluation: true
});
}
dataModel = new DataModel();
ko.applyBindings(dataModel);
Both are dependent upon ko.computed observables
No, they are not. plugin depends only on columns, which is an empty observableArray that never gets populated. Maybe you should use your loadGrid computed observable somewhere.
By the way, loadGrid does not make much sense as computed observable in its current state, the read method does not even return anything.
Related
So what exactly is the ko.observable() doing? Here's the situation. I have a boolean ko.observable(), as you can see. I have click set to that value, so it SHOULD toggle the value of the true false contained within it's method call.
When I watch the array get populated in the developer tools, I see that selected does not = true or false, it instead = a pretty extensive function, and I can't find the true or false value anywhere inside of that, so I have no idea what exactly is happening when ko.observable() is used
What I expected is for tab.selected to be the value of tabArray[tab].selected, and when the page loads, that is correct. However, after clicking, tabArray[tab].selected = [Object object] when the text value is written out. I attempt to use:
<pre data-bind="text: JSON.stringify(ko.toJS(tab.selected)"></pre>
(found here: http://www.knockmeout.net/2013/06/knockout-debugging-strategies-plugin.html) and that prints out either true or false, do I need to do this for the other places where i need that value? Because I'm not sure exactly what ko.observable is doing.
define(['knockout', 'text!../Content/SSB/PartialViews/MainContent.html'], function (ko, MCTemplate) {
ko.components.register('MainContent', {
template: MCTemplate
});
var MainViewModel = {
tabArray: [
{ name: 'bob', selected: ko.observable(true) },
{ name: 'bib', selected: ko.observable(false) },
{ name: 'bab', selected: ko.observable(false) },
{ name: 'bub', selected: ko.observable(false) },
{ name: 'beb', selected: ko.observable(false) },
]
};
ko.applyBindings(MainViewModel);
return {
viewModel: MainViewModel
}
});
the HTML
<div id="tab">
<ul class="nav nav-tabs" role="tablist">
<!--ko foreach: {data: $parent.tabArray, as: 'tab'}-->
<li data-bind="click: tab.selected, css: { 'active': tab.selected}">
<a data-bind="attr: {href: '#' + tab.name}, text: name"></a>
<div data-bind="text: tab.name"></div>
<div data-bind="text: tab.selected"></div>
</li>
<!--/ko-->
</ul>
<!--ko foreach: {data: $parent.tabArray, as: 'tab'}-->
<div class="ui-tabpanel" role="tabpanel" data-bind="visible: tab.selected">
<p data-bind="text: name"></p>
</div>
<!--/ko-->
</div>
The click binding calls the provided function, passing it the current view model (also called $data). That's why you see [Object object] as the observable's value after the click. Since you want the click to toggle the observable, you need to create a function to do that. A nice, clean way to do this is through a custom binding, which I'll call toggle:
ko.bindingHandlers.toggle = {
init: function(element, valueAccessor) {
ko.utils.registerEventHandler(element, 'click', function () {
var obs = valueAccessor();
obs(!obs());
});
}
};
Now you bind using toggle instead of click: <li data-bind="toggle: tab.selected...
I have an issue with Knockout.js . What I try to do is filter a select field. I have the following html:
<select data-bind="options: GenreModel, optionsText: 'name', value: $root.selectedGenre"></select>
<ul data-bind="foreach: Model">
<span data-bind="text: $root.selectedGenre.id"></span>
<li data-bind="text: name, visible: genre == $root.selectedGenre.id"></li>
</ul>
And the js:
var ViewModel = function (){
self.selectedGenre = ko.observable();
self.Model = ko.observableArray([{
name: "Test",
genre: "Pop"
}
]);
self.GenreModel = ko.observableArray([
{
name: "Pop",
id: "Pop"
},
{
name: "Alle",
id: "All"
}
]);
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
JSFiddle: http://jsfiddle.net/CeJA7/1/
So my problem is now that the select list does not update the binding on the span inside the ul and I don't know why...
The value binding should update the property selectedGenre whenever the select value changes, shouldn't it?
Any ideas are welcome.
There are a lot of issues in your code:
1) self is not a magical variable like this. It's something people use to cope with variable scoping. Whenever you see self somewhere in a JavaScript function be sure there's a var self = this; somewhere before.
2) KnockoutJS observables are not plain variables. They are functions (selectedGenre = ko.observable()). ko.observable() returns a function. If you read the very first lines of documentation regarding observables you should understand that access to the actual value is encapsulated in this retured function. This is by design and due to limitations in what JavaScript can and cannot do as a language.
3) By definition, in HTML, <ul> elements can only contain <li> elements, not <span> or anything else.
Applying the above fixes leads to this working updated sample:
HTML:
<select data-bind="options: GenreModel, optionsText: 'name', value: selectedGenre"></select>
<span data-bind="text: $root.selectedGenre().id"></span>
<ul data-bind="foreach: Model">
<li data-bind="text: name, visible: genre == $root.selectedGenre().name"></li>
</ul>
JavaScript:
var ViewModel = function (){
var self = this;
self.selectedGenre = ko.observable();
self.Model = ko.observableArray([
{
name: "Test",
genre: "Pop"
}
]);
self.GenreModel = ko.observableArray([
{
name: "Pop",
id: "Pop"
},
{
name: "Alle",
id: "All"
}
]);
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
Hi I have been getting investing alot of time in learning Knockout and have come to a point where I have to many properties in my application and I am in need to use the mapping pluggin.
It seems easy enought how it should be used but I mussed be missing something because it does not work.I have created a test example.This is my code:
function vm() {
var self = this;
this.viewModel = {};
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
self.viewModel = ko.mapping.fromJS(ajaxData);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());
This is my html:
<ul data-bind="foreach: viewModel">
<li data-bind="text:FirstName"></li>
<input type="text" data-bind="value: FirstName"/>
</ul>
<button data-bind="click : getData">Press me!</button>
My ajax call succesfully retrieves this data from the server:
[
{
FirstName: "Madalina",
LastName: "Ciobotaru",
hobies: [
"games",
"programming",
"hoby"
]
},
{
FirstName: "Alexandru",
LastName: "Nistor",
hobies: [
"games",
"programming",
"movies"
]
}
]
It seems that after data function is called viewModel get's converted into an array but with no items in it.
What am I doing wrong?
I have taken your expected server data and created a jsfiddle here. You needed to change the viewModel property to be an observable array, and change the way the mapping is performed.
Here is a version of your script that will work:
function vm() {
var self = this;
this.viewModel = ko.observableArray([]);
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
ko.mapping.fromJS(ajaxData, {}, self.viewModel);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());
In knockout.js, is it possible to let the right-hand-side of a binding (the value of the binding) be dynamic? For example,
<input data-bind="value: dynamicBinding()"/>
<script type="text/javascript">
var vm = {
dynamicBinding : function() {
return "foo().bar";
},
foo : ko.observable({
bar : ko.observable("hi");
}
};
ko.applyBindings(vm);
</script>
the result should be that the the dynamicBinding function is executed while applying the bindings and the resulting string is used as the binding. The input element should be bound to foo().bar, which is the observable with the value "hi".
If you wonder why I would want this, I am trying to render a dynamic table with knockout, where both the rows and the columns are observableArrays, and I want to allow the column definitions to contain the expression of the binding for that column. I.e., I want to be able to do this:
<table data-bind="foreach: data">
<tr data-bind="foreach: $root.columns">
<td data-bind="text: cellValueBinding()"></td>
</tr>
</table>
<script type="text/javascript">
var vm = {
data: ko.mapping.fromJS([
{title: "Brave New World", author: { name : "Aldous Huxley" },
{title: "1984", author: { name : "George Orwell" },
{title: "Pale Fire", author: { name : "Vladimir Nabokov" }]),
columns: ko.observableArray([
{header: "Title", cellValueBinding: function () { return "$parent.title"; }},
{header: "Author", cellValueBinding: function () { return "$parent.author().name"; }}
])
};
ko.applyBindings(vm);
</script>
As you can see from the example, the column definition knows how to extract the value from the data. The table markup itself is more or less a placeholder. But as far as I can tell, this does not work, due to the way knockout processes the bindings. Are there any other options available?
Thanks.
Solution: I ended up using Ilya's suggestion - I can let cellValueBinding be a function that accepts the row and column as arguments, and returns an observable. This technique is demonstrated in this fiddle.
Use ko.computed for it.
Look on example
JSFiddle
EDIT
In your second example, you can pass $parent value ti the function
<td data-bind="text: cellValueBinding($parent)"></td>
and in model
{header: "Title", cellValueBinding: function (parent) { return parent.title; }},
{header: "Author", cellValueBinding: function (parent) { return parent.author().name; }}
JSFiddle
I have a simple Handlebars helper which simply formats a money value. The helper works property when I test with static data, but not when I load data asynchronously. In other words, {{totalBillable}} will output the expected amount, but {{money totalBillable}} will output zero. But only when the data is loaded via an ajax call. What the heck am I doing wrong?
I've tried to pare the code down as much as possible, and also created a jsfiddle here:
http://jsfiddle.net/Gjunkie/wsZXN/2/
This is an Ember application:
App = Ember.Application.create({});
Here's the handlebars helper:
Handlebars.registerHelper("money", function(path) {
var value = Ember.getPath(this, path);
return parseFloat(value).toFixed(2);
});
Model:
App.ContractModel = Ember.Object.extend({});
App Controller:
App.appController = Ember.Object.create({
proprietor: null,
});
Contracts Controller (manages an array of contracts):
App.contractsController = Ember.ArrayController.create({
content: [],
totalBillable: function() {
var arr = this.get("content");
return arr.reduce(function(v, el){
return v + el.get("hourlyRate");
}, 0);
}.property("content"),
When the proprietor changes, get new contract data with an ajax request. When getting data asynchronously, the handlebars helper does not work.
proprietorChanged: function() {
var prop = App.appController.get("proprietor");
if (prop) {
$.ajax({
type: "POST",
url: '/echo/json/',
data: {
json: "[{\"hourlyRate\":45.0000}]",
delay: 1
},
success: function(data) {
data = data.map(function(item) {
return App.ContractModel.create(item);
});
App.contractsController.set("content", data);
}
});
}
else {
this.set("content", []);
}
}.observes("App.appController.proprietor")
});
If I use this version instead, then the Handlebars helper works as expected:
proprietorChanged: function() {
var prop = App.appController.get("proprietor");
if (prop) {
var data = [{
"hourlyRate": 45.0000}];
data = data.map(function(item) {
return App.ContractModel.create(item);
});
App.contractsController.set("content", data);
}
else {
this.set("content", []);
}
}.observes("App.appController.proprietor")
View:
App.OverviewTabView = Ember.TabPaneView.extend({
totalBillableBinding: "App.contractsController.totalBillable"
});
Kick things off by setting a proprietor
App.appController.set("proprietor", {
ID: 1,
name: "Acme"
});
Template:
<script type="text/x-handlebars">
{{#view App.OverviewView viewName="overview"}}
<div class="summary">
Total Billable: {{totalBillable}}<br/>
Total Billable: {{money totalBillable}}<br/>
</div>
{{/view}}
</script>
when using a helper, handlebars does not emit metamorph tags around your helper call. this way, this part of the template is not re-rendered because there is no binding
to manually bind part of a template to be re-rendered, you can use the bind helper:
<script type="text/x-handlebars">
{{#view App.OverviewView viewName="overview"}}
<div class="summary">
Total Billable: {{totalBillable}}<br/>
Total Billable: {{#bind totalBillable}}{{money this}}{{/bind}}<br/>
</div>
{{/view}}
</script>