Kirk Olson
Kirk Olson

Reputation: 564

Knockout two-way binding to object

I am trying to use Knockout.js to create a two-way binding between a javascript object and some DOM elements. I want my object to reflect the changes in the DOM elements and vice versa. I successfully achieved the first goal, the vice versa part I can't get to work however.

JSFiddle demo

myPerson = {
    firstName : "",
    lastName : "",
    age  : null,
    test : function() {alert('Testing');}
};

myViewModel = function myViewModel(obj) {
    this.firstName = ko.observable();
    this.lastName = ko.observable();
    this.age = ko.observable();
    this.fullName = ko.computed(function() {
        obj.firstName = (this.firstName() || '');
        obj.lastName = (this.lastName() || '');
        obj.age = (this.age() || '');

        return (this.firstName() || '') + ' ' + (this.lastName() || '');
    }, this);
};

ko.applyBindings(new myViewModel(myPerson));



    //How to get property changes reflected in the DOM inputs?

When I make changes in the DOM inputs the properties of the myPerson object change accordingly. Next step is changing these properties manually and automagically see these changes reflected in de DOM inputs. I tried messing around with the mapping plugin but no cigar so far.

Upvotes: 2

Views: 812

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074295

Using just KO, just changing the properties of your myPerson object will not change your viewmodel, and so will not update the DOM.

The "KO way" is to just have the viewmodel, not myPerson separate from it, and update that viewmodel by calling the observables as functions, e.g.:

myViewModel.firstName("TheNewFirstName");

On modern browsers, you could define your properties on myPerson using accessor functions, which could then raise an event or similar that the view model would watch for, but this requires features added in ECMAScript5 (~2009) which are present in modern browsers, but not IE8 and earlier. It also would mean effectively having a double viewmodel (myPerson and myViewModel).

So when using KO, you typically just have the viewmodel itself, and call the observables to update them.

Here's a simple example "the KO way":

var vm = {
  counter: ko.observable(0)
};
ko.applyBindings(vm, document.querySelector('div'));
var timer = setInterval(function() {
  if (vm.counter() >= 20) {
    clearInterval(timer);
  } else {
    vm.counter(vm.counter() + 1);
  }
}, 1000);
<div>
  <span data-bind="text: counter"></span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>


If you're only looking to support modern browsers and want something KO-like that works with plain objects like your myPerson, you might look at RivetsJS, which applies getters/setters to your objects for you.

Here's that same simple example done with Rivets — but it won't work on pre-ECMAScript5 engines:

var obj = {
  counter: 0
};
rivets.bind(document.querySelector('div'), obj);
var timer = setInterval(function() {
  if (obj.counter >= 20) {
    clearInterval(timer);
  } else {
    ++obj.counter;
  }
}, 1000);
<div>
  {counter}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rivets/0.8.1/rivets.bundled.min.js"></script>

(This is not a recommendation, just noting the option. I've happily used KO in proper projects; never had the chance to do the same with Rivets.)

Upvotes: 2

Related Questions