Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone Model : Ajax request in parse override

I have a scenario where a fetch() call of a model will return data from which a property will need be passed to another API and return type from that API will be the actually required data.

var Issue = Backbone.Model.extend({
    urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',
    parse: function(response, options){
        var markdown = new Markdown({ text : response.body });
        markdown.fetch({
            contentType: 'application/json',
            type: 'POST',
            data: JSON.stringify( markdown.toJSON() ),
            success: function(data){
                response.body = data;
            }
        });
        return response;
    }
});

var Markdown = Backbone.Model.extend({
    defaults:{
        'text': '',
        'mode' : 'markdown'
    },
    url: 'https://api.github.com/markdown'
});

So, when an Issue will be fetched:

var issue = new Issue({id: 1});
issue.fetch().then(function(){
  //do stuff
});

It will have a property of body containing markdown syntax text which in turn I need to pass to another API and get the that response which will be passed down to view.

As can be seen from above, I tried overriding parse but its return type has to be an object and fetch will be async so what can I do here to make this work?

NOTE: I know aggregating the data in server and then receiving it will be best idea but that is not possible atm.

like image 490
lbrahim Avatar asked Jul 28 '15 10:07

lbrahim


2 Answers

You could override the sync method in your Issue model to chain your requests.

var Issue = Backbone.Model.extend({
    urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',

    sync: function(method, model, options) {
        if (method !== 'read')
            return Backbone.sync.apply(this, arguments);

        // first request
        var xhr = Backbone.ajax({
            type: 'GET', 
            dataType: 'json',
            url: _.result(model, 'url')
        });

        // second request
        return xhr.then(function (resp1) {
            var markdown = new Markdown({text : resp1.body || 'body'});
            var data = markdown.toJSON();

            // the callback to fill your model, will call parse
            var success = options.success;

            return Backbone.ajax({
                url: _.result(markdown, 'url'),
                dataType: 'html',
                contentType: 'application/json',
                type: 'POST',
                data: data
            }).then(function(resp2) {
                // sets the data you need from the response
                var resp = _.extend({}, resp1, {
                    body: resp2
                });

                // fills the model and triggers the sync event
                success(resp);

                // transformed value returned by the promise
                return resp;
            });
        });
    }
});

The options hash passed to Model.sync contains the callbacks to model.parse, you can use it to set the attributes on your model when you're satisfied with your data.

And a demo http://jsfiddle.net/puwueqe3/5/

like image 123
nikoshr Avatar answered Nov 04 '22 22:11

nikoshr


I think you would have to override the model's fetch to get this to work

Consider what the default fetch looks like:

fetch: function(options) {
  options = _.extend({parse: true}, options);
  var model = this;
  var success = options.success;
  options.success = function(resp) {
    var serverAttrs = options.parse ? model.parse(resp, options) : resp;
    if (!model.set(serverAttrs, options)) return false;
    if (success) success.call(options.context, model, resp, options);
    model.trigger('sync', model, resp, options);
  };
  wrapError(this, options);
  return this.sync('read', this, options);
},

(github)

That implementation would not support an async version of model.parse, but since you create a model class using .extend you can override this with your own implementation so lets look at what it does. It takes an options objects, creates a success callback and then delegates to Backbone.Sync.

It's that callback that calls parse and that's what needs to be made to support async.

The quickest, dirtiest way to do this is probably to just copy and modify the existing default fetch.

var MyModel = Backbone.Model.extend({

    fetch: function(options) {
      options = _.extend({parse: true}, options);
      var model = this;
      var success = options.success;
      options.success = function(resp) {

        function parser(resp, options, cb) {
           ...do your async request stuff and call cb with the result when done...
        }

        parser(resp, options, function(result) {
            if (!model.set(result, options)) return false;
            if (success) success.call(options.context, model, resp, options);
            model.trigger('sync', model, resp, options);
        });

      };
      wrapError(this, options);
      return this.sync('read', this, options);
    }

});

This is just an example of how you might try to solve this. I've not tested it and it might not work but I don't see any immediately obvious reasons why it shouldn't.

like image 38
ivarni Avatar answered Nov 04 '22 21:11

ivarni