Как использовать 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) элементы управления слайдами.

Чертеж, показывающий центрированный прямоугольник с широкоформатным соотношением сторон (слайд) с тремя маленькими кружками внизу (индикаторы). Он окружен двумя тонкими прямоугольниками со стрелками влево и вправо соответственно (элементы управления).
Части карусели Bootstrap.

Цель состоит в том, чтобы иметь возможность создать единственный вспомогательный метод, который берет набор изображений и отображает весь этот компонент карусели, гарантируя, что данные, атрибуты 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. Самое главное, теперь у вас есть новый инструмент в вашем наборе инструментов, который вы также можете использовать для компонентов, специфичных для проекта.

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