Come utilizzare gli aiutanti Rails: una dimostrazione del carosello Bootstrap

Pubblicato: 2022-03-11

Una delle strutture integrate di Rails più utilizzate in modo improprio, frainteso e trascurato è l' assistente di visualizzazione . Situati nella directory app/helpers e generati per impostazione predefinita con ogni nuovo progetto Rails, gli helper spesso hanno una cattiva reputazione per essere una discarica per i metodi una tantum utilizzati nell'intero livello di visualizzazione dell'applicazione. Sfortunatamente, Rails stesso incoraggia questa mancanza di struttura e scarsa organizzazione includendo tutti gli helper in ogni vista per impostazione predefinita, creando uno spazio dei nomi globale inquinato.

Ma cosa accadrebbe se i tuoi aiutanti potessero essere più semantici, meglio organizzati e persino riutilizzabili in tutti i progetti? E se potessero essere più di semplici funzioni sparse in tutta la vista, ma metodi potenti che generavano markup complessi con facilità lasciando le viste libere da logica e codice condizionali?

Vediamo come farlo quando si costruisce un carosello di immagini, con il noto framework Bootstrap di Twitter e una buona programmazione orientata agli oggetti vecchio stile.

Quando utilizzare gli helper Rails

Esistono molti diversi modelli di progettazione che possono essere utilizzati nel livello di visualizzazione di Rails: presentatori, decoratori, parziali e aiutanti, solo per citarne alcuni. La mia semplice regola pratica è che gli helper funzionano alla grande quando si desidera generare markup HTML che richiede una determinata struttura, classi CSS specifiche, logica condizionale o riutilizzo su pagine diverse.

Il miglior esempio della potenza degli helper Rails è dimostrato dal FormBuilder con tutti i metodi associati per generare campi di input, selezionare tag, etichette e altre strutture HTML. Questi metodi utili generano markup per te con tutti gli attributi pertinenti impostati correttamente. Una comodità come questa è il motivo per cui ci siamo tutti innamorati di Rails in primo luogo.

I vantaggi dell'utilizzo di helper ben realizzati sono gli stessi di qualsiasi codice pulito e ben scritto: incapsulamento, riduzione della ripetizione del codice (DRY) e mantenere la logica fuori dalla vista.

Anatomia di una giostra Bootstrap di Twitter

Twitter Bootstrap è un framework front-end ampiamente utilizzato che viene fornito con supporto integrato per componenti comuni come modali, schede e caroselli di immagini. Questi componenti Bootstrap sono un ottimo caso d'uso per gli helper personalizzati perché il markup è altamente strutturato, richiede che determinate classi, ID e attributi di dati siano impostati correttamente affinché JavaScript funzioni e l'impostazione di tali attributi richiede un po' di logica condizionale.

Un carosello Bootstrap 3 ha il seguente markup:

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

Come puoi vedere, ci sono tre strutture principali: (1) gli indicatori (2) le diapositive delle immagini (3) i controlli delle diapositive.

Un progetto che mostra un rettangolo centrato con rapporto widescreen (diapositiva) con tre piccoli cerchi vicino al fondo (indicatori). È fiancheggiato da due sottili rettangoli con rispettivamente le frecce sinistra e destra (controlli).
Le parti di una giostra Bootstrap.

L'obiettivo è essere in grado di creare un unico metodo di supporto che prenda una raccolta di immagini e esegua il rendering dell'intero componente del carosello, assicurando che data, id , attributi href e classi CSS siano tutti impostati correttamente.

L'aiutante

Iniziamo con uno schema di base dell'helper:

 # 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

Il metodo di supporto carousel_for restituirà il markup del carosello completo per gli URL di immagine specificati. Invece di creare una suite di metodi individuali per eseguire il rendering di ogni porzione del carosello (che richiederebbe il passaggio della raccolta di immagini e altre informazioni di stato a ciascun metodo), creeremo una nuova classe Ruby semplice chiamata Carousel per rappresentare i dati del carosello. Questa classe esporrà un metodo html che restituisce il markup completamente renderizzato. Lo inizializziamo con la raccolta di images URL di immagine e la visualizzazione del contesto di view .

Nota che il parametro view è un'istanza di ActionView , in cui sono mischiati tutti gli helper di Rails. Lo passiamo alla nostra istanza dell'oggetto per ottenere l'accesso ai metodi di supporto integrati di Rails come link_to , content_tag , image_tag e safe_join , che useremo per costruire il markup all'interno della classe. Aggiungeremo anche la macro delegate , in modo da poter chiamare direttamente quei metodi, senza fare riferimento a 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

Sappiamo che un carosello è composto da tre componenti separati, quindi analizziamo i metodi che alla fine ci forniranno il markup per ciascuno, quindi facciamo in modo che il metodo html li unisca in un tag div contenitore, applicando le classi Bootstrap necessarie per il carosello stesso.

safe_join è un pratico metodo integrato che concatena una raccolta di stringhe e chiama html_safe sul risultato. Ricorda, abbiamo accesso a questi metodi tramite il parametro view , che abbiamo passato quando abbiamo creato l'istanza.

Costruiremo prima gli indicatori:

 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

Gli indicatori sono un semplice elenco ol che ha un elemento di elenco li per ogni immagine nella raccolta. L'indicatore dell'immagine attualmente attivo necessita della classe CSS active , quindi ci assicureremo che sia impostato per il primo indicatore che creiamo. Questo è un ottimo esempio di logica che normalmente dovrebbe essere nella vista stessa.

Si noti che gli indicatori devono fare riferimento id univoco dell'elemento del carosello che lo contiene (nel caso in cui ci sia più di un carosello nella pagina). Possiamo facilmente generare questo id nell'inizializzatore e usarlo nel resto della classe (in particolare all'interno degli indicatori e dei controlli). L'esecuzione di questa operazione a livello di codice all'interno di un metodo di supporto garantisce che l' id sia coerente tra gli elementi del carosello. Ci sono molte volte in cui un piccolo errore di battitura o la modifica id in un posto ma non negli altri causerà la rottura di un carosello; ciò non accadrà qui perché tutti gli elementi fanno riferimento automaticamente allo stesso id .

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

Successivamente ci sono le diapositive delle immagini:

 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

Eseguiamo semplicemente un'iterazione su ciascuna delle immagini che abbiamo passato all'istanza Carousel e creiamo il markup appropriato: un tag immagine racchiuso in un div con la classe CSS item , assicurandoci ancora una volta di aggiungere la classe active alla prima che creiamo.

Infine, abbiamo bisogno dei controlli Precedente/Successivo:

 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

Creiamo collegamenti che controllano il movimento del carosello avanti e indietro tra le immagini. Nota di nuovo l'uso di uid ; non c'è bisogno di preoccuparsi di non utilizzare l'ID giusto in tutti i diversi punti all'interno della struttura del carosello, è automaticamente coerente e unico.

Il prodotto finito:

Con questo, il nostro aiutante del carosello è completo. Eccolo nella sua interezza:

 # 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

L'aiutante in azione:

Infine, per portare a casa il punto, diamo un'occhiata a un rapido esempio di come questo aiutante può semplificarci la vita. Supponiamo che stiamo costruendo un sito Web per annunci di appartamenti in affitto. Ogni oggetto Apartment ha un elenco di URL immagine:

 class Apartment def image_urls # ... end end

Con il nostro carousel helper, possiamo eseguire il rendering dell'intero carosello Bootstrap con una singola chiamata a carousel_for , rimuovendo completamente la logica abbastanza complessa dalla vista:

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

Non sei sicuro di quando usare gli helper di visualizzazione Rails? Ecco una dimostrazione.

Twitta

Conclusione

Usando questa tecnica semplice ma potente, abbiamo spostato quella che sarebbe stata una quantità significativa di markup e logica fuori dal livello di visualizzazione e in una funzione di supporto che può essere utilizzata per eseguire il rendering dei componenti del carosello ovunque con una semplice chiamata carousel_for(some_images) . Questo helper generico può essere utilizzato in tutti i tuoi progetti Rails ogni volta che utilizzi Twitter Bootstrap. Ancora più importante, ora hai un nuovo strumento nel tuo toolkit che puoi utilizzare anche per componenti specifici del progetto.

Quindi, la prossima volta che ti ritrovi a digitare e ridigitare lo stesso tipo di markup e a incorporare la logica condizionale nelle tue viste, controlla se una funzione di supporto sta solo aspettando di essere scritta per semplificarti la vita.