Un esempio in profondità

Indice

La missione sarà quella di creare un elemento d’interfaccia utente che presenta pezzi di contenuti divisi in diverse sezioni. Cliccando su una sezione si mostrerà un elenco di voci della sezione, cliccando su un elemento di navigazione sinistra si visualizzerà la voce nella zona del contenuto. Ogni volta che una sezione viene mostrata, la prima voce della sezione dovrebbe essere mostrata. La prima sezione deve essere riportata al caricamento della pagina.

Fase 1: Elaborazione del codice HTML
La buona scrittura semantica dell’HTML è un prerequisito per la buona scrittura del codice JavaScript, quindi cominciamo a pensare a come il codice HTML potrebbe essere scritto per fare quello che ci siamo prefissati. Il codice HTML dovrebbe:

  • Dare un senso e lavorare comunque quando JavaScript non è disponibile.
  • Fornire un DOM prevedibile a cui ci si riesca a mettere JavaScript.
  • Evitare ID e classi inutili (e ci si può essere sorpresi da quanto pochi siano necessari).

Con queste linee guida in mente, cominciamo con questo codice HTML.

Codice HTML
Copia codice

<h1>Questo è  il mio Feature Nidificato</h1>
<div id="myFeature">
 <ul class="sections">
  <li>
   <h2><a href="/section/1">Sezione 1</a></h2>
   <ul>
    <li>
     <h3><a href="/section/1/content/1">Sezione 1 Titolo 1</a></h3>
     Il contenuto estratto dall'Item 1
    </li>
    <li>
     <h3><a href="/section/1/content/2">Sezione 1 Titolo 2</a></h3>
     Il contenuto estratto dall'Item 2
    </li>
    <li>
     <h3><a href="/section/1/content/3">Sezione 1 Titolo 3</a></h3>
     Il contenuto estratto dall'Item 3
    </li>
   </ul>
  </li>
  <li>
   <h2><a href="/section/2">Sezione 2</a></h2>
   <ul>
    <li>
     <h3><a href="/section/2/content/1">Sezione 2 Titolo 1</a></h3>
     Il contenuto estratto dall'Item 1
    </li>
    <li>
     <h3><a href="/section/2/content/2">Sezione 2 Titolo 2</a></h3>
     Il contenuto estratto dall'Item 2
    </li>
    <li>
     <h3><a href="/section/2/content/3">Sezione 2 Titolo 3</a></h3>
     Il contenuto estratto dall'Item 3
    </li>
   </ul>
  </li>
  <li>
   <h2><a href="/section/3">Sezione 3</a></h2>
   <ul>
    <li>
     <h3><a href="/section/3/content/1">Sezione 3 Titolo 1</a></h3>
     Il contenuto estratto dall'Item 1
    </li>
    <li>
     <h3><a href="/section/3/content/2">Sezione 3 Titolo 2</a></h3>
     Il contenuto estratto dall'Item 2
    </li>
    <li>
     <h3><a href="/section/3/content/3">Sezione 3 Titolo 3</a></h3>
     Il contenuto estratto dall'Item 3
    </li>
   </ul>
  </li>
 </ul>
</div>

Si noti che non abbiamo incluso nessun marcatore per visualizzare la sezione di navigazione o per la navigazione a item, i pezzi saranno aggiunti da jQuery dal momento che funziona solo con jQuery, gli utenti senza JavaScript abilitato riceveranno una bella markup semantica. – Se c’è qualcosa di sorprendente o di confusione in questo HTML, sarebbe un ora buon momento per leggere qualcosa su POSH (acronimo di plain-old semantic HTML) e aumento progressivo (progressive enhancement).

Fase 2: impalcatura per l'oggetto
Il mio primo passo nella creazione di un oggetto per una funzione è quella di creare delle “tacche” all’interno dell’oggetto. Le “tacche” sono fondamentalmente segnaposto: sono linee guida generali per la funzione che costruiremo. Il nostro oggetto avrà i seguenti metodi:

  1. myFeature.init() verrà eseguito su $(document).ready(). E si accede dal codice HTML per iniziare a creare una interfaccia utente in JavaScript se èabilitato.
  2. myFeature.buildSectionNav() viene chiamato da myFeature.init(). Ci vorrà un oggetto jQuery che contiene tutte le sezioni dal codice HTML per costruire la navigazione in alto. Si legheranno i gestori dei click per ogni voce di navigazione in alto in modo che cliccando su di essi si mostra la sezione appropriata.
  3. myFeature.buildItemNav() viene chiamato da myFeature.showSection(). Ci vorrà un oggetto jQuery che contiene tutti gli elementi connessi con le sezioni del codice HTML, e usarli per costruire la navigazione laterale. Si legheranno i gestori click per elementi di navigazione sul lato in modo che cliccando su di essi mostrerà il contenuto appropriato.
  4. myFeature.showSection() viene chiamato quando l’utente fa click su un elemento dalla barra di navigazione in alto. Userà la voce di navigazione dove è cliccato per capire quale sezione mostrare dal codice HTML.
  5. myFeature.showContentItem() viene chiamato quando l’utente fa click su un elemento nella barra di navigazione laterale. Userà la voce di navigazione che è cliccato per capire quale elemento di contenuto visualizzare dal codice HTML

Ci sarà anche da fare uno spazio per le proprietà di configurazione, myFeature.config(), che sarà un luogo unico per l’impostazione dei valori di default e per non disperdendoli in tutto il codice. Ci includiamo la capacità di sovrascrivere le impostazioni di default quando si definisce il metodo myFeature.init().

Struttura del codice js
Copia codice

var myFeature = {
    'config' : { },
    'init' : function() { },
    'buildSectionNav' : function() { },
    'buildItemNav' : function() { },
    'showSection' : function() { },
    'showContentItem' : function() { }
};
Fase 3: Il Codice js
Una volta che abbiamo costruito lo scheletro, è il momento di iniziare a scrivere codice. Cominciamo con la creazione di un semplice oggetto myFeature.config() e scrivere il metodo myFeature.init():

Dettagi del codice js
Copia codice

var myFeature = {
	'config' : {
	    // il contenitore predefinito è  #myFeature
	    'container' : $('#myFeature')
	},
	'init' : function(config) {
	    // fornisce la configurazione
	    // personalizzata tramite init()
	    if (config && typeof(config) == 'object') {
	        $.extend(myFeature.config, config);
	    }
	    // Crea e/o salva alcuni elementi nella cache
	    // dal DOM da utilizzare lungo tutto il codice
	    myFeature.$container = myFeature.config.container;
	    myFeature.$sections = myFeature.$container.
	        // cerca e seleziona solo i figli prossimi!
	        find('ul.sections > li'); 
	    myFeature.$section_nav = $('<p/>')
	      .attr('id','section_nav')
	      .prependTo(myFeature.$container);     
	    myFeature.$item_nav = $('<p/>')
	      .attr('id','item_nav')
	      .insertAfter(myFeature.$section_nav);     
	    myFeature.$content = $('<p/>')
	      .attr('id','content')
	      . insertAfter(myFeature.$item_nav);   
	  // costruisce la sezione a livello nav
	  // e forza il "click" del suo primo elemento
	  myFeature.buildSectionNav(myFeature.$sections);
	  myFeature.$section_nav.find('li:first').click();      
	  // nasconde tutte le sections dell'HTML dalla vista
	  myFeature.$container.find('ul.sections').hide();      
	  // prende nota che l'inizializzazione è  completata,
	  // ma non abbiamo assolutamente bisogno di questo
	  // per questa iterazione, ma può  tornare utile
	  myFeature.initialized = true;
	},
    //Poi creare il metodo myFeature.buildSectionNav():
	'buildSectionNav' : function($sections) {
	    // itera la lista delle sections trovate
	    $sections.each(function() {
	        // Ottiene la section
	        var $section = $(this);     
	        // crea una lista di item nella parte
	        // del menù  di navigazione
	        $('<li/>')
	          // usa il testo del primo elemento h2
	          // della section come testo per il
	          // menù  di navigazione
	          .text($section.find('h2:first').text())
	          // aggiunge alla lista di items del
	          // menù  di navigazione
	          .appendTo(myFeature.$section_nav)
	          // usa il metodo data() per memorizzare
	          // un riferimento della section originale
	          // sulla nuova lista di items
	          .data('section', $section)
	          // vincola il comportamento del click
	          // sui nuovi items recentemente creati
	          // in modo da visualizzara la section
	          .click(myFeature.showSection);
	     });
     },
    // poi si crea il metodo myFeature.buildItemNav():
	'buildItemNav' : function($items) {
	// itera su ogni item fornito
	$items.each(function() {
	    // Ottiene l'item
	    var $item = $(this);
	    // crea una lista di elementi
	    // per navigare attraverso gli item
	    $('<li>')
	      // usa il testo del primo h3
	      // dell'item come testo per
	      // la voce del menù  di navigazione
	      .text($item.find('h3:first').text())
	      // aggiunge l'elemento dell'elenco
	      // alla voce di navigazione
	      .appendTo(myFeature.$item.nav)
	      // Utilizza data() per memorizzare
	      // un riferimento per l'elemento originale
	      // della nuova voce dell'elenco
	      .data('item', $item)
	      // vincola il comportamento del click per
	      // l'elemento della lista appena creata
	      // in modo che visualizzi il contenuto
	      // dell'elemento
	      .click(myFeature.showContentItem);
		});
     },
	// infine, si scrivono i metodi per visualizzare
	// le sezioni e gli elementi nel contenitore:
	'showSection' : function() {
	  // cattura la voce dall'elenco dove è  stato cliccato
	  var $li = $(this);
	  // elimina il nav a sinistra e l'area dei contenuti
	  myFeature.$item_nav.empty();
	  myFeature.$content.empty();
	  // ottiene l'oggetto jQuery dal codice
	  // HTML originale, memorizzato utilizzando
	  // data() in buildSectionNav()
	  var $section = $li.data('section');
	  // contrassegna la voce dell'elenco selezionato
	  // come 'current' e rimuovere l'indicatore 'current'
	  // dai suoi fratelli
	  $li.addClass('current')
	    .siblings().removeClass('current');
	  // trova tutti gli elementi correlati alla sezione
	  var $items = $section.find('ul li');
	  // costruisce la voce di navigazione per la sezione
	  myFeature.buildItemNav($items);
	  // forza il "click" del primo elemento della lista
	  // di voci di navigazione nella sezione
	  myFeature.$item_nav.find('li:first').click();
	}, 
	'showContentItem' : function() {
	  var $li = $(this);
	  // contrassegna la voce dell'elenco cliccato
	  // come 'current' e rimuove l'indicatore 'current'
	  // dai suoi fratelli
	  $li.addClass('current')
	    .siblings().removeClass('current');
	  // ottiene l'oggetto jQuery dal codice HTML
	  // originale che si è  memorizzato utilizzando
	  // i dati durante buildContentNav()
	  var $item = $li.data('item');
	  // usa l'item dell'HTML per popolare
	  // l'area dei contenuti
	  myFeature.$content.html($item.html());
	}
};

Non resta che chiamare il metodo myFeature.init():

Avvio del codice js
Copia codice

$(document).ready(myFeature.init);

Potete vedere questo esempio scaricando (con un po’ di CSS per renderla presentabile) un file compreso da qui.

Fase 4: nuove esigenze
Nessun progetto è completo senza qualche modifica all’ultimo minuto nei requisiti, giusto? Ecco dove l’approccio dell’Oggetto Letterale brilla davvero rendendolo veloce e abbastanza indolore se si ha bisogno di implementare cambiamenti all’ultimo minuto. Cosa succede se abbiamo bisogno di ottenere i contenuti dell’articolo estratti via AJAX anziché dall’HTML? Supponendo che il server è configurato per gestire la cosa, provare quanto segue:

Estrazione via ajax
Copia codice

var myFeature = {
    'config' : {
        'container' : $('#myFeature'),
        // funzione configurabile per ottenere
        // il contenuto dell'articolo da un URL
        'getItemURL' : function($item) {
            return $item.find('a:first').attr('href');
        }
    },
    'init' : function(config) {
        // rimane lo stesso
    },
    'buildSectionNav' : function($sections) {
        // rimane lo stesso
    },
    'buildItemNav' : function($items) {
        // rimane lo stesso
    },
    'showSection' : function() {
        // rimane lo stesso
    },
    'showContentItem' : function() {
        var $li = $(this);
        $li.addClass('current').
            siblings().removeClass('current');
        var $item = $li.data('item');
        var url = myFeature.config.getItemURL($item);
        // myFeature.$content.html($item.html());
        myFeature.$content.load(url);
    }
};

Bisogno di maggiore flessibilità? Ci sono molte altre cose che, volendo, è possibile impostare (e quindi sovrascrivere) se si vuole veramente rendere flessibile il nostro Oggetto Letterale. Ad esempio, è possibile specificare in myFeature.config il modo di trovare ed elaborare il testo del titolo di ogni elemento nel nemù di navigazione a sinistra.

Più flessibilità
Copia codice

var myFeature = {
    'config' : {
        'container' : $('#myFeature'),
        // specifica il selettore predefinito
        // per trovare il testo da usare
        // in ogni elemento della voce nav
        'itemNavSelector' : 'h3',
        // specifica un callback predefinit
        // per "processare" l'oggetto jQuery
        // restituito dal selettore itemNavTex
        'itemNavProcessor' : function($selection) {
            return 'Anteprima di ' +
                $selection.eq(0).text();
        }
    },
    'init' : function(config) {
        // rimane lo stesso
    },
    'buildSectionNav' : function($sections) {
        // rimane lo stesso
    },
    'buildItemNav' : function($items) {
        $items.each(function() {
            var $item = $(this);
            // usa il selettore e processore
            // dal cofig per ottenere il testo
            // di ogni elemento della voce nav
            var myText = myFeature.config.itemNavProcessor(
                $item.find(myFeature.config.itemNavSelector)
            );
            $('<li/>')
            // usa la nuova variabile
            // come testo per la voce nav
              .text(myText)
              .appendTo(myFeature.$item_nav)
              .data('item', $item)
              .click(myFeature.showContentItem);
      });
  },    
  'showSection' : function() {
    // rimane lo stesso
  },    
  'showContentItem' : function() {
    // rimane lo stesso
  }  
};

Dopo aver aggiunto è l’oggetto per configurazione di default, è possibile ignorare quando si chiama myFeature.init():

Chiamata a init()
Copia codice

$(document).ready(function() {
    myFeature.init({ 'itemNavSelector' : 'h2' });
});

Oltre lo scopo di questo articolo (che sarebbe interessante vedere come è molto più semplice intervenire con il modello dell’Oggetto Letterale) è il seguente: Invertire il pulsante Indietro dal suo senso attraverso le schede usando l’estensione history per jQuery. Lascio come esercizio al lettore.

Conclusione
Se avete fatto i passi lungo tutto il codice di esempio in questa pagina, si dovrebbe avere una conoscenza di base della struttura dell’Oggetto Letterale e come potrebbe essere utile quando si sviluppano le caratteristiche più complesse e interazioni. È inoltre possibile accedere al codice per utilizzare e costruire modelli su questo fondamento di base.
Vi incoraggio a dare a questo modello una prova la prossima volta che vi ritroverete a scrivere più di alcune righe di codice in JavaScript – questo costringe a pensare attraverso gli elementi e i comportamenti che costituiscono una caratteristica complessa o di interazione. Una volta diventati esperti, fornisce una base solida per estendere e riutilizzare il codice.
Approfondimetni