Ember.JS роутинг через модели

Под таким незамысловатым названием скрывается идея меню по моделям с учетом текущего роута. Небольшая демка:

Demo

Что же происходит на картинке выше? Есть три сущности одной модели (Thrall, Khadgar и Malfurion), у каждой есть три поля — classes, affiliation и locations. Каждому пункту левого меню соответствует роут users/:user_id (почему такие персонажи идут под сущностью user, не спрашивайте). А верхнее меню — это users/:user_id/classes, users/:user_id/affiliation и users/:user_id/locations соответственно.

При заходе на страницу персонажа «извне» (например, из роута users), пользователя перенаправляет на вкладку Classes, а вот при переходе с одного персонажа на другого (через левое меню), пользователь попадает на ту же вкладку, где был до этого. То есть, со страницы Thrall Locations пользователь через меню слева может попасть на Malfurion Locations и Khadgar Locations. Тоже самое для Classes и Affiliation.

Как такое можно реализовать? Есть несколько вариантов:

  • Сделай свой helper, аналогичный встроенному link-to (#), который будет проверять текущий роут и делать перенаправление. Этот способ достаточно громоздкий, так как link-to достаточно сложный компонент, чтоб писать его аналог. Если же его расширять, то придется переопределять большое количество методов и обработчиков событий. При чем, это переопределение будет дублированием кода с исходного компонента и заменой пары строк в них.
  • «Вложить» левое меню в шаблон роутов users/:user_id/*. В таком случае не будет проблемы с получением пути для link-to (так как он соответствует текущему роуту). Нужно будет только «подложить» правильную модель. Вроде бы вот оно — решение. Но нет. Такой подход нарушает идею вложенности роутов. Роут для одной модели не должен ничего знать про коллекцию моделей, которая (по-хорошему) должна лежать в родительском роуте (users). Да и обработка вложенных роутов получается слишком громоздкой:
          // app/routes/users/user/classes.js
          import Ember from 'ember';
          export default Ember.Route.extend({
            model(user) {
              return Ember.RSVP.hash({
                single: this.get('store').findRecord('user', user.user_id),
                all: this.modelFor('users')
              });
            },
            setupController(controller, model) {
              controller.set('model', model.single);
              controller.set('all', model.all);
            }
          });

    В добавок еще придется решать, как быть с роутом users. На нем нельзя отобразить левое меню, так как оно будет рисоваться во вложенном роуте. Но ведь пользователю надо же дать возможность попасть на страницы персонажей из «общей» страницы. Вопросов больше, чем ответов.

  • Последний вариант — это на этапе выхода с роута персонажа смотреть, а куда пользователь переходит. Если он идет на роут с другим персонажем, то можно туда передать «весточку» с пожеланием, куда перенаправить пользователя.

При переходе с роута у него вызывается willTransition:

// app/routes/users/user/classes.js тоже самое для affiliation.js и locations.js
 
import Ember from 'ember';
 
const {set, get} = Ember;
 
export default Ember.Mixin.create({
 
  actions: {
    willTransition(transition) {
      if (transition.targetName === 'users.user') {
        let handler = this.router.router.getHandler('users.user');
        set(handler, 'childRouteToCross', get(this, 'routeName'));
      }
      return this._super(...arguments);
    }
  }
 
});

В роуте users.user в afterModel будет следующие:

// app/routes/users/user.js
 
import Ember from 'ember';
 
const {set, get, A, isEmpty} = Ember;
 
export default Ember.Mixin.create({
 
  /**
   * Child route to redirect user
   *
   * @type {string}
   */
  childRouteToCross: '',
 
  afterModel(model) {
    let childRouteToCross = get(this, 'childRouteToCross');
    let models = this._getModels(model);
    let customArgsForTransition = [childRouteToCross, ...models];
    if (childRouteToCross) {
      return this.transitionTo(...customArgsForTransition);
    }
    return this._super(...arguments);
  }
 
});

Что за функция _getModels? Она возвращает массив с моделями, которые соответствуют каждой динамической части всего роута. Пока что мы рассматривали вариант роута users.user, где только одна динамическая часть. Но ведь вполне возможная ситуация, когда будет роут вида games.game.users.user, где динамических частей будет уже две — game и user (game_id и user_id соответственно). Тогда необходимо пройти по всем родительским роутам и взять их context:

_getModels(model) {
  let handlers = this.router.router.currentHandlerInfos || [];
  let models = A(handlers.filter(h => keys(h.params).length)).mapBy('context');
  if (isEmpty(models)) {
    return [model];
  }
  models[models.length - 1] = model;
  return models;
},

В собранном виде демо-приложение доступно по ссылке #. Example1 — это простой пример с одной динамической частью, а Example2 — это пример с несколькими динамическими частями. Код для роутов доступен в виде миксин cross-models-routing-child и cross-models-routing-parent аддона ember-cross-models-routing. Установка:

ember install ember-cross-models-routing

Код демо-приложения находится в tests/dummy/app.

, , , ,

2 комментария
  1. vvscode Said:

    Возможно я не понял идее — но почему это не решено одним роутом с двумя queryParams один для модели, второй для поля?

    нам вроде нужно реализовать переход по сетке — параметры дают такую возможность

  2. KronuS Said:

    Можно и так (не проверял). В предложенном способе ссылка на страницу остается «чистой».

Оставить комментарий

Top ↑ | Main page | Back