binding multi dropdown list in knockout.js - javascript

I have a multi dropdown list and I need to do the following:
1. Make sure that when selecting value in one dropdown list it won't appear in the others (couldn't find a proper solution here).
2. When selecting the value "Text" a text field (<input>) will apear instead of the Yes/no dropdown.
3. "Choose option" will appear only for the first row (still working on it).
4. Make sure that if "Text" is selected, it always will be on the top (still working on it).
JSFiddle
HTML:
<div class='liveExample'>
<table width='100%'>
<tbody data-bind='foreach: lines'>
<tr>
<td>
Choose option:
</td>
<td>
<select data-bind='options: filters, optionsText: "name", value: filterValue'> </select>
</td>
<td data-bind="with: filterValue">
<select data-bind='options: filterValues, optionsText: "name", value: "name"'> </select>
</td>
<td>
<button href='#' data-bind='click: $parent.removeFilter'>Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind='click: addFilter'>Add Choice</button>
JAVASCRIPT:
var CartLine = function() {
var self = this;
self.filter = ko.observable();
self.filterValue = ko.observable();
// Whenever the filter changes, reset the value selection
self.filter.subscribe(function() {
self.filterValue(undefined);
});
};
var Cart = function() {
// Stores an array of filters
var self = this;
self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
// Operations
self.addFilter = function() { self.lines.push(new CartLine()) };
self.removeFilter = function(line) { self.lines.remove(line) };
};
ko.applyBindings(new Cart());
I will appeaciate your assist here! Mainly for the first problem.
Thanks!
Mike

If you want to limit the options based on the options that are already selected in the UI, you'll need to make sure every cartLine gets its own array of filters. Let's pass it in the constructor like so:
var CartLine = function(availableFilters) {
var self = this;
self.availableFilters = availableFilters;
// Other code
// ...
};
You'll have to use this new viewmodel property instead of your global filters array:
<td>
<select data-bind='options: availableFilters,
optionsText: "name",
value: filterValue'> </select>
</td>
Now, we'll have to find out which filters are still available when creating a new cartLine instance. Cart manages all the lines, and has an addFilter function.
self.addFilter = function() {
var availableFilters = filters.filter(function(filter) {
return !self.lines().some(function(cartLine) {
var currentFilterValue = cartLine.filterValue();
return currentFilterValue &&
currentFilterValue.name === filter.name;
});
});
self.lines.push(new CartLine(availableFilters))
};
The new CartLine instance gets only the filter that aren't yet used in any other line. (Note: if you want to use Array.prototype.some in older browsers, you might need a polyfill)
The only thing that remains is more of an UX decision than a "coding decision": do you want users to be able to change previous "Choices" after having added a new one? If this is the case, you'll need to create computed availableFilters arrays rather than ordinary ones.
Here's a forked fiddle that contains the code I posted above: http://jsfiddle.net/ztwcqL69/ Note that you can create doubled choices, because choices remain editable after adding new ones. If you comment what the desired behavior would be, I can help you figure out how to do so. This might require some more drastic changes... The solution I provided is more of a pointer in the right direction.
Edit: I felt bad for not offering a final solution, so here's another approach:
If you want to update the availableFilters retrospectively, you can do so like this:
CartLines get a reference to their siblings (the other cart lines) and create a subscription to any changes via a ko.computed that uses siblings and their filterValue:
var CartLine = function(siblings) {
var self = this;
self.availableFilters = ko.computed(function() {
return filters.filter(function(filter) {
return !siblings()
.filter(function(cartLine) { return cartLine !== self })
.some(function(cartLine) {
var currentFilterValue = cartLine.filterValue();
return currentFilterValue &&
currentFilterValue.name === filter.name;
});
});
});
// Other code...
};
Create new cart lines like so: self.lines.push(new CartLine(self.lines)). Initiate with an empty array and push the first CartLine afterwards by using addFilter.
Concerning point 2: You can create a computed observable that sorts based on filterValue:
self.sortedLines = ko.computed(function() {
return self.lines().sort(function(lineA, lineB) {
if (lineA.filterValue() && lineA.filterValue().name === "Text") return -1;
if (lineB.filterValue() && lineB.filterValue().name === "Text") return 1;
return 0;
});
});
Point 3: Move it outside the foreach.
Point 4: Use an if binding:
<td data-bind="with: filterValue">
<!-- ko if: name === "Text" -->
<input type="text">
<!-- /ko -->
<!-- ko ifnot: name === "Text" -->
<select data-bind='options: filterValues, optionsText: "name", value: "name"'> </select>
<!-- /ko -->
<td>
Updated fiddle that contains this code: http://jsfiddle.net/z22m1798/

Related

KoJs: bind a dynamic number of text boxes to elements of an array

I have a front-end which allows for adding and removing of text boxes suing the foreach binding. A text box looks something like this
<div id="dynamic-filters" data-bind="foreach: filterList">
<p>
<input type="text" data-bind="textInput: $parent.values[$index()], autoComplete: { options: $parent.options}, attr: { id : 'nameInput_' + $index() }"/>
</p>
</div>
What I want to do, as shown in the code above is to bind each of these dynamically generated text boxes to an element in the array using the $index() context provided by knockout.js
However it doesn't work for me, my self.values=ko.observableArray([]) doesn't change when the text boxes change.
My question is, if I want to have a way to bind these dynamically generated text boxes, is this the right way to do it? If it is how do I fix it? If it's not, what should I do instead?
Thanks guys!
EDIT 1
the values array is an observable so I thought I should unwrap it before use. I changed the code to
<input type="text" data-bind="textInput: $parent.values()[$index()], autoComplete: { options: $parent.options}, attr: { id : 'nameInput_' + $index() }"/>
This works in a limited way. When I add or change the content of text boxes, the array changes accordingly. However when I delete an element it fails in two ways:
If I delete the last item, the array simply doesn't change
If I delete an item in between, everything is shifted back
I suppose I have to add a function that changes the text-input value before destroying the text box itself.
Any help or advice on how to do this?
I would suggest taking the array of values and mapping it to some kind of model first, then dumping it into the filterList ko.observableArray. It can be as complex or as simple as need be.
That way you have direct access to those properties at the ko foreach: level instead of having to do the goofy index access.
I've added a simple knockout component example as well to show you what can be achieved.
var PageModel = function() {
var self = this;
var someArrayOfValues = [{label: 'label-1', value: 1},{label: 'label-2', value: 2},{label: 'label-3', value: 3},{label: 'label-4', value: 4}];
this.SimpleInputs = ko.observableArray(_.map(someArrayOfValues, function(data){
return new SimpleInputModel(data);
}));
this.AddSimpleInput = function(){
self.SimpleInputs.push(new SimpleInputModel({value:'new val', label:'new label'}));
};
this.RemoveSimpleInput = function(obj){
self.SimpleInputs.remove(obj);
}
}
var SimpleInputModel = function(r) {
this.Value = ko.observable(r.value);
this.Label = r.label;
};
var SimpleInputComponent = function(params){
this.Id = makeid();
this.Label = params.label;
this.Value = params.value;
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
}
ko.components.register('input-component', {
viewModel: SimpleInputComponent,
template: '<label data-bind="text: Label, attr: {for: Id}"></label><input type="text" data-bind="textInput: Value, attr: {id: Id}" />'
})
window.model = new PageModel();
ko.applyBindings(model);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko if: SimpleInputs -->
<h3>Simple Inputs</h3>
<!-- ko foreach: SimpleInputs -->
<input-component params="value: Value, label: Label"></input-component>
<button data-bind="click: $parent.RemoveSimpleInput">X</button>
<br>
<!-- /ko -->
<!-- /ko -->
<button data-bind="click: AddSimpleInput">Add Input</button>
EDIT (7/16/2020):
Mind explaining this without requiring lodash? I literally googled "how to lodash map using plain javascript". Excellent answer otherwise! – CarComp
In this scenario the lodash _.map method could be overkill unless you are executing the script in an environment that does not have native support for the vanilla array map method. If you have support for the vanilla method, go ahead and use that. The map method essentially iterates over each array using the method it is handed to return a transformed array of the original items. Implementation of vanilla code would look like so.
this.SimpleInputs = ko.observableArray(someArrayOfValues.map(function(data) {
return new SimpleInputModel(data);
}));
Here we are taking the values of someArrayOfValues and telling it to use each item to build a new SimpleInputModel and return it using that item data. [SimpleInputModel, SimpleInputModel, SimpleInputModel, SimpleInputModel] is what the new array turns into after mapping. Each of these items has all the functionality described in the SimpleInputModel class, Value and Label.
So with the new array you could, if you wanted, access the values like this as well self.SimpleInputs[0].Value() or self.SimpleInputs[0].Label
Hope that helps to clarify.

Remove items from Knockout observable array

I have the below structure for knockout model. It contains an observable array which in turn contains an object.
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: ko.observable(),
itemnumber: ko.observable(),
itemDescription: ko.observable()
});
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer("test");
self.newItem().itemDescription("data");
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
First issue:Through this script I am entering a new itemnumber in the textbox and then clicking on add item to have the new item with the itemnumber from the textbox added to the observable array but when I change the item number and hit add it changes all the itemnumber inside the array. How can i have unique data inside the array.
Second issue: I need to remove the specific items from the array but it's not deleting it. Can someone please tell me how I can delete items from the observable array based on say the itemnumber property.
<input type="text" data-bind="value: newItem().itemnumber"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
I have created this fiddle for quick view of the issue. Just started learning knockout so any help is appreciated.
http://jsfiddle.net/N3JaW/138/
Try the following for adding new item, which will solve your first issue:-
HTML code
<input type="text" id="textBox" data-bind="value : textBoxVal"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
JS code:-
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: "",
itemnumber: "",
itemDescription: ""
});
self.textBoxVal = ko.observable();
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer= "test";
self.newItem().itemDescription= "data";
self.newItem().itemnumber = self.textBoxVal();
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
$(document).ready(function() {ko.applyBindings(new ViewModel()); });
Your first issue was because, each time you are trying to add a new item, you were changing the value of itemNumber, which is an observable.
Observable value will be changed every where it is binded, when it's value is changed.
Instead you need to create new object and do push into the observableArray.
Refer doc to know more about observableArray.
For your second problem change removeItem as given below:-
self.removeItem = function(data) {
var dtIndex = self.AllItems.indexOf(data); //Get the index of the object you want to remove.
self.AllItems.splice(dtIndex, 1); //Then do splice
};
You can refer the above doc, to know how to use splice.
EDIT based on the suggestion in the comment :-
For working code of edited answer click here.
Hope this will solve your problem.

Knockout: changing options in select list without clearing value in view-model

I have a Knockout JS based problem with some cascading options lists and switching out the underlying "active" object that they relate to.
I have created a jsfiddle to demonstrate the problem.
I have a UI in which the users are editing a main "header" record and adding/removing/editing child records. There is a central area for editing child records. The idea is to click a child record in a table and this become the record being edited in the middle area.
The problem I have is due to the fact that the list of things in the second drop-down changes depending on the first. This is fine until the active record changes. If the category changes because the active record changes then the list of "things" also changes. At this point, the chosen "thing" (2nd drop-down) on the new active child record is cleared.
I'm assuming the value on the new active record changes, but is cleared because it doesn't appear in the old list (if the category changed). The list of items themselves is then changed (including the appropriate value), but the value has already gone from the view model by this point.
(I realize that's quite a long-winded explanation, hopefuly the jsfiddle makes it clear)
How can I change the list of items in a drop-down AND the selected value in the view model without losing the selected value along the way?
HTML:
<label>Some header field</label>
<input type="text" id="txtSomeHeaderField" data-bind="value: HeaderField" />
<fieldset>
<legend>Active Child Record</legend>
<label>Category</label>
<select id="ddlCategory" data-bind="options: categories, value: ActiveChildRecord().Category, optionsCaption:'Select category...'" ></select>
<label>Things</label>
<select id="ddlThings" data-bind="options: ThingsList, value: ActiveChildRecord().Favorite, optionsCaption:'Select favorite thing...'" ></select>
</fieldset>
<button data-bind="click: AddChildRecord" >Add a child record</button>
<table id="tblChildRecords" border>
<thead>
<tr>
<th>Category</th>
<th>Favorite Thing</th>
</tr>
</thead>
<tbody data-bind="foreach: ChildRecords">
<tr data-bind="click: ChildRecordClicked, css: {activeRow: ActiveChildRecord() === $data}" >
<td data-bind="text: Category"></td>
<td data-bind="text: Favorite"></td>
</tr>
</tbody>
</table>
<p>Steps to reproduce problem:</p>
<ol>
<li>Click "Add child record"</li>
<li>Click on that row to make it the "active" record</li>
<li>Choose category "Pets" and thing "Dog"</li>
<li>Click "Add child record"</li>
<li>Click on the new row to make it the "active" record</li>
<li>Choose category "Colours" and thing "Blue"</li>
<li>Now click back on the first row... <strong>"Dog" disappears!</strong></li>
</ol>
Javascript:
var categories = ["Pets", "Colours", "Foods"];
var MyViewModel = function(){
var _this = this;
this.HeaderField = ko.observable("this value is unimportant");
this.ChildRecords = ko.observableArray([]);
this.ActiveChildRecord = ko.observable({ Category: ko.observable("-"), Favorite: ko.observable("-")});
this.ThingsList = ko.observableArray();
this.AddChildRecord = function(){
_this.ChildRecords.push({ Category: ko.observable("-"), Favorite: ko.observable("-")});
}
this.ChildRecordClicked = function(childRecord){
_this.ActiveChildRecord(childRecord);
}
this.RefreshThingsList = ko.computed(function(){
var strActiveCategory = _this.ActiveChildRecord().Category();
switch(strActiveCategory){
case "Pets": _this.ThingsList(["Dog", "Cat", "Fish"]); break;
case "Colours": _this.ThingsList(["Red", "Green", "Blue", "Orange"]); break;
case "Foods": _this.ThingsList(["Apple", "Orange", "Strawberry"]); break;
}
});
}
ko.applyBindings(MyViewModel);
Knockout's valueAllowUnset binding might be a cleaner approach.
http://jsfiddle.net/5mpwx501/8/
<select id="ddlCategory" data-bind="options: categories, value: ActiveChildRecord().Category, valueAllowUnset: true, optionsCaption:'Select category...'" ></select>
<select id="ddlThings" data-bind="options: ThingsList, value: ActiveChildRecord().Favorite, valueAllowUnset: true, optionsCaption:'Select favorite thing...'" ></select>
#super cool is 100% correct, but the reason it is undefined is that the ActiveChildRecord changes when you click the pet row, but this computed function has not yet executed so you've got a small timeframe where Dog is the Favorite, but the options are still Colours. Since Dog is not an option, the dropdown will set the Favorite property on your ActiveChildRecord to undefined.
I would use the valueAllowUnset binding. Basically it tells the dropdown that if there is no match, don't set my value to undefined, but rather wait because the options might be updating.
A nice side effect of using this binding is that when you add a new child record it doesn't copy the previous row. It naturally resets the selection for you.
I've used a totally different approach, using subscriptions to updates lists and values, and an special observable to hold the edited record.
<fieldset>
<legend>Active Child Record</legend>
<label>Category</label>
<select id="ddlCategory"
data-bind="options: categories, value: category,
optionsCaption:'Select category...'" ></select>
<label>Things</label>
<select id="ddlThings"
data-bind="options: things, value: thing,
optionsCaption:'Select favorite thing...'" ></select>
</fieldset>
<button data-bind="click: AddChildRecord" >Add a child record</button>
<table id="tblChildRecords" border>
<thead>
<tr>
<th>Category</th>
<th>Favorite Thing</th>
</tr>
</thead>
<tbody data-bind="foreach: childRecords">
<tr data-bind="click: ChildRecordClicked,
css: {activeRow: editedRecord() === $data}" >
<td data-bind="text: category"></td>
<td data-bind="text: thing"></td>
</tr>
</tbody>
</table>
JavaScript:
var categories = ["Pets", "Colours", "Foods"];
var MyViewModel = function(){
var _this = this;
this.categories = ko.observableArray(["Pets","Colours","Foods"]);
this.category = ko.observable();
this.category.subscribe(function(newCategory){
_this.refreshThings(newCategory);
if(editedRecord()) {
editedRecord().category(newCategory);
}
});
this.things = ko.observableArray([]);
this.thing = ko.observable();
_this.refreshThings = function(newCategory){
switch(newCategory){
case "Pets": _this.things(["Dog", "Cat", "Fish"]); break;
case "Colours": _this.things(["Red", "Green", "Blue", "Orange"]); break;
case "Foods": _this.things(["Apple", "Orange", "Strawberry"]); break;
}
};
this.thing.subscribe(function(newThing){
if(editedRecord()) {
editedRecord().thing(newThing);
}
});
this.childRecords = ko.observableArray([]);
this.editedRecord = ko.observable();
this.AddChildRecord = function(){
var newRecord = {
category: ko.observable(),
thing: ko.observable()
};
_this.childRecords.push(newRecord);
_this.editedRecord(newRecord);
_this.category('');
_this.thing('');
}
this.ChildRecordClicked = function(childRecord){
_this.editedRecord(null);
_this.category(childRecord.category())
_this.thing(childRecord.thing())
_this.editedRecord(childRecord);
}
}
ko.applyBindings(MyViewModel);
Several notes:
a new observable, named 'editedRecord' is used. This can hold the value of the currently edited record (either new, either selected by clicking it), or a null value, if nothing should be edited (this value is set in AddChildRecord and ChildrecordClicked, to vaoid changes while the lists are updated)
there is an array of categories, a category observable, and a subscription that updates the list of things as well as the category property of the edited record, if present
there is an array of things, a thing observable, and a subscription that updates the thing property of the edited record, if present
the addChildRecord, creates a new, empty record, and set it as the edited record. Besides initializes both lists of categories and things
the childRecordClick sets the clicked record as edited record
As you can see, with this technique the bindings remain very simple, and you have full control of what's ging on in each moment.
You can use techniques similar to this one to allow cancelling of the edition and things like that. In fact, I usually edit the record in a different place, and add it, or apply its changes, once the user accepts them, allowing him to cancel.
This is your modified fiddle.
Finally, if you want to keep the slashes on unedited records, make this change:
this.AddChildRecord = function(){
_this.editedRecord(null);
var newRecord = {
category: ko.observable("-"),
thing: ko.observable("-")
};
_this.childRecords.push(newRecord);
_this.category('');
_this.thing('');
_this.editedRecord(newRecord);
}
included in this version of the fiddle, but it would be better if you applied an style so that the table cell has a minimun height, and keep it empty, like in the previous version.
well i made a small modification in your fiddle which worked perfectly .
View Model:
this.RefreshThingsList = ko.computed(function(){
var store= ActiveChildRecord().Favorite();
var strActiveCategory = _this.ActiveChildRecord().Category();
switch(strActiveCategory){
case "Pets": _this.ThingsList(["Dog", "Cat", "Fish"]); break;
case "Colours": _this.ThingsList(["Red", "Green", "Blue", "Orange"]); break;
case "Foods": _this.ThingsList(["Apple", "Orange", "Strawberry"]); break;
}
alert(ActiveChildRecord().Favorite()); // debug here you get undefined on your step 7 so store the value upfront and use it .
ActiveChildRecord().Favorite(store);
});
working fiddle here
Just in case you looking for something other than this let us know .

knockout checked binding - only one checked

I have a list of Admins with a check box. I want to be able to select only one Admin.
HTML:
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="checkbox" data-bind="attr: { value: id }, checked: $root.selectedAdmin">
<span data-bind="text: name"/>
</td>
</tr>
</tbody
JS:
function Admin(id, name) {
this.id = id;
this.name = name;
}
var listOfAdmin = [
new Admin(10, 'Wendy'),
new Admin(20, 'Rishi'),
new Admin(30, 'Christian')];
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray()
};
ko.applyBindings(viewModel);
For Example if Admin id 10 is selected the other admins should be deselected.
Is that Possible to do with Knockout?
You should really use radio buttons if you only want to allow multiple selection.
However if you still want to use checkboxes then on solution would be to combine the checked and the click binding:
Use the checked to check only when the current id equal to the selectedAdmin property and use the click binding to set the selectedAdmin.
So you HTML should look like this:
<input type="checkbox" data-bind="attr: { value: id },
checked: $root.selectedAdmin() == id,
click: $parent.select.bind($parent)" />
And in your view model you just need to implement the select function:
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray(),
select: function(data) {
this.selectedAdmin(data.id);
return true;
}
};
Demo JSFiddle.
Notes:
the return true; at the end of the select function. This is required to trigger the browser default behavior in this case to check the checkbox.
the .bind($parent) is needed to set the this in the select function to be the "parent" viewModel object.

Using knockoutJS, how to bind list items to same view?

I am new to Knockout and I am building a Simple POC for using knockout to build SPA(Single Page Application).
What I want to do is to show "Business Units" when the app loads and on selection of a business unit show all "Front End Units" under that business unit and on selection of a front end unit, show all "Sales Segments" under that front end unit.
All this will happen in a single page using the same view and the viewmodel will bind the model based on selected business unit or front end unit.
The issue I am facing is that, I have 5 business units that get bound properly first on document ready, but on selection of business unit, the front end units get repeated 5 times each. In this case, I have 2 front end units and each is shown 5 times. Same issue on selection of front end unit.
You can see this issue mimicked in the following jsFiddle sample - jsFiddle Link
Let me know if you can't access the jsfiddle link. In this sample, I have used arrays, but in actual I will be getting the data through async call to the oData service.
This is the view HTML:
<div id="divbu">
<h4 data-bind="text: Heading"></h4>
<ul data-role="listview" data-inset="true" data-bind="foreach: Collection">
<li data-role="list-divider" data-bind="text: EntityName"></li>
<li>
<a href="#" data-bind="click: $root.fnNextLevel">
<table border="0">
<tr>
<td>
<label style="font-size: 12px;">Bus. Plan: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: BusinessPlan"></label>
</td>
<td>
<label style="font-size: 12px;">Forecast: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: Forecast"></label>
</td>
</tr>
<tr>
<td>
<label style="font-size: 12px;">Gross Sales: </label>
</td>
<td colspan="3">
<label style="font-size: 12px;" data-bind="text: GrossSales"></label>
</td>
</tr>
</table>
</a>
</li>
</ul>
</div>
This is the model and view model:
function CommonModel(model, viewType) {
var self = this;
if (viewType == 'BU') {
self.EntityName = model[0];
self.BusinessUnit = model[0];
self.BusinessPlan = model[1];
self.Forecast = model[2];
self.GrossSales = model[3];
} else if (viewType == 'FEU') {
self.EntityName = model[1];
self.BusinessUnit = model[0];
self.FrontEndUnit = model[1];
self.BusinessPlan = model[2];
self.Forecast = model[3];
self.GrossSales = model[4];
} else if (viewType == 'SS') {
self.EntityName = model[2];
self.BusinessPlan = model[3];
self.Forecast = model[4];
self.GrossSales = model[5];
}
}
function ShipmentReportsViewModel(results, viewType) {
var self = this;
self.Collection = ko.observableArray([]);
for (var i = 0; i < results.length; i++) {
self.Collection.push(new CommonModel(results[i], viewType));
}
if (viewType == 'BU') {
self.Heading = "Business Units";
self.fnNextLevel = function (businessUnit) {
FetchFrontEndUnits(businessUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'FEU') {
self.Heading = results[0][0];
self.fnNextLevel = function (frontEndUnit) {
FetchSalesSegments(frontEndUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'SS') {
self.fnNextLevel = function () {
alert('No activity zone');
};
self.Heading = results[0][0] + ' - ' + results[0][1];
self.Home = function () {
FetchBusinessUnits();
};
}
}
You can see the complete code in the jsFiddle link.
I have also tried this with multiple views and multiple view models, where I apply bindings by giving the element ID. In this case, one flow from business unit -> sales segment is fine, but when I click on home or back button and I do binding again to that element, I face the same issue. (home and back button features are not done in jsFiddle example).
Let me know if more details are required. I did look into lot of other links in stack overflow, but nothing addressing this particular problem.
Any help is deeply appreciated. Thanks in advance.
The problem here is that you call your ko.applybindings TWICE and there is a foreach binding that iterate within 5 items, therefore the data are duplicated five times.
you should not call a ko.applybindings more than once on the same model.
Your model is always the same even if it's parametrized.
I had the same problem here: Data coming from an ObservableArray are displayed twice in my table
the fact that you have you business logic inside your viewModel is something that could be discussed, and it makes it not easy to fix this.
Make 3 classes, put them in a common model without logic inside. Then once you have applyed the ko.applyBindings once, you just have to modify the array like this:
viewModel.myArray(newValues)
Here is the fiddle with the amended code: http://jsfiddle.net/MaurizioPiccini/5B9Fd/17/
it does not do exaclty what you need but if remove the multiple bindings by moving the Collection object scope outside of your model.
As you can see the problem IS that you are calling the ko.applybindings twice on the same model.
Finally, I got this working. Thanks to #MaurizioIndenmark.
Though I have removed multiple call for ko.applybindings, I was still calling the view model multiple times. This was causing the issue.
Now, I have cleaner view model and I have different function calls for different actions and modify all the data required to be modified within these functions(events). Now, everything is working as expected.
This is how the view model looks now -
function ShipmentReportsViewModel(results) {
var self = this;
self.Heading = ko.observable();
self.BusinessUnits = ko.observableArray();
self.FrontEndUnits = ko.observableArray();
self.SalesSegments = ko.observableArray();
self.Home = function () {
var bu = FetchBusinessUnits();
self.Heading("Business Units");
self.BusinessUnits(bu);
self.FrontEndUnits(null);
self.SalesSegments(null);
};
self.fnFeu = function (businessUnit) {
var feu = FetchFrontEndUnits(businessUnit);
self.Heading(feu[0].BusinessUnit);
self.FrontEndUnits(feu);
self.BusinessUnits(null);
self.SalesSegments(null);
};
self.fnSalesSeg = function (frontEndUnit) {
var ss = FetchSalesSegments(frontEndUnit);
self.Heading(ss[0].BusinessUnit + ' - ' + ss[0].FrontEndUnit);
self.SalesSegments(ss);
self.BusinessUnits(null);
self.FrontEndUnits(null);
};
self.Home();
}
To see the entire working solution, please refer this jsFiddle
Thanks for all the valuable suggestions in getting this work.

Categories

Resources