Эпоха динамических веб-страниц — FireBase

Firebase adapter for Webix UI

TL; DR Готовый пакет можно скачать с https://github.com/webix-hub/webix-firebase

Динамический веб

Еще не так давно создаваемые приложения были флегматичными и неторопливыми. Для обновления даже небольшой части данных нужно было перезагружать всю страницу. AJAX лишь немного улучшил ситуацию. Вместо перезагрузки всей страницы появилась возможность обновлять отдельные блоки. Не успели мы привыкнуть к этой технике, как произошла новая трансформация веба. Именуется она динамическим вебом, где контент страниц обновляется автоматически.

Элементы динамического веба сегодня можно увидеть на страницах Facebook или Twitter. Данные на них изменяются без нашего участия. Как только кто-то добавил комментарий или ретвитнул запись, изменения тут же отображаются на странице.

Динамический веб имеет в своей основе несколько технологий. Самой важной из них являются веб-сокеты (WebSockets). О том, как они используются, вы можете почитать здесь.

При всей своей красоте, веб-сокеты — это низкоуровневая абстракция, требующая написания довольно большого кода даже для простых действий. К счастью, мы можем воспользоваться преимуществами динамических обновлений, не вникая глубоко в детали синхронизации данных. Существует несколько отличных решений, созданных веб-энтузиастами. Они позволяют создавать отличные динамические приложения без особых трудностей.

FireBase

FireBase — это онлайн база данных, которая может быть использована для быстрого создания мобильных и веб-приложений. При этом, необходим лишь клиентский код.

Давайте посмотрим, как это выглядит на практике:

<script>
      var collection= new Firebase('https://webix-demo.firebaseio.com/books');
      collection.on('child_added', function(snapshot) {
        [MESSAGE CALLBACK CODE GOES HERE]
      });
</script>

Данный код задает некоторую коллекцию данных и регистрирует обработчик, который будет вызываться каждый раз при добавлении новых данных. Вот и все. В мире динамического веба вы не читаете данные, а устанавливаете правила, определяющие те действия, которые должны выполняться при добавлении или изменении данных.

Вы можете посмотреть онлайн-демо и прочитать больше в официальном туториале FireBase.

Кроме того, чтобы получить собственную базу данных онлайн, вам нужно зарегистрироваться на http://firebase.com. Доступна бесплатная версия, которой более чем достаточно, чтобы создавать небольшие приложения и прототипы.

FireBase + Webix

FireBase предоставляет хороший API, поэтому интегрировать его с Webix совсем несложно. Используя прямой подход, вы можете применить описанное выше событие child_added, а затем вызвать метод компонента add. Такой код позволит отобразить новую запись в компоненте сразу же после добавления ее в базу данных (она будет добавлена в онлайн режиме для всех пользователей, у которых есть на странице данный компонент):

myDataRef.on('child_added', function(data){
    var obj = data.val();
    obj.id = data.key();

    //place Webix API
    someView.add(obj);
});

Важным моментом является приписывание data.key() к obj.id. Webix и FireBase используют разные подходы к хранению ID. У FireBase для получения id есть специальный метод key, а Webix ожидает найти свойство “id” прямо в объекте данных.

Код, приведенный выше, вполне работоспособен, но имеет один недостаток — работает слишком медленно. Компонент будет отрисовываться заново каждый раз при добавлении новых данных. Чтобы это исправить, мы можем использовать другое событие FireBase под названием “value”. Это событие вызывается при обновлении коллекции и передает в обработчик все данные сразу.

collection.once("value", function(data){
    var source = data.val();
    var result = [];
    for (var key in source){
        var record = source[key];
        record.id = key;
        result.push(record);
    }

    table.parse(data);
});

В примере выше, вместо вызова метода add для каждой записи, мы ждем получения всех записей этой коллекции, и затем используем метод parse для загрузки данных.

В реальном приложении вам придется обрабатывать как событие value, так и child_added. Первое из них необходимо для первоначальной загрузки данных. А второе событие будет использоваться для динамического добавления новых данных. В действительности, вам нужно будет обрабатывать еще два события: child_changed и child_removed. Эти события срабатывают после изменения и удаления данных, соответственно.

Давайте посмотрим, как происходит сохранение данных:

someView.attachEvent("onAfterAdd", function(id){
    collection.push(obj.data);
});

Вот и все, что нужно сделать, чтобы сохранить данные из Webix компонента обратно в базу данных FireBase (и показать эту новую запись всем остальным пользователям). И снова, код довольно простой. При добавлении данных в компонент, мы вызываем соответствующие методы FireBase API. Подобно операции добавления, мы можем написать код для удаления и редактирования.

Таким образом, мы организовали полностью функционирующую двустороннюю синхронизацию между FireBase и компонентом Webix. Изменения в компоненте сохраняются обратно в FireBase и синхронизируются со всеми клиентами. Отлично! Плохо только то, что каждый раз нужно писать много кода. Мы можем сделать кое-что получше: спрятать всю сложность внутри proxy-объекта, а в конфигурации использовать только имя коллекции.

webix.ui({
    view:"datatable",
    autoConfig:true,
    url: "firebase->books",
    save:"firebase->books"
})

Теперь код выглядит намного лучше, не правда ли? Вся сложность синхронизации данных спрятана в proxy-объекте FireBase, который выглядит следующим образом:

webix.firebase = new Firebase("https://webix-demo.firebaseio.com/");

webix.proxy.firebase = {
  $proxy:true,
  load:function(view, callback){
    //decode string reference if necessary
    if (typeof this.source == "object")
      this.collection = this.source;
    else
      this.collection = this.collection || webix.firebase.child(this.source);

    // ------------------------------ //
    ... all data loading code here ...
    // ------------------------------ //
  },
  save:function(view, obj, dp, callback){
    //decode string reference if necessary
    if (typeof this.source == "object")
      this.collection = this.source;
    else
      this.collection = this.collection || webix.firebase.child(this.source);

  // ------------------------------ //
  ... all data saving code here ...
  // ------------------------------ //
  }
};

При необходимости, полный код proxy-объекта можно найти здесь.

Как видите, мы обернули наш код в proxy-объект. Вся логика загрузки данных описана в методе load, а вся логика сохранения данных задана в методе save.

В обоих методах мы преобразуем имя коллекции в объект перед тем как подключать события и обработчики. Кроме того, чтобы этот код заработал, нужно указать с какой именно базой нам надо работать. Для этого мы сохраняем объект базы в webix.firebase. Это необходимо делать только в том случае, если используется строка в url параметре.

Результаты

Вы можете скачать последнюю версию библиотеку с github: https://github.com/webix-hub/webix-firebase, или установить ее с помощью bower: bower install webix-firebase.

Библиотеку можно использовать для создания односторонней и двусторонней привязки данных между FireBase и компонентами Webix (datatable, list, dataview, chart и т.д.).

Вы также можете посмотреть демо и ознакомиться с технической документацией.

Библиотека доступна под MIT лицензией, поэтому вы можете свободно ее использовать.
Успехов!