0

I have a method which returns promise with an array of complex objects via yelp api. I need to bind it with markup by data-bind="foreach: objects", but I can't. I need to understand how to bind data in markup, and how to work with promises in observable arrays. Can anyone help?

//getDataForPlaces
var getDataForPlaces = function(addresses){
 return Promise.all(Array.prototype.map.call(addresses, function(address)    {
      return getLocationDesc(address);
   }));
 
};


//getLocationDesc

var getLocationDesc = function(address){
 return new Promise(function(resolve, reject) {
   var parameters = [];
            parameters.push(['sort', sort]);
            parameters.push(['limit', limit]);
            parameters.push(['radius_filter', radius_filter]);
            parameters.push(['actionlinks', actionlinks]);
            parameters.push(['location', address]);
            parameters.push(['callback', 'callback']);
            parameters.push(['oauth_consumer_key', auth.consumerKey]);
            parameters.push(['oauth_consumer_secret', auth.consumerSecret]);
            parameters.push(['oauth_token', auth.accessToken]);
            parameters.push(['oauth_signature_method', 'HMAC-SHA1']);

            var message = {
                'action' : 'http://api.yelp.com/v2/search',
                'method' : 'GET',
                'parameters' : parameters
            };

            OAuth.setTimestampAndNonce(message);
            OAuth.SignatureMethod.sign(message, accessor);

            var parameterMap = OAuth.getParameterMap(message.parameters);
            $.ajax({
                url : message.action,
                cache : true,
                method : message.method,
                data : parameterMap,
                dataType : 'jsonp',
                jsonp : 'callback',
                success : resolve,
                error : reject
            });
        });
 };


//View model

function MapViewModel(){
 var self = this;
 self.categories = ["Choose option", "Bars", "Gyms"];
 var addresses = ["address","address, address",
  "address","address",
  "address"]; 
 var yelp = new YelpDataProvider();

 self.places = ko.observableArray();

 yelp.getDataForPlaces(addresses).then(function(place){
  self.places(place);
 })
}

ko.applyBindings(new MapViewModel());
<ul data-bind="foreach: places ">
  <li data-bind="text: business[0].name"></li>
</ul>

complex object: enter image description here

Pavel Kononenko
  • 318
  • 3
  • 15
  • Looks like you're in the right way. The similar code works as expected http://jsfiddle.net/xe9geqhy/2/ What do you have here `self.places(place);` Seems like, you fill an observableArray with object(not array) and then, in the markup, you try to iterate by it. – Sergey May 17 '16 at 10:31
  • I used your advice, it works, but how to get the object in the markup?
    – Pavel Kononenko May 17 '16 at 11:31
  • What do you mean `how to get the object in the markup`? Could you provide the `self.places` array? – Sergey May 17 '16 at 11:34
  • How do I iterate this JSON(see the picture with json model above) using Knockout.js foreach binding?
    – Pavel Kononenko May 17 '16 at 11:36
  • As far as I understand, you can use something like this http://jsfiddle.net/xe9geqhy/3/ – Sergey May 17 '16 at 11:42
  • i think you are looking something similar as [How to use knockout to iterate over an object (not array)](http://stackoverflow.com/questions/14838135/how-to-use-knockout-to-iterate-over-an-object-not-array) – Madhu Ranjan May 17 '16 at 14:41

1 Answers1

0

There's a bit of a conceptual issue going on here.

If MapViewModel() is a constructor, it will be called with new. However, the getLocationDesc() aspect of the constructor is asynchronous, with the consequence that, as written, new MapViewModel() will return an object which is effectively still under construction, and with no access to a promise to signify completion of the async process.

Constructors and asynchronism don't mix.

A workaround is to put the async stuff inside a public .getLocationsAsync() method. Something like this maybe :

function MapViewModel() {
    var self = this;
    self.categories = ["Choose option", "Bars", "Gyms"];
    self.places = ko.observableArray();
    var addresses = ["address", "address, address", "address", "address", "address"];
    var yelp = new YelpDataProvider();
    var locationsPromise = null;// a var in which to cache the promise created by this.getLocationsAsync()
    this.getLocationsAsync = function() {
        if(!locationsPromise) {
            locationsPromise = Promise.all(addresses.map(yelp.getLocationDesc)).then(function(placeDescriptions) {
                placeDescriptions.forEach(function(p) {
                    places.push(p);
                });
                return places;
            });
        }
        return locationsPromise;
    };
}

Probably not 100% correct but hopefully good enough to illustrate the idea.

Now call as follows :

var mapViewModel = new MapViewModel();
mapViewModel.getLocationsAsync().then(function(places) {
    // places is an observableArray
});

Note: To be truly useful, you probably want to pass addresses and categories to MapViewModel(), otherwise every instance will be identical. Or maybe MapViewModel() should be rephrased as a singleton?

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44