little ruby blog

небольшой блог о ruby, rails и разной фигне

Мини-виджеты при помощи CoffeeScript. Немного магии

| Comments

Preamble: по примеру предыдущего поста теперь, показывая какие-либо решения, я буду стараться оформлять их как в предыдущем посте - то есть, формулировать проблему, описывать решение, описывать использование решения и, если есть необходимость, показывать подводные камни решения. Мне кажется, такая форма вполне понятна и имеет право на успех. Читатели, отзовитесь, пожалуйста, в комментариях.

Preamble #2: этот пост посвящается неоднократно упомянутому мной и хорошо известному Богдану Гусеву. Богдан, с днём рождения! Я немного улучшил твое решение :)

Какая проблема?

На своем текущем проекте довелось заметить, что часто требуется менее богатая функциональность, чем та, которую предлагают javascript UI библиотеки, но более динамическая, нежели простое отображение результата на отдельной странице.

Классически принято решать подобные вещи достаточно низкоуровневым путем: создается javascript-обработчик, навешивается на определенный селектор, в нужную область выводятся данные. Но что делать, если однотипных решений несколько? Не очень хочется поддерживать несколько почти одинаковых скриптов, да еще и следить за актуальностью селекторов, потому что на проекте с богатой функциональностью большая часть классов CSS на самом деле используется для скриптов.

Такое решение приводит к низкой сопровождаемости верстки, перенасыщению HTML-кода классами. И как итог - становится очень легко всё поломать.

### Как решить?

Решение я увидел вживую прямо на текущем проекте, и оно сразу покорило меня своим изяществом и простотой.

Суть вкратце: для описания какого-либо общего функционала создается coffeescript-класс, экземпляр которого несет в себе описанное поведение. Единственное, что нужно - создать экземпляр нужного класса и привязать к необходимому нам элементу.

Как это работает?

Этого легко добиться, если использовать следующий coffeescript-код:

Note: в изначальной версии этого скрипта использовался класс css widget и класс css initialized. Но мне это не очень нравится, так как задача CSS всё-таки заключается в оформлении, и семантически правильно располагать подобные данные именно в data-аттрибутах. А сам Widget описан таким образом, чтобы иметь возможность вызвать его в любой необходимый момент, так как виджеты могут добавляться на страницу и динамически - следовательно, потребуется инициализация свежесозданных виджетов.

Основная идея понятна уже сейчас: после загрузки страницы этот скрипт проходит по всем элементам, имеющим аттрибут data-widget, но не имеющим аттрибут data-widget-initialized, добавляет им аттрибут и создает объект класса, чьё название указано в аттрибуте data-widget.

Теперь пришло время описать виджет. Допустим, это будет remote-ссылка, меняющая свой текст в зависимости от ответа.

Мы добавляем в ассеты следующий код, описывающий функционал нашего виджета:

1
2
3
4
5
// remotelink.coffee.js
class window.RemoteLink
  constructor: (@el) ->
    @el.bind 'ajax:success': (xhr, data, status) =>
      @el.text(data.text)

Естественно, этот пример до предела упрощен, в реальном использовании не мешало бы проверять как минимум входные данные.

Сама же ссылка описывается следующим образом:

1
2
<%= link_to 'Changeable link', some_application_path, remote: true, html: {data:
{widget: 'RemoteLink'}} %>

Теперь еще раз посмотрим, что произойдёт при загрузке страницы:

  1. Сначала наш скрипт widget.coffee.js найдёт на странице все элементы, обладающие аттрибутом data-widget и не обладающие аттрибутом data-widget-initialized.
  2. Потом widget.coffee.js добавит им необходимый аттрибут data-widget-initialized со значением true и создаст экземпляр класса, описанного в аттрибуте data-widget, передав его конструктору в качестве параметра сам элемент.
  3. Вызовется конструктор класса, указанного в параметре data-widget и выполнит необходимые действия над элементом: например, изменит его отрисовку и навесит необходимые коллбэки.

Подводные камни

Собственно, подводный камень тут один: можно увлечься и нагородить собственный огромный велосипед вместо зарекомендовавших себя обширных и мощных решений вроде JQuery.UI. Так что если виджетов становится слишком много - посмотрите, может быть, пора переходить на более комплексные решения?

Comments