Создание JavaScript приложения на MVC и Spine.js

Разработчики JavaScript, желающие как можно лучше усовершенствовать свой продукт, всё чаще ищут простые способы внедрения современных технологий, как например, архитектура MVC. Использование данной архитектуры на стороне клиента может быть также эффективна, как и на стороне сервера. Именно поэтому мы хотим представить вам Spine.js, которое вышло совсем недавно и предназначено для решения поставленных нам задач.

Вы может быть, уже знакомы с некоторыми существующими проектами, которые обеспечивают подобную функциональность – например, JavascriptMVC от Джастин Мейер, который прекрасно подходит для больших проектов (решает почти все возникшие проблемы), SproutCore от Ребекки Мурфи, обеспечивающий MVC на Dojo, а также Backbone.js от Джереми Ашкенас – достойное решение для небольших сайтов и проектов, в особенности служат для создания SPA (одностраничные сайты).

Достаточно долгое время я был ярым сторонником JMVC и Backbone, которые обеспечивали нужный мне функционал, однако нельзя стоять на месте, и теперь я всем рекомендую новый альтернативный инструмент Spine.js, который мы сегодня рассмотрим. В этом уроке мы покажем введение в Spine.js, а затем продемонстрируем его работу на небольшом примере, который будет использовать функционал сервиса Bit.ly.

Коротко о Backbone.js

Сейчас я бы хотел коротко рассказать вам о Backbobe.js, для того чтобы вы могли увидеть разницу между ним и Spine: данные в Backbone представлены через модели (которые могут быть созданы, удалены и сохранены). Стоит изменить front-end/UI модель, как сразу же данная информация обновится во всех представлениях, существующих в ваших скриптах.

Такие инструменты как Spine и Backbone предоставляют вам полностью сконцентрироваться исключительно на логике вашего приложения и не терять время на различные другие задачи, касающиеся организации кода на разных этапах его написания.

Теперь давайте познакомимся с Spine.js – новым, альтернативным инструментом, который в последнее время становится всё более и более популярным.

Введение в Spine.js

Предыстория

Несколько месяцев назад я встретился с очень опытным JavaScript разработчиком, которого зовут Алекс Маккоу, чтобы обсудить с ним оглавление книги, которая описывает современные подходы построения веб-приложений на JavаScript. Огромная часть этой книги посвящена работе с MVC – специальной архитектуре современных веб приложений. Там же он и расписал работу со Spine.js – новым простым инструментом создания веб-приложений на JavaScript.

Так что же такое Spine.js?

Spine представляет собой небольшой фрэймворк, который позволяет работать по схеме MVC, создавая приложения непосредственно на языке JavaScript, что обеспечивает логическое разделение кода, наследование моделей через классы и расширения. Также во многом этот инструмент базируется на Backbone.js API, так что те разработчики, которые имели дело с данным фрэймворком, без труда разберутся и в Spine (однако существует целый ряд существенных различий). Spine.js может работать совместно с HTML5 и асинхронными запросами сервера.

Классы Spine, модели и представления.

Официальная документация Spine содержит самое подробное руководство из всех, что я видел. Туда включено очень много вещей: работа с валидацией, сериализацией и ещё целая куча фишек. Однако целью данного урока является знакомство с тремя самыми крупными фичами, а именно: классами, моделями и представлениями.

Классы

В самом сердце Spine, используется эмулированние Object.create для того, чтобы быть уверенным в том, что объекты создаются динамически и могут быть использованы во время работы скрипта. Использование подобных классов можно просмотреть на следующем примере:

var twitterClient = Spine.Class.create();
// или
var twitterClientWithArgs = Spine.Class.create({
testMessage: "If it weren't for WebMD I would have never known what symptoms to mimic so I could get all these prescriptions from my doctor."
});

Для инициализации классов используется метод init(). Разработчики Spine приняли решение не эксплуатировать функцию конструктора, поскольку использование ключевого слова "new" может вызвать некоторые проблемы при создании экземпляров класса.

var twitterClient = Spine.Class.create({
 testMessage: "Hello world"
});
var myclient = twitterClient.init();

Все параметры, которые вы хотите использовать при инициализации объекта, следует передать через метод init(). Пример:

var twitterClient = Spine.Class.create({
 init:function(testMessage){
 this.testMessage = testMessage;
 }
});

Модели

В Spine модели используются для хранения данных вашего приложения, а также для любой другой логики, связанной с этими данными. Этого правила следует придерживаться, т.к. оно является одним из требований в приложении, которое строится на базе MVC; к тому же такая стратегия будет способствовать хорошему разделению логики в вашем коде, тем самым сохранит ваше время. Данные, которые связанны с моделями, хранятся в записи Model.records, и могут быть созданы благодаря функции Spine setup().

В следующем примере мы передаем название модели и набор атрибутов в метод setup():

var Tweets = Spine.Model.setup("Tweet", ["username","tweet_message"]);

Spine модели удобны ещё тем, что в любой момент могут быть расширены в плане функциональности того или иного класса, используя свойства следующим образом:

Tweets.include({
 toTweetString: function(){
 return("@" + this.username + " " + this.tweet_message);
 }
});

Создание модели осуществляется через всё тот же метод .init ():

var mytweets = Tweets.init({username: "addyosmani", tweet_message: "hai twitter"});

В Spine и Backbone существуют различные возможности рендеринга шаблонов и встраивания в DOM, но в этом уроке мы не будем на этом останавливаться и сейчас сразу же перейдём к следующей компоненте MVC - контроллерам.

Контроллеры

Если вам трудно представить, что такое контроллер, то думайте о нём как о связующем звене, которое скрепляет все, что есть в вашем приложении. Подобные моделям, Spine контроллеры расширяют Spine.Class, а также наследуют все его свойства. Сейчас я бы хотел показать вам, как создавать контроллеры в Spine. Впрочем, вы могли и сами догадаться по аналогии:

var TweetController = Spine.Controller.create({
 init:function(){
 //логика при инициализации
 }
})

Как правило, вам просто нужно передать параметры в метод create(). Инициализация контроллеров происходит следующим образом:

var myTweetController = TweetController.init();

Также существует специальный элемент, названный 'el', которые соответствует каждому контроллеру, и может быть передан через свойство экземпляра. Пример:

var myTweetController = TweetController.init({el: $('#tweets')});

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

Документация

Если вас заинтересовала эта тема, то о классах, моделях и контроллерах вы можете почитать в документации к Spine.

Какова принципиальная разница между Spine и Backbone?

Разработчики, которые прочитали документацию к Backbone и Spine, в первые минуты не смогут найти принципиальных отличий. Однако при разработке реального приложения эти отличия могут нарисоваться сами собой. Не буду вас заморачивать и перечислю то, что вы ждёте:

1) Представления в Backbone по своему применению больше похожи на традиционные контроллеры, а Backbone контроллеры больше ответственны за обработку маршрутизации URL. В Spine поддержка маршрутизации была добавлена совсем недавно (потому как является очень необходимым элементом), а контроллеры очень смахивают на представления в Backbone.

2) С точки зрения наследования, Backbone использует функции конструктора и прототипы, в то время как Spine использует эмулированную версию Object.create и моделируемую систему классов – что позволяет добиться того же самого эффекта наследования и на самом деле является очень интересным приёмом. Это одно из принципиальных отличий от Backbone. Оба подхода действенны и имеют право на существование.

3) Я заметил, что немало разработчиков обращают внимание на принципиальную разницу в размере файлов той или другой библиотеки: в этом плане можно отметить тот факт, что в Spine не включает в себя маппинг, фильтрацию, и многие другие функции, которые включены в Backbone. Если для вас размер имеет значение, то вам, безусловно, нужно выбирать Spine, т.к. в этом плане он выигрывает по всем показателям.

Создаём Bit.ly клиент средствами Spine.js

Теперь основы данной темы уже позади, давайте начнём работать над практикой и создадим что-то полезное, используя Spine.

Когда вы работаете над SPA, огромное количество времени уходит на работу и взаимодействие с внешними данными (это могут быть ваши собственные данные или данные, полученные от какого-то API). Вам также хотелось бы использовать маршрутизацию, чтобы позволить сохранить состояние приложения или какую-то закладку. Для этого возможно придётся использовать localStorage, а также обработать запросы ajax, чтобы иметь возможность не беспокоиться о том, откуда данные приходят к вашему приложению.

Учитывая всё вышеперечисленное, мы собираемся создать bit.ly клиент, который позволит вам:

  • создавать привлекательные в размере URL непосредственно из вашего браузера;
  • архивировать свои bit.ly URL так, чтобы вы могли легко получить доступ к ним в любой момент времени;
  • предоставление статистики кликов (это будет реализовано через дополнительное 'представление', таким образом, я смогу продемонстрировать вам процесс роутинга).

Предпосылки

Создание bit.ly плагина

Прежде чем мы начнем, нам необходимо найти удобный и эффективный способ получения доступа к службам bit.ly: 1) сокращённому URL и 2) статистике кликов. Вместо того, чтобы мучиться с обычным JavaScript, мы будем использовать jQuery для того, чтобы более удобным и быстрым способом работать с ajax запросами. Этот подход также позволит нам написать более читабельное и опрятное приложение, где мы без труда сможем проследить логику. Вот bit.ly плагин, который мы будем применять:

Дополнительная поддержка store.js

По умолчанию Spine ориентируется на современные браузеры и именно по этой причине такие вещи как localStorage, не будут одинаково работать во всех браузерах, так что если вы всё же ориентируетесь на кроссбраузерность, то вам следует воспользоваться более старыми инструментами.

Однако существует ещё один способ решить нашу проблему - store.js (и то, на чем он основывается: json2.js). Ниже вы можете найти содержание файла Spine spine.model.local.js, который вы можете обновить, чтобы использовать хранилище, комментируя строки, которые я отметил ниже и заменив их своими.

Spine.Model.Local = {
  extended: function(){
    this.sync(this.proxy(this.saveLocal));
    this.fetch(this.proxy(this.loadLocal));
  },
  saveLocal: function(){
    var result = JSON.stringify(this);
    //localStorage[this.name] = result;
    store.set(this.name, result);
  },
  loadLocal: function(){
    //var result = localStorage[this.name];
    var result = store.get(this.name);
    if ( !result ) return;
    var result = JSON.parse(result);
    this.refresh(result);
  }
};

Обработка jQuery шаблона

Тут вы должны определиться сами, какой подход использовать, т.к. при работе со Spine и Backbone без этого никуда. К тому же данные фрэймворки могут взаимодействовать с несколькими подходами (микрошаблонная обработка, mustache.js и так далее). Сегодня мы собираемся использовать плагин jQuery tmpl, чтобы представить наши сокращенные записи URL и статистику кликов, используя шаблоны.

Пишем наше приложение

Теперь давайте определимся с тем, что нам необходимо реализовать. Итак, вот небольшой список:

  • Модель, чтобы представить данные, которые будут содержаться в каждом сокращённом URL (Модель Url);
  • Контроллер, чтобы представить отдельные записи и действия, которые могут быть выполнены приложением (exports.URL);
  • Контроллер для вывода представления, ответственного за ввод новой записи bit.ly (exports.UrlsList);
  • Контроллер, чтобы вывести представление, ответственное за статистику кликов по той или иной записи (exports.Stats);
  • Универсальный контроллер приложения, который будет обрабатывать маршрутизацию приложения (exports. UrlApp).

В сегодняшнем уроке мы решили использовать jQuery, поскольку мы используем его для работы с шаблоном и плагином, однако Spine может не хуже взаимодействовать с Zepto или другими JavaScript библиотеками. Теперь давайте рассмотрим код нашего приложения:

Начальное кэширование

Простой jQuery плагин

$.fn.toggleDisplay = function(bool){
    if ( typeof bool == "undefined" ) {
      bool = !$(this).filter(":first:visible")[0];
    }
    return $(this)[bool ? "show" : "hide"]();
};

Url модели:

var Url = Spine.Model.setup("Url", ["short_url", "long_url", "stats"]);
Url.extend(Spine.Model.Local);
Url.include({
  validate: function(){
    if ( !this.long_url )
      return "long_url required"
    if ( !this.long_url.match(/:\/\//))
      this.long_url = "http://" + this.long_url
  },
  fetchUrl: function(){
    if ( !this.short_url )
      $.bitly(this.long_url, this.proxy(function(result){
        this.updateAttributes({short_url: result});
      }));
  },
  fetchStats: function(){
    if ( !this.short_url ) return;
    $.bitly.stats(this.short_url, this.proxy(function(result){
      this.updateAttributes({stats: result});
    }));
  }
});
Url.bind("create", function(rec){
  rec.fetchUrl();
});

Контроллер exports.Urls:

  exports.Urls = Spine.Controller.create({
    events: {
      "click .destroy": "destroy",
      "click .toggleStats": "toggleStats"
    },
    proxied: ["render", "remove"],
    template: function(items){
      return $("#urlTemplate").tmpl(items);
    },
    init: function(){
      this.item.bind("update",  this.render);
      this.item.bind("destroy", this.remove);
    },
    render: function(){
      this.el.html(this.template(this.item));
      return this;
    },
    toggleStats: function(){
      this.navigate("/stats", this.item.id, true);
    },
    remove: function(){
      this.el.remove();
    },
    destroy: function(){
      this.item.destroy();
    }
  });

Контроллер exports.UrlsList:

exports.UrlsList = Spine.Controller.create({
    elements: {
      ".items": "items",
      "form":   "form",
      "input":  "input"
    },
    events: {
      "submit form": "create",
    },
    proxied: ["render", "addAll", "addOne"],
    init: function(){
      Url.bind("create",  this.addOne);
      Url.bind("refresh", this.addAll);
    },
    addOne: function(url){
      var view = Urls.init({item: url});
      this.items.append(view.render().el);
    },
    addAll: function(){
      Url.each(this.addOne);
    },
    create: function(e){
      e.preventDefault();
      var value = this.input.val();
      if (value)
        Url.create({long_url: value});
      this.input.val("");
      this.input.focus();
    }
  });

Контроллер exports.Stats:

  exports.Stats = Spine.Controller.create({
    events: {
      "click .back": "back"
    },
    proxied: ["change", "render"],
    init: function(){
      Url.bind("update", this.render);
    },
    template: function(items){
      return $("#statsTemplate").tmpl(items);
    },
    render: function(){
      if ( !this.item ) return;
      this.el.html(this.template(this.item));
    },
    change: function(item){
      this.item = item;
      this.navigate("/stats", item.id);
      this.item.fetchStats();
      this.render();
      this.active();
    },
    back: function(){
      this.navigate("/list", true);
    }
  });

Контроллер exports.UrlApp:

  exports.UrlApp = Spine.Controller.create({
    el: $("body"),
    elements: {
      "#urls": "urlsEl",
      "#stats": "statsEl"
    },
    init: function(){
      this.list = UrlsList.init({el: this.urlsEl});
      this.stats = Stats.init({el: this.statsEl});
      this.manager = Spine.Controller.Manager.init();
      this.manager.addAll(this.list, this.stats);
      this.routes({
        "": function(){ this.list.active() },
        "/list": function(){ this.list.active() },
        "/stats/:id": function(id){ this.stats.change(Url.find(id)) }
      });
      Url.fetch();
      Spine.Route.setup();
    }
  });

Наконец, мы действуем следующим образом, для того чтобы завершить инициализацию нашего контроллера 'app':

exports.App = UrlApp.init();

Код для сокращения URL и статистики кликов для Bit.ly

(function($){
  var defaults = {
    version:    "3.0",
    login:      "legacye",
    apiKey:     "R_32f60d09cccde1f266bcba8c242bfb5a",
    history:    "0",
    format:     "json"
  };
  $.bitly = function( url, callback, params ) {
    if ( !url || !callback ) throw("url and callback required");
    var params = $.extend( defaults, params );
    params.longUrl = url;
    return $.getJSON("http://api.bit.ly/shorten?callback=?", params, function(data, status, xhr){
      callback(data.results[params.longUrl].shortUrl, data.results[params.longUrl], data);
    });
  };
  $.bitly.stats = function( url, callback, params ) {
    if ( !url || !callback ) throw("url and callback required");
    var params = $.extend( defaults, params );
    params.shortUrl = url;
    return $.getJSON("http://api.bitly.com/v3/clicks?callback=?", params, function(data, status, xhr){
      callback(data.data.clicks[0], data);
    });
  };
})(jQuery);

Центр управления приложением:

Ниже я решил использовать LABjs для управления приложением, однако вы легко можете заменить это на то, с чем привыкли работать.

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="/css/application.css" type="text/css" charset="utf-8">
  <script src="/lib/LAB.min.js" type="text/javascript" charset="utf-8"></script>
  <script type="text/javascript">
    $LAB
    .script("lib/json.js")
    .script("lib/jquery.js")
    .script("lib/jquery.tmpl.js")
    .script("lib/jquery.bitly.js")
    .script("lib/store.min.js")
    .script("lib/spine.js")
    .script("lib/spine.model.local.js")
    .script("lib/spine.controller.manager.js")
    .script("lib/spine.route.js")
    .script("app/models/url.js")
    .script("app/application.js");
  </script>
  <script type="text/x-jquery-tmpl" id="urlTemplate">
    <div class="item">
      <div class="show">
        <span class="short">
          ${long_url}
        </span>
        <span class="long">
          {{if short_url}}
            <a href="/${short_url}">${short_url}</a>
          {{else}}
            Generating...
          {{/if}}
        </span>
        <a class="toggleStats"></a>
        <a class="destroy"></a>
      </div>
    </div>
  </script>
  <script type="text/x-jquery-tmpl" id="statsTemplate">
    <div class="stats">
      <a class="back">Back</a>
      <h1>Click Statistics</h1>
      <h1 class="longUrl">${long_url}</h1>
      <p>Short URL:
        {{if short_url}}
          <a href="/${short_url}">${short_url}</a>
        {{else}}
          Generating...
        {{/if}}
      </p>
      {{if stats}}
        <p>Global clicks: ${stats.global_clicks}</p>
        <p>User clicks: ${stats.user_clicks}</p>
      {{else}}
        Fetching...
      {{/if}}
    </div>
  </script>
</head>
<body>
  <div id="views">
    <div id="urls">
      <h1>Bit.ly Client</h1>
      <form>
        <input type="text" placeholder="Enter a URL">
      </form>
      <div class="items"></div>
    </div>
    <div id="stats">
    </div>
  </div>
</body>
</html>

Примечания:

  • Для оптимальной кроссбраузерной совместимости данный пример должен быть запущен на живом или локальном веб сервере. Используйте MAMP/WAMP в случае необходимости;
  • Для проверки статистики кликов я рекомендую использовать URL сайтов, которые являются наиболее популярными на сегодняшний день. Например, информация о сайте http://www.google.com наверняка присутствует в базе данных Bit.ly;
  • Отметим, что демо пример использует мои собственные ключевые API Bit.ly, которые должны быть заменены на ваши собственные.
  • Круговые диаграммы сгенерированы динамически при помощи Google Chart API. Для того чтобы не усложнять и так новый для вас подход, я сам выбирал изменение изображения диаграммы, но вы в любой момент можете легко переключиться на Visualization API, в том случае, если вам требуется более тонкий подход для вывода данных;
  • То, как вы формируете и структурируете свой каталог приложения,- это полностью ваше дело. Некоторые разработчики предпочитают общую структуру папок model / view / controller, в то время как другие разработчики предпочитают иметь универсальную папку приложения, где все, что касается MVC базироваться в единственном файле. В нашем демо приложении мы используем структуру папок, к которой я привык.
  • Что касается сохранения состояния приложения и процесса маршрутизации в Spine, помните, что если вы хотите сохранить уникальные 'представления' для контента (например, одно представление для #ui/dashboard, а другое для #ui/stats), то вам тщательнейшим образом необходимо рассмотреть работу spine.controller.manager.js, поскольку в этом файле присутствует решение данной задачи.

Вот и всё!

Заключение

В этом уроке я хотел попытаться убедить вас, что Spine - это хорошая альтернатива Backbone. Документация является довольно-таки неплохой, чтобы продолжить самостоятельное изучение. Я рекомендую вам учиться на реальных примерах.

Спасибо за то время, которые вы уделили чтению данного урока. Надеюсь, он был для вас полезен. Удачи!

Источник: http://feedproxy.google.com/~r/ruseller/CdHX/~3/jXRmch6Qgwk/lessons.php

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Создание JavaScript приложения на MVC и Spine.js | | 2012-06-19 12:05:10 | | Статьи Web-мастеру | | Разработчики JavaScript, желающие как можно лучше усовершенствовать свой продукт, всё чаще ищут простые способы внедрения современных технологий, как например, архитектура MVC. Использование данной | РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
 
Поделиться с друзьями: