Статьи

Baby step to Backbone.js: изучение коллекций

После первоначального знакомства с представлениями и моделями Backbone мы рассмотрим следующую фундаментальную сущность Backbone.js — Collection . Коллекции представляют собой упорядоченный набор моделей и становятся очень удобными для любого типа приложений. Учтите, что мы почти всегда оперируем множеством разных моделей: постами, твитами, новостями и т. Д. — все это коллекции, обычно представляемые в виде списков или сеток.

В небольшом приложении мы делаем через эту серию у нас есть коллекция отзывов. Но прежде чем я покажу, как интегрировать коллекцию в приложение, я хочу убедиться, что вы правильно понимаете все свойства коллекции. Мы сделаем это с помощью TDD, исследуя коллекцию и исследуя ее поведение.

Сборка конструкций

Итак, для создания коллекции нам нужно расширить Backbone.Collectionобъект,

var FeedbackCollection = Backbone.Collection.extend({
    model: Feedback,
    url: '/feedback'
});

Здесь мы только что указали URL для сохранения коллекции и модель, из которой будет состоять коллекция. Давайте подготовим тестовый набор для сборника историй,

describe('FeedbackCollection.js spec', function () {
    var collection;
});

И создайте нашу первую спецификацию,

describe('when constructing', function () {
    describe('just empty', function () {
        beforeEach(function () {
            collection = new FeedbackCollection();
        });
 
        it('should be created', function () {
            expect(collection).toBeDefined();
        });
    });

Просто чтобы убедиться, что наше определение в порядке, и мы можем создать экземпляр нового объекта коллекции.

FeedbackCollectionКонструктор имеет несколько дополнительных аргументов — models, options. Модели, могут быть либо массивами объектов, либо массивом Backbone.Models. В случае объекта конструктор коллекции «превратит» их в модели (принимая тип, который мы указали в определении коллекции) и добавит эти модели в коллекцию. 

или же,

describe('with models', function () {
    beforeEach(function () {
        var models = [
            new Feedback({email: '[email protected]', website: 'a.com', feedback: 'hello'}),
            new Feedback({email: '[email protected]', website: 'b.com', feedback: 'good bye'})
        ];
        collection = new FeedbackCollection(models);
    });
 
    it('should be lenght of 2', function () {
        expect(collection.length).toBe(2);
    });
 
    it('should contain models inside', function () {
        expect(collection.models).toBeDefined();
    });
});

Обе вещи эквивалентны. Более того, обычно для инициализации коллекций вам нужны только юнит-тесты, поэтому я обычно предпочитаю первый вариант.

optionsПараметр может содержать тип модели, коллекции содержат. Итак, если в коллекции не указано modelсвойство, Backbone.Modelбудет создано по умолчанию.

describe('with options', function () {
    beforeEach(function () {
        var models = [
            {email: '[email protected]', website: 'a.com', feedback: 'hello'},
            {email: '[email protected]', website: 'b.com', feedback: 'good bye'}
        ];
        collection = new Backbone.Collection(models);   // not specifying model
    });
 
    it('should be created', function () {
        expect(collection).toBeDefined();
    });
 
    it('should have models of Backbone.Model type', function () {
        expect(collection.models[0].constructor).toBe(Backbone.Model);
    });

Вы можете переопределить это, передав {model: MyModel}объект параметров,

describe('while passing model option', function () {
    beforeEach(function () {
        var models = [
            {email: '[email protected]', website: 'a.com', feedback: 'hello'},
            {email: '[email protected]', website: 'b.com', feedback: 'good bye'}
        ];
        collection = new Backbone.Collection(models, { model: Feedback });
    });
 
    it('should have models of Feedback type', function () {
        expect(collection.models[0].constructor).toBe(Feedback);
    });
});

Несмотря на такую ​​возможность, я редко использую это на практике. Лучше просто указать тип модели в определении коллекции, что облегчает понимание кода.

Доступ к элементам коллекции

После создания коллекции можно получить доступ к внутренним моделям. Есть несколько способов сделать это.

Самый простой по индексу,

describe('when accessing collection elements', function () {
    var first, second, models;
 
    describe('by index', function () {
        beforeEach(function () {
            models = [
                {email: '[email protected]', website: 'a.com', feedback: 'hello'},
                {email: '[email protected]', website: 'b.com', feedback: 'good bye'}
            ];
            collection = new FeedbackCollection(models);
        });
 
        beforeEach(function () {
            first = collection.at(0);
            second = collection.at(1);
        });
 
        it('should get first model by index', function () {
            expect(first.toJSON()).toEqual(models[0]);
        });
 
        it('should get second model by index', function () {
            expect(second.toJSON()).toEqual(models[1]);
        });
    });

Даже если это возможно, в реальных приложениях вы, вероятно, не знаете индекс модели, который нужно получить из коллекции, поскольку они могут поступать с сервера в непредсказуемом порядке. Таким образом, вместо индекса более подходящим является получение по id.

describe('by id', function () {
    beforeEach(function () {
        models = [
            {id: 'feedback-1', email: '[email protected]', website: 'a.com', feedback: 'hello'},
            {id: 'feedback-2', email: '[email protected]', website: 'b.com', feedback: 'good bye'}
        ];
        collection = new FeedbackCollection(models);
    });
 
    beforeEach(function () {
        first = collection.get('feedback-1');
        second = collection.get('feedback-2');
    });
 
    it('should get first model by id', function () {
        expect(first.toJSON()).toEqual(models[0]);
    });
 
    it('should get second model by id', function () {
        expect(second.toJSON()).toEqual(models[1]);
    });
});

И, наконец, то, что я поймал в ловушку много раз, когда запускал Backbone — индексаторы на коллекции, не работает.

describe('indexer does not work', function () {
    beforeEach(function () {
        models = [
            {id: 'feedback-1', email: '[email protected]', website: 'a.com', feedback: 'hello'},
            {id: 'feedback-2', email: '[email protected]', website: 'b.com', feedback: 'good bye'}
        ];
        collection = new FeedbackCollection(models);
    });
 
    it('should be undefined', function () {
        expect(collection[0]).not.toBeDefined();
    });
});

Добавление и удаление предметов

Далее нам нужно понять, как добавлять и удалять элементы из коллекций.

Есть 2 способа добавления элемента в коллекцию магистральной: add, push. Они очень похожи, но между ними есть разница. Метод add берет модель или массив моделей и опции, которые вы можете указать позицию, к которой должен быть представлен элемент. Метод Push, просто добавит новый элемент в конец коллекции.

describe('by add method', function () {
    beforeEach(function () {
        collection.add({id: 'feedback-1', email: '[email protected]', website: 'a.com', feedback: 'hello'});
    });
 
    it('should be added', function () {
        expect(collection.get('feedback-1')).toBeDefined();
    });
 
    it('should be converted to model', function () {
        expect(collection.get('feedback-1').constructor).toBe(Feedback);
    });
 
    describe('with index specified', function () {
        beforeEach(function () {
            collection.add({id: 'feedback-2', email: '[email protected]', website: 'b.com', feedback: 'good bye'}, {at: 0});
        });
 
        it('should have 2 items in collection', function () {
            expect(collection.length).toBe(2);
        });
 
        it('should have feedback-2 item at index 0', function () {
            expect(collection.at(0).id).toBe('feedback-2');
        });
    });
});

Толчком,

describe('by push method', function () {
    beforeEach(function () {
        collection.push({id: 'feedback-1', email: '[email protected]', website: 'a.com', feedback: 'hello'});
    });
 
    it('should be added', function () {
        expect(collection.get('feedback-1')).toBeDefined();
    });
 
    it('should be converted to model', function () {
        expect(collection.get('feedback-1').constructor).toBe(Feedback);
    });
 
    describe('with next push', function () {
        beforeEach(function () {
            collection.push({id: 'feedback-2', email: '[email protected]', website: 'b.com', feedback: 'good bye'});
        });
 
        it('should have 2 items in collection', function () {
            expect(collection.length).toBe(2);
        });
 
        it('should have feedback-1 item at index 0', function () {
            expect(collection.at(0).id).toBe('feedback-1');
        });
    });
});

Обратите внимание, что он pushполучает те же параметры, что и add, но это просто сокращение для addметода (посмотрите, как он реализован , чтобы сделать его полностью понятным)

Для удаления элементов, мы также имеем 2 метода: remove, pop. Они противоположны симметричны add, push. Удалить, удаляет указанную модель из коллекции, pop удаляет последнюю модель в коллекции. Это показано в следующей спецификации,

describe('when removing items', function () {
    beforeEach(function () {
        collection = new FeedbackCollection();
    });
 
    beforeEach(function () {
        collection.push({id: 'feedback-1', email: '[email protected]', website: 'a.com', feedback: 'hello'});
        collection.push({id: 'feedback-2', email: '[email protected]', website: 'b.com', feedback: 'good bye'});
    });
 
    describe('by remove method', function () {
        beforeEach(function () {
            var model = collection.get('feedback-1');
            collection.remove(model);
        });
 
        it('should be removed', function () {
            expect(collection.get('feedback-1')).not.toBeDefined();
        });
    });
 
    describe('by pop method', function () {
        beforeEach(function () {
            collection.pop();
        });
 
        it('should be removed', function () {
            expect(collection.get('feedback-2')).not.toBeDefined();
        });
    });
});

Выводы

Мы только что ознакомились с базовыми функциями типа Backbone.Collection. В следующий раз мы подробнее рассмотрим коллекции, которые они генерируют, извлекают и сохраняют данные на сервере