Em.computed на основе underscore.js

Ember

В Ember есть ряд методов для работы с коллекциями (см. Ember.Enumerable). Однако, там есть далеко не все, что хотелось бы. Например, тот же groupBy. Написать такую функцию самому — дело нескольких минут (и конечно еще немного времени на покрытие ее тестами):

function groupBy(collection, key) {
    var grouped = {};
    collection.forEach(function(item) {
        var value = Em.get(item, key);
        if (Em.isNone(grouped[value])) {
            grouped[value] = [item];
        }
        else {
            grouped[value].pushObject(item);
        }
    });
    return grouped;
}
 
var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v2'}, {n: 'v4'}];
var key = 'n';
var result = groupBy(collection, key); // {v1: [{n: 'v1'}], v2: [{n: 'v2'}, {n: 'v2'}], v4: [{n: 'v4'}]}

Ничего трудного. Однако, если необходимо, чтоб у меня было computed property на основе этой функции? То есть, чтобы можно было написать так:

export default Ember.Component.extend({
 
    someCollection: [
        {n: 'v1'},
        {n: 'v2'},
        {n: 'v2'},
        {n: 'v4'}
    ],
 
    groupedBy: groupBy('someCollection', 'n')
 
});

И надо, чтоб при обновлении someCollection обновлялось и значение groupedBy. Выход довольно простой. Опишем функцию, которая возвращает computed property с нужным функционалом:

const {
    get,
    computed
} = Ember;
 
function groupBy(collectionKey, propertyName) {
    return computed(`${collectionKey}.@each.${propertyName}`, {
        get() {
            return _.groupBy(get(this, collectionKey), item => get(item, propertyName));
        }
    });
}

Но ведь можно было просто сделать:

export default Ember.Component.extend({
 
    someCollection: [
        {n: 'v1'},
        {n: 'v2'},
        {n: 'v2'},
        {n: 'v4'}
    ],
 
    groupedBy: computed('someCollection.@each.n', {
        get() {
            return _.groupBy(get(this, collectionKey), item => get(item, propertyName));
        }
    })
});

Да, так написать можно. А если позже понадобится еще одно computed property на основе _.groupBy, но для другой коллекции и другого имени свойства? Надо будет снова описывать такую же логику. А дублирования лучше избегать. Потому в дело и вступает «выделение и параметризация метода» (если ничего не спутал по Фаулеру). Еще несколько примеров того, что можно «обернуть» в computed property:

// _.indexBy
const {
    get,
    computed
} = Ember;
 
function indexBy(collectionKey, propertyName) {
    return computed(`${collectionKey}.@each.${propertyName}`, {
        get() {
            return _.indexBy(get(this, collectionKey), propertyName);
        }
    });
}
// _.countBy
const {
    get,
    computed
} = Ember;
 
function countBy(collectionKey, propertyName) {
  return computed(`${collectionKey}.@each.${propertyName}`, {
        get() {
            return _.countBy(get(this, collectionKey), item => get(item, propertyName) % 2 == 0 ? 'even': 'odd');
        }
    });
}

Небольшая демка доступна на jsbin — ссылка (нажать несколько раз кнопку «Add Item»).

На такой нехитрой концепции базируется несколько дополнений с макросами для Ember, например — ember-macaroni, ember-cpm.

, , ,

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

Top ↑ | Main page | Back