Knockout Combo

by Ben Clayton

HTML

<div style="height:100px"></div>
<combo-widget params="value:123"></combo-widget>
<HR>
<combo-widget params="value:789"></combo-widget>

CSS

.ncselect {
  position: relative;
}

.optionlist {
  border: 1px solid gray;
  width: 225px;
  max-height: 200px;
  overflow: auto;
  position:absolute;
  z-index:2;
  bottom:28px;
  background-color:#FFFFFF;
}

.selectinput {
  width: 200px;
}

.ipwrapper {
  display: inline-block;
  position: relative;
}

.ipwrapper input {
  height: 20px;
}

.combotrigger {
  font-family: arial;
  font-weight:bold;
  line-height: 24px;
  display: inline-block;
  position: relative;
  background-color: white;
  width: 20px;
  height: 24px;
  cursor: pointer;
  text-align: center;
  border: 1px solid #C0C0C0;
  left: -4px;
  top: 1px;
}

JavaScript

ko.components.register('combo-widget', {
  viewModel: function(params) {
    var self = this;
    self.value = ko.observable(params.value);
    self.optionsOpen = ko.observable(false); 
    self.people = ko.observableArray([{
      firstName: "Bert",
      lastName: "Bertington"
    },{
      firstName: "Bess",
      lastName: "Bessington"
    }]);

    self.openCombo = function() {
    	this.optionsOpen( ! this.optionsOpen() );
    }.bind(this);

    self.addPerson = function() {
      self.people.push({
        firstName: "first ",
        lastName: "last "
      });
    }.bind(this);

    self.selectFromList = function(option) {
      this.value(option.firstName);
      this.optionsOpen(false);
    }.bind(this);

    self.removePerson = function() {
      self.people.remove(this);
    }.bind(this);
    
    self.currentFilter = ko.observable(); // property to store the filter
    self.filterProducts = ko.computed(function() {
        if(!this.currentFilter()) {
            return this.people(); 
        } else {
            return ko.utils.arrayFilter(this.people(), function(option) {
                return option.firstName == this.currentFilter();
            }.bind(this));
        }
    }.bind(this));
    
    self.filter = function() {
    		this.currentFilter( this.value() );
		}.bind(this);
    
  },
  template:
  	'<div class="ncselect">\
    	<div class="optionlist" data-bind="visible: optionsOpen">\
    		<table>\
    			<tbody data-bind="foreach: people" >\
    				<tr data-bind="click: $parent.selectFromList" >\
    					<td data-bind="text: firstName" ></td>\
            	<td data-bind="text: lastName" > </td>\
    				</tr>\
    			</tbody>\
    		</table>\
    	</div>\
    	<div class="ipwrapper" >\
    		<input class="selectinput" type="text" data-bind="value: value, event: { keypress: filter}" />\
        <div data-bind="click: openCombo" class="combotrigger">v</div>\
    	</div>\
    	<button data-bind="click: addPerson">Add</button>\
   ...