В данной заметке речь пойдет об использовании Backbone, а в частности, примеры кода работающего с сервером. Это должен быть некий промежуточный пункт в блужданиях Ищущего, так как иначе начать им пользоваться очень сложно, а вероятность отказаться от идеи перехода стремится к единице.
Зачем вообще нужна данная публикация? Тут уже были статьи по теме, но они затрагивают лишь очень поверхностно то, о чем и так написано в документации, хотя следовало бы показать пути недокументированные. Именно так: backbone это не комплексная standalone библиотека вроде Jquery, которую и не тронешь, не сломав. Данная библиотека предназначена лишь для построения приблизительной структуры; мы же можем лепить из данного материала то, что нам нужно. Еще раз: тут нет смысла искать готовые паттерны, буква в букву примеры не нужно перепечатывать, все равно не поможет. Нужно научиться пользоваться инструментом, после чего уже бросаться в бой.
Итак, вы перешагнули рубеж хабраката. Я не ставлю своей целью объяснение базовых принципов библиотеки, статей было уже предостаточно:
habrahabr.ru/blogs/javascript/129928
habrahabr.ru/blogs/javascript/118782
habrahabr.ru/blogs/webdev/123130
Замечу, что все три описывают стандартное использование Backbone, но очень редко действительно нужно, например, использовать роутер. Или же нужно банально связаться с сервером — а как это сделать? Все отсылают к Backbone.sync, а примеров почему-то никто не предоставляет. Считайте предыдущее предложение одной из основных причин написания данной заметки. Если вы с ним не согласны — дальше можно не читать.
Начнем. Писать мы будем фронтенд редактирования заметок. Сфокусируемся на сриптах, нагло игнорируя как эстетические пожелания пользователей, так и право выбора: тестировать я буду только в хроме. Ссылки на пример и код в архиве приведу в конце.
Что нам нужно, чтобы начать? Создаем пустую xhtml страничку со стандартной структурой, подключаем jquery, underscore, backbone (именно в таком порядке). В конце добавляем ссылку на наш скрипт. Со стороны сервера создаем php файл, который будет отвечать за чтение/запись данных (лежит в архиве, app.php, код приводить не буду, скрипт просто обрабатывает запросы вида ?method=).
Когда приготовления закончены, начнем писать скрипт на js. Создадим контейнер для хранения моделей и лога:
Функция Backbone.sync спроектирована так, что ее очень легко переобъявить, ничего не испортив. Кроме того, у кажой модели может быть свой метод синхронизации (угадайте, как он должен называться?). Мы будем писать глобальную функцию для всего нашего срипта. Цели, преследуемые нами:
Что получилось у меня (ваша реализация может отличаться):
Я немного слукавил. Если вызвать у модели методы так: read list read, то последний read не оборвет первый, но статья не об этом, так что кладем огромный болт.
Код модели записи:
Список записей:
И последнее, модель страницы:
Что-то забыли… Ах да, запускаем рендеринг:
Как видите, все просто. Я намеренно приводил код кусками, вместо разжевывания каждой функции, т.к. статья ориентированна на человека хоть немного знакомого с js/backbone. Если это не про вас — выше я давал ссылки, там подробно все расписано. Если возникнут сложности в понимании или нужны ополнительные пояснения к коду — пишите.
Код в действии: yurov.me/art
Весь код в архиве: yurov.me/art/art.tar.gz
P.s. код на сервере дубовый, возможны глюки. Важно показать фронтенд. Если не будет работать — можете попробовать запустить у себя локально либо просто просмотреть архив
P.P.S. Товарищ oWeRQ привел код в порядок, позже обновлю статью (его код значительно чище):
owerq.dyndns.org/test/art
Зачем вообще нужна данная публикация? Тут уже были статьи по теме, но они затрагивают лишь очень поверхностно то, о чем и так написано в документации, хотя следовало бы показать пути недокументированные. Именно так: backbone это не комплексная standalone библиотека вроде Jquery, которую и не тронешь, не сломав. Данная библиотека предназначена лишь для построения приблизительной структуры; мы же можем лепить из данного материала то, что нам нужно. Еще раз: тут нет смысла искать готовые паттерны, буква в букву примеры не нужно перепечатывать, все равно не поможет. Нужно научиться пользоваться инструментом, после чего уже бросаться в бой.
Итак, вы перешагнули рубеж хабраката. Я не ставлю своей целью объяснение базовых принципов библиотеки, статей было уже предостаточно:
habrahabr.ru/blogs/javascript/129928
habrahabr.ru/blogs/javascript/118782
habrahabr.ru/blogs/webdev/123130
Замечу, что все три описывают стандартное использование Backbone, но очень редко действительно нужно, например, использовать роутер. Или же нужно банально связаться с сервером — а как это сделать? Все отсылают к Backbone.sync, а примеров почему-то никто не предоставляет. Считайте предыдущее предложение одной из основных причин написания данной заметки. Если вы с ним не согласны — дальше можно не читать.
Начнем. Писать мы будем фронтенд редактирования заметок. Сфокусируемся на сриптах, нагло игнорируя как эстетические пожелания пользователей, так и право выбора: тестировать я буду только в хроме. Ссылки на пример и код в архиве приведу в конце.
Что нам нужно, чтобы начать? Создаем пустую xhtml страничку со стандартной структурой, подключаем jquery, underscore, backbone (именно в таком порядке). В конце добавляем ссылку на наш скрипт. Со стороны сервера создаем php файл, который будет отвечать за чтение/запись данных (лежит в архиве, app.php, код приводить не буду, скрипт просто обрабатывает запросы вида ?method=).
Когда приготовления закончены, начнем писать скрипт на js. Создадим контейнер для хранения моделей и лога:
app = { debug: true, log: function(obj) { if(this.debug) { console.log(obj); } }, models: {} }
Функция Backbone.sync спроектирована так, что ее очень легко переобъявить, ничего не испортив. Кроме того, у кажой модели может быть свой метод синхронизации (угадайте, как он должен называться?). Мы будем писать глобальную функцию для всего нашего срипта. Цели, преследуемые нами:
- Заставить Backbone работать с нашим бекэндом
- При получении данных фронтеном аукать в лог для проверок
- Проверять данные с сервера на флаг ошибки (is_error — устанафливается нашим скриптом)
- Упростить добавление/сохранение (сливаем методы в один)
- Производить проверку входных данных
- Прерывать старый запрос при новом (только для пары модель/метод)
Что получилось у меня (ваша реализация может отличаться):
Backbone.sync = function(method, model, options) { // Сливаем методы дубовым способом var method = (method=='update'||method=='create')?'save':method; // Прерываем старый запрос if(model.loading && model.loading.method == method) { model.loading.xhr.abort(); } app.log('Запрос на "'+model.url(method)+'"'); var xhr = $.ajax({ type: 'POST', url: model.url(method), data: (model instanceof Backbone.Model)?model.toJSON():{}, success: function(ret) { // Проверка наличия ошибки if(ret.is_error) { app.log('Ошибка запроса'); } else { app.log('Запрос завершен'); (function(data){ app.log('Backbone.sync получил данные:', data); if(data.res) { // Ответ - строка, вместо записей model.trigger('load:res', data.res); } else { // Ответ - записи, либо данные options.success(data.rows?data.rows:data); } model.trigger((method=='save')?'save':'load', data); })(ret.data); } }, error: function (req_obj, msg, error) { app.log('Ошибка запроса'); }, dataType: 'json' }); // Сохраняем ссылку на запрос в модель model.loading = { method: method, xhr: xhr } };
Я немного слукавил. Если вызвать у модели методы так: read list read, то последний read не оборвет первый, но статья не об этом, так что кладем огромный болт.
Код модели записи:
app.models.note = (Backbone.Model.extend({ defaults: { id: 0, text: '' }, url: function(method){ return './app.php?method='+method; } })); app.models.Note = (Backbone.View.extend({ tagName: 'li', className: 'entity', render: function(){ var data = this.model.toJSON(); var that = this; $(this.el).html('').data('rowId', data.id); $(this.el).append($('<input type="text" />').val(data.text)); $(this.el).append($('<button>Сохранить</button>').click(function(){ app.models.page.trigger('post:save', { 'id': $(this).closest('li').data('rowId'), 'text': $(this).closest('li').find('input').val() }); })); $(this.el).append($('<button>Удалить</button>').click(function(){ if(!confirm('Вы уверены, что хотите удалить эту запись?')) return; app.models.notes.get($(this).closest('li').data('rowId')).destroy(); })); return this; } }));
Список записей:
app.models.notes = new (Backbone.Collection.extend({ model: app.models.note, initialize: function(){ this.bind('destroy', function(){ this.reload(); }, this); }, reload: function(){ var that = this; var options = ({ error:function(){ app.log('Ошибка обновления записей!'); that.trigger('change'); }, success: function(){ app.log('Записи обновлены'); that.trigger('change'); } }); app.log('Обновление записей...'); this.fetch(options); }, url: function(method){ return './app.php?method=list'; } }));
И последнее, модель страницы:
app.models.page = new (Backbone.View.extend({ el: null, el_list: null, notes: null, initialize: function(){ this.bind('page:load', this.pageLoad, this); this.bind('list:reload', this.listReload, this); this.bind('post:save', this.postSave, this); this.notes = app.models.notes; this.notes.bind('change', this.onListChange, this); this.notes.bind('load:res', this.onListChange, this); return this; }, pageLoad: function(data) { var that = this; this.el = $('.layout'); this.el_list = this.el.find('.items-list'); // Кнопка обновления this.el.find('.title .refresh').bind('click', function(){ that.trigger('list:reload') }); // Кнопка добавления this.el.find('.items-add-submit').bind('click', function(){ that.trigger('post:save', { id: false, text: $('.items-add-text').val() }); }); this.trigger('list:reload'); }, render: function(ret){ $(this.el_list).html(''); if(!ret) { app.log('Вывод записей. Количество: '+this.notes.length); _(this.notes.models).each(function(item){ this.appendItem(item); }, this); } else { app.log('Вывод записей. Результат: "'+ret+'"'); $(this.el_list).html('').append($('<li class="res"></li>').text(ret)); } return this; }, appendItem: function(item) { var view = new app.models.Note({ model: item }); $(this.el_list).append(view.render().el); }, onListChange: function(ret){ this.render(ret); }, postSave: function(obj){ var model = new app.models.note(); if(obj.id) { model.set({ id:obj.id }); } model.set({ text:obj.text }); model.save(); this.trigger('list:reload'); }, listReload: function(){ this.notes.reload(); } }));
Что-то забыли… Ах да, запускаем рендеринг:
$(document).ready(function(){ app.models.page.trigger('page:load'); });
Как видите, все просто. Я намеренно приводил код кусками, вместо разжевывания каждой функции, т.к. статья ориентированна на человека хоть немного знакомого с js/backbone. Если это не про вас — выше я давал ссылки, там подробно все расписано. Если возникнут сложности в понимании или нужны ополнительные пояснения к коду — пишите.
Код в действии: yurov.me/art
Весь код в архиве: yurov.me/art/art.tar.gz
P.s. код на сервере дубовый, возможны глюки. Важно показать фронтенд. Если не будет работать — можете попробовать запустить у себя локально либо просто просмотреть архив
P.P.S. Товарищ oWeRQ привел код в порядок, позже обновлю статью (его код значительно чище):
owerq.dyndns.org/test/art
