I have a page with a select and an input-box being bound to the same value. The idea is that normally one would select a value from the select, however, the user should also be able to enter an arbitrary string in the input-box. The problem is that if I enter something not present in the select, because of the binding, the value is set to the first item in the select.
This is the behavior I want to achieve:
User selects value from select
Value is set to selected item.
Input is updated with selected value.
User enters text in input
Value is set to entered text.
Select does not change unless Value is present in the collection of available values.
In other words, what I want is for the last changed control to be the valid Value. But I also want both controls to be up to date as long as a given value is valid for that control.
My code looks like this:
js
var viewModel = { Value: ko.observable('1'), Set: ['1', '2', '3'] };
ko.applyBindings(viewModel);
html
<!-- ko if: Set.length > 1 || (Set.length > 0 && Set[0] != '') -->
<select type="text" class="form-control input-small" data-bind="options: Set, value: Value">
</select>
<!-- /ko -->
<input class="form-control input-small" data-bind="value: Value" style="margin-top: 5px;" />
Here is a jsfiddle showing how the code currently works: http://jsfiddle.net/b2RwG/
[Edit]
I've found a working solution (http://jsfiddle.net/b2RwG/2/), however it's really not pretty, and there has to be a better way to solve this problem.
As you can see I add an inputValue observable that is bound to the text input.
I also add an computed named virtualSet that contains both original items and the new item (from the text input).
I susbcribe to the inputValue so the select will be automatically set when you are typing.
var viewModel = {
inputValue: ko.observable('1'),
Value: ko.observable('1'),
Set: ['1', '2', '3']
};
viewModel.virtualSet = ko.computed({
read: function () {
var vs = this.Set.slice(0);
if (this.inputValue() && this.inputValue().length)
vs.unshift(this.inputValue());
return vs;
},
owner: viewModel
});
viewModel.inputValue.subscribe(function (value) {
viewModel.Value(value);
});
See fiddle
I hope it helps.
You can have the select use a computed observable instead, which updates only if the value makes sense.
I made an example where i added a caption to the select. The result is that it doesn't automatically pick the first value, but instead tries to set undefined value, when it reads a value that isn't included in the Set array.
<select type="text" class="form-control input-small" data-bind="options: Set, value: SelectValue, optionsCaption: 'Other value'"></select>
To do that, a constructor function instead of an object literal will make it easier, because then you can access the Value observable through the self reference.
function ViewModel() {
var self=this;
this.Value = ko.observable('1');
this.Set = ['1', '2', '3'];
this.SelectValue= ko.computed({
read: function() {
var val = self.Value();
return val;
},
write: function(value) {
if(value) self.Value(value);
}
});
}
See http://jsfiddle.net/b2RwG/4/
Related
I have a custom X-Select that acts like a select input. It expects two props - value and options.
The problem is that when options are reloaded in the parent component (root), they are usually prepended with a new item. In that case, select changes its label (like the index stayed the same) even if value hasn't changed.
Example:
options = [{value:'3', text:'3'},{value:'2', text:'2'},{value:'1', text:'1'},}]
current value = '2', current label = '2'
// options have been reloaded and one more item appeared at the beginning
options = [{value:'4', text:'4'},{value:'3', text:'3'},{value:'2', text:'2'},{value:'1', text:'1'},}]
current value = '2', current label = '3'
As you can see the value now does not correspond to the label. I think it's because something wasn't refreshed properly (label '2' was on index 1 and now label '3' is on that index).
<template>
<select id="type" name="type" :value="value" #input="$emit('input',$event.target.value)"
class="select form-control w-75 mx-auto">
<option v-for="option in options" :value="option.value">
{{ option.text }}
</option>
</select>
</template>
<script>
module.exports = {
name: "XSelect",
props: ['options', 'value'],
}
</script>
ROOT
<x-select :key="form.sourceUniAccountId" v-model="form.sourceUniAccountId"
:options="sourceInputOptions"
class="form-select"></x-select>
COMPUTED
sourceAppUniAccounts() {
if (!this.userUniAccounts) return []
return this.userUniAccounts.filter(obj => obj.name_or_provider === this.journeySourceAppName)
},
sourceInputOptions() {
return this.sourceAppUniAccounts.map(obj => ({text: obj.identifier, value: obj.uni_account_id}))
}
The variable userUniAccounts is the one that is in fact reloaded.
Do you know how to make this work? Setting key to combination of value and options works but I think there is a more "elegant" option.
<x-select :key="form.sourceUniAccountId+sourceInputOptions.length" v-model="form.sourceUniAccountId"
:options="sourceInputOptions"
class="form-select"></x-select>
I am working on "Form Builder" application, where I can set the rules for each control.
I am trying to select the dropdown value from selectedValue which I kept as object. But it is not working.
To understand my question in depth, here is my previous question where you get the clear understanding of my question :
Show/Hide based on Dropdown selection in knockoutJS with model inside another model
Here is my fiddle which I worked out for filling the dropdown values(Current question) :
https://jsfiddle.net/vikash208/6b6ntoam/20/
What I tried :
HTML Code:
<div class="tab-content" data-bind="with: TextBoxModel">
<div class="tab-pane" id="rules" data-bind="with: rules">
<div data-bind="foreach: ruleList">
<div class="form-group col-md-12">
<div class="col-sm-3">
<select class="form-control" data-bind="options: $parent.ruleConditions, optionsText: 'Name', value: selectedCondition, optionsCaption: 'Choose condition'"></select>
</div>
<div class="col-md-5" data-bind="visible: isExpressionValueRequired()">
<input type="text" class="form-control" data-bind="value: expressionValue" />
</div>
</div>
</div>
KnockoutJS of assigning selectedOption:
var RuleConditionArray = {
// Options for "Text Field" under Rules tab
textField: ko.observableArray(
[
{ Name: 'is filled out',
Value: 'isfilledout',
isExpressionValueRequired: false
},
{
Name: 'is not filled out',
Value: 'isnotfilledout',
isExpressionValueRequired: true
}
]
)
};
function Rule() {
var rule = this;
//For Expression input field
rule.expressionValue = ko.observable("");
//Keep track of selected ruleConditions - returns "Condition object"
rule.selectedCondition = ko.observable({ Name: 'is filled out', Value: 'isfilledout', isExpressionValueRequired: false });
//This is computed value to show/hide Expression input field
rule.isExpressionValueRequired = ko.computed(function () {
return this.selectedCondition() && this.selectedCondition().isExpressionValueRequired
}, this);
};
From the above knockoutJS code, I am passing
{
Name: 'is filled out',
Value: 'isfilledout',
isExpressionValueRequired: false
}
object inside selectedCondition. But it is not working.
What I want:
I need to pass object as parameter to select the dropdown Value. Kindly help me to solve this.
Your <select> element has an array of options passed to it by the options binding. Each option represents an instance of an object. When you select an option, the value binding makes sure the object is written to selectedCondition.
The other way around, when you set your selectedCondition, knockout looks within the array of options to see if it's an option that's already known. If it can find it, it will update the UI.
Now, the problem here, is that you're setting the selectedCondition to an object that looks the same as one of the options, but is not one of the actual option instances.
Knockout doesn't use a (deep) object comparison method for looking up the selected object, it just does something along the lines of: options.indexOf(newSelection). Since you've created a new object, this won't work.
Solution
Pass an actual reference to the selectedCondition, like so:
rule.selectedCondition = ko.observable(ruleModel.ruleConditions()[0]);
If you want to add a new option, push it to the options first:
var newOption = {
Name: ko.observable(""),
Value: ko.observable(""),
isExpressionValueRequired: ko.observable(false)
};
ruleModel.ruleConditions.push(newOption);
selectedCondition(newOption);
I have the following in my view
<div>
<select ng-model="obj.arr[otherObj.variable]" ng-change="otherObj.variable=SOMETHING">
<option ng-repeat="label in obj.arrs">{{label}}</option>
</select>
</div>
Without the ng-change attribute, this code does what I want when otherObj.variable is one of the indexes of the obj.arr - it selects the correct item in the list.
What I want in addition to this is to set otherObj.variable to the index of the array item that is picked when the dropdown variable is changed. So, if the second value in the dropdown is picked then otherObj.variable should be set to 1. I tried to do this with a
ng-change="otherObj.variable=SOMETHING"
Problem is., I don't know what that SOMETHING should be. Am I doing this right?
EDIT
My requirements are
Select the top option in the dropdown by default
select the appropriate item in the array depending on the value of otherObj.variable (this gets set by some external code so if I come to the page with this value set then I want the correct option selected)
Make sure otherObj.variable is updated if I change the value in the dropdown.
angular.module('selects.demo', [])
.controller('SelectCtrl', function($scope){
$scope.values = [{
id: 1,
label: 'aLabel',
}, {
id: 2,
label: 'bLabel',
}];
$scope.selectedval = $scope.values[0];
});
<script src="https://code.angularjs.org/1.3.15/angular.js"></script>
<div ng-app="selects.demo">
<div ng-controller="SelectCtrl">
<p>Using ngOptions without select as:</p>
<select ng-model="selectedval" ng-options="value.label for value in values"></select>
<p>{{selectedval}}</p>
<p>Using ngOptions with select as statement: (this will return just the id in the model)</p>
<select ng-model="selectedval2" ng-options="value.id as value.label for value in values"></select>
<p>{{selectedval2}}</p>
</div>
</div>
Sorry if my comment was a little cryptic. Select elements like other form elements are actually directives in AngularJS, so they do a lot of stuff for you automatically. You don't need to use an ngChange to populate the ngModel associated with your select element. AngularJS will handle that for you.
Also, you can use ngOptions instead of ngRepeat on select elements to generate the values automatically on options.
Assuming that you have an object with values:
$scope.values = [{
id: 1,
label: 'aLabel',
}, {
id: 2,
label: 'bLabel',
}];
You would write:
<select ng-model="selectedval" ng-options="value.label for value in values"></select>
Now your ngModel is going to be bound to the selected element. It will be set with the value of the object that was chosen. If you add {{selectedval.id}} to your view, it will display the id of the selected element.
If you want to set the value to the first item, in your controller, you would add:
$scope.selectedval = $scope.values[0];
If you want to update some property on $scope.values based on the selected value, you could use something like:
$scope.addActiveProp = function() {
var selected = $scope.values.filter(function(e) { return e == $scope.selectedval; });
selected.active = true;
}
And then run the addActiveProp fn in ngChange on the select.
Please give a try with below code
<select ng-model="obj.arr[otherObj.variable]" ng-change="otherObj.variable=key" ng-options="key as value for (key , value) in obj.arrs"></select>
I am using knockout.js, and it's not setting the value of an empty option (Four):
<select data-bind="value: item.widgetValue, attr: {id: item.widgetName, name: item.widgetName}, options: item.options, optionsText: ‘label’, optionsValue: ‘value’” id=”fld-“ name=”fld0”>
<option value=”one”>One</option>
<option value=”two”>Two</option>
<option value=”three”>Three</option>
<option value>Four</option>
...
</select>
This is creating a problem: when you're on any option and try to select Four, it selects One; it will only select Four the second time you try to select it.
I have tried changing the knockout data-bind to fix it:
value: $.trim(item.widgetValue)
This allows you to select Four immediately, but incorrectly shows One as being selected after you submit the form with Four selected.
Any ideas as to what could be causing this, or how to fix it?
You shouldn't be manually setting options if you are using the options binding on your select element. If those are being dynamically created by the binding (ie. you are actually using item.options for your source) then check the objects you are binding the select element to -
item.options probably looks like this (missing a value or is somehow not like the other options) -
item.options = [
{ label: 'someLabel1', value: 'someValue1' },
{ label: 'someLabel2', value: 'someValue2' },
{ label: 'someLabel3', 'someValue3' }
];
but should be a more uniform object like this (well defined model) -
function optionModel(label, value) {
var self = this;
self.label = ko.observable(label);
self.value = ko.observable(value);
}
item.options = [
new optionModel('someLabel1', 'someValue1'),
new optionModel('someLabel2', 'someValue2'),
new optionModel('someLabel3', 'someValue3')
];
Help me please with knockout.js problem:
Why variable currentObject is undefinded ? How I can save current selected object in some variable ?
I have follow html view for down drop list:
<select data-placeholder="Select object" class="span5" id="objects" data-bind="options: objects, optionsText: 'Name', optionsValue: 'Id', value: currentObject">
<option></option>
</select>
ModelView:
function baseViewModel() {
self.objects = ko.observableArray([]);
...
self.currentObject = ko.observable();
...
self.func = function() {
//allert(self.objects()[0].Name) //return correct Name
alert(self.currentObject().Name) //returns undefinded
}
}
In your data-bind, you have value: currentObject which will indeed do a two-way bind between currentObject and the select's value.
The select's value is set to the Id field of the selected option's object (because of optionsValue: 'Id' in your data-bind).
So, currentObject will be set to the Id field of the selected object, and that's why doing .Name gets you undefined.
I suggest not using optionsValue at all, this way KO will handle the value and it will be as if the value of the selectbox is the actual selected object, and value: currentObject will correctly set currentObject to the selected object. (And if you do want to use optionsValue, then know that currentObject will be set to the object's field, not the object itself)
Fiddle: http://jsfiddle.net/antishok/KXhem/78/