Si tratta di una traduzione di un articolo molto interessante originariamente apparso nel numero di marzo 2009 su JSMag, che descrive i passi a seguire per creare un Oggetto Letterale.

Usando oggetti per organizzare il codice

Andando oltre ai semplici frammenti di jQuery e cominciando a sviluppare le interazioni degli utenti più complesse, il codice può rapidamente diventare ingombrante e difficile da depurare. In questo articolo viene descritto come iniziare a pensare a queste interazioni in termini di comportamento della funzione utilizzando il modello di Oggetto Letterale.
Negli ultimi anni le librerie JavaScript hanno facilitato agli sviluppatori la possibilità di aggiungere interazioni sviluppati presso i loro siti. Alcuni, come jQuery, ha una sintassi così semplice che persone con esperienza di programmazione zero possono aggiungere rapidamente campane e fischietti ai loro siti. Aggiungendo tutte quelle campane e fischietti, anche alcuni tra quelli piuttosto elaborate, sembra essere facile con solo poche ricerche su Google. Un copia qui e un incolla là, un plugin o alcune linee di codice personalizzato – il cliente rimane impressionato, e si aggiunge la voce jQuery ner il proprio curriculum.
Ma aspetta. Ora le esigenze sono cambiate. Ora la cosa che aveva bisogno di lavorare per tre elementi ha bisogno di lavorare per dieci. Ora il codice deve essere riutilizzato per una richiesta leggermente diversa, dove tutti gli ID sono diversi. Abbiamo visto tutti i frammenti che compongono jQuery (e altre biblioteche) sembrano semplice da morire. Ma quali frammenti lasciare fuori? È come progettare il codice quando le esigenze vanno oltre le capacità in un plugin o fare qualcosa come show()?.
[nextpage title=”Introduzione al Modello di Oggetto Letterale”] Il modello di Oggetto Letterale offre un modo per organizzare il comportamento del codice. È anche un mezzo per mantenere il codice libero da “contaminazioni del namespace globale“, che è una buona pratica per tutti i progetti e imperativo per quelle più grandi. Questo costringe a pensare fin dall’inizio su ciò che il codice farà e quali pezzi devono essere in un dato posto affinché avvenga. Un Oggetto Letterale è un modo per incapsulare i relativi comportamenti, come illustrato di seguito:

Un oggetto letterale
Copia codice

var myObjectLiteral = {
    myBehavior1 : function() {
        /* Fare qualcosa */
    },
    myBehavior2 : function() {
        /* Fare qualcosa di diverso */
    }
};

A titolo di esempio e artificialmente semplicistico, si supponga di avere come nell’esempio seguente del codice per mostrare e nascondere il contenuto quando un elemento della lista viene cliccato.

Mostrare e nascondere il contenuto
Copia codice

$(document).ready(function() {
    $('#myFeature li')
    .append('<div/>')
    .each(function() {
        $(this).find('div')
        .load('foo.php?item=' + $(this).attr('id'));
    })
    .click(function() {
        $(this).find('div').show();
        $(this).siblings().find('div').hide();
    });
});

Abbastanza semplice, eppure anche in questo esempio ci sono diverse cose che si potrebbe desiderare di cambiare in seguito – per esempio, il modo in cui determinare l’URL per caricare il contenuto, la destinazione del contenuto caricato, o il mostrare e nascondere il contenuto. Una rappresentazione di funzioni in un Oggetto Letterale separa nettamente questi aspetti. Potrebbe apparire così:

Rappresentazione in un oggetto letterale
Copia codice

var myFeature = {
    config : {
        wrapper : '#myFeature',
        container : 'div',
        urlBase : 'foo.php?item='
    },
    init : function(config) {
        $.extend(myFeature.config, config);
        $(myFeature.config.wrapper).find('li').
            each(function() {
                myFeature.getContent($(this));
            }).
            click(function() {
                myFeature.showContent($(this));
            });
    },
    buildUrl : function($li) {
        return myFeature.config.urlBase + $li.attr('id');
    }, 
    getContent : function($li) {
        $li.append(myFeature.config.container);
        var url = myFeature.buildUrl($li);
        $li.find(myFeature.config.container).load(url);
    },
    showContent : function($li) {
        $li.find('div').show();
        myFeature.hideContent($li.siblings());
    },
    hideContent : function($elements) {
        $elements.find('div').hide();
    }
};
$(document).ready(function() { myFeature.init(); });

Perché l’esempio iniziale era incredibilmente semplicistico, l’incarnazione dell’Oggetto Letterale è più lungo. A dire il vero, il metodo dell’Oggetto Letterale generalmente non vi farà risparmiare righe di codice. La cosa fà risparmiare è mal di testa. Utilizzando un Oggetto Letterale, abbiamo diviso il nostro codice nelle sue parti logiche, rendendo più facile d’individuare le cose che si potrebbero desiderare di cambiare in futuro. Abbiamo fatto la nostra funzione estendibile, fornendo la possibilità di sovrascrivere le impostazioni predefinite. E abbiamo un numero limitato di auto-documentazione – è facile vedere a colpo d’occhio ciò che fa la funzione. Appena le nostre esigenze crescono oltre la semplicità di questo esempio i benefici della struttura diventerà più chiara, come si vedrà di seguito.

Per una eccellente introduzione agli oggetti, proprietà e metodi, vedere Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries di Stoyan Stefanov. Si consiglia inoltre di leggere qualcosa su JSON (JavaScript Object Notation).
[nextpage title=”Un esempio in profondità”] 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