Ваша первая чашечка CoffeeScript: Часть 3. Использование CoffeeScript на стороне клиента

Этот цикл статей посвящен изучению популярного языка программирования CoffeeScript, который построен поверх JavaScript. CoffeeScript компилируется в эффективный код JavaScript, соответствующий многими практическим рекомендациям. Этот код JavaScript можно выполнять в Web-браузере или использовать с такими технологиями, как Node.js, для серверных приложений. В первой статье этого цикла объяснялось, как начать работу с CoffeeScript и какие преимущества он дает разработчикам. Вторая учит использовать CoffeeScript для решения некоторых задач программирования. В этой статье рассматривается процесс создания полного приложения с использованием CoffeeScript.

Майкл Галпин, инженер по программному обеспечению, Vitria Technology

Майкл Галпин (Michael Galpin) имеет учёную степень по математике в Калифорнийском Технологическом институте. Он является Java-разработчиком с конца 90-х гг. и работает инженером по программному обеспечению в Vitria Technology, в Саннивейл, Калифорния.



20.07.2012

Введение

CoffeeScript – это новый язык программирования, построенный поверх JavaScript. CoffeeScript обеспечивает рациональный синтаксис, который понравится всем сторонникам Python и Ruby, а также предоставляет многие функциональные возможности программирования в духе таких языков, как Haskell и Lisp.

В первой статье этого цикла мы рассказали о преимуществах использования CoffeeScript вместо JavaScript. Мы установили среду разработки и выполнили несколько сценариев. Вторая статья была посвящена практическому изучению CoffeeScriptна на примерах решения математических задач.

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


Поиск с помощью CoffeeScript

Часто используемые сокращения

  • JSON: JavaScript Object Notation
  • UI: User Interface - интерфейс пользователя

Пример приложения, который мы создадим, позволяет вводить ключевой слово для поиска, выполнять поиск в Google и Twitter, а затем отображать объединенные результаты. Клиентская часть приложения получает данные, вводимые пользователем, и отправляет их на сервер для получения результатов. Когда результаты возвращаются с сервера, клиент создает для них элементы пользовательского интерфейса и отображает на экране. О том, как работает серверная часть, пока говорить не будем.

Начнем с определения модели данных для приложения. Приложение должно отображать результаты поиска, так что определим класс SearchResult. Определение показано в листинге 1.

Листинг 1. Базовый класс SearchResult
class SearchResult
    constructor: (data) ->
        @title = data.title
        @link = data.link
        @extras = data

    toHtml: -> "<a href='#{@link}'>#{@title}</a>"
    toJson: -> JSON.stringify @extras

Класс SearchResult довольно прост. Он:

  • начинается с конструктора, который определяет две переменные экземпляров: title и link;
  • ищет эти два значения в объекте данных, который передается конструктору;
  • сохраняет остальные данные, переданные переменной экземпляра extras.

    Это удобно, так как у нас будет два разных типа результатов поиска (из Google и из Twitter);

  • определяет два метода для класса SearchResult:
    • toHtml, который создает простую HTML-строку из экземпляра SearchResult, воспользовавшись возможностью интерполяции строк в CoffeeScript;
    • toJson для превращения объекта SearchResult в строку JSON.

Класс, приведенный в листинге 1, обеспечивает основные особенности результата поиска. На самом деле Google и Twitter будут выдавать гораздо больше данных. Для моделирования этого создадим подклассы для результатов поиска каждого типа. В листинге 2 показан пример результатов поиска Google.

Листинг 2. Класс GoogleSearchResult
    class GoogleSearchResult extends SearchResult
    constructor: (data) ->
        super data
        @content = @extras.content
    toHtml: ->
        "#{super} <div class='snippet'>#{@content}</div>"

Из листинга 2 видно, как легко осуществляется объектно-ориентированное программирование в CoffeeScript. GoogleSearchResult расширяет базовый класс SearchResult из листинга 1. Его конструктор вызывает конструктор суперкласса. Те, кто когда-нибудь применял в JavaScript наследование в стиле наследования класса, знают, насколько это может быть сложно. В листинге 3 приведен сгенерированный код JavaScript.

Листинг 3. Код JavaScript GoogleSearchResult
GoogleSearchResult = (function() {
  __extends(GoogleSearchResult, SearchResult);
  function GoogleSearchResult(data) {
    GoogleSearchResult.__super__.constructor.call(this, data);
    this.content = this.extras.content;
  }
  GoogleSearchResult.prototype.toHtml = function() {
    return "" + GoogleSearchResult.__super__.toHtml.apply(this, arguments) 
+ " <div class='snippet'>" + this.content + "</div>";
  };
  return GoogleSearchResult;
})();

Чтобы вызвать конструктор суперкласса, нужно сохранить экземпляр суперкласса в переменной __super__ (имя может быть любым), а затем явно вызвать функцию конструктора. Возвращаясь к листингу 2, можно увидеть, насколько это легче сделать в CoffeeScript. Обратите внимание, что в классе GoogleSearchResult из примера определена новая переменная экземпляра content. В основном это фрагмент HTML-кода с Web-страницы, на который указывает результат поиска. Вас не должно удивлять, что в GoogleSearchResult он есть, а в базовом классе ― нет. Наконец, обратите внимание на переопределение метода toHtml. В примере используется метод toHtml суперкласса, но также добавлен лишний div с фрагментом контента. Посмотрите на листинг 3 еще раз и обратите внимание на то, как осуществляется вызов метода суперкласса toHtml. Так как у нас есть подкласс GoogleSearchResult , нам нужен также подкласс TwitterSearchResult, как показано в листинге 4.

Листинг 4. Класс TwitterSearchResult
class TwitterSearchResult extends SearchResult
    constructor: (data) ->
        super data
        @source = @extras.from_user
        @link = "http://twitter.com/#{@source}/status/#{@extras.id_str}"
        @title = @extras.text
    toHtml: ->
        "<a href='http://twitter.com/#{@source}'>@#{@source}</a>: #{super}"

Класс TwitterSearchResult следует той же схеме, что и класс GoogleSearchResult в листинге 2. Его конструктор использует конструктор своего суперкласса. Кроме того, он:

  • определяет свою собственную переменную экземпляра source;
  • создает переменную экземпляра link с помощью шаблона строки и его переменных экземпляра;
  • устанавливает исходное значение переменной экземпляра title из входных данных в другом поле;
  • переопределяет метод суперкласса toHtml, добавив ссылку для пользователя, создавшего твит.

Опять же, интерполяция строк CoffeeScript позволяет легко использовать метод суперкласса toHtml при создании нового метода. Для вызова метода toHtml суперкласса достаточно вызвать super. Вместо этого можно было бы вызвать super.toHtml, но в этом нет необходимости, и на самом деле это приведет к ошибке. CoffeeScript немного облегчает жизнь программисту, предполагая, что вызывается тот же метод суперкласса.

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


Использование макета данных

При создании приложения клиент-сервер, такого как современные Web-приложения, часто имеет смысл создать общий интерфейс, где встречаются две части приложения, а затем создать макет данных. Это позволяет разрабатывать клиентскую и серверную части приложения параллельно. Этот подход особенно хорошо работает с CoffeeScript, потому что можно использовать один и тот же язык программирования и на клиенте, и на сервере. В листинге 5 показан макет результатов поиска Google.

Листинг 5. Макет результатов поиска Google
mockGoogleData = [
        GsearchResultClass:"GwebSearch",
        link:"http://jashkenas.github.com/coffee-script/",
        url:"http://jashkenas.github.com/coffee-script/",
        visibleUrl:"jashkenas.github.com",
        cacheUrl:"http://www.google.com/search?q\u003dcache:nuWrlCK4-v4J:jashkenas
.github.com",
        title:"\u003cb\u003eCoffeeScript\u003c/b\u003e",
        titleNoFormatting:"CoffeeScript",
        content:"\u003cb\u003eCoffeeScript\u003c/b\u003e is a little language that 
compiles into JavaScript. Underneath all of   those embarrassing braces and 
semicolons, JavaScript has always had a \u003cb\u003e...\u003c/b\u003e"
    ,
        GsearchResultClass:"GwebSearch",
        link:"http://en.wikipedia.org/wiki/CoffeeScript",
        url:"http://en.wikipedia.org/wiki/CoffeeScript",
        visibleUrl:"en.wikipedia.org",
        cacheUrl:"http://www.google.com/search?q\u003dcache:wshlXQEIrhIJ
:en.wikipedia.org",
        title:"\u003cb\u003eCoffeeScript\u003c/b\u003e - Wikipedia, the free 
encyclopedia",
        titleNoFormatting:"CoffeeScript - Wikipedia, the free encyclopedia",
        content:"\u003cb\u003eCoffeeScript\u003c/b\u003e is a programming language 
that transcompiles to JavaScript. The   language adds syntactic sugar inspired by 
Ruby, Python and Haskell to enhance \u003cb\u003e...\u003c/b\u003e"
    ,
        GsearchResultClass:"GwebSearch",
        link:"http://codelikebozo.com/why-im-switching-to-coffeescript",
        url:"http://codelikebozo.com/why-im-switching-to-coffeescript",
        visibleUrl:"codelikebozo.com",
        cacheUrl:"http://www.google.com/search?q\u003dcache:VDKirttkw30J:
codelikebozo.com",
        title:"Why I\u0026#39;m (Finally) Switching to \u003cb\u003eCoffeeScript
\u003c/b\u003e - Code Like Bozo",
        titleNoFormatting:"Why I\u0026#39;m (Finally) Switching to CoffeeScript - 
Code Like Bozo",
        content:"Sep 5, 2011 \u003cb\u003e...\u003c/b\u003e You may have already heard
about \u003cb\u003eCoffeeScript\u003c/b\u003e and some of the hype surrounding it
but you still have found several reasons to not make the \u003cb\u003e...
\u003c/b\u003e"   
]

Как видите, простой синтаксис CoffeeScript упрощает даже создание макета данных. Пример иллюстрирует объектные литералы в CoffeeScript. Листинг 5 представляет собой массив. Для обозначения объекта используется дополнительный отступ, и каждое свойство объекта опять записано с отступом. Этот код выгодно отличается от записи JSON. Место фигурных скобок заняли пробелы. Они напоминают литералы JavaScript, где свойства не берутся в кавычки. В JSON эти свойства тоже должны заключаться в кавычки.

В листинге 6 показан аналогичный макет данных для Twitter.

Листинг 6. Макет результатов поиска Twitter
mockTwitterData = [
        created_at:"Wed, 09 Nov 2011 04:18:49 +0000",
        from_user:"jashkenas",
        from_user_id:123323498,
        from_user_id_str:"123323498",
        geo:null,
        id:134122748057370625,
        id_str:"134122748057370625",
        iso_language_code:"en",
        metadata:
            recent_retweets:4,
            result_type:"popular"
        profile_image_url:"http://a3.twimg.com/profile_images/1185870726/gravatar
_normal.jpg",
        source:"<a href="http://itunes.apple.com/us/app/twitter/id409789998?mt
=12&quot; rel="nofollow">Twitter for Mac</a>",
        text:""CoffeeScript [is] the closest I felt to the power I had twenty 
years ago in Smalltalk" - Ward Cunningham (http://t.co/2Wve2V4l) Nice.",
        to_user_id:null,
        to_user_id_str:null
]

Макет данных из листинга 6 похож на макет данных из листинга 5, но содержит поля, специфические для Twitter. Теперь нужно создать интерфейс, который возвращает этот макет данных. В листинге 7 показан еще один класс, который делает именно это.

Листинг 7. Класс макета механизма поиска
class MockSearch
    search: (query, callback) ->
        results = 
            google: (new GoogleSearchResult obj for obj in mockGoogleData)
            twitter: (new TwitterSearchResult obj for obj in mockTwitterData)
        callback results

Класс MockSearch содержит единственный метод search. Он принимает два параметра: query для использовании при поиске и функцию callback. MockSearch быстро возвращает результаты, но при реальном поиске нужно будет общаться с серверами по сети. Для управления этим в JavaScript и чтобы гарантировать, что пользовательский интерфейс не будет "заморожен", обычно используется функция обратного вызова.

На следующем шаге создается объект с именем results. Опять же, используется синтаксис литерального объекта CoffeeScript. Объект results имеет два поля: google и twitter. Значения каждого поля выражаются с помощью разделения массива. Выражение создает массив соответствующего типа SearchResult (GoogleSearchResult для Google и TwitterSearchResult для Twitter). Наконец вызывается функция callback с передачей ей объекта results.

Макет поиска работает, так что мы готовы написать клиентский код, связанный с пользовательским интерфейсом.


Объединение всего вместе в браузере

Прежде чем внедрять в код приложения пользовательский интерфейс, рассмотрим тот интерфейс пользователя, с которым мы будем работать. В листинге 8 показана очень простая Web-страница.

Листинг 8. Web-страница CoffeeSearch
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">

<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>CoffeeSearch</title>
    <script type="text/javascript" src="search.js"></script>
</head>
<body>
    <div>
        <label for="searchQuery">Keyword:</label>
        <input type="text" name="searchQuery" id="searchQuery"></input>
        <input type="button" value="Search" onclick="doSearch()"/>
    </div>
    <div class="goog" id="gr"/>
    <div class="twit" id="tr"/>
</body>
</html>

В ней есть простая форма для ввода ключевого слова и его передачи в поисковую систему. Есть два раздела, которые определены и готовы принять результаты поиска. Web-страница не содержит никакого определенного в ней кода JavaScript. Все находится в файле search.js, которым станет скомпилированная версия CoffeeScript. Обратите внимание на то, что при нажатии кнопки поиска вызывается функция под названием doSearch. Эта функция должна находиться в файле search.js ― это единственное, чего вы еще не видели в этом файле. Определение на языке CoffeeScript показано в листинге 9.

Листинг 9. Функция doSearch Web-страницы
@doSearch = ->
    $ = (id) -> document.getElementById(id)
    kw = $("searchQuery").value
    appender = (id, data) ->
        data.forEach (x) -> 
            $(id).innerHTML += "<p>#{x.toHtml()}</p>"
    ms = new MockSearch
    ms.search kw, (results) ->
        appender("gr", results.google)
        appender("tr", results.twitter)

Вы, наверное, заметили, что перед функцией стоит символ @ (означающий this). Определенный на верхнем уровне сценария, this становится глобальным объектом. В случае сценария на Web-странице глобальный объект ― это объект окна, что позволяет ссылаться на него в пределах Web-страницы, представленной в листинге 8.

Функция DoSearch делает много вещей всего в нескольких строках кода. Она:

  • определяет локальную функцию $, которая главным образом указывает на всегда полезную функцию document.getElementById. Она используется для извлечения ключевого слова из формы поиска, приведенной в листинге 8;
  • определяет другую локальную функцию, appender, которая принимает идентификатор элемента DOM и массив. Затем она перебирает элементы массива, создает строку HTML и добавляет ее к элементу с заданным идентификатором;
  • создает объект MockSearch и вызывает его метод search;
  • передает ключевое слово из формы и создает функцию обратного вызова.

    Функция обратного вызова использует appender для добавления в один div результатов поиска Google, а в другой ― результатов поиска Twitter.

Теперь можно просто скомпилировать весь код и установить его. На рисунке 1 показана Web-страница с макетом данных.

Рисунок 1. Страница поиска с макетом данных
Страница поиска с макетом данных

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


Заключение

В этой статье CoffeeScript вынесен из лаборатории и начинает применяться для создания чего-то реального. В сочетании с node.js CoffeeScript предоставляет возможность написать полное приложение для клиента и для сервера с использованием одного и того же элегантного языка программирования. Теперь вы хорошо подготовлены к тому, чтобы в последней статье этого цикла рассмотреть многократное использование частей одного и того же кода при построении серверной части приложения.


Загрузка

ОписаниеИмяРазмер
Исходный код примера для статьиcs3.zip5 КБ

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Open source
ArticleID=826734
ArticleTitle=Ваша первая чашечка CoffeeScript: Часть 3. Использование CoffeeScript на стороне клиента
publish-date=07202012