main.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. * Codice javascript per l'applicazione
  3. */
  4. // All'interno del file index.html il luogo migliore per inserire ed eseguire il codice javascript
  5. // è l'header della pagina, all'interno del tag <head>. Ma effettuare subito operazioni sul DOM all'interno
  6. // del tag <body> prima ancora che sia caricato interamente non è una buona idea...
  7. //
  8. // Inseriamo quindi tutto il codice in una funzione di callback e passiamola come argomento alla funzione speciale di jQuery, "$()".
  9. // La nostra funzione sarà così eseguita automaticamente non appena il caricamento della pagina sarà completato
  10. // e il DOM pronto per essere manipolato.
  11. //
  12. $(function() {
  13. // La libreria d3 espone un oggetto accessibile globalmente: d3.
  14. // L'uso della libreria d3 richiede quindi l'esecuzione opportuna dei metodi dell'oggetto d3.
  15. //
  16. // In questo caso il metodo select([selettore]) seleziona l'elemento in base al selettore passato ("body")
  17. // e ritorna un oggetto "selezione" (assegnato alla variabile "container").
  18. //
  19. // Una selezione d3 a sua volta possiede il metodo append([tag]) che crea un nuovo
  20. // nodo del tipo specificato ("div") nel DOM all'interno del nodo selezionato in precedenza ("body").
  21. //
  22. // Anche append() torna una selezione (questa volta "div", non più "body"),
  23. // per cui è possibile accedere immediatamente al metodo attr([string],[string])
  24. // sfruttando un pattern di programmazione noto con il nome di chaining. La stringa passata come secondo argomento
  25. // ("container-fluid") viene dunque inserita come classe all'interno dell'attributo "class" del nodo selezionato ("div").
  26. //
  27. // Creiamo subito un contenitore della pagina, figlio del body per usare al meglio bootstrap.
  28. //
  29. var container = d3.select("body")
  30. .append("div")
  31. .attr("class","container-fluid");
  32. // Inseriamo il titolo in un contenitore che funga da "header" con classe "row", sempre per sfruttare bootstrap.
  33. var header = container.append("header") // Definiamo semanticamente le parti della pagina con i nuovi tag dell'HTML5
  34. .attr("class","row page-header");
  35. // Rendiamo il tutto ricercabile mediante shufflejs attraverso un campo di input testuale
  36. header.append("nav")
  37. .append("input")
  38. .attr("id","search")
  39. .attr("class","center-block input-lg")
  40. .attr("placeholder","Cerca per produttore, mercato o prodotto")
  41. .attr("value","");
  42. // Abbiamo ora bisogno di dati memorizzati in un file tsv (Tab-separated values),
  43. // per cui dobbiamo recuperarli con una chiamata AJAX. Il metodo tsv([url], callback) di d3
  44. // pensa a tutto: chiamata asincrona, gestione della risposta (evetuali errori in "error" e parsing dei dati testuali
  45. // dal tsv a un array di oggetti (passato in "data").
  46. //
  47. d3.tsv("data/prodotti.csv", function(error, data) {
  48. // Gestione di eventuali errori (es. file non trovato, ecc.),
  49. // se c'è qualche problema l'esecuzione si blocca e viene notificato un messaggio in console.
  50. if (error) {
  51. throw "Error in loading data...";
  52. }
  53. // Assicuriamoci che tutto sia ordinato per numero di albo (e quindi per data di uscita)
  54. // e per ora limitiamoci ai primi 20 albi per non sovraccaricare di richieste il server della Bonelli
  55. // (le immagini sono linkate direttamente dal sito ufficiale).
  56. //
  57. /*data = data.sort(function(a,b) { // Il metodo sort() passa alla callback una coppia di elementi
  58. // Bisogna indicare dei due elementi quale viene prima e quale dopo,
  59. // in questo ci aiuta un metodo di d3 già predisposto allo scopo per semplici ordinamenti.
  60. //
  61. // Ovviamente dobbiamo confrontare il valore degli attributi "Numero" degli oggetti "a" e "b" e non gli oggetti in sé
  62. // e prima di farlo li convertiamo a interi (inizialmente sono letti come stringhe) anteponendo un "+".
  63. return d3.ascending(+a["Numero"],+b["Numero"]);
  64. }).slice(0,20); // Il metodo slice() applicato a un array prende 20 elementi consecutivi a partire dal numero 0 (il primo)
  65. */
  66. // Per aggiungere la funzionalità di autocomplete al form di ricerca,
  67. // ricaviamo la lista dei nomi di tutti gli autori degli albi.
  68. // Soggetto Sceneggiatura Disegni Copertina
  69. var authors = _.uniq(_.flatten(data.map(function(el) {
  70. var groups = [];
  71. return groups
  72. .concat(el["Nome"].split(","))
  73. .concat(el["Prodotto"].split(","))
  74. .concat(el["Mercati"].split(","));
  75. })));
  76. // Qui uno dei pilastri concettuali della libreria d3:
  77. // prendiamo la variabile container (è una selezione del div contenitore globale), poi selezioniamo tutti gli elementi "div"
  78. // in esso contenuti con il metodo selectAll([selettore]).
  79. // Inizialmente non ce ne sono, quindi la selezione è vuota, ma esiste.
  80. //
  81. // A questa selezione (vuota) associamo il nostro array per posizione con il metodo data([array]):
  82. // il primo oggetto con il primo div (che non esiste),
  83. // il secondo oggetto con il secondo div (che sempre non esiste), ecc.
  84. //
  85. // Il metodo enter() opera la magia: esegue tutto ciò che viene dopo tante volte quanti sono i dati
  86. // che non sono stati assegnati ad alcun elemento del DOM, nel nostro caso tutti. Per cui append("div")
  87. // viene eseguito per tutti i dati e così vengono creati nel DOM tanti paragrafi quanti sono i dati
  88. // e a essi vengono associati in ordine i dati uno a uno.
  89. //
  90. // Il metodo append("div") torna una selezione, per cui possiamo subito impostare gli attributi e i contenuti dei div,
  91. // che non sono fissi, ma dipendono dai dati: la funzione di callback, infatti, viene eseguita passandole
  92. // il dato associato all'elemento corrente: "d" è un oggetto che rappresenta una riga del dataset originario
  93. // (una riga del file tsv), con le chiavi uguali ai nomi delle colonne e i valori quelli delle celle della riga.
  94. //
  95. // Questa funzione di callback deve ritornare un valore compatibile con il metodo che l'ha chiamata: nel nostro caso
  96. // per lo più stringhe con cui valorizzare gli attributi nominati.
  97. //
  98. var albi = container.append("section")
  99. .attr("id","grid")
  100. .attr("class","row page-body") // Dopo l'header, un'altra "row", ma con classe "body"
  101. .selectAll("div") // La selezione dei "div" contenitori viene assegnata alla variabile "albi" e poi riutilizzata successivamente.
  102. .data(data)
  103. .enter()
  104. .append("div")
  105. .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
  106. .attr("data-groups", function(d) { // Shufflejs effettua il filtro su categorie personalizzate che vanno definite nell'attributo data-groups
  107. var groups = [];
  108. // Nel nostro caso le categorie sono i nomi degli autori e devono comparire come json di un array di stringhe: ["nome1","nome2",...]
  109. // Sappiamo però che nelle nostre colonne ci possono essere più nomi, che divideremo in un array con split() usando la virgola come separatore.
  110. // Non possiamo tornare però un array, perché l'attributo si aspetta una stringa, per cui... stringify, non prima di aver eliminato dall'array
  111. // gli elementi duplicati.
  112. return JSON.stringify(_.uniq(groups.concat(d["Nome"].split(",")).concat(d["Prodotto"].split(",")).concat(d["Mercati"].split(",")))).replace(/"/g,"'");
  113. })
  114. .attr("data-title", function(d) { // Perché allora non inserire tutte e informazioni negli attributi data-?
  115. return d["Prodotto"].replace(/"/g,"");
  116. })
  117. .attr("data-summary", function(d) {
  118. return d["Nome"].replace(/"/g,"");
  119. })
  120. .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
  121. .attr("class","comics");
  122. // Da qui in poi tutti gli elementi vanno creati all'interno dei div contenitori creati precedentemente.
  123. // La variabile "albi" è un array e tutti i metodi invocati si applicano a tutti gli elementi dell'array.
  124. // Noi lo scriviamo una sola volta, ma il tutto è eseguito per tutti gli elementi, tanti quanti sono i dati.
  125. albi.append("figure")
  126. .append("a") // Questa volta inseriamo nel DOM un link alla scheda dell'albo sul sito ufficiale
  127. .attr("href", function(d) { // L'URL del link va inserita nell'attributo "href" mediante il metodo "attr"
  128. return d["Immagine"];
  129. //return d["Scheda"];
  130. })
  131. .attr("target","_blank") // Il link si apre in un'altra finestra
  132. .attr("data-lightbox","cover")
  133. .attr("data-title", function(d) {
  134. return d["Descrizione"].replace(/"/g,"");
  135. })
  136. .append("img") // Ora l'append è consecutivo al precedente, quindi agisce su "a" (non su "div"), inserendo al suo interno un'immagine
  137. .attr("class","cover img-responsive center-block") // Associamo la classe "cover" e alcune classi utili definite da bootstrap
  138. .attr("src", function(d) { // La sua URL va inserita nell'attributo "src" mediante il metodo "attr"
  139. return "./images/"+d["Prodotto"]+".svg";
  140. })
  141. .attr("title", function(d) {
  142. return d["Prodotto"];
  143. });
  144. /*albi.append("footer")
  145. .append("p")
  146. .attr("class","date")
  147. .html(function(d) {
  148. return '<a href="'+d["Scheda"]+'" target="_blank">'+d["Nome"]+'</a>';
  149. });*/
  150. // E infine un footer a chiudere la pagina
  151. container.append("footer")
  152. .attr("class","row page-footer")
  153. .append("figure")
  154. .append("a")
  155. .attr("href","http://www.campiaperti.org/")
  156. .attr("target","_blank");
  157. //
  158. // Il DOM è pronto con tutti gli elementi
  159. // Ora possiamo agire su quegli elementi, inizializzando la grid dei comics con shufflejs
  160. //
  161. $("#grid").shuffle({
  162. itemSelector: ".comics-container"
  163. });
  164. // Attacchiamo una funzione di callback a un evento del form di input: viene eseguita ogni volta che il contenuto cambia
  165. // a causa della digitazione di un testo all'interno da parte dell'utente
  166. $("#search").on('keyup change', function() { // Eventi "rilascio di un pulsante della tastiera" e "cambio del contenuto"
  167. // Effettuando una ricerca in data-groups è necessario ripulire un po' sia le stringa di ricerca
  168. // (ignorando per esempio le maiuscole e altri caratteri non letterali) che quella in cui viene effettuata
  169. // la ricerca (che è il json di un array di stringhe)
  170. var val = this.value.toLowerCase().replace(/[^a-z] /g,""); // Il valore digitato corrente
  171. $('#grid').shuffle('shuffle', function($el, shuffle) {
  172. // La funzione viene valutata per ogni elemento della grid:
  173. // se vera l'elemento viene tenuto, altrimenti viene nascosto
  174. return $el.data('groups').toLowerCase().indexOf(val) > -1;
  175. });
  176. });
  177. $("#search").autocomplete({
  178. source: authors
  179. });
  180. });
  181. });