Passing $index and $data as arguments to function for click handler - javascript

I am passing $index and $data to the change_model function. The function is expecting 2 parameters in the following order: (index, data).
From the viewModel I am passing click: $root.change_model.bind($data, $index()). Within the function index prints $data, and data prints index: values are reversed.
self.change_model = function(index, data) {
self.patternSelectedIndex(index);
selected_door = data.file;
create_door();
};
<div data-bind="foreach: x.patterns">
<div class="thumbnail" data-bind="css: { selected: $index() === $root.patternSelectedIndex() }">
<img class='img model' style='width:164px;height:90px;padding:5px' data-bind="attr:{src:'images/models/' + $data.file + '.png'}, click: $root.change_model.bind($data, $index())" />
<div class="caption">
<span data-bind="text: $data.name"></span>
</div>
</div>
</div>

The first argument of bind will become this inside your function, because Knockout is merely using the regular bind function.
You can either pass $data or $root as the first (thisArg) argument, or pass null or undefined, as you don't really need it since you seem to use the self = this idiom.
For example:
var ViewModel = function () {
var self = this;
self.change_model = function (index, data) {
console.log(this);
console.log(index);
console.log(data);
// Actual code here
};
self.x = { patterns: [{ file: 'some-file', name: 'some-name' }] };
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: x.patterns">
<button data-bind="click: $root.change_model.bind($data, $index(), $data)">Click me!</button>
<span data-bind="text: $data.name"></span>
</div>

Related

Detect observable change inside observableArray

There's a paragraph in knockout docs that said you can create an observableArray with properties as observables but there isn't an example of that:
http://knockoutjs.com/documentation/observableArrays.html
So what I'm trying to achieve is to add an element to an observableArray that has an observable property to detect state changes when it's clicked. So here is my code what I have so far
export class Team {
Name: KnockoutObservable<string>;
Score: KnockoutObservable<number>;
ListTeamsArray: KnockoutObservableArray<any>;
selectedTeam: KnockoutObservable<boolean>;
constructor(json) {
var self = this;
if (json !== null) {
self.Name = ko.observable(json.Name);
self.Score = ko.observable(0);
self.ListTeamsArray = ko.observableArray();
self.selectedTeam = ko.observable(false);
}
}
addTeam = () => {
var self = this;
//Working correctly and I'm declaring "selectedTeam" as an observable with initial value of "false"
var newTeam = { Name: self.Name(), Score: 0, selectedTeam: ko.observable(false)};
self.ListTeamsArray.push(newTeam);
}
//Here I create a function that is passing a "team" parameter (the one in the array and it's working fine
teamSelectedFn = (team: Team, event) => {
var self = this;
$(".teamPanel").css("background", "none");
//Loop thru array in knockout to assign selected value, probably there's a better way
ko.utils.arrayForEach(self.ListTeamsArray(), (item) => {
if (item.Name === team.Name) {
$(event.currentTarget).css("background", "#a4e4ce");
item.selectedTeam = ko.observable(true);
} else {
item.selectedTeam = ko.observable(false);
}
});
//just for testing
ko.utils.arrayForEach(self.ListTeamsArray(), (item) => {
console.log(item);
console.log(item.selectedTeam());
});
}
}
And this is the HTML
<div class="row" id="teamCrud">
<div class="col-sm-3" >
<div class="form-group">
<input class="form-control" data-bind="value: Name" />
#*<span data-bind="text: Score"></span>*#
<button data-bind="click: addTeam" class="btn btn-success">Add</button>
</div>
</div>
<div class="col-sm-9">
Equipos
<div data-bind="foreach: ListTeamsArray" class="row">
<div class="col-sm-3">
<div class="panel panel-default teamPanel" data-bind="click: $parent.teamSelectedFn, style: { border: selectedTeam() ? '2px solid red' : 'none'}#*, style: { background: $data.selectedTeam() ? 'red' : 'none'}*#">
<div class="panel-heading" data-bind="text: Name"></div>
<div class="panel-body">
Score:
<p data-bind="text: Score"></p>
Seleccino
<p data-bind="text: selectedTeam()"></p>
</div>
</div>
</div>
</div>
</div>
</div>
Everything it's working, I know I can change the background color of the HTML element with knockout but I need to detect the dependency change. It's not detecting the changes from the observable inside the array. Is there something else I need to do or I'm handling this the wrong way?
In your click function you are overwriting the bound observable with a new observable. You probably just need to change your function to update the existing observable instead of replacing it.
teamSelectedFn = (team: Team, event) => {
var self = this;
$(".teamPanel").css("background", "none");
//Loop thru array in knockout to assign selected value, probably there's a better way
ko.utils.arrayForEach(self.ListTeamsArray(), (item) => {
if (item.Name === team.Name) {
$(event.currentTarget).css("background", "#a4e4ce");
item.selectedTeam(true);
} else {
item.selectedTeam(false);
}
});

How to bind Knockout viewModel for only available keys?

I am creating knockout data-bind properties dynamically using .cshtml in MVC. I want to bind only those properties which are available in viewModel which again I am creating dynamically from the result of restful WCF.
So there may be or may be not some keys available in the viewModel for which
e.g.: <span data-bind="text: cli"></span> is created.
But when I bind the viewModel, I get an error along the lines of "'cli' property not found in viewModel". However, I wanted to bind that property only if that key is there in viewModel in the first place.
$(document).ready(function () {
debugger;
$.ajax({
cache: false,
type: "GET",
async: false,
dataType: "json",
url: requestURL,
success: function (data) {
debugger;
if (data.GetCircuitCheckStatusResponse.Status.HasErrors == false) {
networkData = data.GetCircuitCheckStatusResponse.Response.RunningStatus.networkData;
diagnosticData = data.GetCircuitCheckStatusResponse.Response.RunningStatus.diagnosticData;
diagnosticsInfo = {};
//To Create boxPanel Datas
for (var i = 0; i < networkData.length; i++) {
diagnosticsInfo[networkData[i].ItemTitle] = networkData[i].itemValue;
}
//To Bind the data using Knockout
}
},
error: function (xhr) {
debugger;
alert(xhr.responseText);
}
});
debugger;
var viewModel = ko.mapping.fromJS(diagnosticsInfo);
ko.applyBindings(viewModel);
// Every time data is received from the server:
//ko.mapping.fromJS(data, viewModel);
});
#foreach (var nameValue in childContainer.NameValueImageItemsList)
{
var cssClass = "nameValueItem floatLeft" + " " + nameValue.DataBindName;
<div class="#cssClass" style="">#nameValue.DisplayName</div>
<div class="#cssClass" style="width: 200px; margin-right: 10px;" ><span data-bind="text: CLI"></span></div>
<div class="#cssClass" style="width: 200px; margin-right: 10px;">
<a>
#if (nameValue.IconImageURL != null && nameValue.IconImageURL != "")
{
<img src="#nameValue.IconImageURL" alt="i"/>
}
</a>
</div>
<div class="clearBOTH"></div>
}
Here's a really straightforward way to do this:
ko.applyBindings({
description: 'some description'
// ,title: 'my title'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Description: <span data-bind="text: description"></span><br />
Title: <span data-bind="text: !!title ? title : ''"></span>
A related option may be that you create a safeTitle computed property on your view model:
var Vm = function() {
var self = this;
self.description = 'my description';
//self.title = 'my title';
self.safeTitle = ko.computed(function() {
return !!self.title ? self.title : '';
});
};
ko.applyBindings(new Vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Description: <span data-bind="text: description"></span><br />
Title: <span data-bind="text: safeTitle"></span>
Furthermore, you could also do it with a function, so you don't have to create an observable for each property:
var Vm = function() {
var self = this;
self.description = 'my description';
//self.title = 'my title';
self.safeGet = function(prop) {
return !!self[prop] ? self[prop] : '';
};
};
ko.applyBindings(new Vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Description: <span data-bind="text: description"></span><br />
Title: <span data-bind="text: safeGet('title')"></span>
Note that this code would be slightly different if those properties are observables, and even more different (and complicated) if it can be either.
Yet another option may be to check out point 3 of this blog post, on wrapping existing bindings: you could create another "text" binding that guards against this situation.
PS. I'd carefully rethink your design. Most likely the fact that properties are "optional" is related to some domain concept.
PPS. You could also consider using the Null Object Pattern server side, and this problem goes away entirely.
PPPS. Here's one final way to circumvent the problem, (logical, but) much to my surprise:
ko.applyBindings({
desc: 'some description'
// ,title: 'my title'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Description: <span data-bind="text: $data.desc"></span><br />
Title: <span data-bind="text: $data.title"></span>
This is how it worked for me:
<span data-bind="text: $data['#nameValue.DataBindName'] "></span>

knockout JS inner and outer bindings

I would like to include an inner element in an outer element using knockout, is this in any way possible?
HTML:
<div id='id1'>
<span data-bind="text: outerText()"></span>
<div id='id2'>
<span data-bind="text: innerText()"></span>
</div>
</div>
JavaScript:
var outerModel = function() {
this.outerText = ko.observable("Outer Model - Outer Text");
};
ko.applyBindings(new outerModel(), document.getElementById('id1'));
var innerModel = function() {
this.innerText = ko.observable("Inner Model - Inner Text");
};
ko.applyBindings(new innerModel(), document.getElementById('id2'));
This gives me an error:
ReferenceError: Unable to process binding "text: function(){return innerText() }"
Message: 'innerText' is undefined
I understand the error as the outer model doesn't contain the innertext and therefore the thing crashes.
My questions is if there is a proper/better/correct way of having an inner element and getting it to work in knockout.
Note: I do not want the innerModel to be a member/child of the outerModel as they are just in this HTML layout for layout purposes but aren't necessarily related.
Any help appreciated.
Thanks
Usually your best bet there is to make the inner stuff a property of your outer stuff and then just bind normally (possibly with with). E.g.:
var innerModel = function() {
this.innerText = ko.observable("Inner Model - Inner Text");
};
var outerModel = function() {
this.outerText = ko.observable("Outer Model - Outer Text");
this.inner = ko.observable(new innerModel());
};
ko.applyBindings(new outerModel(), document.getElementById('id1'));
...and then:
<div id='id1'>
<span data-bind="text: outerText()"></span>
<div id='id2' data-bind="with: inner">
<span data-bind="text: innerText()"></span>
</div>
</div>
Example:
var innerModel = function() {
this.innerText = ko.observable("Inner Model - Inner Text");
};
var outerModel = function() {
this.outerText = ko.observable("Outer Model - Outer Text");
this.inner = ko.observable(new innerModel());
};
ko.applyBindings(new outerModel(), document.getElementById('id1'));
<div id='id1'>
<span data-bind="text: outerText()"></span>
<div id='id2' data-bind="with: inner">
<span data-bind="text: innerText()"></span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
But in cases where that's not possible, you can add a new binding to KO that says "don't bind within this element" as described here:
ko.bindingHandlers.stopBinding = {
init: function () {
return { controlsDescendantBindings: true };
}
};
Usage:
<div id='id1'>
<span data-bind="text: outerText()"></span>
<div data-bind="stopBinding: true">
<div id='id2'>
<span data-bind="text: innerText()"></span>
</div>
</div>
</div>
...and then do the two applyBindings in your question. (Note that I added a div around your id2 div. If you want to use a "virtual element" instead, add this line after the binding handler:
ko.virtualElements.allowedBindings.stopBinding = true;
...to enable using it with virtual elements.)
Example:
// Somewhere where you'll only do it once
ko.bindingHandlers.stopBinding = {
init: function () {
return { controlsDescendantBindings: true };
}
};
// Then:
var outerModel = function() {
this.outerText = ko.observable("Outer Model - Outer Text");
};
var innerModel = function() {
this.innerText = ko.observable("Inner Model - Inner Text");
};
ko.applyBindings(new outerModel(), document.getElementById('id1'));
ko.applyBindings(new innerModel(), document.getElementById('id2'));
<div id='id1'>
<span data-bind="text: outerText()"></span>
<div data-bind="stopBinding: true">
<div id='id2'>
<span data-bind="text: innerText()"></span>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

knockout.js get parent observable

Is it possible to get parent observable in knockout? Like I have
<div data-bind="parent: {...}">
<div data-bind="child: {...}">
...
</div>
</div>
I want to get access to parent in child but not in the markup but in the code. How can I achieve in a standard knockout way?
UPDATE
I have found a simple way to achieve this by simply accessing the last argument of custom handler like this:
ko.bindingHandlers.custom = {
init: function (element, valueAccessor, allBindingsAccessor, vm, bindingContext) {
console.log(bindingContext.$parent);
}
}
You can use $parent to access the parent item:
<div data-bind="parent: {...}">
<div data-bind="child: {...}">
<span data-bind="text: $parent.someObservable()"></span>
<span data-bind="text: somefunction($parent.someObservable())"></span>
</div>
</div>
The most simple way it to past parent object to child model as a parameter (pointer) when child object constructed. But it more standard javascript way then knockout
var Parent = function (item) {
var self = this;
this.value = ko.observable(item.value);
this.child = new Child(item.child, self);
}
var Child = function (item, parent) {
var self = this;
this.parent = parent;
this.value = ko.observable(item.value);
}
and HTML markup will look like
This is <b><span data-bind="text: value"></span></b>
<div data-bind="with: child">
This is <b><span data-bind="text: value"></span></b>
<br/>
This is <b><span data-bind="text: parent.value"></span></b> of <b><span data-bind="text: value"></span></b>
</div>
JSFIDDLE
To prevent big amount of code mapping plugin could be used and it will be more knockout way
var Parent = function (item) {
var self = this;
var map = {
'child': {
update: function(options) {
return new Child(options.data, self);
}
}
}
ko.mapping.fromJS(item, map, self);
}
var Child = function (item, parent) {
var self = this;
this.parent = parent;
ko.mapping.fromJS(item, null, self);
}
JSFIDDLE
And the most knockout way - it to create custom binding that will controls descendant bindings. In this way you can extend child context with extra properties.
ko.bindingHandlers.withParent = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// Make a modified binding context, with a extra properties, and apply it to descendant elements
ko.mapping.fromJS({
parent: bindingContext.$rawData
}, null,valueAccessor());
var childBindingContext = bindingContext.createChildContext(valueAccessor, null, null);
ko.applyBindingsToDescendants(childBindingContext, element);
// Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
return { controlsDescendantBindings: true };
}
};
Model
var Parent = function (item) {
var self = this;
var map = {
'child': {
update: function(options) {
return new Child(options.data);
}
}
}
ko.mapping.fromJS(item, map, self);
}
var Child = function (item, parent) {
var self = this;
ko.mapping.fromJS(item, null, self);
}
and HTML
This is <b><span data-bind="text: value"></span></b>
<div data-bind="withParent: child">
This is <b><span data-bind="text: value"></span></b>
<br/>
This is <b><span data-bind="text: parent.value"></span></b> of <b><span data-bind="text: value"></span></b>
<br/>
<input type="button" value="Test from code" data-bind="click: test"/>
</div>
JSFIDDLE
But personaly me not like this approach, because using together with 'with', 'foreach' or 'tempalate' bindings it could cause errors like
Message: You cannot apply bindings multiple times to the same element.

Knockout refresh viewModel using mapping plugin

I am trying refresh a small widget with knockout and the mapping plugin. Here is my code so far:
var AppViewModel = function (data, total, qty) {
var self = this;
self.Products = ko.mapping.fromJS(data, {}, this);
self.CartTotals = ko.observable(total);
self.TotalQty = ko.observable(qty);
};
var func = function(u) {
return $.ajax({ type: "POST", contentType: "application/json; charset=utf-8", data: "{}", dataType: "json", url: u });
},
getQtyTotal = function(b) {
var a = 0;
$.each(b.Table.Rows, function(c) {
a += parseInt(b.Table.Rows[c].Quantity) || 0;
});
return a;
};
$.when(func("/store/MiniCart.aspx/GetShoppingCartInfo"), func("/store/MiniCart.aspx/GetCartTotal")).done(function (jsonA, jsonB) {
var ds = $.parseJSON(jsonA[0].d), ds2 = $.parseJSON(jsonB[0].d), qtyTotal = getQtyTotal(ds);
ko.applyBindings(new AppViewModel(ds, ds2, qtyTotal));
});
<div class="cartDropDownProductItemWrapper" data-bind="foreach: Products.Table.Rows">
<div class="cartDropDownProductItem">
<div class="cartDropDownProductImg">
<img id="cart_details_rpt_prod_image_0" style="height: 71px; width: 55px;" data-bind="attr: { src: ProductImageURL }">
</div>
<div class="cartDropDownProductDesc">
<h6><a data-bind="text: ModelName, attr: { href: ProductLink }"></a></h6>
<div class="cartDropDownProductDescInner">
<div class="cartDropDownColor"> COLOR
<strong><span data-bind="text:ColorName"></span></strong>
</div>
<div class="cartDropDownSize"> SIZE
<strong><span data-bind="text: SizeName"></span></strong>
</div>
<div class="cartDropDownSize"> QTY
<strong><span data-bind="text: Quantity"></span></strong>
</div>
<div class="cartDropDownPrice"> PRICE
<strong><span data-bind="text: UnitCost().toFixed(2)"></span></strong>
</div>
<div class="cartDropDownRemove">
<a href="javascript:void(0);" class="remove" onclick="removeItem('v3BuhngpE4c=')">
<img src="/images/layout/icons/remove.gif" alt="Remove Item">
</a>
</div>
</div>
</div>
<div class="clear"></div>
</div>
<!-- end fo reach -->
<div class="clear"></div>
<div class="cartDropDownButtons clearfix">
<ul class="clearfix">
<li class="countItems"><span data-bind="text: TotalQty"></span> Items</li>
<li class="subTotal" id="subTotal">SUBTOTAL: $<span data-bind="text: CartTotals().toFixed(2)"></span></li>
</ul>
</div>
It renders fine intially but when I try to rebind on a jQuery click event and call:
ko.applyBindings(new AppViewModel(ds, ds2, qtyTotal));
It duplicates the data.
If you start off by creating an empty viewModel... which takes no arguments via it's constructor, like so:
function ViewModel()
{
var self = this;
}
var viewModel = new ViewModel();
...Then you can reference it by name to load in your data using ko.mapping, like this:
ko.mapping.fromJS({ "PropertyName": plainJsObject }, {}, viewModel);
What this does is runs the ko.mapping magic on plainJsObject, and stuffs the result into a property (in this case, called PropertyName) on your viewModel object.
The part you would particularly care about is this:
If you want to refresh the data located in the viewModel.PropertyName with fresh data from the server... you just call the exact same method, and it updates that same property on your viewModel. Just call this same thing again, with new values in your plainJsObject:
ko.mapping.fromJS({ "PropertyName": plainJsObject }, {}, viewModel);
Since you have (I assume) already performed your ko.applyBindings() at some point in your code, this line above will immediately update your view.
You should only need to perform the binding once so just keep that outside of any event driven function calls.
ko.applyBindings(new AppViewModel(ds, ds2, qtyTotal));
I think you are over complicating your code in the above example. I would just return set of Product objects which contains properties for description, unit price, quantity etc and then to calculate the total, use a ko computed variable which will update automatically if a user tries to increase/decrease quantity.
function Product(item) {
var self = this;
self.description = ko.observable(item.description);
self.quantity = ko.observable(item.quantity);
self.unitPrice = ko.observable(item.unitPrice);
self.total = ko.computed(function() {
return self.quantity() * self.unitPrice();
});
}
function AppViewModel() {
var self = this;
self.products = ko.observableArray([]);
self.overvallTotal = ko.computed(function() {
var total = 0;
for (var i = 0; i < self.products().length; i++) {
total += self.products()[i].total;
}
return total;
});
self.removeProduct = function(item) { self.products.remove(item) };
// Load initial state from server, convert it to Product instances
$.getJSON("/store/MiniCart.aspx/GetShoppingCartInfo", function(allData) {
var mappedProducts = $.map(allData, function(item) { return new Product(item) });
self.products(mappedProducts);
});
}
ko.applyBindings(new AppViewModel());
You will have to adjust your server side json results to work with this.

Categories

Resources