IkeDoud
IkeDoud

Reputation: 87

KnockoutJS Observable being rebound appropriately, but calling gives an empty string?

So I've got a section that updates based on a ko.observable called toClicked, see below:

  <div id="longInfoWindow">
     <div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
     <div id="location_mainInfo">
        <h1 id="location_title" data-bind="text: toClicked.title">Title</h1>
        <h2 id="location_address" data-bind="text: toClicked.address">Address</h2>
        <h6 class="location_latlng">LAT: <span data-bind="text: toClicked.lat">LATITUDE</span></h6>
        <h6 class="location_latlng">LNG: <span data-bind="text: toClicked.lng">LONGITUDE</span></h6>
        <p id="location_description" data-bind="text: toClicked.content">
           Empty
        </p>
     </div>
  </div>

toClicked is data-bound via a for-each ul that passes in bits of information from an observableArray, so this data is constantly changing - here's what the click function looks like in the viewmodel.

var viewModel = {
  // Nav open and close via knockoutjs
  self : this,

  userQuery : ko.observable(''),
  toClicked : ko.observable({}),

  logClick : function(clicked){
    var toClicked = ko.observable({});
    toClicked().title = clicked.title;
    toClicked().lat = clicked.lat;
    toClicked().lng = clicked.lng;
    toClicked().placeID = clicked.placeID;
    toClicked().address = clicked.address;
    toClicked().content = clicked.content;
    return toClicked();
  }
};

// at the end of the document...
ko.applyBindings(viewModel);

For some reason, I can call any toClicked parameter, like toClicked.title, and I get the proper output. But I can't get anything to bind in the longInfoWindow bit of code, it removes the filler text with empty strings. Is there something I'm missing here with Knockout that's keeping it from updating appropriately?

As a side note, I've tried setting the databinds to viewModel.toClicked.title with no joy. Have also tried $root, $parent. either comes back as not defined or gives the same result.

Upvotes: 0

Views: 47

Answers (2)

Mathias Mamsch
Mathias Mamsch

Reputation: 343

The more obvious way without having to use valueHasMutated would be to assign directly to the observable:

  self.logClick = function(clicked) {
    self.toClicked({ 
        lat: clicked[0].lat, 
        lng: clicked[0].lng, 
        placeID: clicked[0].placeID, 
        adress: clicked[0].address,
        content: clicked[0].content
    }); 
  };

You normally do not need to use valueHasMutated when using knockout. Also there is no need to return the observable from the click handler. In your bindings you need then to access the properties as already stated like this:

<h1 id="location_title" data-bind="text: toClicked().title">Title</h1>

Knockout will automatically update the heading, whenever toClicked changed.

Upvotes: 0

Nisarg Shah
Nisarg Shah

Reputation: 14561

You need to change the way toClicked is accessed. Given that it is an observable, it must access the properties using syntax like toClicked().property. Another problem is that you should specify toClicked as an object, before setting properties on it.

Also, since clicked is an array, it should be accessed by index, like clicked[0].title.

Note the use of self.toClicked.valueHasMutated(); in function logClick. It tells the view model that observable variable toClicked has some non-observable properties that might have changed. As a result the view model is updated. You should avoid using it excessively.

var viewModel = function() {
  // Nav open and close via knockoutjs
  var self = this;

  self.test =  ko.observable('text');

  self.userQuery = ko.observable('');
  self.toClicked = ko.observable({});

  self.markers = ko.observableArray([
    { title: 'Eagle River Airport', lat: 45.934099, lng: -89.261834, placeID: 'ChIJdSZITVA2VE0RDqqRxn-Kjgw', content: 'This is the Eagle River Airport. Visit us for fly-ins!' }
  ]);

  self.logClick = function(clicked) {
    // var toClicked = ko.observable({});
    self.toClicked().title = clicked[0].title;
    self.toClicked().lat = clicked[0].lat;
    self.toClicked().lng = clicked[0].lng;
    self.toClicked().placeID = clicked[0].placeID;
    self.toClicked().address = clicked[0].address;
    self.toClicked().content = clicked[0].content;
    self.toClicked.valueHasMutated();
    return self.toClicked();
  };
};

// at the end of the document...
var vm = new viewModel();
ko.applyBindings(vm);
var markers = vm.markers();
vm.logClick(markers);

Your view model must also change slightly. Note the () brackets after toClicked, they are used to access the properties of an observable.

<div id="longInfoWindow">
 <div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
 <div id="location_mainInfo">
    <h1 id="location_title" data-bind="text: toClicked().title">Title</h1>
    <h2 id="location_address" data-bind="text: toClicked().address">Address</h2>
    <h6 class="location_latlng">LAT: <span data-bind="text: toClicked().lat">LATITUDE</span></h6>
    <h6 class="location_latlng">LNG: <span data-bind="text: toClicked().lng">LONGITUDE</span></h6>
    <p id="location_description" data-bind="text: toClicked().content">
       Empty
    </p>
 </div>

I'd also suggest that instead of accessing toClicked directly within logClick you use something like self.toClicked to avoid ambiguity. See this.

Here's the working fiddle: https://jsfiddle.net/Nisarg0/hx0q6tt6/13/

Upvotes: 1

Related Questions