Knockout.js options binding - deselecting option when option has been removed - javascript

I have a dropdown (select control) bound to an observable array using the options, optionsValue, optionsText, and optionsCaption bindings.
If I have an option selected and then later remove it, the dropdown selects the first item. I would like it to set the selected value to undefined without having to add an empty item to the observable array.
Here is a simple example:
<select data-bind="value: selectedItemValue, options: items, optionsValue: 'value', optionsText: 'text', optionsCaption: ''"></select>
<button type="button" data-bind="click: selectLast">Select Last</button>
<button type="button" data-bind="click: removeLastItem">Remove Last</button>
var viewmodel = function () {
var self = this;
this.items = ko.observableArray();
this.selectedItemValue = ko.observable(null);
this.selectLast = function () {
self.selectedItemValue(
self.items()[self.items().length - 1].value);
};
this.removeLastItem = function () {
self.items.pop();
};
this.items.push({
value: "item1",
text: "First item"
});
this.items.push({
value: "item2",
text: "Second item"
});
this.items.push({
value: "item3",
text: "Third item"
});
};
var vm = new viewmodel();
ko.applyBindings(vm);
jsfiddle
Click the Select Last button or manually select the last item in the drop down
Click the Remove Last button
Result: First item gets selected
What is the best approach to achieve the behavior I want?

With the introduction of the valueAllowUnset in the pull request:
#647 - add option that allows the value binding to accept a selected value that is not currently in the list of options
This old bug/feature has been fixed. So if you upgrade your fiddle to use at least KO version 3.1 it is working as you described:
Demo JSFiddle

In addition to what nemesv mentions you can simply remove the value if the last one is matching the current selected value -
this.removeLastItem = function () {
var lastItem = self.items()[self.items().length - 1];
if (self.selectedItemValue() === lastItem.value) {
self.selectedItemValue(null);
}
self.items.pop();
};
http://jsfiddle.net/guvbxor2/4/
Basically just check if you are about to remove the option that is selected and if so null it

Related

select options not updating dynamically with angular 2 bindings

I am unable to get select options to update dynamically with Angular2 bindings. How do you update the contents of the select dynamically?
Plnkr explaining my issue. Notice the select options never update to the new array i set.
Because when you are reassinging options value you have to point to options of current by using this before options.
handleChange(event) {
console.log(event.target.value);
//this to indicate options from current class
this.options = [{id: 0, name:"First"}, {id: 1, name:"Second"}, {id: 2, name:"Third"}];
this.selected1 = this.options.filter((option) => {
return option.id == event.target.value;
})[0];
}
Demo Plunkr

knockout: remove one element from multiselect control when other is selected

I'm using knockout and I have a list of item, let say:
Tomato,
Potato,
Broccoli,
Bean
all those item are allowed to user to select from multiselect form-control.
<select class="form-control" multiple
data-bind="selectPicker: Types,
optionsText: 'Name',
optionsValue: 'VegetableTypeId',
selectPickerOptions: { optionsArray: AvailableVegetableTypes }">
</select>
Except one scenario - when the user selects tomato, potato should unselect.
I was trying to use subscription on selected items array:
this.Types.subscribe(changes => {
var VegetableTypes = this.AvailableVegetablesTypes();
var company = VegetableTypes.First(element => element.VegetableTypeId == changes[0].value);
if (changes[0].status == "added") {
if (Vegetable.IsTomato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsPotato));
} else if (Vegetable.IsPotato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsTomato));
}
}
}, null, "arrayChange");
Problem is that I'm using ObservableArray.Remove, so it's again call my function before current run is finish. This should not be a problem, because after remove first change is "deletion" type, so whole logic should not be executed.
But after this, when I select tomato/potato again, nothing is fired. In the end I actually have both tomato and potato selected.
Then, when I deselect one of these two and select it again, everything works fine, and then the whole situation repeats.
Do you have any ideas?
I didn't understand why you are using selectPicker bindings instead of the normal options and selectedOptions bindings available in Knockout.
However, I built a simple demo which implements the desired behaviour. You can find it here:
http://jsbin.com/fofojaqohi/1/edit?html,js,console,output
Note that, whenever you select Tomato after Potato, Potato will become unselected.
You were on the right track: you need to subscribe to the array of selected items and check if there are any invalid selections. I hope this helps.
For reference, here is the code:
HTML:
<select class="form-control" multiple="true"
data-bind="options: availableVegetableTypes, selectedOptions: selected">
</select>
JS:
var availableVegetableTypes = ['Tomato',
'Potato',
'Broccoli',
'Bean'];
var selected = ko.observableArray();
var unselectMap = {
'Tomato': 'Potato'
};
selected.subscribe(function(selectedOptions){
var keysToUnselect = [];
console.log("Selected", selectedOptions);
selectedOptions.forEach(function(selectedOption){
if (unselectMap[selectedOption] != null) {
// This key exists in the unselect map
// Let's check if the value is in the array
if (_.contains(selectedOptions, unselectMap[selectedOption])) {
// The invalid key exists. Let's mark it for removal.
keysToUnselect.push(unselectMap[selectedOption]);
}
}
});
if (keysToUnselect.length > 0) {
console.log("Unselect", keysToUnselect);
var reject = function(v){
return _.contains(keysToUnselect, v);
};
filteredSelectedOptions = _.reject(selectedOptions, reject);
console.log("Filtered", filteredSelectedOptions);
selected(filteredSelectedOptions);
}
});
ko.applyBindings({
availableVegetableTypes:availableVegetableTypes,
selected: selected
});

How to filter kendo grid with a column that contains multiselect - multiple values?

I have a multiselect grid column same as onabai (awesome guy on so!) I need to put filter menu on that column.
http://onabai.wordpress.com/2013/07/17/kendoui-multiselect-in-a-grid-yes-we-can/
I have added custom filter on the multivalue column
filterable: {
ui: function(element) {
element.kendoDropDownList({
dataSource: [ "London", "Surat", "New York"] //etc
});
}
The filter shows up on the grid column but the filter does not filter.
I guess, I need to somehow capture the filter change event and filter data source based on value selected.
Any pointers?
Thanks.
This took me a while to grasp but you can use kendoGrid custom filters for this.
I've created a function to setup the multiselect as the UI and its filter function:
function userFilter(element) {
console.log(element);
element.removeAttr("data-bind");
//$scope.kGrid.dataSource.query({ filter: filter });
$scope.userFilterElement = element.kendoMultiSelect({
dataSource: $scope.users,
optionLabel: "--Select Value--",
change: function(e){
var filter = { name: "user", logic: "or", filters: [] };
var values = this.value();
$.each(values, function(i, v) {
filter.filters.push({field: "user", operator: "eq", value: v });
});
//$scope.kGrid.dataSource.filter(filter);
if ($scope.gridFilter.filters[0] && $scope.gridFilter.filters[0].name == 'user'){
if (filter.filters.length > 0)
$scope.gridFilter.filters.splice(0,1,filter);
else
$scope.gridFilter.filters.splice(0,1);
}
else
$scope.gridFilter.filters.push(filter);
$scope.kGrid.dataSource.filter($scope.gridFilter);
}
});
}
That's a relatively crude and basic example which I simplified for the sake of easier code reading.
Here's a working code: http://plnkr.co/edit/8N1oNpsd10CJwBrWKpK3?p=preview
After the grid loads, click on "add data" a few times, and then filter the users and use multiple variables.
Hope this helps.

Computed observable in knockoutjs doesn't update on change?

I'm currently studying knockoutjs and I was just following the tutorial on list and collection on the official website of knockoutjs, currently what I have is a dropdown that lists the items I have and then adjacent to it is a text that displays the text(price), now what I want to happen is that the text displayed should change based from the selected item of the dropdown.
Here is a jsfiddle of my code: http://jsfiddle.net/UQskS/
Also, if you notice something wrong with my code aside from what I mention above please do advise me, for best practice or for correction.
// Class to represent a row in the seat reservations grid
function SeatReservation(name, initialMeal) {
//var self = this;
//self.name = ko.observable(name);
this.name = name;
this.meal = ko.observable(initialMeal);
this.formattedPrice = ko.computed(function () {
var price = this.meal().price;
return price ? "$" + price.toFixed(2) : "None";
}, this);
}
function ReservationsViewModel(name, meal) {
//var self = this;
// Non-editable catalog data - would come from the server
this.availableMeals = [
{ mealId: 1, mealName: "Standard (sandwich)", price: 47.55 },
{ mealId: 2, mealName: "Premium (lobster)", price: 34.95 },
{ mealId: 3, mealName: "Ultimate (whole zebra)", price: 290.123 }
];
//editable data
this.seats = ko.observableArray([
new SeatReservation("Randel", this.availableMeals[2]),
new SeatReservation("Knockout", this.availableMeals[1])
]);
//operations
this.addSeat = function () {
this.seats.push(new SeatReservation("", this.availableMeals[0]));
};
this.removeSeat = function (seat) {
this.seats.remove(seat);
;}
}
ko.applyBindings(new ReservationsViewModel());
Sir/Ma'am, your answers would be of great help. Thank you++
The only thing you really need to change from how you currently have it coded is the options binding parameters.
You are using optionsValue and then trying to update the id on the meal property. This is just going to overwrite the mealId property without changing the object it is referring to.
This jsFiddle demonstrates what is going on. Notice that you have to click the refresh link in order to force the UI to update since mealId is not an observable.
To fix this, you just need to bind the value directly to the selected object instead.
<select data-bind="
options: $root.availableMeals,
value: meal,
optionsText: 'mealName',
optionsCaption: 'Choose...'">
</select>
Here is the updated (and working) fiddle: http://jsfiddle.net/jwcarroll/nrHcs
Update:
If you want to use the mealId because you will be pulling these values from the database, then at some point you will have to do a lookup to get the other values. Either you pay the price up front, or when you are saving.
Here is an updated fiddle that shows one way of how you might accomplish this.
http://jsfiddle.net/jwcarroll/YAMS5/

Display data, one object element at a time in knockout

In a basic table structure, I want to be able to display a set of data from an array of objects one at a time. Clicking on a button or something similar would display the next object in the array.
The trick is, I don't want to use the visible tag and just hide the extra data.
simply you can just specify property that indicate the current element you want to display and index of that element inside your observableArray .. i have made simple demo check it out.
<div id="persons"> <span data-bind="text: selectedPerson().name"></span>
<br/>
<button data-bind="click: showNext" id="btnShowNext">Show Next</button>
<br/>
</div>
//here is the JS code
function ViewModel() {
people = ko.observableArray([{
name: "Bungle"
}, {
name: "George"
}, {
name: "Zippy"
}]);
showNext = function (person) {
selectedIndex(selectedIndex() + 1);
};
selectedIndex = ko.observable(0);
selectedPerson = ko.computed(function () {
return people()[selectedIndex()];
});
}
ko.applyBindings(new ViewModel());
kindly check this jsfiddle
Create observable property for a single object, then when clicking next just set that property to other object and UI will be updated.

Categories