Alxandr
Alxandr

Reputation: 12431

Knockout select and textbox sharing binding

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

  1. Value is set to selected item.
  2. Input is updated with selected value.

User enters text in input

  1. Value is set to entered text.
  2. 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.

Upvotes: 2

Views: 1934

Answers (2)

dan.p
dan.p

Reputation: 416

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/

Upvotes: 0

Damien
Damien

Reputation: 8987

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.

Upvotes: 1

Related Questions