/* * Codice javascript per l'applicazione */ // All'interno del file index.html il luogo migliore per inserire ed eseguire il codice javascript // è l'header della pagina, all'interno del tag . Ma effettuare subito operazioni sul DOM all'interno // del tag prima ancora che sia caricato interamente non è una buona idea... // // Inseriamo quindi tutto il codice in una funzione di callback e passiamola come argomento alla funzione speciale di jQuery, "$()". // La nostra funzione sarà così eseguita automaticamente non appena il caricamento della pagina sarà completato // e il DOM pronto per essere manipolato. // $(function() { // La libreria d3 espone un oggetto accessibile globalmente: d3. // L'uso della libreria d3 richiede quindi l'esecuzione opportuna dei metodi dell'oggetto d3. // // In questo caso il metodo select([selettore]) seleziona l'elemento in base al selettore passato ("body") // e ritorna un oggetto "selezione" (assegnato alla variabile "container"). // // Una selezione d3 a sua volta possiede il metodo append([tag]) che crea un nuovo // nodo del tipo specificato ("div") nel DOM all'interno del nodo selezionato in precedenza ("body"). // // Anche append() torna una selezione (questa volta "div", non più "body"), // per cui è possibile accedere immediatamente al metodo attr([string],[string]) // sfruttando un pattern di programmazione noto con il nome di chaining. La stringa passata come secondo argomento // ("container-fluid") viene dunque inserita come classe all'interno dell'attributo "class" del nodo selezionato ("div"). // // Creiamo subito un contenitore della pagina, figlio del body per usare al meglio bootstrap. // var container = d3.select("body") .append("div") .attr("class","container-fluid"); // Inseriamo il titolo in un contenitore che funga da "header" con classe "row", sempre per sfruttare bootstrap. var header = container.append("header") // Definiamo semanticamente le parti della pagina con i nuovi tag dell'HTML5 .attr("class","row page-header"); // Rendiamo il tutto ricercabile mediante shufflejs attraverso un campo di input testuale header.append("nav") .append("input") .attr("id","search") .attr("class","center-block input-lg") .attr("placeholder","Cerca per produttore, mercato o prodotto") .attr("value",""); // Abbiamo ora bisogno di dati memorizzati in un file tsv (Tab-separated values), // per cui dobbiamo recuperarli con una chiamata AJAX. Il metodo tsv([url], callback) di d3 // pensa a tutto: chiamata asincrona, gestione della risposta (evetuali errori in "error" e parsing dei dati testuali // dal tsv a un array di oggetti (passato in "data"). // d3.tsv("data/prodotti.csv", function(error, data) { // Gestione di eventuali errori (es. file non trovato, ecc.), // se c'è qualche problema l'esecuzione si blocca e viene notificato un messaggio in console. if (error) { throw "Error in loading data..."; } // Assicuriamoci che tutto sia ordinato per numero di albo (e quindi per data di uscita) // e per ora limitiamoci ai primi 20 albi per non sovraccaricare di richieste il server della Bonelli // (le immagini sono linkate direttamente dal sito ufficiale). // /*data = data.sort(function(a,b) { // Il metodo sort() passa alla callback una coppia di elementi // Bisogna indicare dei due elementi quale viene prima e quale dopo, // in questo ci aiuta un metodo di d3 già predisposto allo scopo per semplici ordinamenti. // // Ovviamente dobbiamo confrontare il valore degli attributi "Numero" degli oggetti "a" e "b" e non gli oggetti in sé // e prima di farlo li convertiamo a interi (inizialmente sono letti come stringhe) anteponendo un "+". return d3.ascending(+a["Numero"],+b["Numero"]); }).slice(0,20); // Il metodo slice() applicato a un array prende 20 elementi consecutivi a partire dal numero 0 (il primo) */ // Per aggiungere la funzionalità di autocomplete al form di ricerca, // ricaviamo la lista dei nomi di tutti gli autori degli albi. // Soggetto Sceneggiatura Disegni Copertina var authors = _.uniq(_.flatten(data.map(function(el) { var groups = []; return groups .concat(el["Nome"].split(",")) .concat(el["Prodotto"].split(",")) .concat(el["Mercati"].split(",")); }))); // Qui uno dei pilastri concettuali della libreria d3: // prendiamo la variabile container (è una selezione del div contenitore globale), poi selezioniamo tutti gli elementi "div" // in esso contenuti con il metodo selectAll([selettore]). // Inizialmente non ce ne sono, quindi la selezione è vuota, ma esiste. // // A questa selezione (vuota) associamo il nostro array per posizione con il metodo data([array]): // il primo oggetto con il primo div (che non esiste), // il secondo oggetto con il secondo div (che sempre non esiste), ecc. // // Il metodo enter() opera la magia: esegue tutto ciò che viene dopo tante volte quanti sono i dati // che non sono stati assegnati ad alcun elemento del DOM, nel nostro caso tutti. Per cui append("div") // viene eseguito per tutti i dati e così vengono creati nel DOM tanti paragrafi quanti sono i dati // e a essi vengono associati in ordine i dati uno a uno. // // Il metodo append("div") torna una selezione, per cui possiamo subito impostare gli attributi e i contenuti dei div, // che non sono fissi, ma dipendono dai dati: la funzione di callback, infatti, viene eseguita passandole // il dato associato all'elemento corrente: "d" è un oggetto che rappresenta una riga del dataset originario // (una riga del file tsv), con le chiavi uguali ai nomi delle colonne e i valori quelli delle celle della riga. // // Questa funzione di callback deve ritornare un valore compatibile con il metodo che l'ha chiamata: nel nostro caso // per lo più stringhe con cui valorizzare gli attributi nominati. // var albi = container.append("section") .attr("id","grid") .attr("class","row page-body") // Dopo l'header, un'altra "row", ma con classe "body" .selectAll("div") // La selezione dei "div" contenitori viene assegnata alla variabile "albi" e poi riutilizzata successivamente. .data(data) .enter() .append("div") .attr("class","comics-container col-lg-1 col-md-1 col-sm-2 col-xs-3") // Associamo una classe ai div contenitori degli albi per sfruttare la grid di bootstrap che ci assicura la responsiveness .attr("data-groups", function(d) { // Shufflejs effettua il filtro su categorie personalizzate che vanno definite nell'attributo data-groups var groups = []; // Nel nostro caso le categorie sono i nomi degli autori e devono comparire come json di un array di stringhe: ["nome1","nome2",...] // Sappiamo però che nelle nostre colonne ci possono essere più nomi, che divideremo in un array con split() usando la virgola come separatore. // Non possiamo tornare però un array, perché l'attributo si aspetta una stringa, per cui... stringify, non prima di aver eliminato dall'array // gli elementi duplicati. return JSON.stringify(_.uniq(groups.concat(d["Nome"].split(",")).concat(d["Prodotto"].split(",")).concat(d["Mercati"].split(",")))).replace(/"/g,"'"); }) .attr("data-title", function(d) { // Perché allora non inserire tutte e informazioni negli attributi data-? return d["Prodotto"].replace(/"/g,""); }) .attr("data-summary", function(d) { return d["Nome"].replace(/"/g,""); }) .append("article") // Perché due div uno dentro l'altro? Perché vogliamo il bordo di ogni elemento e una certa distanza tra l'uno e l'altro .attr("class","comics"); // Da qui in poi tutti gli elementi vanno creati all'interno dei div contenitori creati precedentemente. // La variabile "albi" è un array e tutti i metodi invocati si applicano a tutti gli elementi dell'array. // Noi lo scriviamo una sola volta, ma il tutto è eseguito per tutti gli elementi, tanti quanti sono i dati. albi.append("figure") .append("a") // Questa volta inseriamo nel DOM un link alla scheda dell'albo sul sito ufficiale .attr("href", function(d) { // L'URL del link va inserita nell'attributo "href" mediante il metodo "attr" return d["Immagine"]; //return d["Scheda"]; }) .attr("target","_blank") // Il link si apre in un'altra finestra .attr("data-lightbox","cover") .attr("data-title", function(d) { return d["Descrizione"].replace(/"/g,""); }) .append("img") // Ora l'append è consecutivo al precedente, quindi agisce su "a" (non su "div"), inserendo al suo interno un'immagine .attr("class","cover img-responsive center-block") // Associamo la classe "cover" e alcune classi utili definite da bootstrap .attr("src", function(d) { // La sua URL va inserita nell'attributo "src" mediante il metodo "attr" return "./images/"+d["Prodotto"]+".svg"; }) .attr("title", function(d) { return d["Prodotto"]; }); /*albi.append("footer") .append("p") .attr("class","date") .html(function(d) { return ''+d["Nome"]+''; });*/ // E infine un footer a chiudere la pagina container.append("footer") .attr("class","row page-footer") .append("figure") .append("a") .attr("href","http://www.campiaperti.org/") .attr("target","_blank"); // // Il DOM è pronto con tutti gli elementi // Ora possiamo agire su quegli elementi, inizializzando la grid dei comics con shufflejs // $("#grid").shuffle({ itemSelector: ".comics-container" }); // Attacchiamo una funzione di callback a un evento del form di input: viene eseguita ogni volta che il contenuto cambia // a causa della digitazione di un testo all'interno da parte dell'utente $("#search").on('keyup change', function() { // Eventi "rilascio di un pulsante della tastiera" e "cambio del contenuto" // Effettuando una ricerca in data-groups è necessario ripulire un po' sia le stringa di ricerca // (ignorando per esempio le maiuscole e altri caratteri non letterali) che quella in cui viene effettuata // la ricerca (che è il json di un array di stringhe) var val = this.value.toLowerCase().replace(/[^a-z] /g,""); // Il valore digitato corrente $('#grid').shuffle('shuffle', function($el, shuffle) { // La funzione viene valutata per ogni elemento della grid: // se vera l'elemento viene tenuto, altrimenti viene nascosto return $el.data('groups').toLowerCase().indexOf(val) > -1; }); }); $("#search").autocomplete({ source: authors }); }); });