Set Value of Dynamically Populated Select in Knockout - javascript

So I'm using KnockoutJS to populate a <select> with options and to get the value of the select.
<select data-bind="enable: cols1().length > 0, options: cols1(), optionsText: 'name', value: jCol1" id="col1"></select>
The variable cols1 holds objects with the simple format of { name: "name" } just because it needs to be objects for some of the other stuff I do on the page. Is there any way to set the value of the select from outside of the data-binds on this element?

The value part of the binding says:
Store a reference to an item that is in cols1 in jCol1
If you want to change the selection from outside of the UI, you'll have to set jCol1 to a value that is in the cols1 array. If you try to set it to anything else, knockout will reset it to the first value immediately. Switch out the commented lines of code in the example below to see this happen:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
// This does not work because knockout does not do a
// deep comparison of objects
// this.selection({ name: "Item 3" });
// This _does_ work, because it references one of the
// options objects
this.selection(this.options()[2]);
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>
Now, you can also choose to just store a string ID (or other primitive) of your selection. This makes it easier to set things from the outside, because you only need the ID instead of a reference to the actual item:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
this.selection("Item 3");
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name', optionsValue: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>

Let's use the states example:
//list of US states in array
self.usStates = [
{ StateName: 'Alabama', Abbr: 'AL' },
{ StateName: 'Alaska', Abbr: 'AK' },
...
//observable from that array
self.States = ko.observableArray(self.usStates);
//the selected state
self.selectedState = ko.observable();
//set selectedState from some value received from server
self.selectedState(self.States.find("Abbr", { StateName: "", Abbr: '<<Value i.e. TX>>' }).Abbr);
//finds TX, sets state to 'Texas'
//find custom function used to find specific object in array
ko.observableArray.fn.find = function (prop, data) {
var valueToMatch = data[prop];
return ko.utils.arrayFirst(this(), function (item) {
return item[prop] === valueToMatch;
});
};
This may be overly complicated for what you're looking to do, but this is how I do it when I want to choose a value from a select based on a value from the record in the database.

Related

Modifying observableArray does not instantly update select UI

I have a multi-select dropdown. If the user selects the option all, I want all the other options to be deselected and only select all. I have this almost working, but my issue is that the select does not show the updated value until minimise the dropdown. The state of the observableArray appears to be correct.
Here is the HTML:
<select data-bind="options: games, selectedOptions: selectedGame, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>
And the javascript:
this.games= [
{
name: 'All',
id: 'all'
},
{
name: 'Game1',
id: 'game1'
},
{
name: 'Game2',
id: 'game2'
},
]
this.selectedGame = ko.observableArray(['all']);
this.selectedGameBeforeChange = ko.observableArray([]);
this.selectedGame.subscribe((oldValue) =>
{
this.selectedGameBeforeChange(oldValue);
}, null, 'beforeChange');
this.selectedGame.subscribe((newValue) =>
{
const newValueAdded = newValue.filter(x => !this.selectedGameBeforeChange().includes(x));
if (newValueAdded.length > 0 && newValueAdded[0] === 'all'){
this.selectedGame.removeAll();
this.selectedGame.push('allCombined');
}
this.updateTable();
});
The code above works, but the change is only reflected in the UI once I have 'minimised' the select and reopen it. Is there a way to force the UI to update as soon my observableArray is updated?
You've got 2 bugs:
Instead of push('allCombined'), it should be push('all').
It works when all is selected last, but not when it's selected as the first option. To fix that, we need to modify the condition a bit.
Here's the final code (with few more minor modifications, e.g using self instead of this):
var vm = function () {
var self = this;
self.games = [
{ name: 'All', id: 'all' },
{ name: 'Game1', id: 'game1' },
{ name: 'Game2', id: 'game2' }
];
self.selectedGames = ko.observableArray(['all']);
self.selectedGamesBeforeChange = ko.observableArray([]);
self.selectedGames.subscribe((oldValue) =>
{
self.selectedGamesBeforeChange(oldValue);
}, null, 'beforeChange');
self.selectedGames.subscribe((newValue) =>
{
if (newValue.length > 1 &&
newValue.includes('all')){
self.selectedGames.removeAll();
self.selectedGamesBeforeChange.removeAll();
self.selectedGames.push('all');
}
});
};
ko.applyBindings(new vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: games, selectedOptions: selectedGames, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>

Change select drop-down option based on input value using autocomplete in Knockout JS

Using Knockout JS, when a user types into an input field and selects a value (ex: Fruit) using jquery-ui autocomplete selection, I'm trying to change the select drop-down of options in a separate drop-down.
Example scenario:
(1) User begins to type "Fru.."
(2) Selects "Fruit" in autocomplete input field.
(3) Dropdown options changes based on the value: "Fruit"
(4) Dropdown options only shows "Apples" or other options with equals id (ex: ABC)
Autocomplete Input Field
HTML
<input type="text"
id="searchItem"
placeholder="Search"
tabindex="0"
data-bind="textInput: searchItem, valueUpdate: 'onkeyup'"/>
ViewModel/JQuery (Autocomplete)
// Search Item
$(function() {
var searchItem = [
{ id: "ABC", name: "Fruit" },
{ id: "DEF", name: "Animal" },
{ id: "GHI", name: "Color" },
{ id: "JKL", name: "Clothing" }
];
$("#searchItem").autocomplete({
source: searchItem
});
});
Select dropdown
HTML
<select class="form-control"
id="alphabetList"
data-toggle="tooltip"
tabindex="0"
data-bind=" foreach: { data: alphabetList, as: 'item' }, value: selectedItem">
<option data-bind=" attr: { 'value': item.id }, text: item.name"></option>
</select>
ViewModel
// Alphabet List
this.alphabetList = ko.observableArray([
{ id: "ABC", name: "Apples" },
{ id: "DEF", name: "Dog" },
{ id: "GHI", name: "Green" },
{ id: "JKL", name: "Jacket" }
]);
On selection of an item in autocomplete, populate an observable called selectedId. Then create a computed property which filters the alphabetList based on selectedId
You have not mentioned where this autocomplete code exists. But, since you have mentioned ViewModel, I'm assuming you have access to the viewModel's instance in your jquery code.
Also, you don't need to use the foreach binding for displaying options. You can use options binding.
Here's a working snippet with all these changes:
var viewModel = function() {
var self = this;
self.selectedAlphabet = ko.observable();
self.selectedId = ko.observable();
self.searchItem = ko.observable();
self.alphabetList = ko.observableArray([
{ id: "ABC", name: "Apples" },
{ id: "DEF", name: "Dog" },
{ id: "GHI", name: "Green" },
{ id: "JKL", name: "Jacket" }
]);
// this gets triggerred everytime selectedId changes
self.availableAlphabetList = ko.pureComputed(() => {
return self.alphabetList().filter(item => item.id == self.selectedId());
});
}
// I have created an instance to use it in jquery code
var instance = new viewModel();
ko.applyBindings(instance);
$(function() {
var searchItem = [
{ id: "ABC", name: "Fruit" },
{ id: "DEF", name: "Animal" },
{ id: "GHI", name: "Color" },
{ id: "JKL", name: "Clothing" }];
$("#searchItem").autocomplete({
// creating an array with a "label" property for autocomplete
source: searchItem.map(function(item) {
return {
label: item.name,
id: item.id
}
}),
// on select populate the selectedId
select: function(event, ui) {
// if this jquery code is within viewModel, then use "self.selectedId"
instance.selectedId(ui.item.id)
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.9.2/themes/base/jquery-ui.css">
<input type="text" id="searchItem" placeholder="Search" tabindex="0" data-bind="textInput: searchItem, valueUpdate: 'onkeyup'" />
<select class="form-control" id="alphabetList" data-toggle="tooltip" tabindex="0" data-bind="options: availableAlphabetList,
optionsText: 'name',
optionsValue: 'id',
value: selectedAlphabet
optionsCaption: 'Choose..'">
</select>
You can also go through this question which has good answers for creating a custom binding for autocomplete

how do you set a value that is an observable in a dropdown?

var vm = (function() {
var selectedFoo = ko.observable(),
foos = [
{ id: 1, fooName: 'fooName1', fooType: 'fooType1' },
{ id: 2, fooName: 'fooName2', fooType: 'fooType2' },
{ id: 3, fooName: 'fooName3', fooType: 'fooType3' },
];
return {
selectedFoo: selectedFoo,
foos: foos
};
}());
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: foos,
optionsText: 'fooName',
optionsCaption: 'Select foo',
value: selectedFoo"></select><br />
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
While above code works, how would set the initial value of the dropdown? Say you got an id value of 2 from an ajax call. How would you set the selected option based on the id?
I've looked in the for solutions but I only found adding a optionsValue but I need the member of the selected option as display
Any help would be much appreciated.
You're misundertanding something. I've added the selected value in your snippet, and, if you change the id, the select list is updated correcty, and you still display what you want. I've added a bound textbox where you can type the id so that you can check it works as expected.
NOTE: just in case the comment below is what I couldn't understand from your question, I'm implementing a new writable computed observable that allos to use the whole object as selection.
var vm = (function() {
var foos = [
{ id: 1, fooName: 'fooName1', fooType: 'fooType1' },
{ id: 2, fooName: 'fooName2', fooType: 'fooType2' },
{ id: 3, fooName: 'fooName3', fooType: 'fooType3' },
],
selectedFoo = ko.observable(),
selectedFooId = ko.computed({
read: function() {
return selectedFoo() ? selectedFoo().id : null;
},
write: function(value) {
var newSel = foos.find(function(f) {return f.id == value;});
selectedFoo(newSel);
}
});
return {
selectedFooId: selectedFooId,
selectedFoo: selectedFoo,
foos: foos
};
}());
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: foos,
optionsText: 'fooName',
optionsCaption: 'Select foo',
value: selectedFoo"></select><br />
<input type=text data-bind="value: selectedFooId, valueUpdate:'keyup'" />
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

Dynamically update and check an array of Objects

So I have a callback function that returns a data object from the dom (there is a list of items and every time you select an item it returns it as an object). Here is the call back function:
$scope.fClick = function( data ) {
$scope.x = data;
}
The object returned from fClick looks like this when you select an item from the dropdown : { name: "English", ticked: true }
When you deselect it from the dropdown it would return something like this:
{ name: "English", ticked: false }
Now I keep an array something like $scope.output to maintain a list of the returned objects. However, what I want to do is add an object returned from scope.fClick to $scope.output only if there isn't an object in output already with the same property "name" value. So right now in my implementation both { name: "English", ticked: false } and { name: "English", ticked: true } get added to the array. Instead I want it to update the ticked property. So, for instance if if $scope.output = { name: "English", ticked: false } and then scope.fClick returns { name: "English", ticked: true}. When I push this value to $scope.output I want it to the update the existing object's tick property so $scope.output = { name: "English", ticked: false } becomes $scope.output = { name: "English", ticked: true }
This is what I have tried:
$scope.fClick = function( data ) {
$scope.x = data;
}
$scope.$watch(function () {
return $scope.y = $scope.x;
},function (newValue, oldValue) {
var id = $scope.output.indexOf(newValue);
if(id === -1){
$scope.output.push(newValue);
}
else {
$scope.output[id].tick = newValue.tick;
}
console.log($scope.output);
},true);
How do I make this work?
Set & Get selected values, name and text of Angularjs isteven-multi-select
<div isteven-multi-select
input-model="marks"
output-model="filters.marks"
button-label="name"
item-label="name"
tick-property="ticked"
selection-mode="multiple"
helper-elements="all none filter"
on-item-click="fClick( data )"
default-label="Select marks"
max-labels="1"
max-height="250px">
</div>
Add items
$scope.marks= [
{ name: 'Mark I', value: 'Mark i', text: 'This is Mark 1', ticked: true },
{ name: 'Mark II', value: 'Mark ii', text: 'This is Mark 2' },
{ name: 'Mark III', value: 'Mark iii', text: 'This is Mark 3' }
];
Get selected item (on change)
$scope.fClick = function (data) {
console.log(data.name);
console.log(data.value);
console.log(data.text);
return;
}
Select item (Dynamically)
$scope.abc = function (data) {
console.log(data.element1, data.element2);
angular.forEach($scope.marks, function (item) {
if (item.value == data.element1) {
item.ticked = true;
}
else {
item.ticked = false;
}
});
}
Deselect item
$scope.marks.map(function (item) {
if (item.value == "")
item.ticked = true;
else
item.ticked = false
});
You can do the following by "simulate" a key/value map.
Controller
(function(){
function Controller($scope) {
$scope.data = [
{name: 'English', ticked: true},
{name: 'French', ticked: false}
];
//Represent a map key - value
$scope.output = {};
$scope.update = function(index){
//Change the ticked value by opposite
$scope.data[index].ticked = !$scope.data[index].ticked;
//Set the value to our map
$scope.output[index] = $scope.data[index].ticked;
}
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Here, when you will update $scope.output, if the key exist, it will erase it by the new value, instead of it will create a new key/value pair.
HTML
<body ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="item in data">{{item.name}} {{item.ticked}} <button type="button" ng-click="update($index)">update</button></li>
</ul>
</body>

Angularjs: update select options

I have two select menus . One for country selection and other for state. I need to update states based country selected. I am able to log states but not able to list them in select menu.Please help.
Angular:
angular.module('demoApp', []).controller('DemoController', function($scope) {
$scope.countries = [
{ label: 'Please select', value: 0 },
{ label: 'India', value: 1 },
{ label: 'US', value: 2 }
];
$scope.data = [{'1':[{ label: 'Delhi', value: 0 },{ label: 'Mumbai', value: 1 },{ label: 'Chennai', value: 2 }]},
{'2':[{ label: 'Alabama', value: 3 },{ label: 'Alaska', value: 4 },{ label: 'Arizona', value: 5 }]}];
$scope.vm = {states: []};
$scope.updateStates = function(countryCode){
$scope.vm.states = $scope.data[countryCode-1];
console.log($scope.vm.states);
};
$scope.correctlySelected = $scope.countries[0];
});
HTML:
<body ng-app="demoApp">
<div ng-controller="DemoController">
<select ng-model="correctlySelected" ng-change="updateStates(correctlySelected.value)" ng-options="opt as opt.label for opt in countries">
</select>
<select ng-options="opt as opt.label for opt in vm.states">
</select>
</div>
</body>
JS Bin:
http://jsbin.com/pafosewedo/1/edit?html,js,console,output
You need to add ng-model to your states <select> - this is required when you are using ng-options
You also have an inconvenient model for the states data. Each element of the data array that corresponds to the country's states is an object with a changing key whose value is an array of states. You could make it work, but it's better to change it to something more reasonable:
$scope.data = {
1: [{ label: 'Delhi', value: 0 }, {...}, ],
2: [{...}, {...}, ] // same for US
}
Then it would work with how you specified your ng-options for states, and you wouldn't have to deal with indices:
$scope.updateStates = function(countryCode){
$scope.vm.states = $scope.data[countryCode]; // access by property
};
I think, that you should use some filter like that if you don't want to change your model:
.filter('stateFilter', function() {
return function(states, countryID) {
var filtered = [];
angular.forEach(states, function(state){
if(state.value === countryID)
filtered.push(state);
});
return filtered;
};
});
to filter out all values that have value equal to selected country.value in first select control.
To use that filter you need to modify your ng-repeat directive value in state select control:
ng-options="state as state.label for data | stateFilter:correctlySelected"
I came up with the following solution, view my JSBin
This solutions works by setting the countryCode in the scope when we are updatingStates.
$scope.updateStates = function(countryCode){
$scope.countryCode = countryCode;
$scope.vm.states = $scope.data[countryCode-1];
console.log($scope.vm.states[countryCode]);
};
This change is then reflected in the view.
<select>
<option ng-repeat='i in vm.states[countryCode]'> {{i.label}}
</option>
</select>

Categories

Resources