Рекомендуемый шаблон для реализации фильтра JSONAPI с параметрами запроса в Ember? - PullRequest
0 голосов
/ 31 мая 2018

Вчера я потратил много времени, пытаясь включить фильтр (отражающий спецификацию JSONAPI ) в параметры запроса части приложения Ember.С Ember Data достаточно просто передать массив фильтров в конечную точку, проблема, которую я имею, заключается в отражении этого массива фильтров в параметрах запроса для определенного маршрута.Примечание: другие параметры, не относящиеся к массиву, работают нормально.

TL; DR Я безуспешно пробовал различные варианты, и у меня есть решение, которое на самом деле кажется неудовлетворительным и совсем не СУХОЙ.Я полагаю, что многие другие, должно быть, решили эту проблему и наверняка нашли лучшее решение.Прочтите подробности того, что я пробовал до сих пор.

Я начал с чего-то подобного (я изначально предполагал, что это сработает, прочитав Ember документы по параметрам запроса ):

Контроллер:

import Controller from '@ember/controller';

export default Controller.extend({
  queryParams: ['sort', 'filter'],
  sort: 'id',

  init() {
    this._super(...arguments);
    this.set('filter', []);
  },
});

Маршрут:

import Route from '@ember/routing/route';

export default Route.extend({
  queryParams: {
    filter: {
      refreshModel: true
    },
    sort: {
      refreshModel: true
    }
  },

  model(params) {
    console.log(JSON.stringify(params)); // filter is always []
    return this.get('store').query('contact', params);
  }
});

Приемочный тест (это было только доказательством концептуального теста, прежде чем я начал на более сложных вещах):

  test('visiting /contacts with query params', async function(assert) {
    assert.expect(1);
    let done = assert.async();

    server.createList('contact', 10);

    server.get('/contacts', (schema, request) => {
      let params = request.queryParams;

      assert.deepEqual(
        params,
        {
          sort: '-id',
          "filter[firstname]": 'wibble'
        },
        'Query parameters are passed in as expected'
      );
      done();
      return schema.contacts.all();
    });

    await visit('/contacts?filter[firstname]=wibble&sort=-id');
  });

Независимо от того, как я настраивал приведенный выше код, params.filter всегда был [] в функции модели маршрута.

Я искал лучшие практики в отношении того, что может показаться распространенным.дела, но не нашли ничего недавнего. Решение sarus здесь с ноября 2015 года работает, но означает, что каждый возможный ключ фильтра должен быть жестко задан в контроллере и маршруте, что мне кажется далеко не идеальным.Только представьте, что вы делаете это для 20 возможных ключей фильтра!Используя решение sarus, вот код, который работает для вышеуказанного приемочного теста, но, как я уже сказал, представьте себе, что нужно жестко закодировать 20+ потенциальных ключей фильтра:

Контроллер:

import Controller from '@ember/controller';

export default Controller.extend({
  queryParams: ['sort',
    { firstnameFilter: 'filter[firstname]' }
  ],
  sort: 'id',
  firstnameFilter: null,

  init() {
    this._super(...arguments);
  }
});

Маршрут:

import Route from '@ember/routing/route';

export default Route.extend({
  queryParams: {
    firstnameFilter: {
      refreshModel: true
    },
    sort: {
      refreshModel: true
    }
  },

  model(params) {
    if (params.firstnameFilter) {
      params.filter = {};
      params.filter['firstname'] = params.firstnameFilter;
      delete params.firstnameFilter;
    }
    return this.get('store').query('contact', params);
  }
});

Надеюсь, есть лучший способ!

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

Если у вас нет требований поддерживать поля динамического фильтра, @jelhan уже дал действительно хороший ответ на этот вопрос.

Если вам нужно поддерживать поля динамического фильтра, читайте дальше.

Прежде всего, следует отдать должное @jelhan, который поставил меня на правильный путь, упомянув о возможности сериализации URL-адреса приложения.с JSON.stringify () и encodeURIComponent () вместе.

Вот пример кода с этой рабочей ...

Контроллер:

import Controller from '@ember/controller';

export default Controller.extend({
  queryParams: ['sort', {
    filter: {
      type: 'array'
    }
  }],

  sort: 'id',

  init() {
    this._super(...arguments);
    this.set('filter', []);
  },
});

Маршрут (без изменений):

import Route from '@ember/routing/route';

export default Route.extend({
  queryParams: {
    filter: {
      refreshModel: true
    },
    sort: {
      refreshModel: true
    }
  },

  model(params) {
    return this.get('store').query('contact', params);
  }
});

Приемочный тест:

  test('visiting /contacts with query params', async function(assert) {
    assert.expect(1);
    let done = assert.async();

    server.createList('contact', 10);

    server.get('/contacts', (schema, request) => {
      let params = request.queryParams;

      assert.deepEqual(
        params,
        {
          sort: '-id',
          "filter[firstname]": 'wibble',
          "filter[lastname]": 'wobble'
        },
        'Query parameters are passed in as expected'
      );
      done();
      return schema.contacts.all();
    });

    // The filter is represented by a Javascript object
    let filter = {"firstname":"wibble", "lastname":"wobble"};

    // The object is converted to a JSON string and then URI encoded and added to the application URL 
    await visit('/contacts?sort=-id&filter=' + encodeURIComponent(JSON.stringify(filter)));
  });

Отлично!Этот тест проходит.Фильтр, определенный в URL-адресе приложения, передается в маршрут.Хук модели Маршрута делает запрос JSONAPI с правильно определенным фильтром.Ух!

Как видите, там нет ничего умного.Все, что нам нужно сделать, это установить фильтр в правильном формате в URL приложения, и стандартная настройка Ember Query Params будет работать только с полями динамического фильтра.

Но как я могу обновить параметр запроса фильтра через действие или ссылку и увидеть, что отражено в URL приложения, а также сделать правильный запрос JSONAPI через ловушку модели маршрута.Оказывается, это тоже просто:

Пример действия (в контроллере):

changeFilter() {
  let filter = {
    firstname: 'Robert',
    lastname: 'Jones',
    category: 'gnome'
  };

  // Simply update the query param `filter`. 
  // Note: although filter is defined as an array, it needs to be set
  //  as a Javascript object to work
  // this.set('filter', filter); - this seems to work but I guess I should use transitionToRoute
  this.transitionToRoute('contacts', {queryParams: {filter: filter}});
}

Для ссылки (скажем, вы хотите применить специальный фильтр) вам понадобится свойство контроллера дляудерживая фильтр, мы назовем его otherFilter и можем ссылаться на него в свойстве link-to:

Example Controller (определено в init):

init() {
    this._super(...arguments);
    this.set('filter', []);    
    this.set('otherFilter', {occupation:'Baker', category: 'elf'});
}

Example link-на:

{{#link-to 'contacts' (query-params filter=otherFilter)}}Change the filters{{/link-to}}

Вот оно!

0 голосов
/ 31 мая 2018

Нет причин представлять значения фильтров в URL-адресах приложений так же, как они должны быть, чтобы внутренний вызов представлял собой жалобу JSON API.Поэтому я бы не использовал этот формат для URL-адресов приложений.

Если у вас нет необходимости поддерживать поля динамического фильтра, я бы жестко закодировал все из них, чтобы иметь хорошие URL-адреса, такие как /contacts?firstname=wibble&sort=-id.

Ваш код будет выглядеть так, если вы хотите поддерживать фильтрацию для firstname и lastname:

// Controller
import Controller from '@ember/controller';

export default Controller.extend({
  queryParams: ['sort', 'page', 'firstname', 'lastname'],
  sort: 'id',
});

// Route
import Route from '@ember/routing/route';

export default Route.extend({
  queryParams: {
    firstname: {
      refreshModel: true
    },
    lastname: {
      refreshModel: true
    }
    sort: {
      refreshModel: true
    }
  },

  model({ firstname, lastname, sort, page }) {
    console.log(JSON.stringify(params)); // filter is always []
    return this.get('store').query('contact', {
      filter: {
        firstname,
        lastname
      },
      sort,
      page
    });
  }
});

Если вы должны поддерживать динамические поля фильтра, я бы представлял объект фильтра вURL приложения.Для сериализации вы можете использовать JSON.stringify() и encodeURIComponent() вместе.URL тогда будет выглядеть как /contacts?filter=%7B%22firstname%22%3A%22wibble%22%7D&sort=-id.

...