В данной заметке речь пойдет об использовании 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