I have an array doorsForSitewhere each item will have a Doors Array and each door will have a Schedules array.
It looks like :
var scheduleList = new[]
{
new { ScheduleId = "Schedule1",ScheduleName = "Always On" },
new { ScheduleId = "Schedule2",ScheduleName = "Never On"}
};
var doorsForSite = new[]
{
new { ControllerId ="controller1",ControllerName="Eagle",IsChecked = "false",
Doors = new[]
{
new { DoorId="Door1",DoorName="DoorOne",Schedules = scheduleList},
new { DoorId = "Door2", DoorName = "DoorTwo",Schedules = scheduleList}
}
},
new { ControllerId ="controller2",ControllerName="NetAxis",IsChecked = "false",
Doors = new[]
{
new { DoorId= "Door3",DoorName="DoorThree",Schedules = scheduleList},
new { DoorId = "Door4", DoorName = "DoorFour",Schedules = scheduleList},
new { DoorId = "Door5", DoorName = "DoorFive",Schedules = scheduleList}
}
}
};
Now in UI ..
<ul class="accgrouptableaccordian scroll-x scroll-y">
<!-- ko foreach: $data.AddModel.ControllerDoors -->
<li>
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<span>
<span>
<span class="ispicon ispicon_accordian_droparrow">
</span>
<span class="title" style="line-height:20px;" data-bind="text: ControllerName() + ' - ' + Doors().length + ' Doors'">
</span>
</span>
<span>
</span>
</span>
</h4>
</div>
<div class="panel-collapse">
<div class="panel-body">
<div class="table-responsive panel-body">
<table class="table">
<tbody data-bind="foreach:Doors">
<tr>
<td>
<div>
<span data-bind="text:DoorId"></span>
</div>
</td>
<td class="col-md-4">
<select name="drpSchedulesAccess" class="form-control drpSchedulesAccess" data-bind="options:$data.Schedules,
optionsText: 'ScheduleName',
optionsValue: 'ScheduleId',
value: $data.ScheduleId"></select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</li>
<!-- /ko -->
</ul>
But in Viewmodel I only want to get the values of the checked Door's selected Schedule in the dropdown.
But that's not happening.
I did
ko.utils.arrayForEach(this.AddModel.ControllerDoors(), function (item) {
ko.utils.arrayForEach(item.Doors(), function (item) {
doorsSelected.push(item);
});
});
var doors = ko.utils.arrayFilter(doorsSelected, function (item) {
return item.IsChecked == true;
});
var doorIds = ko.utils.arrayMap(doors, function (door) {
if (jQuery.inArray(door.DoorId, doorIds) == -1) {
return door.DoorId;
}
});
ko.utils.arrayForEach(doors, function (item) {
debugger;
ko.utils.arrayForEach(item.Schedules, function (item) {
$('.drpSchedulesAccess option:selected').each(function (i) {
schedulesSelected.push($(this).val());
});
});
});
and I checked 3 doors with 3 selected Schedule from dropdown.
But I am getting a schedule array length of 30.
Why is it so ?
you might need to slightly tweek your code & most importantly do everthing is knockout-ish way .
viewModel:
var ViewModel = function() {
var self = this;
self.ControllerDoors = ko.observableArray(ko.mapping.fromJS(doorsForSite)()); // mapping to convert everything to observable
self.check = function() {
var doorsSelected = [];
ko.utils.arrayForEach(self.ControllerDoors(), function(item) {
//you can add condition here based on controllerName IsChecked & reduce looping
ko.utils.arrayForEach(item.Doors(), function(item) {
if (item.IsChecked())
doorsSelected.push(item);
});
});
console.log(doorsSelected);
}
};
catch the working demo here and check console window to find Results .
Things to note :
() : used to read observable (you can find it's usage on current view)
The way you look up the selected options in your view is not "the knockout way". In these lines:
$('.drpSchedulesAccess option:selected').each(function (i) {
schedulesSelected.push($(this).val());
});
you're using the DOM to store your ViewModel state, which isn't bad per se, but not how knockout works.
When using knockout, your ViewModel should contain all data and communicate with the DOM through data-binds.
You haven't shown us a snippet of working code, so I'll try to recreate parts of your UI to show how it should work.
var DoorViewModel = function(schedules, door) {
return Object.assign({
checked: ko.observable(true),
selectedSchedule: ko.observable(),
schedules: schedules
}, door);
};
var ViewModel = function(doors, schedules) {
this.doors = ko.observableArray(doors.map(DoorViewModel.bind(null, schedules)));
this.selectedScheduleIds = ko.computed(function() {
return this.doors()
.filter(function(door) { return door.checked(); })
.map(function(door) {
return door.selectedSchedule()
? door.selectedSchedule().ScheduleId
: null;
});
}, this);
};
var schedules = [{
'ScheduleId': "Schedule1",
'ScheduleName': "Always On"
}, {
'ScheduleId': "Schedule2",
'ScheduleName': "Never On"
}];
var doors = [{
'DoorId': "Door1",
'DoorName': "DoorOne"
}, {
'DoorId': "Door2",
'DoorName': "DoorTwo"
}];
ko.applyBindings(new ViewModel(doors, schedules));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: doors">
<li>
<input type="checkbox" data-bind="checked: checked">
<span data-bind="text: DoorName"></span>
<select data-bind="options: schedules,
value: selectedSchedule,
optionsText: 'ScheduleName'"></select>
</li>
</ul>
<pre data-bind="text: JSON.stringify(selectedScheduleIds(), null, 2)"></pre>
Related
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);
}
});
I have a script that produces an array of forms, with each form affecting the available options for the next form. The awesome martin booth solved the problem of getting the displayed values to update as new forms are added.
however, I have an observable array (defaultSampleRates) that sits outside the forms array, and for the life of me I can't get the form to push items into that array. I've tried declaring it in a dozen different places in a dozen different ways, but it just won't stick.
basically I need the 'Default sample rate' drop-down to show the sample rates that have been selected in the form above (the user must only be able to choose a default sample rate from a displayed one, rather than from the full list).
Any tips much helpo brain pain. fiddle here: http://jsfiddle.net/3lliot/9vsa4hh7/
html:
<body>
<div style="float:left; width:60%">
<div data-bind="foreach: forms">
<div style="float:left; margin-right:20px"> <span>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: sampleRates, value: selectedSampleRate"></select>
</p>
</span>
</div>
</div>
<div style="float:left; clear:both; margin-bottom:20px">
<hr/>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<p>Default sample rate:
<select data-bind="options: defaultSampleRates, value: selectedDefaultSampleRate"></select>
</p>
</div>
</div>
<div style="float:right; width:38%; overflow:scroll; border-left:thin; border-left-style:solid; border-left-color:#dfdfdf;padding-left: 1%"> <span class="code"><audio></span>
<ul data-bind="foreach: forms">
<li>
<!-- render the json --> <span class="code"> <srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> </srate></span>
</li>
</ul> <span class="code"> <default_srate><span data-bind="text: selectedDefaultSampleRate"></span></default_srate></span>
<br/><span class="code"></audio></span>
</div>
</body>
js:
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
//window.onload = startKnockout;
window.onload = startKnockout;
var formNum;
var i = -1;
var selectedSampleRates = [];
function Form(allSampleRates, forms) {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.formNum = ko.observable();
self.sampleRates = ko.computed(function () {
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function (sampleRate) {
return !ko.utils.arrayFirst(formsValue, function (form) {
if (form != self) {
if (form.selectedSampleRate() === sampleRate) {
if (selectedSampleRates.indexOf(sampleRate) === -1) {
selectedSampleRates.push(sampleRate);
}
}
return form.selectedSampleRate() === sampleRate;
} else {
return form != self;
}
});
});
return availableSampleRates;
});
// count how many srate elements there are
i++;
self.formNum = i;
}
var Vm = function () {
var self = this;
var item = 0,
allSampleRates = ['192000', '176400', '96000', '88200', '48000', '44100'];
// declare observables for options outside the srate elements
self.selectedDefaultSampleRate = ko.observable();
// add remove forms stuff
self.forms = ko.observableArray([]);
self.forms.push(new Form(allSampleRates, self.forms));
item++;
self.addForm = function () {
if (i < 5) {
self.forms.push(new Form(allSampleRates, self.forms));
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function () {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
// define arrays for options outside srate elements
self.defaultSampleRates = ko.observableArray([]);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
You can make use of selectedOptions binding to add defaultSample rate.
I changed select sampleRates code to this
<select data-bind="options: sampleRates, value: selectedSampleRate, selectedOptions: $root.defaultSampleRates"></select>
Notice selectedOptions binding there..
Should work as per your need.
Updated Fiddle Demo here : http://jsfiddle.net/rahulrulez/9vsa4hh7/3/
I hope that's what you wanted.
I have a little knockout form which I am able to duplicate using a button to allow multiple sets of data to be provided.
However, each value for the 'sample rate' select can only be used once. For example, the first form by default is set to 192000. So when I click 'add srate element' the form that is generated should not include 192000 as an option in the Sample rate drop-down.
Obviously if the Sample rate for the first form is set to something else, that value should be removed for the second form, and so on.
How do I remove the already-selected Sample rate from the array when a new form is added, so that it is not available in further copies of the form? It is even possible, given the structure of the view model?
There's a fiddle here: http://jsfiddle.net/3lliot/x3cg131g/
There's a bit of logic included already to prevent more than 6 forms.
Any tips will be appreciated ...
Html code:
<body>
<ul data-bind="foreach: forms">
<li>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: $parent.sampleRate, value: selectedSampleRate"></select>
</p>
<p>TDM channels per line:
<select data-bind="options: tdmChans, value: selectedTdmchan"></select>
</p>
</li>
</ul>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<hr/>
<ul data-bind="foreach: forms">
<li>
<!-- render the json -->
<p class="code"><srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> <tdm_chan><span data-bind="text: selectedTdmchan"></span></tdm_chan>
<br/>
</p>
</li>
</ul>
</body>
JS code:
window.onload = startKnockout;
var formNum;
var i = -1;
function Form() {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.selectedTdmchan = ko.observable();
self.formNum = ko.observable();
// Define controls
self.tdmChans = ko.computed(function() {
if (self.selectedSampleRate() == 44100 || self.selectedSampleRate() == 48000) {
return ['2', '4', '8', '16'];
} else if (self.selectedSampleRate() == 88200 || self.selectedSampleRate() == 96000) {
return ['2', '4', '8'];
} else if (self.selectedSampleRate() == 176400 || self.selectedSampleRate() == 192000) {
return ['2', '4'];
} else {
// do nothing
}
}, self);
i++;
self.formNum = i;
}
var Vm = function() {
var self = this;
var item = 0;
self.forms = ko.observableArray([]);
self.forms.push(new Form());
item++;
self.addForm = function() {
if (i < 5) {
self.forms.push(new Form());
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function() {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
self.sampleRate = ko.observableArray(['192000', '176400', '96000', '88200', '48000', '44100']);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
Take a look at this:
http://jsfiddle.net/martinbooth/x3cg131g/1/
importantly, compute the available samples rates based on what has been selected in other forms:
self.sampleRates = ko.computed(function(){
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function(sampleRate){
return !ko.utils.arrayFirst(formsValue, function(form){
return form != self && form.selectedSampleRate() === sampleRate;
});
});
return availableSampleRates;
});
Here is a sample for the problem, in this case someone selects toppings for a pizza (my real world problem is analogue to that):
http://jsfiddle.net/csabatoth/aUH2C/4/
HTML:
<h2>Pizza toppings (<span data-bind="text: toppings().length"></span>)</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody data-bind="foreach: toppings">
<tr>
<td><select data-bind="options: $root.availableToppings(), value: name, optionsText: 'name'"></select></td>
<td><input data-bind="value: num" /></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-primary" data-bind="click: addTopping, enable: toppings().length < 5">Add another topping</button>
JS:
// Class to represent a possible topping
function Topping(name) {
var self = this;
self.name = name;
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(topping, num) {
var self = this;
self.topping = ko.observable(topping);
self.num = ko.observable(num);
self.toppingName = ko.computed(function() {
return self.topping().name;
});
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
// Editable data
self.toppings = ko.observableArray([
new ToppingRow(self.availableToppings()[0], 1)
]);
// Operations
self.addTopping = function() {
self.toppings.push(new ToppingRow(self.availableToppings()[0], 1));
}
self.removeTopping = function(topp) { self.toppings.remove(topp) }
}
ko.applyBindings(new ToppingsViewModel());
What I would like is: when the user selects a topping, that option should disappear from the popup list. Once the user removes the topping, it should reappear in the popup. In other words: I don't want the user to add the same topping more than once. How to do that?
(Or now I think if I should approach this in a totally different way and would have a list with the toppings on the left, and the user could drag&drop to the right destination list from there...). In the real world example the number of "toppings" would be maybe some dozen I think.
You could simplify this slightly by having a topping selector with 1 drop down. Then when you click add it inserts the currently selected item to a selected toppings section and then removes the option from the available list.
If you are feeling clever you could also bind the drop-down to a computed collection of items that does not include already selected items. (underscore.js will also help with this immensely).
(Fiddle)
JS
// Class to represent a possible topping
function Topping(name) {
var self = this;
self.name = name;
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(topping, num) {
var self = this;
self.topping = topping;
self.num = ko.observable(num);
self.toppingName = ko.computed(function() {
return self.topping.name;
});
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.allToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
self.selectedToppings = ko.observableArray([]);
self.availableToppings = ko.computed(function(){
return _.reject(self.allToppings(), function(topping) {
return _.contains(_.pluck(self.selectedToppings(), 'topping'), topping);
})
});
self.currentlySelectedTopping = ko.observable();
self.currentlySelectedToppingNumber = ko.observable(1);
// Operations
self.addTopping = function() {
self.selectedToppings.push(new ToppingRow(self.currentlySelectedTopping(), self.currentlySelectedToppingNumber()));
}
//self.removeTopping = function(topp) { self.toppings.remove(topp) }
}
ko.applyBindings(new ToppingsViewModel());
HTML
<h2>Pizza toppings</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody>
<tr>
<td><select data-bind="options: availableToppings, value: currentlySelectedTopping, optionsText: 'name'"></select></td>
<td><input data-bind="value: currentlySelectedToppingNumber" /></td>
<td>Remove</td>
</tr>
<!-- ko foreach: selectedToppings -->
<tr><td data-bind="text: toppingName"></td><td data-bind="text: num"></td></tr>
<!-- /ko -->
</tbody>
</table>
<button type="button" class="btn btn-primary" data-bind="click: addTopping, enable: availableToppings().length">Add another topping</button>
I dont why you are making simple things difficult. Here is some modified version.
function Topping(name) {
var self = this;
self.name = name;
self.active = ko.observable(false)
self.toggle = function () {
self.active(!self.active())
}
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(name, num) {
var self = this;
self.name = name;
self.num = num;
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
self.list = ko.observableArray()
self.num = ko.observable(1)
self.selected = ko.observable()
// Operations
self.addTopping = function() {
self.list.push(new ToppingRow(self.selected(),self.num()));
self.setAvailableToppings(self.selected())
}
self.removeTopping = function(item) {
self.list.remove(item)
self.setAvailableToppings(item.name)
}
self.setAvailableToppings = function (name) {
var items = []
ko.utils.arrayForEach(self.availableToppings(),function (item) {
if(item.name == name){
item.toggle()
}
items.push(item)
})
self.availableToppings([])
self.availableToppings(items)
var selected = ko.utils.arrayFirst(self.availableToppings(),function (item) {
return item.active() == false
})
if(selected){
self.selected(selected.name)
}
}
self.setOptionDisable = function(option, item) {
ko.applyBindingsToNode(option, {disable: item.active()}, item);
}
}
$(document).ready(function(){
ko.applyBindings(new ToppingsViewModel());
})
And view
<h2>Pizza toppings (<span data-bind="text: list().length"></span>)</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody data-bind="foreach: list">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text: num"></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<br clear="all"/>
<select data-bind="
options: $root.availableToppings(),
value: $root.selected,
optionsText: 'name',
optionsValue : 'name',
optionsAfterRender: $root.setOptionDisable,
enable: list().length < 5
">
</select>
<input data-bind="value: $root.num,enable: list().length < 5" />
<br clear="all"/>
<button type="button" class="btn btn-primary"
data-bind="
click: addTopping,
enable: list().length < 5
">Add another topping</button>
Fiddle Demo
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.