Backbone.js — мой любимый современный MVC-фреймворк для клиентского JavaScript. Не то чтобы я серьезно пробовал другие … просто не чувствовал необходимости.
Приложения, как правило, организованы в Backbone, это то, что данные являются главными. Представление реагирует на некоторые действия пользователя и изменяет некоторые данные в модели. Каждый, кто выслушивает это изменение, реагирует и делает что-то либо для своего представления (например, добавляя что-то новое на экран), либо данные изменяются. Иногда изменение данных приводит к изменению URL браузера .
Как бы то ни было, важной частью является то, что события обычно передаются через изменения состояния.
События
Несмотря на богоподобный статус моделей, большая часть вашего кода попадает в представления. Вот где вы должны реагировать на два типа событий:
- изменения данных
- действия пользователя
Прослушивание изменений данных проще всего выполнить в функции initialize , что-то вроде этого:
initialize: function () {
this.model.on("change", this.render, this);
}
Хорошо, а как насчет действий пользователя? Например, кто-то нажимает на кнопку или вводит определенное поле формы? Ну, вы могли бы сделать это старым способом jQuery , который все теперь любят.
var that = this;
this.$el.find('a.button', function () {
that.button_clicked();
});
Это может стать громоздким очень быстро … Backbone покрыл нас хэшем событий:
events: {
"click a.button": "button_clicked"
},
Оба этих подхода приводят к тому, что this.button_clicked вызывается , когда пользователь нажимает кнопку. За исключением того, что второй подход намного проще для глаз и значительно менее запутан, когда у вас есть десять различных событий, делающих вещи.
Но что происходит, когда вы добавляете socket.io в микс?
Добавление socket.io
Backbone не имеет никакой встроенной поддержки для третьего источника событий, поэтому вы в конечном итоге делаете все по-старому. Добавление слушателей в глобальный сокет в вашей функции инициализации . Глобальные сокеты могут показаться плохой идеей, но я нахожу наличие одного сокета, взаимодействующего с сервером, чтобы упростить процесс.
Я обычно держу сокет в глобальном объекте приложения .
Но это грязно
window.app.socket.on("message", function(message) {
that.got_message(message);
});
Или хуже, обрабатывая все о сообщении в обратном вызове!
Вместо этого я начал заставлять все мои представления расширять общий MainView, который позволяет мне прослушивать события сокета так же, как и действия пользователя:
var MyView = MainView.extend({
socket_events: {
"message": "got_message"
}
Затем MainView заботится о связывании событий сокетов с их обратными вызовами так же, как это делает нативный Backbone.
var MainView = Backbone.View.extend({
initialize: function () {
this.__initialize();
},
__initialize: function () {
if (this.socket_events && _.size(this.socket_events) > 0) {
this.delegateSocketEvents(this.socket_events);
}
},
delegateSocketEvents: function (events) {
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) {
method = this[events[key]];
}
if (!method) {
throw new Error('Method "' + events[key] + '" does not exist');
}
method = _.bind(method, this);
window.app.socket.on(key, method);
};
}
});
Причина в том, что есть две функции инициализации в том, что мы не можем правильно подключиться к объекту View по умолчанию и добавить некоторые функции. Поэтому мы используем инициализацию для делегирования событий при создании объекта — вызовы магистрали инициализируются из конструктора — но если дочернему представлению требуется собственная функция инициализации, они все равно должны иметь возможность делегировать события сокетов.
Такое дочернее представление просто вызвало бы это .__ initialize (), чтобы выполнить функцию инициализации MainView и заставить все работать.
Это далеко не идеально, и чтобы все это работало так, как мне хотелось бы — так же легко, как и действия пользователя — мне нужно было бы сделать запрос на извлечение Backbone… но это хорошее начало для элегантного использования socket.io в backbone. Программы.