2008-09-29 8 views
12

Ho un'app Ruby/Rails che ha due o tre "sezioni" principali. Quando un utente visita quella sezione, desidero visualizzare alcuni sotto-navigatori. Tutte e tre le sezioni usano lo stesso layout, quindi non posso "hardcoded" la navigazione nel layout.Come si implementa la navigazione specifica per sezione in Ruby on Rails?

Posso pensare a diversi metodi per farlo. Credo che per aiutare le persone a votare li metterò come risposte.

Altre idee? O per cosa votate?

risposta

9

È possibile eseguire facilmente questa operazione utilizzando partial, presupponendo che ogni sezione disponga del proprio controller.

Diciamo che avete tre sezioni chiamato Messaggi, Utenti e Admin, ciascuno con il proprio controllore: PostsController, UsersController e AdminController.

In ogni views directory corrispondente, si dichiara una _subnav.html.erb parziale:

 
/app/views/users/_subnav.html.erb 
/app/views/posts/_subnav.html.erb 
/app/views/admin/_subnav.html.erb 

In ognuno di questi parziali subnav dichiari le opzioni specifiche per quella sezione, in modo da /users/_subnav.html.erb potrebbe contenere:

<ul id="subnav"> 
    <li><%= link_to 'All Users', users_path %></li> 
    <li><%= link_to 'New User', new_user_path %></li> 
</ul> 

mentre /posts/_subnav.html.erb potrebbe contenere:

<ul id="subnav"> 
    <li><%= link_to 'All Posts', posts_path %></li> 
    <li><%= link_to 'New Post', new_post_path %></li> 
</ul> 

Infine, una volta che hai fatto questo, basta includere il subnav parziale nel layout:

<div id="header">...</div>  
<%= render :partial => "subnav" %> 
<div id="content"><%= yield %></div> 
<div id="footer">...</div> 
7
  1. Rendering parziale. Questo è molto simile al metodo di supporto tranne forse la disposizione avrebbe qualche if, o passare che fuori ad un aiutante ...
+0

come nel rendering: partial => 'navigation', giusto? – webmat

+0

sì, qualcosa come render: partial => 'section1_subnav' –

1

Ti suggerisco di utilizzare partial. Ci sono alcuni modi in cui puoi farlo. Quando creo dei partial che sono un po 'pignoli in quanto hanno bisogno di variabili specifiche, creo anche un metodo di supporto per questo.

module RenderHelper 
    #options: a nested array of menu names and their corresponding url 
    def render_submenu(menu_items=[[]]) 
    render :partial => 'shared/submenu', :locals => {:menu_items => menu_items} 
    end 
end 

Ora il partial ha una variabile locale denominata menu_items su cui è possibile iterare per creare il proprio sottomenu. Nota che suggerisco un array nidificato invece di un hash perché l'ordine di un hash è imprevedibile.

Si noti che la logica che decide quali elementi dovrebbero essere visualizzati nel menu potrebbe anche essere all'interno di render_submenu se ciò ha più senso per voi.

7

Per quanto riguarda il contenuto dei sottomenu, è possibile procedere in modo dichiarativo in ciascun controller.

class PostsController < ApplicationController 
#... 
protected 
    helper_method :menu_items 
    def menu_items 
    [ 
     ['Submenu 1', url_for(me)], 
     ['Submenu 2', url_for(you)] 
    ] 
    end 
end 

Ora ogni volta che si chiama menu_items da una vista, avrete la lista di destra per scorrere per il controller specifico.

Questo mi sembra una soluzione più pulita rispetto a mettere questa logica nei modelli di visualizzazione.

Si noti che si potrebbe anche voler dichiarare un menu di default (vuoto?) All'interno di ApplicationController.

+1

Non sarebbe meglio avere il testo per le opzioni di sottomenu nella vista piuttosto che il controller? – Olly

+1

Questo è l'approccio più umano che si possa scegliere. Come detto in precedenza, rompe completamente MVC. – maurycy

3

Attenzione: Trucchi avanzati in anticipo!

Renderli tutti. Nascondi quelli che non ti servono usando CSS/Javascript, che possono essere banalmente inizializzati in vari modi. (Javascript può leggere l'URL utilizzato, i parametri di query, qualcosa in un cookie, ecc. Ecc.) Questo ha il vantaggio di giocare potenzialmente meglio con la cache (perché memorizzare nella cache tre visualizzazioni e poi scadere tutte contemporaneamente quando è possibile memorizzarne una nella cache ?) e può essere utilizzato per presentare un'esperienza utente migliore.

Ad esempio, facciamo finta di avere un'interfaccia di barra delle linguette comune con navigazione secondaria. Se si esegue il rendering del contenuto di tutte e tre le schede (vale a dire il suo scritto nell'HTML) e si nascondono due di esse, il passaggio da una scheda all'altra è Javascript banale e non colpisce nemmeno il server. Grande vincita!Nessuna latenza per l'utente. Nessun carico del server per te.

Vuoi un'altra grande vittoria? Puoi utilizzare una variante di questa tecnica per imbrogliare pagine che potrebbero essere comuni al 99% per gli utenti, ma contenere comunque lo stato dell'utente. Ad esempio, potresti avere una prima pagina di un sito che è relativamente comune tra tutti gli utenti, ma dire "Hiya Bob" quando hanno effettuato l'accesso. Inserisci la parte non comune ("Hiya, Bob") in un cookie. Leggere la parte della pagina in Javascript leggendo il cookie. Cache l'intera pagina per tutti gli utenti indipendentemente dallo stato di accesso nella memorizzazione nella cache della pagina. Questo è letteralmente in grado di tagliare il 70% degli accessi dall'intero stack di Rails su alcuni siti.

Chi se ne frega se Rails può scalare o meno quando il sito è davvero Nginx servire le attività statiche con nuove pagine HTML a volte ottenendo consegnati da alcuni di Ruby in esecuzione su ogni accesso millesimo o giù di lì;)

+0

Upvotes per originalità del pensiero. :-) – maurycy

+0

Si noti che questa risposta * fallirà per alcuni utenti se prima non test (o presumi) JavaScript o Cookies, rispettivamente. Una risposta migliore sarebbe utilizzare JavaScript con HTML parziale o Varnish + ESI. –

1

c'è un altro possibile modo per farlo: nidificati Layout

non ricordo dove ho trovato questo codice in modo scuse all'autore originale.

creare un file chiamato nested_layouts.rb nella cartella lib e includere il seguente codice:

module NestedLayouts 
    def render(options = nil, &block) 
    if options 
     if options[:layout].is_a?(Array) 
     layouts = options.delete(:layout) 
     options[:layout] = layouts.pop 
     inner_layout = layouts.shift 
     options[:text] = layouts.inject(render_to_string(options.merge({:layout=>inner_layout}))) do |output,layout| 
      render_to_string(options.merge({:text => output, :layout => layout})) 
     end 
     end 
    end 
    super 
    end 
end 

quindi, creare i diversi layout nella cartella layout, (ad esempio 'admin.rhtml' e 'l'applicazione .rhtml ').

Ora nel tuo controller aggiungere questo solo all'interno della classe:

include NestedLayouts 

E, infine, alla fine delle vostre azioni fare questo:

def show 
    ... 
    render :layout => ['admin','application'] 
end 

l'ordine dei layout nella matrice è importante . Il layout dell'amministratore verrà reso all'interno del layout dell'applicazione ovunque sia lo 'yeild'.

questo metodo può funzionare molto bene a seconda del design del sito e di come sono organizzati i vari elementi. per esempio, uno dei layout inclusi potrebbe contenere solo una serie di div che contengono il contenuto che deve essere mostrato per una determinata azione, e il CSS su un layout più alto potrebbe controllare dove sono posizionati.

0

ci sono alcuni approcci a questo problema.

È possibile utilizzare layout diversi per ciascuna sezione.

Si potrebbe voler utilizzare un parziale incluso da tutte le viste in una determinata directory.

È possibile utilizzare content_for riempito da una vista o parziale e richiamato nel layout globale, se disponibile.

Personalmente credo che in questo caso dovresti evitare ulteriori astrazioni.