I have nested loops with Knockout. I would like to refer to something in a parent "scope". If you see below I always want to refer to the same parent/grandparent regardless how deep I nest the loops. I have seen the "with" binding, not sure it will help me. Is there any way I can create an alias to a particular scope, so further down in the nested loop I can refer to this alias and still be able to refer to the scope of the current loop also?
<!-- Somewhere up there is the "scope" I want to capture -->
<!-- ko foreach: getPages() -->
<span data-bind="text: pageName" ></span>
<button data-bind="click: $parents[1].myFunction()" >Press me</button>
<!-- ko foreach: categories -->
<span data-bind="text: categoryName" ></span>
<button data-bind="click: $parents[2].myFunction()" >Press me</button>
<!-- ko foreach: questions -->
<span data-bind="text: questionText" ></span>
<button data-bind="click: $parents[3].myFunction()" >Press me</button>
<!-- /ko -->
<!-- /ko -->
<!-- /ko -->
The foreach binding supports as aliases as does the (custom) withProperties binding.
<!-- ko withProperties: { book: $root.getBook() } -->
<!-- ko foreach: {data: book.pages, as: 'page'} -->
<span data-bind="text: page.pageName" ></span>
<button data-bind="click: book.bookClicked" >Press me</button>
<!-- ko foreach: {data: page.categories, as: 'category'} -->
<span data-bind="text: category.categoryName" ></span>
<button data-bind="click: page.pageClicked" >Press me</button>
<!-- etc -->
<!-- /ko -->
<!-- /ko -->
<!-- /ko -->
None of my declarative bindings directly use $parent.
The problem with the #user2864740 answer is that it doesn't work out of the box jsFiddle.
The first issue:
The binding 'withProperties' cannot be used with virtual elements
To fix that simply add the following code:
ko.virtualElements.allowedBindings.withProperties = true;
After that, you'll get another exception:
Unable to parse bindings. Message: ReferenceError: 'book' is
undefined; Bindings value: foreach: {data: book.pages, as: 'page'}
Which indicates that the withProperties isn't working at all - it hasn't created the book property in the binding context for its child bindings as you might expect.
Below is the fully working and reusable custom binding (jsFiddle):
ko.bindingHandlers.withProperties = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// Make a modified binding context, with a extra properties, and apply it to descendant elements
var value = ko.utils.unwrapObservable(valueAccessor()),
innerBindingContext = bindingContext.extend(value);
ko.applyBindingsToDescendants(innerBindingContext, element);
// Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings.withProperties = true;
I think this will help you
Calling a function in a parent's scope in nested view model
and the jsfiddle demo
jsfiddle
Related
I want to show/hide a div in my code based on a certain value. Using Knockout JS context debugger I found the property I need to compare in $root context. The property path I get from developer console is :
$root_toJS.items.mainItems[0].itemDescription.productId
I tried several methods, basically different variations of the 'ko if' in the following code, but nothing works:
<!-- ko if: $root.items.mainItems[0].itemDescription.productId != 1 -->
<div class="action-row">
<a href="#" data-bind="click: execute" class="btn-primary fiori3-btn-primary">
<span data-bind="text: name"></span>
</a>
</div>
<!-- /ko -->
Is there any way I can acces the value at the specified path in a 'ko if' condition?
Thank you
when you're referring to your name variable I'm assuming it's in the context of your itemDescription, so you have to make sure you're using the exact location of it also.
In my 2nd example I'm using a foreach loop to go over all the mainItems. Pay attention to the as: mainItem alias where I don't need to enter the entire thing anymore, you could also use $data but that only complicates it imo.
class ViewModel {
constructor() {
this.items = {
mainItems: [{
itemDescription: {
productId: 1,
name: 'item one',
}
}, {
itemDescription: {
productId: 2,
name: 'item two',
}
}]
};
}
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h5>this breaks any generic approach but is as your example:</h5>
<!-- ko if: $root.items.mainItems[0].itemDescription.productId !== 1 -->
<div>
<span data-bind="text: $root.items.mainItems[0].itemDescription.name"></span>
</div>
<!-- /ko -->
<!-- ko if: $root.items.mainItems[1].itemDescription.productId !== 1 -->
<div>
<span data-bind="text: $root.items.mainItems[1].itemDescription.name"></span>
</div>
<!-- /ko -->
<h5>this embraces it what is probably more what you'd want:</h5>
<!-- ko foreach: { data: $root.items.mainItems, as: 'mainItems' } -->
<!-- ko if: mainItems.itemDescription.productId !== 1 -->
<div>
<span data-bind="text: mainItems.itemDescription.name"></span>
</div>
<!-- /ko -->
<!-- /ko -->
I would like to understand why knockout behaves differently when a property is a prototype, and especially important, how to avoid it, while still using prototypes. I want some methods to be overridden because I have a base view model that we are to inherit
The following demonstrates what I mean
JSFIDDLE WITH PROTOTYPE [try typing into the first input box and it will appear in the other]
var viewModel = function(params) {
this.params = params;
};
viewModel.prototype.text = ko.observable(this.params && this.params.initialText || '');
ko.components.register('message-editor', {
viewModel: viewModel,
template: 'Message: <input data-bind="value: text" /> '
+ '(length: <span data-bind="text: text().length"></span>)'
});
ko.applyBindings();
<!-- ko component: "message-editor" -->
<!-- /ko -->
<br />
<!-- ko component: "message-editor" -->
<!-- /ko -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
JSFIDDLE WITHOUT PROTOTYPE [try typing into the first input box and it will not appear in the other]
var viewModel = function(params) {
this.params = params;
this.text = ko.observable(this.params && this.params.initialText || '');
};
ko.components.register('message-editor', {
viewModel: viewModel,
template: 'Message: <input data-bind="value: text" /> '
+ '(length: <span data-bind="text: text().length"></span>)'
});
ko.applyBindings();
<!-- ko component: "message-editor" -->
<!-- /ko -->
<br />
<!-- ko component: "message-editor" -->
<!-- /ko -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Why are they behaving differently?
In JavaScript, the prototype is used only for functions that are common to each object of that type. All instances can share the same copy of the function because the function, when called, gets the reference to the instance object through this. Knockout observables are not designed to be stored in the prototype because they store data specific to an object instance.
I am trying to show and hide a div section based on the corresponding buttons that are created through foreach loop. At the moment, whenever I click the button it shows all div sections rather than the one the button is under. I am quite new to knockout and I have spent many hours trying different methods and tutorials to resolve this issue but still unsuccessful.
This is the view section:
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="firstDiv">
<!-- ko if: $root.filteredAvailabilities().length > 0 -->
<!-- ko foreach: $root.filteredAvailabilities -->
<div class="secondDiv">
<div class="thirdDiv">
<div class="fourthDiv">
<div class="fifthDiv">
<!-- ko with: Items -->
<div class="sixthDiv">
<!-- ko if: !$root.viewPrices() -->
<input class="actionButton" type="button" data-bind="upperCaseValue: $parents[1].ViewPrices, click: $root.ViewPrices" />
<!-- /ko -->
<!-- ko if: $root.viewPrices() -->
<input class="actionButton" type="button" data-bind="upperCaseValue: $parents[1].HidePrices, click: $root.HidePrices" />
<!-- /ko -->
</div>
<!-- /ko -->
</div>
<!-- ko if: $root.viewPrices() -->
<!-- ko foreach: Rooms -->
<div class="seventhRoomDiv">
<table class="roomPriceTable">
<tr>
<td><span class="roomPriceTableRoomLabel" data-bind="text: Room.Name"></span></td>
</tr>
</table>
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</div>
<!-- ko if: $root.viewPrices() -->
<div class="eighthBottomDiv">
<input class="actionButton chooseRoomButton" type="button" data-bind="upperCaseValue: $parent.ChooseRoom, click: $root.ChooseRoom" />
</div>
<!-- /ko -->
</div>
<!-- /ko -->
<!-- /ko -->
</div>
In the view model all its doing is setting viewPrices to true:
/// <summary>
/// View the prices.
/// </summary>
self.ViewPrices = function ()
{
self.viewPrices(true);
};
I just want the corresponding seventhDivRoom to display after I click the button that's attached to it rather than displaying all.
Before expanding:
After expanding - Expanded all three rather than the second one only:
EDIT
I have tried using Rafael Companhoni example and apply it to my versions, but I am coming across some difficulties to display the div. I have added
self.ShowRoomPrice = ko.observable(false);
to the view model. Then added
availability.ShowRoomPrice = false;
to the availability callback which is similar to how you created the observable array. Furthermore I have added
self.Test = function (availability){
availability.ShowRoomPrice = !availability.ShowRoomPrice
model.availability(availability);
};
Finally the view looks like this
<!-- ko if: ShowRoomPrice === true -->
<input class="actionButton" type="button" data-bind="upperCaseValue: 'Show / Hide Prices', click: $root.ChooseHotel" />
<!-- /ko -->
It does change the state of ShowRoomPrice between true and false but the div does not appear. Is there something that's still missing?
You are using the property 'viewPrices' which is in the root view model to decide if the divs should be rendered. In the example below I've added a simple view model with an observableArray of FilteredAvailabilty objects. It demonstrates how you could use each sub-view model 'viewPrices' property in the 'if' binding:
var FilteredAvailability = function (viewPrices, items, rooms) {
var self = this;
self.viewPrices = ko.observable(viewPrices);
self.Items = ko.observableArray(items);
self.Rooms = ko.observableArray(rooms);
}
var ViewModel = function() {
var self = this;
// initialize with some data
self.filteredAvailabilities = ko.observableArray([
new FilteredAvailability(true, items, rooms),
new FilteredAvailability(true, items, rooms),
new FilteredAvailability(false, items, rooms),
]);
};
And then in the HTML
<div>
<!-- ko if: filteredAvailabilities().length > 0 -->
<!-- ko foreach: filteredAvailabilities -->
<div>
<!-- ko if: viewPrices -->
<div>Depends only on each the property 'viewPrices' of each item</div>
<!-- /ko -->
</div>
<!-- /ko -->
<!-- /ko -->
</div>
I have a jquery knockout template which renders a foreach.
Inside each item, i am using the same function many times (for css binding and for visibility of other child elements)
Is it possible that instead of calling the same function many times for each item in the foreach, to temporary save it and then re-use it inside the template?
PS: i know i can set the visibility of the <i class="fa " /> tags using css selectors, but this doesn't answers the question.
<script type="text/html" id="properties-template">
<!-- ko foreach: Groups -->
<!-- saving the result in a variable instead of calling it so many times -->
<!-- var isValid = isGroupValid($data); !-->
<div class="group" data-bind="css: { 'valid': isGroupValid($data) }">
<div class="iconContainer">
<!-- ko if: $root.isGroupValid($data) === false -->
<i class="fa fa-square-o"></i>
<!-- /ko -->
<!-- ko if: $root.isGroupValid($data) === true -->
<i class="fa fa-check-square"></i>
<!-- /ko -->
</div>
</div>
<!-- /ko -->
</script>
As $data is itself the viewmodel, you could just have a computed observable there which does whatever logic isGroupValid currently does. So I assume your view model at the moment looks something like
function ViewModel(){
this.isGroupValid = function(data){
// some logic returning boolean
}
}
Here's a live example demonstrating the "before": http://jsfiddle.net/hbSj7/
change it to this:
function ViewModel(){
this.isGroupValid = ko.computed(function(){
// some logic returning boolean
// just use "this" where you used to use "data"
}, this);
}
Then just change your template to, eg/
<div class="group" data-bind="css: { 'valid': isGroupValid() }">
<div class="iconContainer">
<!-- ko if: !isGroupValid() -->
<i class="fa fa-square-o"></i>
<!-- /ko -->
<!-- ko if: isGroupValid() -->
<i class="fa fa-check-square"></i>
<!-- /ko -->
</div>
</div>
Here's a live example having changed to this method: http://jsfiddle.net/ug9ax/
The important difference is in your current method the function gets executed every time you call it, with the changed method the computed only gets re-evaluated if anything it depends on changes, like other observable properties in your viewmodel.
I wanted to add an if statement using knockout in my view page to display an item or not and this is what i have but i am not sure if i have the proper syntax:
<!--ko if: $idx.ViewModel.isGroup = false -->
<span id="Reading" class="column_title">#ViewBag.Title</span>
<!-- ko -->
In my javascript file i have:
$idx.GetGroups={
ByTime: function(url){
Ajax.Get({
....
$idx.ViewModel.isGroup = ko.observable(window.location.href.toLowerCase().indexOf("groupproject") > 0);
});
}
}
Is this the right syntax for checking knockout value in the view?
You don't need to compare to false, its a boolean expression like :
<!--ko if: !$idx.ViewModel.isGroup() -->
<span id="Reading" class="column_title">#ViewBag.Title</span>
<!-- ko -->
In your case, use ifnot
<!--ko ifnot: $idx.ViewModel.isGroup() -->
<span id="Reading" class="column_title">#ViewBag.Title</span>
<!-- ko -->
(http://knockoutjs.com/documentation/ifnot-binding.html)
to solve your problem you can test the evaluated value... like below
<!--ko if: !$idx.ViewModel.isGroup() -->
<span id="Reading" class="column_title">#ViewBag.Title</span>
<!-- ko -->
if you don't put the "()" you will converting a function in boolean...