Case Study Using Backbone

This article will discuss the use of Backbone, and in particular, examples of code working with the server. This should be a kind of intermediate point in the walks of the Seeker, since otherwise it is very difficult to start using it, and the probability of abandoning the idea of ​​transition tends to unity.

Why is this publication needed at all? There have already been articles on the topic, but they affect only very superficially what is already written in the documentation, although paths should be shown undocumented. That's right: the backbone is not a complex standalone library like Jquery, which you won’t touch without breaking it. This library is intended only to build an approximate structure; we can sculpt from this material what we need. Once again: there is no point in looking for ready-made patterns, letter-by-letter examples do not need to be reprinted, it still will not help. You need to learn how to use the tool, and then rush into battle.

So, you have crossed the line of habrakat. I do not set as my goal the explanation of the basic principles of the library, there were already plenty of articles:
habrahabr.ru/blogs/javascript/129928
habrahabr.ru/blogs/javascript/118782
habrahabr.ru/blogs/webdev/123130

I note that all three describe the standard use of Backbone, but very rarely, for example, you need to use a router. Or you need to corny contact with the server - but how to do it? All refer to Backbone.sync, but for some reason no one provides examples. Consider the previous sentence as one of the main reasons for writing this note. If you do not agree with him, then you can not read further.

Let's get started. We will write front-end editing notes. We focus on the scripts, brazenly ignoring both the aesthetic wishes of users and the right to choose: I will only test in chrome. I will give links to an example and code in the archive at the end.

What do we need to get started? We create an empty xhtml page with a standard structure, connect jquery, underscore, backbone (in that order). At the end, add a link to our script. On the server side, we create a php file that will be responsible for reading / writing data (it is in the archive, app.php, I will not give the code, the script simply processes requests of the form? Method =).

When the preparations are finished, we will start writing a script in js. Let's create a container for storing models and logs:
app = {
	debug: true,
	log: function(obj) {
		if(this.debug) {
			console.log(obj);
		}
	},
	models: {}
}

The Backbone.sync function is designed so that it is very easy to redeclare without messing up anything. In addition, each model may have its own synchronization method (guess what it should be called?). We will write a global function for our entire script. The objectives pursued by us:
  • Get Backbone to work with our backend
  • When data is received by a front-line agent, look in the log for checks
  • Check data from the server for an error flag (is_error - set by our script)
  • Simplify adding / saving (merging methods into one)
  • Perform input validation
  • Abort the old request with the new one (only for model / method pair)

What happened to me (your implementation may differ):
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
  }
};

I was a little cunning. If you call the model methods like this: read list read, then the last read will not break the first, but the article is not about that, so we put a huge bolt.

Record Model Code:
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($('').val(data.text));
    $(this.el).append($('').click(function(){
      app.models.page.trigger('post:save', {
        'id': $(this).closest('li').data('rowId'),
        'text': $(this).closest('li').find('input').val()
      });
    }));
    $(this.el).append($('').click(function(){
      if(!confirm('Вы уверены, что хотите удалить эту запись?')) return;
      app.models.notes.get($(this).closest('li').data('rowId')).destroy();
    }));
    return this;
  }
}));

List of entries:
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';
  }
}));

And last, page model:
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($('
  • ').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(); } }));

    They forgot something ... Oh yes, we start rendering:
    $(document).ready(function(){
    	app.models.page.trigger('page:load');
    });
    


    As you can see, everything is simple. I intentionally cited the code in pieces, instead of chewing each function, because The article is focused on a person who is at least a little familiar with js / backbone. If this is not about you, I gave links above, everything is described in detail there. If you have difficulty understanding or need additional explanations for the code - write.
    Code in action: yurov.me/art
    All code in the archive: yurov.me/art/art.tar.gz

    Ps the code on the server is oak, glitches are possible. It is important to show the frontend. If it doesn’t work, you can try to run it locally or just browse the

    PPS archive. Comrade oWeRQ put the code in order, I’ll update the article later (its code is much cleaner):
    owerq.dyndns.org/test/art

    Also popular now: