Un ghid pentru construirea primei aplicații Ember.js

Publicat: 2022-03-11

Pe măsură ce aplicațiile web moderne fac din ce în ce mai mult pe partea client (faptul în sine că acum ne referim la ele ca „aplicații web” spre deosebire de „site-uri web” este destul de grăitor), a existat un interes în creștere pentru cadrele de pe partea clientului . Există o mulțime de jucători în acest domeniu, dar pentru aplicații cu multă funcționalitate și multe părți mobile, doi dintre ei ies în evidență în special: Angular.js și Ember.js.

Am publicat deja un [tutorial Angular.js][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], așa că am publicat deja un [tutorial Angular.js] Ne vom concentra pe Ember.js în această postare, în care vom construi o aplicație Ember simplă pentru a vă cataloga colecția de muzică. Veți fi introdus în principalele blocuri ale cadrului și veți obține o privire asupra principiilor de proiectare ale acestuia. Dacă doriți să vedeți codul sursă în timp ce citiți, acesta este disponibil ca rock-and-roll pe Github.

Ce vom construi?

Iată cum va arăta aplicația noastră Rock & Roll în versiunea sa finală:

Versiunea finală a aplicației cu ember.js

În stânga, veți vedea că avem o listă de artiști și, în dreapta, o listă de melodii ale artistului selectat (se vede și că am bun gust în muzică, dar mă digresez). Artiști și cântece noi pot fi adăugate pur și simplu tastând în caseta de text și apăsând butonul alăturat. Vedetele de lângă fiecare melodie servesc pentru a o evalua, la iTunes.

Am putea descompune funcționalitatea rudimentară a aplicației în următorii pași:

  1. Făcând clic pe „Adaugă” se adaugă un artist nou în listă, cu un nume specificat de câmpul „Artist nou” (același lucru este valabil și pentru melodiile pentru un anumit artist).
  2. Golirea câmpului „Artist nou” dezactivează butonul „Adăugați” (același lucru este valabil și pentru melodiile pentru un anumit artist).
  3. Făcând clic pe numele unui artist, se afișează melodiile acestuia în partea dreaptă.
  4. Făcând clic pe stele evaluează un anumit cântec.

Avem un drum lung de parcurs pentru ca acest lucru să funcționeze, așa că să începem.

Rute: cheia pentru aplicația Ember.js

Una dintre caracteristicile distinctive ale lui Ember este accentul puternic pe care îl pune pe URL-uri. În multe alte cadre, a avea URL-uri separate pentru ecrane separate fie lipsește, fie este considerată ca o idee ulterioară. În Ember, routerul - componenta care gestionează adresele URL și tranzițiile dintre ele - este piesa centrală care coordonează lucrul dintre blocurile de construcție. În consecință, este, de asemenea, cheia pentru înțelegerea funcționării interioare a aplicațiilor Ember.

Iată rutele pentru aplicația noastră:

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

Definim o rută de resurse, artists și o rută de songs imbricate în interiorul acesteia. Această definiție ne va oferi următoarele rute:

Trasee

Am folosit excelentul plugin Ember Inspector (există atât pentru Chrome, cât și pentru Firefox) pentru a vă arăta rutele generate într-un mod ușor de citit. Iată regulile de bază pentru rutele Ember, pe care le puteți verifica pentru cazul nostru particular cu ajutorul tabelului de mai sus:

  1. Există o rută de application implicită.

    Aceasta este activată pentru toate solicitările (tranzițiile).

  2. Există o rută index implicită.

    Acesta este introdus atunci când utilizatorul navighează la rădăcina aplicației.

  3. Fiecare rută de resurse creează o rută cu același nume și implicit creează o rută index sub ea.

    Această rută indexată este activată atunci când utilizatorul navighează la rută. În cazul nostru, artists.index este declanșat atunci când utilizatorul navighează la /artists .

  4. O rută imbricată simplă (fără resurse) va avea numele rutei părinte ca prefix.

    Ruta pe care am definit-o ca this.route('songs', ...) va avea artists.songs drept nume. Se declanșează atunci când utilizatorul navighează la /artists/pearl-jam sau /artists/radiohead .

  5. Dacă calea nu este dată, se presupune că este egală cu numele rutei.

  6. Dacă calea conține un : , este considerat un segment dinamic .

    Numele care i-a fost atribuit (în cazul nostru, slug ) se va potrivi cu valoarea din segmentul corespunzător al url-ului. Segmentul slug de mai sus va avea valoarea pearl-jam , radiohead sau orice altă valoare care a fost extrasă din URL.

Afișează lista artiștilor

Ca prim pas, vom construi ecranul care afișează lista artiștilor din stânga. Acest ecran ar trebui să fie afișat utilizatorilor atunci când navighează la /artists/ :

Artiști

Pentru a înțelege cum este redat acel ecran, este timpul să introducem un alt principiu general al designului Ember: convenția asupra configurației . În secțiunea de mai sus, am văzut că /artists activează ruta artists . Prin convenție, numele acelui obiect rută este ArtistsRoute . Este responsabilitatea acestui obiect rută să preia date pentru ca aplicația să poată fi redate. Asta se întâmplă în cârligul model al rutei:

 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; } });

În acest fragment, datele sunt preluate printr-un apel XHR din back-end și, după conversie la un obiect model, sunt împinse într-o matrice pe care o putem afișa ulterior. Cu toate acestea, responsabilitățile rutei nu se extind la furnizarea logicii de afișare, care este gestionată de controler. Hai să aruncăm o privire.

Hmmm—de fapt, nu trebuie să definim controlerul în acest moment! Ember este suficient de inteligent pentru a genera controlerul atunci când este necesar și pentru a seta atributul M.odel al controlerului la valoarea de returnare a cârligului model în sine, și anume, lista artiștilor. (Din nou, acesta este un rezultat al paradigmei „convenție peste configurare”.) Putem trece cu un strat în jos și putem crea un șablon pentru a afișa lista:

 <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>

Dacă acest lucru pare familiar, se datorează faptului că Ember.js folosește șabloane Handlebars, care au o sintaxă și ajutoare foarte simple, dar nu permit o logică non-trivială (de exemplu, termeni OR sau AND într-un condițional).

În șablonul de mai sus, repetăm ​​modelul (configurat mai devreme de ruta către o matrice care conține toți artiștii) și pentru fiecare articol din acesta, redăm un link care ne duce la ruta artists.songs pentru acel artist. Link-ul conține numele artistului. #each din Handlebars schimbă domeniul din interiorul acestuia la elementul curent, astfel încât {{name}} se va referi întotdeauna la numele artistului care se află în prezent în iterare.

Rute imbricate pentru vederi imbricate

Un alt punct de interes în fragmentul de mai sus: {{outlet}} , care specifică spațiile din șablon unde poate fi redat conținutul. La imbricarea rutelor, șablonul pentru ruta exterioară a resurselor este randat mai întâi, urmat de ruta interioară, care redă conținutul șablonului său în {{outlet}} definit de ruta externă. Este exact ceea ce se întâmplă aici.

Prin convenție, toate rutele își redau conținutul în șablonul cu același nume. Mai sus, atributul data-template-name al șablonului de mai sus este artists ceea ce înseamnă că va fi randat pentru ruta exterioară, artists . Specifică o ieșire pentru conținutul panoului din dreapta, în care ruta interioară, artists.index își redă conținutul:

 <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>

În rezumat, un traseu ( artists ) își redă conținutul în bara laterală din stânga, modelul său fiind lista de artiști. O altă cale, artists.index redă propriul conținut în slotul oferit de șablonul artists . Ar putea prelua unele date pentru a servi drept model, dar în acest caz tot ceea ce vrem să afișăm este text static, deci nu este nevoie.

Înrudit : 8 întrebări esențiale la interviu Ember.js

Creează un artist

Partea 1: Legarea datelor

În continuare, vrem să putem crea artiști, nu doar să ne uităm la o listă plictisitoare.

Când am arătat acel șablon de artists care redă lista artiștilor, am înșelat puțin. Am tăiat partea de sus pentru a mă concentra pe ceea ce este important. Acum, voi adăuga asta înapoi:

 <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>

Folosim un ajutor Ember, input , cu tip text pentru a reda o introducere de text simplă. În acesta, legăm valoarea textului de intrare la proprietatea newName a controlerului care face backup pentru acest șablon, ArtistsController . Prin urmare, atunci când proprietatea valoare a intrării se modifică (cu alte cuvinte, când utilizatorul introduce text în ea), proprietatea newName de pe controler va fi menținută sincronizată.

De asemenea, facem cunoscut faptul că acțiunea createArtist ar trebui să fie declanșată atunci când se face clic pe butonul. În cele din urmă, legăm proprietatea dezactivată a butonului de proprietatea disabled a controlerului. Deci cum arată controlerul?

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

newName este setat să golească la început, ceea ce înseamnă că textul introdus va fi necompletat. (Îți amintești ce am spus despre legături? Încearcă să schimbi newName și vezi că se reflectă ca text în câmpul de introducere.)

disabled este implementat astfel încât atunci când nu există text în caseta de introducere, acesta va reveni true și astfel butonul va fi dezactivat. Apelul .property de la sfârșit face din aceasta o „proprietate calculată”, o altă felie delicioasă din tortul Ember.

Proprietățile calculate sunt proprietăți care depind de alte proprietăți, care pot fi ele însele „normale” sau calculate. Ember memorează în cache valoarea acestora până când una dintre proprietățile dependente se schimbă. Apoi recalculează valoarea proprietății calculate și o memorează din nou în cache.

Iată o reprezentare vizuală a procesului de mai sus. Pentru a rezuma: atunci când utilizatorul introduce numele unui artist, proprietatea newName actualizează, urmată de proprietatea disabled și, în final, numele artistului este adăugat în listă.

Ocol: O singură sursă de adevăr

Gândește-te la asta pentru o clipă. Cu ajutorul legăturilor și proprietăților calculate, putem stabili datele (modelul) ca sursă unică a adevărului . Mai sus, o modificare a numelui noului artist declanșează o modificare a proprietății controlerului, care la rândul său declanșează o modificare a proprietății dezactivate. Pe măsură ce utilizatorul începe să tasteze numele noului artist, butonul devine activat, ca prin farmec.

Cu cât sistemul este mai mare, cu atât câștigăm mai multă pârghie din principiul „sursei unice a adevărului”. Ne menține codul curat și robust, iar definițiile proprietăților noastre mai declarative.

Unele alte cadre pun, de asemenea, accent pe faptul ca datele modelului să fie singura sursă de adevăr, dar fie nu merg atât de departe până la Ember, fie nu reușesc să facă o treabă atât de amănunțită. Angular, de exemplu, are legături în două sensuri, dar nu are proprietăți calculate. Poate „emula” proprietățile calculate prin funcții simple; problema aici este că nu are de unde să știe când să reîmprospăteze o „proprietate calculată” și astfel recurge la verificarea murdară și, la rândul său, duce la o pierdere de performanță, notabilă mai ales în aplicațiile mai mari.

Dacă doriți să aflați mai multe despre acest subiect, vă recomand să citiți postarea de blog a lui eviltrout pentru o versiune mai scurtă sau această întrebare Quora pentru o discuție mai lungă în care dezvoltatorii de bază din ambele părți intervin.

Partea 2: Operatorii de acțiune

Să ne întoarcem să vedem cum este creată acțiunea createArtist după declanșare (după apăsarea butonului):

 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'); } }); } } });

Operatorii de acțiune trebuie să fie înfășurați într-un obiect de actions și pot fi definiți pe rută, controler sau vizualizare. Am ales să o definesc pe traseu aici, deoarece rezultatul acțiunii nu se limitează la controler, ci mai degrabă „global”.

Nu se întâmplă nimic de lux aici. După ce back-end-ul ne-a informat că operația de salvare s-a încheiat cu succes, facem trei lucruri, în ordine:

  1. Adăugați noul artist la modelul șablonului (toți artiștii), astfel încât să fie redat din nou și noul artist să apară ca ultimul element al listei.
  2. Ștergeți câmpul de introducere prin legarea newName , evitându-ne să manipulăm direct DOM-ul.
  3. Tranziția către un nou traseu ( artists.songs ), trecând pe artistul proaspăt creat ca model pentru acel traseu. transitionTo este modalitatea de deplasare internă între rute. ( link-to ajutor servește pentru a face acest lucru prin acțiunea utilizatorului.)

Afișați melodii pentru un artist

Putem afișa melodiile pentru un artist fie făcând clic pe numele artistului. Trecem și pe artistul care urmează să devină modelul noului traseu. Dacă obiectul model este astfel transmis, cârligul model al rutei nu va fi apelat deoarece nu este nevoie să rezolvăm modelul.

Ruta activă aici este artists.songs și, prin urmare, controlerul și șablonul vor fi ArtistsSongsController și, respectiv, artists/songs . Am văzut deja cum șablonul este redat în priza oferită de șablonul artists , astfel încât să ne putem concentra doar pe șablonul la îndemână:

 <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>

Rețineți că am scos codul pentru a crea o melodie nouă, deoarece ar fi exact același cu cel pentru crearea unui artist nou.

Proprietatea songs este configurată în toate obiectele artistului din datele returnate de server. Mecanismul exact prin care se face nu prezintă un interes redus pentru discuția actuală. Deocamdată este suficient să știm că fiecare melodie are un titlu și un rating.

Titlul este afișat direct în șablon, iar ratingul este reprezentat prin stele, prin vizualizarea StarRating . Să vedem asta acum.

Widget de evaluare cu stele

Evaluarea unei melodii se situează între 1 și 5 și este afișată utilizatorului printr-o vizualizare, App.StarRating . Vizualizările au acces la contextul lor (în acest caz, melodia) și controlerul lor. Aceasta înseamnă că pot citi și modifica proprietățile acestuia. Acest lucru este în contrast cu un alt bloc de construcție Ember, componente, care sunt comenzi izolate, reutilizabile, cu acces doar la ceea ce le-a fost transmis. (Am putea folosi și o componentă de evaluare cu stele în acest exemplu.)

Să vedem cum afișarea afișează numărul de stele și stabilește evaluarea melodiei atunci când utilizatorul face clic pe una dintre stele:

 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 și numStars sunt proprietăți calculate despre care am discutat anterior cu proprietatea disabled a ArtistsController . Mai sus, am folosit o așa-numită macrocomandă de proprietăți calculate, dintre care aproximativ o duzină sunt definite în Ember. Acestea fac proprietățile calculate tipice mai succinte și mai puțin predispuse la erori (la scriere). Am setat rating să fie evaluarea contextului (și, prin urmare, a melodiei), în timp ce am definit atât proprietățile fullStars , cât și numStars , astfel încât acestea să citească mai bine în contextul widgetului de rating de stele.

Metoda stars este principala atracție. Returnează o serie de date pentru stele în care fiecare articol conține o proprietate de rating (de la 1 la 5) și un steag ( full ) pentru a indica dacă steaua este plină. Acest lucru face extrem de simplu să parcurgeți ele în șablon:

 <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>

Acest fragment conține mai multe puncte de remarcat:

  1. În primul rând, each ajutor desemnează că folosește o proprietate de vizualizare (spre deosebire de o proprietate de pe controler) prefixând numele proprietății cu view .
  2. În al doilea rând, atributul class al etichetei span are clase mixte dinamice și statice atribuite. Orice lucru prefixat de un : devine o clasă statică, în timp ce full:glyphicon-star:glyphicon-star-empty este ca un operator ternar în JavaScript: dacă proprietatea completă este adevărată, prima clasă ar trebui să fie atribuită; daca nu, al doilea.
  3. În cele din urmă, când se face clic pe etichetă, acțiunea setRating ar trebui să fie declanșată, dar Ember o va căuta în vizualizare, nu pe traseu sau controler, ca în cazul creării unui artist nou.

Acțiunea este astfel definită pe punctul de vedere:

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

Primim evaluarea din atributul de date de rating pe care l-am atribuit în șablon și apoi îl setăm ca rating pentru melodie. Rețineți că noul rating nu este menținut pe back-end. Nu ar fi dificil să implementăm această funcționalitate pe baza modului în care am creat un artist și este lăsată ca exercițiu pentru cititorul motivat.

Încheind totul

Am gustat mai multe ingrediente din tortul Ember menționat mai sus:

  • Am văzut cum rutele sunt cheia aplicațiilor Ember și cum servesc ca bază pentru convențiile de denumire.
  • Am văzut cum legăturile de date în două sensuri și proprietățile calculate fac din datele modelului nostru o singură sursă de adevăr și ne permit să evităm manipularea directă a DOM.
  • Și am văzut cum să declanșăm și să gestionăm acțiuni în mai multe moduri și să construim o vizualizare personalizată pentru a crea un control care nu face parte din HTML-ul nostru.

Frumos, nu-i așa?

Citiri suplimentare (și vizionare)

Ember are mult mai mult decât aș putea încadra în această postare. Dacă doriți să vedeți o serie de screencast despre cum am construit o versiune ceva mai dezvoltată a aplicației de mai sus și/sau să aflați mai multe despre Ember, vă puteți înscrie pe lista mea de corespondență pentru a primi articole sau sfaturi săptămânal.

Sper că v-am trezit pofta de a afla mai multe despre Ember.js și că depășiți cu mult aplicația exemplu pe care am folosit-o în această postare. Pe măsură ce continuați să aflați despre Ember.js, asigurați-vă că aruncați o privire la articolul nostru despre Ember Data pentru a afla cum să utilizați biblioteca ember-data. Distrează-te construind!

Înrudit : Ember.js și cele 8 cele mai frecvente greșeli pe care le fac dezvoltatorii