Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I show a spinner while waiting for an AJAX request in Mithril JS?

I'm using Mithril JS in a project and I'm having trouble understanding exactly how to go about hooking into the Ajax lifecycle. Like if I have an Ajax request takes awhile, I want to show a spinner. Pretty basic, but I can't seem to figure out how that can happen.

I want to use the same container for the spinner as the content that the Ajax request is looking for.

Here's my setup:

var Thing = function (data) {
  var p = m.prop;
  this.title = p(data.title);
  this.timestamp = p(moment.unix(data.timestamp));
}

Thing.list = function(options) {
  m.request({method: "GET", url: "/things.json", type: Thing, background: true});
};

MyApp.components.thingsList = {
  controller: function ThingListController() {
    this.things = m.prop([]);
    Thing.list().then(this.things).then(m.redraw);
  },

  view: function thingListView(ctrl) {
    return m('div#thing-tab', [
      m('ul#things', [
        ctrl.things().map(thingView)
      ])
    ]);
  }
};

function thingView(thing) {
  ...some view stuff...
}

I've got it working the way I want, but I just can't figure out how to hook into the ajax lifecycle. Again, I just wanna show a spinner when the request starts and then replace that with the result of the ajax request.

Any and all help is greatly appreciated!

Thanks,

like image 438
Jeff Miller Avatar asked Aug 18 '14 20:08

Jeff Miller


People also ask

Which object can be used to retrieve data in Ajax?

XMLHttpRequest (XHR) objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing. XMLHttpRequest is used heavily in AJAX programming.

When working with Ajax which is faster?

They are all equally fast, the only question is which you find most readable. If you will be making numerous similar ajax calls then it is best to use $. ajaxSetup() in an accessible place (read: near top).


2 Answers

One way is to wrap m.request in another function that returns both the completion state (based on a flag that you set via the m.request promise chain), and the data, and then use the background: true option to prevent the deferral of the redraw, and also bind m.redraw to the promise chain in order to have redrawing happen after the request.

This was originally described here: https://github.com/lhorie/mithril.js/issues/192

var requestWithFeedback = function(args) {
  var completed = m.prop(false)
  var complete = function(value) {
    completed(true)
    return value
  }
  args.background = true
  return {
    data: m.request(args).then(complete, complete).then(function(value) {
      m.redraw()
      return value
    }),
    ready: completed
  }
}

var MyController = function() {
  this.things = requestWithFeedback({method: "GET", url: "/things"})
}
var myView = function(ctrl) {
  return !ctrl.things.ready() ? m("img[src=loading.gif]") : m("ul", [
    ctrl.things.data().map(function(thing) {
      return m("li", thing.name)
    })
  ]) 
}

m.module(document.body, {controller: MyController, view: myView})
like image 177
LeoHorie Avatar answered Oct 21 '22 18:10

LeoHorie


I found what I think is an elegant way to do this, based on the principle that Mithril re-renders the entire UI (with differencing) on model update. The following example is for saving an inline update.

When I have some part of the model which is changing via AJAX, I set a temporary flag in the model (you can as easily do it in the view-state model if you want to keep it separate), and on completion, I just delete the flag and invoke m.redraw():

function updateRecord(ctl,evt,row,idx,rcd) {
    rcd._action="save";
    apiSender({ method: "PATCH", url: apiUrl, data: dropFlags(rcd) }).then(done,fail).then(null,logObject);

    function done(rspdta) {
        delete rcd._action;
        m.redraw();
        };

    function fail(rspdta) {
        ajaxError(ctl,rspdta,"Update customer "+rcd.CustomerId+" ("+rcd.ContactName+")");
        rcd._action="edit";
        m.redraw();
        };
    }

In the view, which is rebuilt from the model data, I condition on the flag:

if     (rcd._action=="edit"   ) { con=crtListRecordView_Edit   (rcd,idx             ); }
else if(rcd._action=="save"   ) { con=crtListRecordView_Waiting(rcd,idx,"Saving"    ); }
else if(rcd._action=="delete" ) { con=crtListRecordView_Waiting(rcd,idx,"Deleting"  ); }
else if(rcd._action=="merge"  ) { con=crtListRecordView_Waiting(rcd,idx,"Merging"   ); }
else if(rcd._action=="refresh") { con=crtListRecordView_Waiting(rcd,idx,"Refreshing"); }
else                            { con=crtListRecordView_Normal (rcd,idx             ); }
return m("tr", con);

This allows multiple concurrent actions on different records, and a polished, clear and unobtrusive feedback to the user.

Here's what it looks like:

Normal:

Normal

Editing:

Editing

Saving:

Saving

like image 37
Lawrence Dol Avatar answered Oct 21 '22 19:10

Lawrence Dol