Как использовать Rails Helpers: демонстрация Bootstrap Carousel
Опубликовано: 2022-03-11Одна из самых неправильно используемых, неправильно понятых и пренебрегаемых встроенных структур Rails — view helper . Расположенные в вашем каталоге app/helpers
и генерируемые по умолчанию с каждым новым проектом Rails, помощники часто имеют плохую репутацию из-за того, что являются свалкой одноразовых методов, используемых на уровне представления всего приложения. К сожалению, сам Rails поощряет это отсутствие структуры и плохой организации, включая все вспомогательные функции в каждое представление по умолчанию, создавая загрязненное глобальное пространство имен.
Но что, если бы ваши помощники могли быть более семантическими, лучше организованными и даже повторно используемыми в проектах? Что, если бы они могли быть не просто разовыми функциями, разбросанными по всему представлению, а мощными методами, которые с легкостью генерируют сложную разметку, освобождая ваши представления от условной логики и кода?
Давайте посмотрим, как это сделать при создании карусели изображений с помощью знакомой платформы Twitter Bootstrap и некоторых старых добрых объектно-ориентированных программ.
Когда использовать помощники Rails
Существует множество различных шаблонов проектирования, которые можно использовать на уровне представления Rails: презентаторы, декораторы, партиалы, а также помощники, и это лишь некоторые из них. Мое простое эмпирическое правило заключается в том, что помощники отлично работают, когда вы хотите сгенерировать HTML-разметку, требующую определенной структуры, определенных классов CSS, условной логики или повторного использования на разных страницах.
Лучший пример мощи вспомогательных функций Rails демонстрирует FormBuilder
со всеми связанными с ним методами для создания полей ввода, тегов выбора, меток и других структур HTML. Эти полезные методы генерируют разметку для вас с правильно установленными всеми соответствующими атрибутами. Именно за такое удобство мы все влюбились в Rails.
Преимущества использования хорошо продуманных помощников такие же, как и у любого хорошо написанного, чистого кода: инкапсуляция, уменьшение повторения кода (DRY) и сохранение логики вне поля зрения.
Анатомия карусели Twitter Bootstrap
Twitter Bootstrap — это широко используемый интерфейсный фреймворк со встроенной поддержкой распространенных компонентов, таких как модальные окна, вкладки и карусели изображений. Эти компоненты Bootstrap — отличный вариант использования пользовательских помощников, потому что разметка хорошо структурирована, требует правильной установки определенных классов, идентификаторов и атрибутов данных для работы JavaScript, а установка этих атрибутов требует некоторой условной логики.
Карусель Bootstrap 3 имеет следующую разметку:
<div class="carousel slide" data-ride="carousel"> <!-- Indicators --> <ol class="carousel-indicators"> <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li> <li data-target="#carousel-example-generic" data-slide-to="1"></li> <li data-target="#carousel-example-generic" data-slide-to="2"></li> </ol> <!-- Wrapper for slides --> <div class="carousel-inner"> <div class="item active"> <img src="..." alt="..."> </div> <div class="item"> <img src="..." alt="..."> </div> ... </div> <!-- Controls --> <a class="left carousel-control" href="#carousel-example-generic" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left"></span> </a> <a class="right carousel-control" href="#carousel-example-generic" data-slide="next"> <span class="glyphicon glyphicon-chevron-right"></span> </a> </div>
Как видите, есть три основные структуры: (1) индикаторы (2) слайды изображений (3) элементы управления слайдами.
Цель состоит в том, чтобы иметь возможность создать единственный вспомогательный метод, который берет набор изображений и отображает весь этот компонент карусели, гарантируя, что данные, атрибуты id
, href
и классы CSS установлены правильно.
Помощник
Давайте начнем с базовой схемы помощника:
# app/helpers/carousel_helper.rb module CarouselHelper def carousel_for(images) Carousel.new(self, images).html end class Carousel def initialize(view, images) @view, @images = view, images end def html # TO FILL IN end private attr_accessor :view, :images end end
Вспомогательный метод carousel_for
вернет полную разметку карусели для заданных URL-адресов изображений. Вместо того, чтобы создавать набор отдельных методов для рендеринга каждой части карусели (что потребовало бы от нас передачи коллекции изображений и другой информации о состоянии каждому методу), мы создадим новый простой класс Ruby под названием Carousel
для представлять карусельные данные. Этот класс предоставляет html
-метод, который возвращает полностью обработанную разметку. Мы инициализируем его коллекцией URL-адресов images
и представлением контекста view
.
Обратите внимание, что параметр view
является экземпляром ActionView
, в который смешаны все помощники Rails. Мы передаем его экземпляру нашего объекта, чтобы получить доступ к встроенным вспомогательным методам Rails, таким как link_to
, content_tag
, image_tag
и safe_join
, которые мы будем использовать для создания разметки внутри класса. Мы также добавим макрос delegate
, чтобы мы могли вызывать эти методы напрямую, не обращаясь к view
:
def html content = view.safe_join([indicators, slides, controls]) view.content_tag(:div, content, class: 'carousel slide') end private attr_accessor :view, :images delegate :link_to, :content_tag, :image_tag, :safe_join, to: :view def indicators # TO FILL IN end def slides # TO FILL IN end def controls # TO FILL IN end
Мы знаем, что карусель состоит из трех отдельных компонентов, поэтому давайте заглушим методы, которые в конечном итоге дадут нам разметку для каждого из них, а затем добавим метод html
в контейнерный тег div
, применяя необходимые классы Bootstrap для самой карусели.

safe_join
— это удобный встроенный метод, который объединяет набор строк и вызывает html_safe
для результата. Помните, что у нас есть доступ к этим методам через параметр view
, который мы передали при создании экземпляра.
Сначала мы создадим индикаторы:
def indicators items = images.count.times.map { |index| indicator_tag(index) } content_tag(:ol, safe_join(items), class: 'carousel-indicators') end def indicator_tag(index) options = { class: (index.zero? ? 'active' : ''), data: { target: uid, slide_to: index } } content_tag(:li, '', options) end
Индикаторы представляют собой простой упорядоченный список ol
, в котором есть элемент списка li
для каждого изображения в коллекции. Текущему активному индикатору изображения требуется active
класс CSS, поэтому мы удостоверимся, что он установлен для первого индикатора, который мы создаем. Это отличный пример логики, которая обычно должна быть в самом представлении.
Обратите внимание, что индикаторы должны ссылаться на уникальный id
содержащего карусель элемента (в случае, если на странице более одной карусели). Мы можем легко сгенерировать этот id
в инициализаторе и использовать его во всем остальном классе (в частности, в индикаторах и элементах управления). Выполнение этого программно внутри вспомогательного метода гарантирует, что id
будет согласованным для всех элементов карусели. Во многих случаях небольшая опечатка или изменение id
в одном месте, но не в других, приведет к поломке карусели; здесь этого не произойдет, потому что все элементы автоматически ссылаются на один и тот же id
.
def initialize(view, images) # ... @uid = SecureRandom.hex(6) end attr_accessor :uid
Далее идут слайды изображений:
def slides items = images.map.with_index { |image, index| slide_tag(image, index.zero?) } content_tag(:div, safe_join(items), class: 'carousel-inner') end def slide_tag(image, is_active) options = { class: (is_active ? 'item active' : 'item'), } content_tag(:div, image_tag(image), options) end
Мы просто перебираем каждое из изображений, которые мы передали экземпляру Carousel
, и создаем правильную разметку: тег изображения, заключенный в div
с классом CSS item
, снова убедившись, что добавляем active
класс к первому созданному нами.
Наконец, нам нужны элементы управления «Предыдущий/Следующий»:
def controls safe_join([control_tag('left'), control_tag('right')]) end def control_tag(direction) options = { class: "#{direction} carousel-control", data: { slide: direction == 'left' ? 'prev' : 'next' } } icon = content_tag(:i, nil, class: "glyphicon glyphicon-chevron-#{direction}") control = link_to(icon, "##{uid}", options) end
Мы создаем ссылки, которые управляют движением карусели вперед и назад между изображениями. Обратите внимание на использование uid
снова; не нужно беспокоиться о том, что вы не используете правильный идентификатор во всех разных местах структуры карусели, он автоматически согласован и уникален.
Готовый продукт:
На этом наш помощник-карусель готов. Вот он целиком:
# app/helpers/carousel_helper.rb module CarouselHelper def carousel_for(images) Carousel.new(self, images).html end class Carousel def initialize(view, images) @view, @images = view, images @uid = SecureRandom.hex(6) end def html content = safe_join([indicators, slides, controls]) content_tag(:div, content, id: uid, class: 'carousel slide') end private attr_accessor :view, :images, :uid delegate :link_to, :content_tag, :image_tag, :safe_join, to: :view def indicators items = images.count.times.map { |index| indicator_tag(index) } content_tag(:ol, safe_join(items), class: 'carousel-indicators') end def indicator_tag(index) options = { class: (index.zero? ? 'active' : ''), data: { target: uid, slide_to: index } } content_tag(:li, '', options) end def slides items = images.map.with_index { |image, index| slide_tag(image, index.zero?) } content_tag(:div, safe_join(items), class: 'carousel-inner') end def slide_tag(image, is_active) options = { class: (is_active ? 'item active' : 'item'), } content_tag(:div, image_tag(image), options) end def controls safe_join([control_tag('left'), control_tag('right')]) end def control_tag(direction) options = { class: "#{direction} carousel-control", data: { slide: direction == 'left' ? 'prev' : 'next' } } icon = content_tag(:i, '', class: "glyphicon glyphicon-chevron-#{direction}") control = link_to(icon, "##{uid}", options) end end end
Помощник в действии:
Наконец, чтобы довести до конца суть дела, давайте рассмотрим краткий пример того, как этот помощник может облегчить нашу жизнь. Скажем, мы создаем веб-сайт для объявлений об аренде квартир. Каждый объект Apartment
имеет список URL-адресов изображений:
class Apartment def image_urls # ... end end
С помощью нашего помощника карусели мы можем визуализировать всю карусель Bootstrap одним вызовом carousel_for
, полностью удаляя довольно сложную логику из представления:
<% apartment = Apartment.new %> # ... <%= carousel_for(apartment.image_urls) %>
Не знаете, когда использовать помощники представлений Rails? Вот демонстрация.
Твитнуть
Заключение
Используя эту простую, но мощную технику, мы переместили значительный объем разметки и логики из уровня представления во вспомогательную функцию, которую можно использовать для рендеринга компонентов карусели в любом месте с помощью вызова carousel_for(some_images)
. Этот общий помощник можно использовать во всех ваших проектах Rails, когда вы используете Twitter Bootstrap. Самое главное, теперь у вас есть новый инструмент в вашем наборе инструментов, который вы также можете использовать для компонентов, специфичных для проекта.
Итак, в следующий раз, когда вы обнаружите, что печатаете и перепечатываете один и тот же тип разметки и встраиваете условную логику в свои представления, посмотрите, не ждет ли вспомогательная функция написания, чтобы облегчить вашу жизнь.