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...
Related
I have a a collection of panels each with a simple list of items that needs to either be sorted by 'computedLoad' or 'Name'. I have the following objects and methods to accomplish this generically over all of the panels (only showing one panel among many).
scope.orderBy = {
name: {
displayName: "Name",
sort: "Name",
reverse: false
},
load: {
displayName: "Load",
sort: "-computedLoad",
reverse:false
}
};
scope.selectOrder = function (panel, order) {
timeout(function () {
panel.activeOrder = order;
});
};
scope.panels = {
methods: {
activeOrder: scope.orderBy.name
}
};
I have the following html:
<div>
<ul class="nav nav-pills">
<li class="list-label"><a>Order By:</a></li>
<li ng-repeat="order in orderBy">{{order.displayName}}</li>
</ul>
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="item in suite.Methods | orderBy:panel.methods.activeOrder.sort"><span class="text">{{item.Name}}</span></li>
</ul>
</div>
The selectOrder method doesn't seem to work. Any ideas? Am I missing something?
Here is an example: http://jsbin.com/puxoxi/1/
Setting panel.activeOrder happens asynchronously, so it is outside of angulars so called "digest cycle".
To make angular re-evaluate your scope, use the $apply function:
It could look like this:
scope.$apply(function() {
panel.activeOrder = order;
});
I am using Knockout.js to populate a set of HTML5 <details> elements. Here is the structure:
<div class="items" data-bind="foreach: Playlists">
<details class="playlist-details" data-bind="attr: {id: 'playlist-details-' + $index(), open: isOpen}">
<summary>
<span data-bind="text: name"></span> - <span data-bind="text: count"></span> item(s)
<div class="pull-right">
<button data-bind="click: $parent.play, css: {disabled: count() == 0}, attr: {title: playbtn_title}" class="btn"><i class="icon-play"></i> Play</button>
<button data-bind="click: $parent.deleteList" class="btn btn-danger"><i class="icon-trash"></i> Delete</button>
</div>
</summary>
<div class="list" data-bind="with: items" style="padding-top: 2px;">
...
</div>
</details>
</div>
The data in the ViewModel looks something like this:
var VM = {
Playlists: [
{
name: "My Playlist1",
count: 3,
items: [<LIST OF SONG ID'S>],
playbtn_title: "Play this playlist",
isOpen: true
},
{
name: "My Playlist2",
count: 5,
items: [<LIST OF SONG ID'S>],
playbtn_title: "Play this playlist",
isOpen: null
},
{
name: "My Playlist3",
count: 0,
items: [],
playbtn_title: "You need to add items to this list before you can play it!",
isOpen: null
}
]
};
I have added the ability to remember the open or closed state of the details view using the isOpen property of the ViewModel and an attr binding (As originally described here).
However, when I click the <summary> to expand the details, the ViewModel does not get updated - unlike value bindings, attr bindings aren't two-way.
How can I get this binding to update when the attribute value changes?
I know that the browser triggers a DOMSubtreeModified event when the element is opened or closed, but I;m not sure what I would put there - several things I have tried (including .notifySubscribers(), if (list.open()) ..., etc.) cause looping, where the property being changed makes the event trigger again, which changes the property again, which triggers the event again, etc.
Using $ to play DOM directly is not ko way :-)
Just create a two-way binding for HTML5 details tag, it's cheap in ko.
http://jsfiddle.net/gznf3/
ko.bindingHandlers.disclose = {
init: function(element, valueAccessor) {
if (element.tagName.toLowerCase() !== 'details') {
throw "\"disclose\" binding only works on <details> tag!";
}
var value = valueAccessor();
if (ko.isObservable(value)) {
$(element).on("DOMSubtreeModified", function() {
value($(element).prop('open'));
});
}
},
update: function(element, valueAccessor) {
$(element).prop('open', ko.unwrap(valueAccessor()));
}
};
The way that I found in the end that works is simply to have the DOMSubtreeModified "manually" update the value:
$(document).on('DOMSubtreeModified', 'details.playlist-details', function(e) {
var list = ko.dataFor(this);
list.open(this.getAttribute('open'));
});
(Somehow, this does not cause the looping that more-complex constructs I tried had caused.)
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);
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.
The menu is what I want, when mouse over the left, the right should changes but doesn't.
Here is my simplified viewmodel:
var currentSelectIndex = 0;
var AppModel = {
CurrentIndex: ko.observable(currentSelectedIndex),
OnMouseOver: function (data, event) {
// change currentIndex or currentSelectedIndex here
// CurrentSubCategory didn't updated
},
CurrentSubCategory: ko.computed({
read: function() {
return AppModel.Menu[AppModel.CurrentIndex()].subcategory;
},
deferEvaluation: true
}),
Menu: [
{
subcategory: [
{ name: '1', id: 50000436 },
{ name: '2', id: 50010402 },
{ name: '3', id: 50010159 }
],
}
};
And my html:
<div class="categories" id="categories">
<div class="first-category" id="first-category">
<ul data-bind="foreach:Menu">
<li data-bind="text:name,attr:{id:id,class:className},event{ mouseover: $root.myfunction}"></li>
</ul>
</div>
<div class="sub-category" id="sub-category">
<ul data-bind="foreach:CurrentSubCategory()">
<li><a data-bind="text:name,attr:{href:getListUrl(id)}"></a></li>
</ul>
<div class="clear">
</div>
</div>
<div class="clear">
</div>
</div>
Sorry, can't post images due to less than 10 reputation.
Thanks for any help!
There were several syntax errors in your code which I imagine are a result of your making it simpler to post.
I have posted a working jsFiddle here: http://jsfiddle.net/Gy6Gv/2/
I changed Menu to be an observable array only because knockout provides the helper method .indexOf to make it easier to get the index of the menu from the mouseover. Other than that there was no problem with the computed. I imagine there is some other syntactical error in your actual code.