Cómo usar Rails Helpers: una demostración de carrusel Bootstrap

Publicado: 2022-03-11

Una de las estructuras integradas de Rails que más se usa mal, se malinterpreta y se descuida es el asistente de vista . Ubicados en el directorio app/helpers y generados de forma predeterminada con cada nuevo proyecto de Rails, los helpers a menudo tienen una mala reputación por ser un vertedero de métodos únicos utilizados en toda la capa de vista de la aplicación. Desafortunadamente, el mismo Rails fomenta esta falta de estructura y mala organización al incluir a todos los ayudantes en cada vista de forma predeterminada, creando un espacio de nombres global contaminado.

Pero, ¿y si sus ayudantes pudieran ser más semánticos, mejor organizados e incluso reutilizables en todos los proyectos? ¿Qué pasaría si pudieran ser más que solo funciones únicas esparcidas por toda la vista, sino métodos poderosos que generaran marcas complejas con facilidad, dejando sus vistas libres de código y lógica condicional?

Veamos cómo hacer esto al construir un carrusel de imágenes, con el marco familiar de Twitter Bootstrap y algo de buena programación orientada a objetos a la antigua.

Cuándo usar los ayudantes de Rails

Hay muchos patrones de diseño diferentes que se pueden usar en la capa de vista de Rails: presentadores, decoradores, parciales, así como ayudantes, solo por nombrar algunos. Mi regla general simple es que los ayudantes funcionan muy bien cuando desea generar marcado HTML que requiere una estructura determinada, clases CSS específicas, lógica condicional o reutilización en diferentes páginas.

El mejor ejemplo del poder de los ayudantes de Rails lo demuestra FormBuilder con todos sus métodos asociados para generar campos de entrada, seleccionar etiquetas, etiquetas y otras estructuras HTML. Estos métodos útiles generan marcado para usted con todos los atributos relevantes configurados correctamente. Una comodidad como esta es la razón por la que todos nos enamoramos de Rails en primer lugar.

Los beneficios de usar ayudantes bien diseñados son los mismos que cualquier código limpio y bien escrito: encapsulación, reducción de la repetición de código (DRY) y mantener la lógica fuera de la vista.

Anatomía de un carrusel Bootstrap de Twitter

Twitter Bootstrap es un marco de front-end ampliamente utilizado que viene con soporte integrado para componentes comunes como modales, pestañas y carruseles de imágenes. Estos componentes de Bootstrap son un gran caso de uso para los ayudantes personalizados porque el marcado está muy estructurado, requiere que ciertas clases, ID y atributos de datos se configuren correctamente para que JavaScript funcione, y configurar esos atributos requiere un poco de lógica condicional.

Un carrusel de Bootstrap 3 tiene el siguiente marcado:

 <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>

Como puede ver, hay tres estructuras principales: (1) los indicadores (2) las diapositivas de imagen (3) los controles deslizantes.

Un plano que muestra un rectángulo de proporción de pantalla ancha centrado (diapositiva) con tres círculos pequeños cerca de su parte inferior (indicadores). Está flanqueado por dos rectángulos delgados con flechas izquierda y derecha, respectivamente (controles).
Las partes de un carrusel Bootstrap.

El objetivo es poder construir un único método auxiliar que tome una colección de imágenes y represente todo este componente de carrusel, asegurando que los atributos data, id , href y clases CSS estén configurados correctamente.

el ayudante

Comencemos con un esquema básico del ayudante:

 # 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

El método auxiliar carousel_for devolverá el marcado de carrusel completo para las URL de imagen dadas. En lugar de crear un conjunto de métodos individuales para representar cada parte del carrusel (lo que requeriría que pasáramos la colección de imágenes y otra información con estado a cada método), crearemos una nueva clase de Ruby llamada Carousel para representar los datos del carrusel. Esta clase expondrá un método html que devuelve el marcado completo. Lo inicializamos con la colección de imágenes URL de images y la vista de contexto de view .

Tenga en cuenta que el parámetro de view es una instancia de ActionView , en la que se mezclan todos los ayudantes de Rails. Lo pasamos a nuestra instancia de objeto para obtener acceso a los métodos auxiliares integrados de Rails, como link_to , content_tag , image_tag y safe_join , que usaremos para crear el marcado dentro de la clase. También agregaremos la macro de delegate , para que podamos llamar a esos métodos directamente, sin hacer referencia a la 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

Sabemos que un carrusel se compone de tres componentes separados, así que vamos a extraer métodos que eventualmente nos darán el marcado para cada uno, luego hagamos que el método html los una en una etiqueta div de contenedor, aplicando las clases de Bootstrap necesarias para el carrusel en sí.

safe_join es un práctico método incorporado que concatena una colección de cadenas y llama a html_safe en el resultado. Recuerde, tenemos acceso a esos métodos a través del parámetro de view , que pasamos cuando creamos la instancia.

Construiremos los indicadores primero:

 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

Los indicadores son una lista ordenada simple ol que tiene un elemento de lista li para cada imagen de la colección. El indicador de imagen actualmente activo necesita la clase CSS active , por lo que nos aseguraremos de que esté configurado para el primer indicador que creamos. Este es un gran ejemplo de lógica que normalmente tendría que estar en la vista misma.

Tenga en cuenta que los indicadores deben hacer referencia a la id única del elemento del carrusel que lo contiene (en caso de que haya más de un carrusel en la página). Podemos generar fácilmente esta id en el inicializador y usarla en el resto de la clase (específicamente dentro de los indicadores y los controles). Hacer esto programáticamente dentro de un método auxiliar garantiza que la id sea consistente en todos los elementos del carrusel. Hay muchas ocasiones en que un pequeño error tipográfico o cambiar la id en un lugar pero no en los demás hará que se rompa un carrusel; eso no sucederá aquí porque todos los elementos hacen referencia automáticamente al mismo id .

 def initialize(view, images) # ... @uid = SecureRandom.hex(6) end attr_accessor :uid

A continuación están las diapositivas de imágenes:

 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

Simplemente iteramos sobre cada una de las imágenes que pasamos a la instancia de Carousel y creamos el marcado adecuado: una etiqueta de imagen envuelta en un div con la clase CSS del item , nuevamente asegurándonos de agregar la clase active a la primera que creamos.

Por último, necesitamos los controles Anterior/Siguiente:

 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

Creamos enlaces que controlan el movimiento del carrusel de ida y vuelta entre las imágenes. Tenga en cuenta el uso de uid nuevamente; no hay necesidad de preocuparse por no usar la identificación correcta en todos los diferentes lugares dentro de la estructura del carrusel, es automáticamente consistente y único.

El producto terminado:

Con eso, nuestro asistente de carrusel está completo. Aquí está en su totalidad:

 # 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

El ayudante en acción:

Finalmente, para recalcar el punto, veamos un ejemplo rápido de cómo este ayudante puede hacernos la vida más fácil. Digamos que estamos construyendo un sitio web para listados de apartamentos en alquiler. Cada objeto Apartment tiene una lista de las URL de las imágenes:

 class Apartment def image_urls # ... end end

Con nuestro asistente de carrusel, podemos representar todo el carrusel de Bootstrap con una sola llamada a carousel_for , eliminando por completo la lógica bastante compleja de la vista:

 <% apartment = Apartment.new %> # ... <%= carousel_for(apartment.image_urls) %> 

¿No está seguro de cuándo usar los asistentes de visualización de Rails? Aquí hay una demostración.

Pío

Conclusión

Usando esta técnica simple pero poderosa, hemos movido lo que sería una cantidad significativa de marcado y lógica fuera de la capa de vista y dentro de una función auxiliar que se puede usar para representar componentes de carrusel en cualquier lugar con solo una llamada carousel_for(some_images) . Esta ayuda genérica se puede utilizar en todos sus proyectos de Rails cada vez que utilice Twitter Bootstrap. Lo que es más importante, ahora tiene una nueva herramienta en su kit de herramientas que también puede usar para componentes específicos del proyecto.

Por lo tanto, la próxima vez que se encuentre escribiendo y volviendo a escribir el mismo tipo de marcado e incrustando lógica condicional en sus vistas, vea si una función auxiliar está esperando a ser escrita para facilitarle la vida.