redOctober13
redOctober13

Reputation: 3994

Ember: How to cleanly replace model data and have progress indicators

I have a certain route that shows a list of projects, and it gets initial data from my RESTAdapter based on who the user is.

I am now implementing a search function that will issue a new API call so the user can get records besides the default ones for them, and the response should replace the model for that route. I have all that working, but I'm not sure how to do a loading or progress indicator (as the response from the database could potentially take 5-10 seconds depending on the amount of data). I know about loading substates, but in this case I'm not transitioning between routes. I just want to have at minimum a spinner so the user knows that it's working on something.

Would anyone that's done this before be willing to share how they handled a)replacing the model with new data, and b)keeping the user informed with a spinner or something?

Form action called when user clicks the Search button

searchProjects: function() {
  var query = this.get('queryString');
  if (query) {
    var _this = this;
    var projects = this.store.find('project', {q: query});
    projects.then(function(){
      _this.set('model', projects);
    });
  }
}

Upvotes: 4

Views: 1533

Answers (2)

redOctober13
redOctober13

Reputation: 3994

Just in case others want to see, here's my working code based on @lolmaus's answers.

These Docs pages were helpful as well Route's queryParams and Find method

Controller

//app/controllers/project.js
export default Ember.ArrayController.extend({
  queryParams: ['q'],
  q: null,

  actions: {
    searchProjects: function() {
      var query = this.get('queryString');
      if (query) {
        this.set('q', query);
      }
    }
  }
})

Route

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  model: function(params) {
    if (params.q) {
      return this.store.find('project', params);
    } else {
      return this.store.findAll('project');
    }
  },
  queryParams: {
    q: {
      refreshModel: true
    }
  },
  actions: {
    loading: function(/*transition, route*/) {
      var _this = this;
      this.controllerFor('projects').set('showSearchSpinner', true);

      this.router.one('didTransition', function() {
        _this.controllerFor('projects').set('showSearchSpinner', false);
      });

      return true; // Bubble the loading event
    }
  }
});

My issue now is that when I use the parameter query, it works great, but then if I clear the query (with an action, to effectively "go back") then the records fetched by the query stay in the store, so when it does a findAll() I have both sets of records, which is not at all what I want. How do I clear out the store before doing findAll again?

Upvotes: 0

a) replacing the model with new data

You don't need to do anything. If you sideload records properly from the backend, Ember will automatically update them on the frontend.

b) keeping the user informed with a spinner or something

The loading substate is an eager transition. Ember also supports lazy transitions via the loading event.

You can use that event in order to display the spinner.

Here's an example from the docs:

App.ApplicationRoute = Ember.Route.extend({
  actions: {
    loading: function(transition, route) {
      showSpinner();

      this.router.one('didTransition', function() {
        hideSpinner();
      });

      return true; // Bubble the loading event
    }
  }
});

UPD1

I need to do at least what I'm doing right? Setting the model to the response?

You need to reflect the search in the URL via query params. This will let the router automatically update the model for you.

what I would put in showSpinner to affect stuff on the page (like, can I use jQuery to show or hide a spinner element?), or show the actual loading substate.

I would set a property on that page's controller:

App.IndexRoute = Ember.Route.extend({
  queryParams: {
    search: {
      refreshModel: true
    }
  },
  model () {
    return new Ember.RSVP.Promise( resolve => setTimeout(resolve, 1000));
  },

  actions: {
    loading (transition, route) {

      this.controller.set('showSpinner', true);

      this.router.one('didTransition', () => {
        this.controller.set('showSpinner', false);
      });

      return true;
    }
  }
});

App.IndexController = Ember.Controller.extend({
  queryParams: ['search'],
  search:      null,
  showSpinner: false,
});

Demo: http://emberjs.jsbin.com/poxika/2/edit?html,js,output

Or you could simply put the spinner into the loading template, which will hide obsolete data:

http://emberjs.jsbin.com/poxika/3/edit?html,js,output

Or you could put your spinner into the loading template:

Upvotes: 2

Related Questions