Una guida per creare la tua prima app Ember.js

Pubblicato: 2022-03-11

Poiché le moderne applicazioni Web fanno sempre di più sul lato client (il fatto stesso che ora le chiamiamo "applicazioni Web" anziché "siti Web" è piuttosto indicativo), c'è stato un crescente interesse per i framework lato client . Ci sono molti attori in questo campo, ma per le applicazioni con molte funzionalità e molte parti mobili, due di loro spiccano in particolare: Angular.js e Ember.js.

Abbiamo già pubblicato un completo [tutorial di Angular.js][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], quindi ci concentreremo su Ember.js in questo post, in cui creeremo una semplice applicazione Ember per catalogare la tua collezione musicale. Verrai presentato ai principali elementi costitutivi del framework e potrai dare un'occhiata ai suoi principi di progettazione. Se vuoi vedere il codice sorgente durante la lettura, è disponibile come rock-and-roll su Github.

Cosa costruiremo?

Ecco come apparirà la nostra app Rock & Roll nella sua versione finale:

Versione finale dell'app con ember.js

A sinistra vedrai che abbiamo un elenco di artisti e, a destra, un elenco di brani dell'artista selezionato (puoi vedere anche che ho buon gusto musicale, ma sto divagando). È possibile aggiungere nuovi artisti e brani semplicemente digitando nella casella di testo e premendo il pulsante adiacente. Le stelle accanto a ogni canzone servono a valutarla, alla maniera di iTunes.

Potremmo suddividere le funzionalità rudimentali dell'app nei seguenti passaggi:

  1. Facendo clic su "Aggiungi" si aggiunge un nuovo artista all'elenco, con un nome specificato nel campo "Nuovo artista" (lo stesso vale per i brani di un determinato artista).
  2. Svuotando il campo "Nuovo artista" si disabilita il pulsante "Aggiungi" (lo stesso vale per i brani di un determinato artista).
  3. Facendo clic sul nome di un artista vengono elencate le sue canzoni sulla destra.
  4. Fare clic sulle stelle valuta una determinata canzone.

Abbiamo ancora molta strada da fare per farlo funzionare, quindi iniziamo.

Percorsi: la chiave dell'app Ember.js

Una delle caratteristiche distintive di Ember è la forte enfasi che pone sugli URL. In molti altri framework, avere URL separati per schermate separate o manca o viene aggiunto come ripensamento. In Ember, il router, il componente che gestisce gli URL e le transizioni tra di loro, è il pezzo centrale che coordina il lavoro tra i blocchi costitutivi. Di conseguenza, è anche la chiave per comprendere il funzionamento interno delle applicazioni Ember.

Ecco i percorsi per la nostra applicazione:

 App.Router.map(function() { this.resource('artists', function() { this.route('songs', { path: ':slug' }); }); });

Definiamo un percorso di risorse, artists e un percorso di songs annidato al suo interno. Questa definizione ci darà i seguenti percorsi:

Itinerari

Ho usato il fantastico plugin Ember Inspector (esiste sia per Chrome che per Firefox) per mostrarti i percorsi generati in un modo facilmente leggibile. Ecco le regole di base per le rotte Ember, che puoi verificare per il nostro caso particolare con l'aiuto della tabella sopra:

  1. Esiste un percorso di application implicito.

    Questo viene attivato per tutte le richieste (transizioni).

  2. Esiste un percorso index implicito.

    Viene inserito quando l'utente passa alla radice dell'applicazione.

  3. Ciascun percorso di risorse crea un percorso con lo stesso nome e crea implicitamente un percorso di indice al di sotto di esso.

    Questo percorso indice viene attivato quando l'utente naviga sul percorso. Nel nostro caso, artists.index viene attivato quando l'utente passa a /artists .

  4. Una route nidificata semplice (non risorsa) avrà il nome della route padre come prefisso.

    Il percorso che abbiamo definito this.route('songs', ...) avrà artists.songs come nome. Viene attivato quando l'utente passa a /artists/pearl-jam o /artists/radiohead .

  5. Se il percorso non viene fornito, si presume che sia uguale al nome del percorso.

  6. Se il percorso contiene un : viene considerato un segmento dinamico .

    Il nome assegnatogli (nel nostro caso, slug ) corrisponderà al valore nel segmento appropriato dell'URL. Il segmento slug sopra avrà il valore pearl-jam , radiohead o qualsiasi altro valore estratto dall'URL.

Visualizza l'elenco degli artisti

Come primo passo, costruiremo la schermata che mostra l'elenco degli artisti sulla sinistra. Questa schermata dovrebbe essere mostrata agli utenti quando navigano su /artists/ :

Artisti

Per capire come viene visualizzata la schermata, è tempo di introdurre un altro principio di progettazione Ember generale: la convenzione sulla configurazione . Nella sezione precedente, abbiamo visto che /artists attiva il percorso degli artists . Per convenzione, il nome di tale oggetto di percorso è ArtistsRoute . È responsabilità di questo oggetto route recuperare i dati per il rendering dell'app. Ciò accade nel modello hook del percorso:

 App.ArtistsRoute = Ember.Route.extend({ model: function() { var artistObjects = []; Ember.$.getJSON('http://localhost:9393/artists', function(artists) { artists.forEach(function(data) { artistObjects.pushObject(App.Artist.createRecord(data)); }); }); return artistObjects; } });

In questo frammento, i dati vengono recuperati tramite una chiamata XHR dal back-end e, dopo la conversione in un oggetto modello, inviati a un array che possiamo successivamente visualizzare. Tuttavia, le responsabilità del percorso non si estendono alla fornitura della logica di visualizzazione, che è gestita dal controllore. Diamo un'occhiata.

Hmmm, in effetti, a questo punto non è necessario definire il controller! Ember è abbastanza intelligente da generare il controller quando necessario e impostare l'attributo Model del controller sul valore restituito M.odel del modello stesso, ovvero l'elenco degli artisti. (Ancora una volta, questo è il risultato del paradigma "convenzione sulla configurazione".) Possiamo scendere di un livello e creare un modello per visualizzare l'elenco:

 <script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> {{#each model}} {{#link-to "artists.songs" this class="list-group-item artist-link"}} {{name}} <span class="pointer glyphicon glyphicon-chevron-right"></span> {{/link-to}} {{/each}} </div> </div> <div class="col-md-8"> <div class="list-group"> {{outlet}} </div> </div> </script>

Se questo sembra familiare, è perché Ember.js utilizza i modelli Handlebars, che hanno una sintassi e helper molto semplici ma non consentono una logica non banale (ad esempio, ORing o ANDing termini in un condizionale).

Nel modello sopra, eseguiamo l'iterazione del modello (impostato in precedenza dal percorso a un array che contiene tutti gli artisti) e per ogni elemento in esso, eseguiamo il rendering di un collegamento che ci porta al percorso artists.songs per quell'artista. Il link contiene il nome dell'artista. L #each helper in Handlebars cambia l'ambito al suo interno nell'elemento corrente, quindi {{name}} farà sempre riferimento al nome dell'artista che è attualmente in fase di iterazione.

Percorsi nidificati per viste nidificate

Un altro punto di interesse nello snippet sopra: {{outlet}} , che specifica gli slot nel modello in cui è possibile eseguire il rendering del contenuto. Quando si annidano i percorsi, viene visualizzato per primo il modello per il percorso delle risorse esterno, seguito dal percorso interno, che esegue il rendering del contenuto del modello nel {{outlet}} definito dal percorso esterno. Questo è esattamente ciò che accade qui.

Per convenzione, tutti i percorsi rendono il loro contenuto nel modello con lo stesso nome. Sopra, l'attributo data-template-name del modello sopra è artists , il che significa che verrà renderizzato per il percorso esterno, artists . Specifica uno sbocco per il contenuto del pannello di destra, in cui il percorso interno, artists.index rende il suo contenuto:

 <script type="text/x-handlebars" data-template-name="artists/index"> <div class="list-group-item empty-list"> <div class="empty-message"> Select an artist. </div> </div> </script>

In sintesi, un percorso ( artists ) rende il suo contenuto nella barra laterale sinistra, il suo modello è l'elenco degli artisti. Un altro percorso, artists.index il rendering del proprio contenuto nello slot fornito dal modello artists . Potrebbe recuperare alcuni dati da utilizzare come modello, ma in questo caso tutto ciò che vogliamo visualizzare è il testo statico, quindi non è necessario.

Correlati: 8 domande essenziali per l'intervista a Ember.js

Crea un artista

Parte 1: Rilegatura dei dati

Successivamente, vogliamo essere in grado di creare artisti, non solo guardare un elenco noioso.

Quando ho mostrato quel modello di artists che rende l'elenco degli artisti, ho barato un po'. Ho ritagliato la parte superiore per concentrarmi su ciò che è importante. Ora lo aggiungo di nuovo:

 <script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> <div class="list-group-item"> {{input type="text" class="new-artist" placeholder="New Artist" value=newName}} <button class="btn btn-primary btn-sm new-artist-button" {{action "createArtist"}} {{bind-attr disabled=disabled}}>Add</button> </div> < this is where the list of artists is rendered > ... </script>

Usiamo un helper Ember, input , con tipo text per eseguire il rendering di un semplice input di testo. In esso, leghiamo il valore dell'input di testo alla proprietà newName del controller che esegue il backup di questo modello, ArtistsController . Di conseguenza, quando la proprietà value dell'input cambia (in altre parole, quando l'utente digita il testo in esso) la proprietà newName sul controller verrà mantenuta sincronizzata.

Facciamo anche sapere che l'azione createArtist dovrebbe essere attivata quando si fa clic sul pulsante. Infine, leghiamo la proprietà disabilitata del pulsante alla proprietà disabled del controller. Allora, che aspetto ha il controller?

 App.ArtistsController = Ember.ArrayController.extend({ newName: '', disabled: function() { return Ember.isEmpty(this.get('newName')); }.property('newName') });

newName è impostato su vuoto all'inizio, il che significa che l'input di testo sarà vuoto. (Ricordi cosa ho detto sulle associazioni? Prova a cambiare newName e vedrai che si riflette come testo nel campo di input.)

disabled è implementato in modo tale che quando non c'è testo nella casella di input, restituirà true e quindi il pulsante sarà disabilitato. La chiamata .property alla fine rende questa una "proprietà calcolata", un'altra deliziosa fetta della torta Ember.

Le proprietà calcolate sono proprietà che dipendono da altre proprietà, che possono essere esse stesse "normali" o calcolate. Ember memorizza nella cache il valore di questi fino a quando una delle proprietà dipendenti non cambia. Quindi ricalcola il valore della proprietà calcolata e lo memorizza nuovamente nella cache.

Ecco una rappresentazione visiva del processo di cui sopra. Riassumendo: quando l'utente inserisce il nome di un artista, la proprietà newName si aggiorna, seguita dalla proprietà disabled e, infine, il nome dell'artista viene aggiunto all'elenco.

Deviazione: un'unica fonte di verità

Pensaci un momento. Con l'aiuto di associazioni e proprietà calcolate, possiamo stabilire i dati (modello) come l' unica fonte di verità . Sopra, una modifica nel nome del nuovo artista attiva una modifica nella proprietà del controller, che a sua volta attiva una modifica nella proprietà disabilitata. Quando l'utente inizia a digitare il nome del nuovo artista, il pulsante si abilita, come per magia.

Più grande è il sistema, maggiore è la leva che otteniamo dal principio dell'"unica fonte di verità". Mantiene il nostro codice pulito e robusto e le nostre definizioni di proprietà più dichiarative.

Alcuni altri framework pongono anche l'accento sul fatto che i dati del modello siano l'unica fonte di verità, ma non arrivano fino a Ember o non riescono a svolgere un lavoro così completo. Angular, ad esempio, ha associazioni a due vie, ma non ha proprietà calcolate. Può “emulare” proprietà calcolate attraverso semplici funzioni; il problema qui è che non ha modo di sapere quando aggiornare una "proprietà calcolata" e quindi ricorre al controllo sporco e, a sua volta, porta a una perdita di prestazioni, particolarmente evidente nelle applicazioni più grandi.

Se desideri saperne di più sull'argomento, ti consiglio di leggere il post del blog di eviltrout per una versione più breve o questa domanda su Quora per una discussione più lunga in cui prendono parte gli sviluppatori principali di entrambe le parti.

Parte 2: Gestori di azioni

Torniamo a vedere come viene creata l'azione createArtist dopo che è stata lanciata (a seguito della pressione del pulsante):

 App.ArtistsRoute = Ember.Route.extend({ ... actions: { createArtist: function() { var name = this.get('controller').get('newName'); Ember.$.ajax('http://localhost:9393/artists', { type: 'POST', dataType: 'json', data: { name: name }, context: this, success: function(data) { var artist = App.Artist.createRecord(data); this.modelFor('artists').pushObject(artist); this.get('controller').set('newName', ''); this.transitionTo('artists.songs', artist); }, error: function() { alert('Failed to save artist'); } }); } } });

I gestori di azioni devono essere racchiusi in un oggetto actions e possono essere definiti sul percorso, sul controller o sulla vista. Ho scelto di definirlo sul percorso qui perché il risultato dell'azione non è confinato al controller ma piuttosto "globale".

Non c'è niente di speciale qui. Dopo che il back-end ci ha informato che l'operazione di salvataggio è stata completata con successo, facciamo tre cose, nell'ordine:

  1. Aggiungi il nuovo artista al modello del modello (tutti gli artisti) in modo che venga ridisegnato e il nuovo artista appaia come l'ultimo elemento dell'elenco.
  2. Cancella il campo di input tramite l'associazione newName , risparmiandoci di dover manipolare direttamente il DOM.
  3. Passaggio a un nuovo percorso ( artists.songs ), passando nell'artista appena creato come modello per quel percorso. transitionTo è il modo per spostarsi internamente tra i percorsi. (L'helper link-to serve a farlo tramite l'azione dell'utente.)

Mostra i brani per un artista

Possiamo visualizzare le canzoni per un artista facendo clic sul nome dell'artista. Passiamo anche nell'artista che diventerà il modello del nuovo percorso. Se l'oggetto del modello viene quindi passato, l'hook del model del percorso non verrà chiamato poiché non è necessario risolvere il modello.

Il percorso attivo qui è artists.songs e quindi il controller e il modello saranno ArtistsSongsController e artists/songs . Abbiamo già visto come viene eseguito il rendering del modello nell'outlet fornito dal modello degli artists , quindi possiamo concentrarci solo sul modello a portata di mano:

 <script type="text/x-handlebars" data-template-name="artists/songs"> (...) {{#each songs}} <div class="list-group-item"> {{title}} {{view App.StarRating maxRating=5}} </div> {{/each}} </script>

Nota che ho eliminato il codice per creare una nuova canzone poiché sarebbe esattamente lo stesso di quello per creare un nuovo artista.

La proprietà delle songs è impostata in tutti gli oggetti artista dai dati restituiti dal server. L'esatto meccanismo con cui viene fatto ha scarso interesse per la discussione attuale. Per ora ci basta sapere che ogni canzone ha un titolo e una valutazione.

Il titolo viene visualizzato direttamente nel modello e la valutazione viene rappresentata da stelle, tramite la visualizzazione StarRating . Vediamolo ora.

Widget di valutazione a stelle

La valutazione di un brano è compresa tra 1 e 5 e viene mostrata all'utente tramite una vista, App.StarRating . Le visualizzazioni hanno accesso al loro contesto (in questo caso, il brano) e al loro controller. Ciò significa che possono leggere e modificare le sue proprietà. Ciò è in contrasto con un altro elemento costitutivo di Ember, i componenti, che sono controlli isolati e riutilizzabili con accesso solo a ciò che è stato loro passato. (Potremmo usare anche un componente di valutazione a stelle in questo esempio.)

Vediamo come la visualizzazione mostra il numero di stelle e imposta la valutazione del brano quando l'utente fa clic su una delle stelle:

 App.StarRating = Ember.View.extend({ classNames: ['rating-panel'], templateName: 'star-rating', rating: Ember.computed.alias('context.rating'), fullStars: Ember.computed.alias('rating'), numStars: Ember.computed.alias('maxRating'), stars: function() { var ratings = []; var fullStars = this.starRange(1, this.get('fullStars'), 'full'); var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty'); Array.prototype.push.apply(ratings, fullStars); Array.prototype.push.apply(ratings, emptyStars); return ratings; }.property('fullStars', 'numStars'), starRange: function(start, end, type) { var starsData = []; for (i = start; i <= end; i++) { starsData.push({ rating: i, full: type === 'full' }); }; return starsData; }, (...) });

rating , fullStars e numStars sono proprietà calcolate di cui abbiamo discusso in precedenza con la proprietà disabled di ArtistsController . Sopra, ho usato una cosiddetta macro di proprietà calcolata, circa una dozzina delle quali sono definite in Ember. Rendono le proprietà calcolate tipiche più concise e meno soggette a errori (da scrivere). Ho impostato la rating come valutazione del contesto (e quindi della canzone), mentre ho definito entrambe le proprietà fullStars e numStars in modo che si leggano meglio nel contesto del widget di valutazione a stelle.

Il metodo delle stars è l'attrazione principale. Restituisce un array di dati per le stelle in cui ogni elemento contiene una proprietà di rating (da 1 a 5) e un flag ( full ) per indicare se la stella è piena. Ciò rende estremamente semplice esaminarli nel modello:

 <script type="text/x-handlebars" data-template-name="star-rating"> {{#each view.stars}} <span {{bind-attr data-rating=rating}} {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}} {{action "setRating" target=view}}> </span> {{/each}} </script>

Questo frammento contiene diversi punti di nota:

  1. Innanzitutto, each helper indica di utilizzare una proprietà view (in opposizione a una proprietà sul controller) anteponendo al nome della proprietà view .
  2. In secondo luogo, all'attributo class del tag span sono assegnate classi dinamiche e statiche miste. Qualsiasi cosa preceduta da : diventa una classe statica, mentre la full:glyphicon-star:glyphicon-star-empty è come un operatore ternario in JavaScript: se la proprietà full è truey, dovrebbe essere assegnata la prima classe; se no, il secondo.
  3. Infine, quando si fa clic sul tag, l'azione setRating dovrebbe essere attivata, ma Ember la cercherà nella vista, non nel percorso o nel controller, come nel caso della creazione di un nuovo artista.

L'azione è così definita sulla vista:

 App.StarRating = Ember.View.extend({ (...) actions: { setRating: function() { var newRating = $(event.target).data('rating'); this.set('rating', newRating); } } });

Otteniamo la valutazione dall'attributo dei dati di rating che abbiamo assegnato nel modello e quindi lo impostiamo come rating per la canzone. Si noti che la nuova valutazione non viene mantenuta sul back-end. Non sarebbe difficile implementare questa funzionalità in base a come abbiamo creato un artista e viene lasciata come esercizio per il lettore motivato.

Avvolgendo il tutto

Abbiamo assaggiato diversi ingredienti della suddetta torta Ember:

  • Abbiamo visto come le rotte siano il punto cruciale delle applicazioni Ember e come servano come base per le convenzioni di denominazione.
  • Abbiamo visto come le associazioni di dati a due vie e le proprietà calcolate rendono i dati del nostro modello l'unica fonte di verità e ci consentono di evitare la manipolazione diretta del DOM.
  • E abbiamo visto come attivare e gestire le azioni in diversi modi e creare una vista personalizzata per creare un controllo che non fa parte del nostro HTML.

Bello, vero?

Ulteriore lettura (e visione)

C'è molto di più in Ember di quanto potrei inserire in questo post da solo. Se desideri vedere una serie di screencast su come ho creato una versione un po' più sviluppata dell'applicazione di cui sopra e/o saperne di più su Ember, puoi iscriverti alla mia mailing list per ricevere articoli o suggerimenti su base settimanale.

Spero di aver stuzzicato il tuo appetito per saperne di più su Ember.js e che tu vada ben oltre l'applicazione di esempio che ho usato in questo post. Mentre continui a conoscere Ember.js, assicurati di dare un'occhiata al nostro articolo su Ember Data per imparare a usare la libreria di ember-data. Divertiti a costruire!

Correlati: Ember.js e gli 8 errori più comuni commessi dagli sviluppatori