lundi 26 mars 2018

ember.js page context with multiple nested routes and hasMany relationships

I'm brand new to ember.js (started with 3.0) and struggling to achieve what I would expect is a fairly conventional use case without using what seems like unconventional methods or soon to be deprecated features.

The use case in Ember is to display, and paginate, multiple related resources on a parent resources page.

I'm really enjoying using Ember so far but a little exasperated that such a simple use case has been so difficult to overcome. Been ok with the learning curve with everything else so far, but I've hit a wall here.

I have a JSON API backend, with a reasonably complex, (mostly) normalized DB where there are a few key resources and multiple join tables describing the various ways they can relate to each other.

Stripped down setup below:

Models:

// app/models/label.js
export default DS.Model.extend({
  name: DS.attr(),
  contactInfo: DS.attr(),
  releases: DS.hasMany('release'),
  artists: DS.hasMany('artist'),
  //...
});

// app/models/artist.js
export default DS.Model.extend({
  name: DS.attr(),
  realname: DS.attr(),
  profile: DS.attr(),
  labels: DS.hasMany('label'),
  //...
});

// app/models/release.js
export default DS.Model.extend({
  title: DS.attr(),
  released: DS.attr(),
  label: DS.belongsTo('label'),
  //...
});

Router:

  // app/router.js
  //...

  // labels
  this.route('labels', function() {
    this.route('label', {path: '/:id'}, function(){
      this.route('artists');
      this.route('releases');
    });
  });
  // artists
  this.route('artists', function() {
    this.route('show', {path: '/:id'});
  });
  // releases
  this.route('releases', function() {
    this.route('show', {path: '/:id'});
  });
  // ...

Routes:

// app/routes/labels/label.js
export default Route.extend({
  model(params){
    return this.store.findRecord('label', params.id)
  }
});

// app/routes/labels/label/artists.js
export default Route.extend({
  model(){
    let label = this.modelFor('labels.label')
    return label.query('artists')
  },
});

// app/routes/labels/label/releases.js
export default Route.extend({
  model(){
    let label = this.modelFor('labels.label')
    return label.query('releases')
  },
)};

Templates:

// app/templates/application.hbs
<h1> Welcome to Ember! </h1>


// app/templates/labels/label.js
<h2></h2>

<h4>Artists</h4>
// insert sensible way to render artists here

<h4>Releases</h4>
// insert sensible way to render releases here


Strategies I've attempted so far:

  1. Calling a labels related artists and releases directly in the labels/label template, ignoring the nested routes for relations and passing the promise into a component, or using

This gets the context lazily onto the page, but pagination here was a nightmare. Using the ember-data-has-many-query extension I managed to cobble together a controller action for the labels.label route:

// app/controllers/labels/label.js
export default Controller.extend({
  queryParams: ['artistsPage'],
  artistsPage = 1,
  actions:{
    pageArtists(direction){
      // console.log(direction)
      const label = this.currentModel;
      if(direction == 'forward'){
        let artists = label.query('artists', {page: this.artistsPage+1});
        this.set('artistsPage', this.artistsPage+1);
      }else if (direction == 'back'){
        let artists = label.query('artists', {page: this.artistsPage-1});
        this.set('artistsPage', this.artistsPage-1);
      }

    }
  }
 });

This is very broken though in that the route knows nothing about the param being set in the action, so on any page refresh they decouple. I abandoned trying to get the labels.label routes model hook to play nicely with the controller params in lieu of another approach:

  1. Using nested routes (as shown above) to handle the relations independently of the parent, and load them in individually into the label route.

Status Quo:

I'm attempting to use named outlets, but haven't been able to get the context onto the page without hitting a single related resources url, eg /labels/123/artists or /labels/123/releases.

So :

  // app/routes/labels/label/artists.js
  // ...
  renderTemplate(){
    this.render({
      'into':'labels/label',
      'outlet':'artists'
    })
  }

Will get the context into the named outlet in templates/labels/label, but I can't repeat this for the releases relation because we are at /labels/123/artists.

Attempting to set renderTemplate in the labels.label route, ie:

this.render('labels/label/artists',{
  into: 'label/label',
  outlet: 'artists',
  controller: 'labels.label.artists'
})

doesn't actually render the route and/or send it to the outlet.

Ultimately I'm hoping to use a pagination approach similar to the one described by Balint Erdi here which implements nested routes and some custom adapter logic to handle the pagination for related resources.

The desired behaviour is for the relationship contexts to be displayed, along with pagination at the url labels/123.

I've exhausted looking through the guides, API documentation, discourse forum and here, and still at a loss.

What (assuming multiple things) am I doing wrong here?




Aucun commentaire:

Enregistrer un commentaire