knockoutJs ko.utils.arrayFirst not allowing duplicate values as results - javascript

I am new to knockoutJs, as i am trying to display the selected values by binding. But am facing issues with duplicate values.
For example:
As per the below example snippet, If i select one from the dropdown, the binding result is displaying name "one" as selected, this is correct.
{name:"one",price:32.50}, {name:"two",price:32.50},
After that if I select other option from drop down i.e name two, the binding result is not displaying name two as selected, instead it is keep displaying the name="one" only, which is in-correct.
Observation: I see that this is happening due to the same price set for all the option values, if I update them with different prices the options values are binding properly.
Here my confusion is that why the binding logic is not applying properly when the price value is same but the name is different.
I am trying to achieve this by below code.
Html
<select data-bind="options: beforeEventPedersensDropoffCustomerLocation,optionsCaption: 'Please Choose Closest Location',
optionsText: 'name', optionsValue: 'price', value: selectedPricebepdcl" id="before_event_pedersens_dropoff_customer_location_time" ></select>
Js
self.beforeEventPedersensDropoffCustomerLocation = [
{name:"one",price:32.50},
{name:"two",price:32.50},
{name:"three",price:32.50},
{name:"four",price:32.50},
{name:"five",price:32.50},
{name:"six",price:32.50},
{name:"seven",price:0}
];
self.selectedPricebepdcl = ko.observable("");
console.log()
self.beforeEventVal = ko.computed(function() {
if(self.selectedPricebepdcl() !== "")
return ko.utils.arrayFirst(self.beforeEventPedersensDropoffCustomerLocation, function(time) {
return self.selectedPricebepdcl() === time.price;
});
return null;
}, this);
Result
<p data-bind="with: beforeEventVal">
<span data-bind="text: name"></span>
</p>
<p data-bind="with: beforeEventVal">
<span data-bind="text: price"></span>
</p>
Can anyone help me on this.

The problem is that you have set optionsValue: 'price', so the only information you have about which item is selected is the price. Then you try to use that to find the selected item from among the available items, but you cannot do that because it is not a unique identifier.
Instead, if you don't specify optionsValue, Knockout will use the entire item as the value of the select. That also lets you do away with looking up the selected value, because you have the selected value.
function VM() {
self = this;
self.beforeEventPedersensDropoffCustomerLocation = [{
name: "one",
price: 32.50
},
{
name: "two",
price: 32.50
},
{
name: "three",
price: 32.50
},
{
name: "four",
price: 32.50
},
{
name: "five",
price: 32.50
},
{
name: "six",
price: 32.50
},
{
name: "seven",
price: 0
}
];
self.selectedBepdcl = ko.observable("");
self.selectedName = ko.pureComputed(() => {
const sb = self.selectedBepdcl();
return sb && sb.name ? sb.name : '';
});
}
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: beforeEventPedersensDropoffCustomerLocation,
optionsCaption: 'Please Choose Closest Location',
optionsText: 'name',
value: selectedBepdcl" id="before_event_pedersens_dropoff_customer_location_time">
</select>
<p data-bind="with: selectedBepdcl">
<span data-bind="text: name"></span>
</p>
<p data-bind="with: selectedBepdcl">
<span data-bind="text: price"></span>
</p>
Pretend this is hidden:
<input data-bind="value: selectedName">

Related

Knockout.js - Updating a textbox when a dropdown has changed

I'm trying update a textbox when I change the value of a select.
Now, I know this is fairly simple however I'm not looking to update the textbox with easy to get data.
Here's what I can do so far:
Catch the change event
Get the ID from the Select dropdown
Here's what I want to do:
Use the selected ID to return a property value from inside an array of objects inside my viewmodel. However, the ID does not match the array index (i.e. the selected ID may be "43" but the index is 0).
Not much point in posting my Knockout code as it's fairly basic so instead I'll post my VM structure.
ViewModel
--> Property1
--> Property2
--> Array
--> Object[0]
--> "Property to match with the selected ID"
--> "Property that I want to return"
--> Object[1]
Not really sure how much sense this is making, hope it makes some.
Any other info can be provided.
Thanks!
EDIT
VM
var PurchaseOrderViewModel = function (data) {
var self = this;
self.UpdateCurrency = function (data, event) {
//
}
self.UpdateSupplierContactDetails = function (data, event) {
//
}
ko.mapping.fromJS(data, {}, self);
}
$(document).ready(function () {
var viewModel = new PurchaseOrderViewModel(#Html.Raw(jsonString));
ko.applyBindings(viewModel);
});
EDIT #2
Managed to get a working solution, in case anyone else has issues here is how I worked around it.
var contact = ko.unwrap(ko.utils.arrayFirst(self.AllSupplierContacts(),
function (item) {
return ko.unwrap(item.Id) === newID;
}).BusinessTelephoneNumber);
This answer has two parts: the code I think you're looking for (1), and the code I think you should write (2).
1. Finding an item that matches an id
ko.applyBindings(new function() {
this.ids = ["A", "B", "C", "D"];
this.selectedId = ko.observable();
this.items = [
{ key: "A", value: 1 },
{ key: "B", value: 2 },
{ key: "C", value: 3 },
];
this.selectedValue = ko.pureComputed(function() {
var selectedId = this.selectedId();
// Find the object in items of which the property
// `key` matches the `selectedId` and return it
var match = this.items.find(function(item) {
return item.key === selectedId;
});
return match ? match.value : "No item found";
}, this);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: ids, value: selectedId"></select>
<div>
Your selected value: <strong data-bind="text: selectedValue"></strong>
</div>
2. Binding the actual elements to the select:
Knockout's options binding does many things out of the box. You probably don't need to store ids and items separately. By telling the binding which property it should render in the dropdown (optionsText), and which to store as a value (optionsValue), you'll need a lot less code to do the same thing:
ko.applyBindings(new function() {
this.items = [
{ key: "A", value: 1 },
{ key: "B", value: 2 },
{ key: "C", value: 3 },
{ key: "D", value: 4 }
];
this.selectedValue = ko.observable();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: items,
optionsText: 'key',
optionsValue: 'value',
value: selectedValue"></select>
<div>
Your selected value: <strong data-bind="text: selectedValue"></strong>
</div>
The whole issue of "having to look up an ID in an array" can be side-stepped by not working with IDs at all, but with the array items themselves.
function MyList(params) {
var self = this;
// observables
self.items = ko.observableArray();
self.selectedItem = ko.observable();
// init
ko.mapping.fromJS(params, {}, self);
// post-init
self.items.sort(function (a, b) {
return ko.unwrap(a.key) < ko.unwrap(b.key) ? -1 : 1;
});
}
ko.applyBindings(new MyList({
items: [
{ key: "D", id: 4 },
{ key: "A", id: 3 },
{ key: "B", id: 2 },
{ key: "C", id: 1 }
]
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<select data-bind="
optionsCaption: 'Please select...',
options: items,
optionsText: 'key',
value: selectedItem
"></select>
<div data-bind="with: selectedItem">
Your selected value: <strong data-bind="text: id"></strong>
</div>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

How to auto select users state in data-bind options select in Knockout

i have a drop down of states and their ID:
<select data-bind="options: States, optionsText: 'text', value: SelectedState"></select>
Javascript
function ViewModel() {
this.States = ko.observableArray(states);
this.SelectedState = ko.observable(usersState);
};
var states = [
{ value: 10, text: "California" },
{ value: 3, text: "New York" },
{ value: 9, text: "Florida" }
];
ko.applyBindings(new ViewModel());
usersState is a variable that may or maynot contain the users info. By default its null. But if the user has already logged in then it should populate with the users selected state. For this example, the users has logged in and their select state is 9 for florida.
so i declared usersState = 9; at the top.
What i am trying to do is simply auto select Florida in the drop down based on the users info.
not sure why its not selecting it. Here is my fiddle: http://jsfiddle.net/neosketo/sw9dzjk1/2/
The SelectedState refers to a state object. Your initial selection is a number. You'll have to find the state object corresponding to the number:
var usersState = 9;
// This method finds an object by value property
var findStateById = function(states, id) {
return states.find(function(state) {
return state.value === id;
});
};
function ViewModel() {
this.States = ko.observableArray(states);
// In this example, SelectedState is an object with a value and text property
this.SelectedState = ko.observable(findStateById(states, usersState));
};
// Test data
var states = [{
value: 10,
text: "California"
}, {
value: 3,
text: "New York"
}, {
value: 9,
text: "Florida"
}];
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: States, optionsText: 'text', value: SelectedState"></select>
(Note that I used Array.prototype.find which isn't supported by all browsers)
Alternatively, you could use the optionsValue option to tell knockout to use the value property to match selection to options. Personally, I prefer using the actual object: knockout being able to work with references to real instances rather than using strings makes developing easier.
var usersState = 9;
function ViewModel() {
this.States = ko.observableArray(states);
// In this example, SelectedState is a number
this.SelectedState = ko.observable(usersState);
};
var states = [{
value: 10,
text: "California"
}, {
value: 3,
text: "New York"
}, {
value: 9,
text: "Florida"
}];
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: States,
optionsText: 'text',
optionsValue: 'value',
value: SelectedState"></select>

bootstrap selectpicker knockoutjs disable option

I have the following binding in js fiddle.
<div class="container body-content">
<div>Name : <span data-bind="text: Name"></span>
</div>The select control should be below
<select multiple data-bind="selectPicker: teamID, optionsText: 'text', optionsValue : 'id', selectPickerOptions: { optionsArray: teamItems, disabledOption: IsDisabled }"></select>
<div>Selected Value(s)
<div data-bind="text: teamID"></div>
</div>
</div>
I am thinking of doing this disabledOption: IsDisabled and then adding
this.teamItems = ko.observableArray([{
text: 'Chris',
id: 1,
IsDisabled: false
}, {
text: 'Peter',
id: 2,
IsDisabled: false
}, {
text: 'John',
id: 3,
IsDisabled: false
}]);
I would like to know how to disable an option in the select.
In the knockout docs, there's an example that shows how you can disable an item using an optionsAfterRender method.
About the method you can pass to it:
It has to be in your viewmodel, not in your items
It takes in the option HTML node, and the item it's bound to
So step one is to find a place to store which items are disabled. The easiest option would be to store it inside your teamItems' objects:
{
text: 'Chris',
id: 1,
disable: ko.observable(true)
}
Now, we need to add a method that takes in an item and creates a binding. We can take this straight from the example:
this.setOptionDisable = function(option, item) {
ko.applyBindingsToNode(option, {
disable: item.disable
}, item);
}
Finally, we need to tell knockout to call this method:
<select multiple data-bind="optionsAfterRender: setOptionDisable, ...
Note that after changing a disable property in one of your items, you'll have to call teamItems.valueHasMutated manually.
Here's an updated fiddle:
http://jsfiddle.net/nq56p9fz/

Knockout : Unusual Mapping Pattern

Scenario: I make a request to the server for a part. It gives me this back (it's pseudo, but represents what I'm looking at):
{
PartNumber : "XYZ",
Description: "ABCFOO",
ProductClass: "Widget",
FieldList:[
{Name: "PROGRAM TYPE", Value: "Program3"},
{Name: "SHIP", Value: false},
{Name: "NOTES", Value: "SomeValue1"}
],
MoreStuff : [{
...
}]
}
Note the FieldList list of elements, that's the focus here.
The server also gave me a list of certain fields, their types and default values. It looks like this:
[
{FieldName : "PROGRAM TYPE", FieldType: "List", Defaults: [{Name:"Program 1", Value: "Program1"},{Name:"Program 2", Value: "Program2"},{Name:"Program 3", Value: "Program3"}]},
{FieldName : "SHIP", FieldType : "Boolean", Defaults: []},
{FieldName : "NOTES", FieldType: "TextArea", Defaults: []}
]
That comes in a seperate REST call, and the prior to loading my Part. I use it to create part of the HTML page for the Part. You can see they're similarly related to the FieldList section from when I ask for Part.
From that "list of fields" and defaults -- I generate the appropriate HTML elements on the page. If it's a Boolean field type, I create a checkbox - if it's a list, I create a SELECT (with options given in Defaults), TextArea is a text-area, etc. That all works fine. It ends up looking like:
<input data-bind="textInput: PartNumber"/>
<textarea data-bind="textInput: Description"></textarea>
<!-- generating fieldlist - i create a pseudo attr because the field name can have spaces-->
<select field_label="PROGRAM TYPE"> <!-- how the heck do i bind to this??-->
<option value="Program1">Program 1</option>
<option value="Program2">Program 2</option>
<option value="Program3">Program 3</option>
</select >
<input type="checkbox" field_label="SHIP" value="true"/> <!-- or this, how to bind to it?!-->
<!-- end of field list generation -->
Now I take the object (the part I'm given) and put that into my ViewModel - that is all working just swimmingly. I make it easy and just use ko.mapping.fromJS(rest_data); Works just fine.
Data binding is ducky -- for what I am able to bind it to. My issue comes from -- how the heck do I map my FieldList to the HTML I generated for the fields the server gave me?. My data / my viewmodel object has FieldList in it, with a buncha stuff I want to map to that generated stuff. The only real "key" I have is the self-created field_label I have, because the server's FieldName can have spaces.
So I guess what I'm asking is, I have that array of FieldList from my part. I have the whole Part object in my view model and it's all fine. How do I take that FieldList and map it into my self-generated set of fields from the other object (ie., take the FieldList name and tie it to the element with field_label of the same value?)
Spelled out - it'd be like: How to map FieldList with Name of "PROGRAM TYPE" to HTML element having field_label of "PROGRAM TYPE".
I begin to think something like this might be the direction I should be going:
http://jsfiddle.net/MhdZp/128/
but it goes over my head.
One way to approach this:
function Option(definition) {
this.definition = definition;
this.value = ko.observable();
this.templateName = 'input-template-' + definition.FieldType;
}
function ViewModel() {
var self = this;
// from REST call
var fieldDefinition = [{
FieldName: "PROGRAM TYPE",
FieldType: "List",
Defaults: [
{ Name: "Program 1", Value: "Program1" },
{ Name: "Program 2", Value: "Program2" },
{ Name: "Program 3", Value: "Program3" }
]
}];
self.options = ko.observableArray();
// for the sake of the example
self.options.push(new Option(fieldDefinition[0]));
// methods
self.optionByName = function (name) {
return ko.utils.arrayFirst(self.options(), function (option) {
return option.Name = name;
});
};
// poor man's init, imagine 2nd rest call instead
self.optionByName("PROGRAM TYPE").value("Program3");
}
and
<script type="text/html" id="input-template-List">
<label data-bind="text: definition.FieldName"></label>
<select data-bind="
value: value,
options: definition.Defaults,
optionsText: 'Name',
optionsValue: 'Value',
optionsCaption: 'Please select...'
"></select>
</script>
and
<div data-bind="foreach: options">
<div data-bind="template: templateName"></div>
</div>
Add more templates as needed, this should be very easy to extend.
jsFiddle: http://jsfiddle.net/0nxt2zte/

KnockoutJS: Handling Select Boxes. Selecting a default value and removing an existing item

I am having trouble handling checkboxes in Knockout JS.
jsfiddle link: http://jsfiddle.net/wenbert/Xyuhk/72/
Note that I have provided 2 select boxes for each parent (Hero). Each one is using a different way but both are more or less "observing" the same object.
Workflow
Click on a gray box
A box with a blue dotted line should appear containing the items of the Select box.
From here, you can edit the items of the select box.
Fiddle here: http://jsfiddle.net/wenbert/Xyuhk/72/
The Problems
When I remove an item, I am not able to remove it from the Select boxes. Note that I do not want to completely remove it from the object. I just want the current item to be flagged as "isDeleted".
Select A - hides the item but it leaves an empty option in the select box.
Select B - the "ifnot: isDeleted" is not having any effect on the options.
The Question
How do I handle Select Boxes? I have rendered 2 Select Boxes in 2 different ways to try to have the ifnot: isDeleted take effect but none of them are working.
Update: With this setup, how do I do the "selected" value of the select box?
HTML
<button data-bind="click: addHero">Add Hero</button>
<ul data-bind="foreach: heroes">
<li class="parent" data-bind="ifnot: isDeleted, click: $parent.selectHero">
<input class="big-box" type="text" data-bind="value: name" />
<button class="btn-small" data-bind="click: $parent.removeHero">Remove Hero</button>
<br/>
SKILLS:
Select A) <select data-bind="foreach: skills">
<option data-bind="value: name, text: name, ifnot: isDeleted"></option>
</select>
Select B) <select data-bind="options: skills, optionsText: 'name', value: selected_skill.name, ifnot: isDeleted">
</select>
<br/>
<span class="instructions">(Step 1: Click somewhere here)</span>
</li>
</ul>
<div class="edit-panel" data-bind="if: selectedHero">
Edit Skill:<br/>
<div data-bind="with: selectedHero">
<button class="btn-small" data-bind="click: addSkill">Add Skill</button>
<ul data-bind="foreach: skills">
<li data-bind="ifnot: isDeleted">
<button class="btn-small" data-bind="click: $parent.setAsDefaultSkill">Set as default</button>
<input data-bind="value: name" />
<button class="btn-small" data-bind="click: $parent.removeSkill">Remove Skill</button>
</li>
</ul>
<span class="instructions">(Step 2: Remove a Skill, then have a look at the Select Box above.)</span>
</div>
</div>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Javascript
var initialData = [
{
id: '1',
name: "Batman",
isDelete: false,
selected_skill: {name: "Boxing", isDeleted: false},
skills: [
{ id: '1', name: "Karate", isDeleted: false },
{ id: '2', name: "Boxing", isDeleted: false},
{ id: '6', name: "Sonar", isDeleted: false}
]
},
{
id: '2',
name: "Hulk",
isDelete: false,
skills: [
{ id: '3', name: "MMA", isDeleted: false },
{ id: '4', name: "Rage", isDeleted: false},
{ id: '5', name: "Extra Strength", isDeleted: false}
]
},
];
function Hero(data) {
var self = this;
self.name = ko.observable(data.name);
self.selected_skill= ko.observable(data.selected_skill);
self.skills = ko.observableArray(ko.utils.arrayMap(data.skills, function(i) {
return new Skills(i);
}));
self.addSkill = function() {
self.skills.push(new Skills({name: '---', isDeleted: false}));
}
self.setAsDefaultSkill = function(item) {
self.selected_skill(item);
}
self.isDeleted = ko.observable(data.isDeleted);
self.removeSkill = function(item) {
item.isDeleted(true);
}
}
function Skills(data) {
var self = this;
self.name = ko.observable(data.name);
self.isDeleted = ko.observable(data.isDeleted);
}
function SuperheroViewModel(data) {
var self = this;
self.heroes = ko.observableArray(ko.utils.arrayMap(data, function(i){
return new Hero(i);
}));
self.selectedHero = ko.observable();
self.selectedHero.skills = ko.observableArray();
self.addHero = function() {
self.heroes.push(
new Hero({
name: 'Wolverine',
isDelete: false,
skills: [{name: 'Breathing', isDeleted: false}],
})
);
}
self.selectHero = function(item) {
self.selectedHero(item);
}
self.removeHero= function(item) {
item.isDeleted(true);
}
}
ko.applyBindings(new SuperheroViewModel(initialData ));
​
​
I hope everything is clear.
Any reply will be greatly appreciated.
THanks!
I would do the filtering in the viewmodel. So I would create a filtered collection something like availableSkills
self.availableSkills = ko.computed(function() {
return ko.utils.arrayFilter(self.skills(), function(item) {
return !item.isDeleted();
})
});
And then I would use this in the selects:
<select data-bind="foreach: availableSkills">
<option data-bind="value: name, text: name"></option>
</select>
Demo fiddle.

Categories