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. Программы.