Eine Anleitung zum Erstellen Ihrer ersten Ember.js-App

Veröffentlicht: 2022-03-11

Da moderne Webanwendungen immer mehr auf der Client-Seite arbeiten (die Tatsache, dass wir sie jetzt als „Webanwendungen“ statt als „Websites“ bezeichnen, ist ziemlich aufschlussreich), hat das Interesse an Client-seitigen Frameworks zugenommen . Es gibt viele Akteure auf diesem Gebiet, aber für Anwendungen mit vielen Funktionen und vielen beweglichen Teilen stechen zwei besonders hervor: Angular.js und Ember.js.

Wir haben bereits ein umfassendes [Angular.js-Tutorial][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app] veröffentlicht, also In diesem Beitrag konzentrieren wir uns auf Ember.js, in dem wir eine einfache Ember-Anwendung zum Katalogisieren Ihrer Musiksammlung erstellen. Sie werden in die Hauptbausteine ​​des Frameworks eingeführt und erhalten einen Einblick in seine Designprinzipien. Wenn Sie den Quellcode beim Lesen sehen möchten, steht er als Rock-and-Roll auf Github zur Verfügung.

Was werden wir bauen?

So wird unsere Rock & Roll-App in ihrer endgültigen Version aussehen:

Endgültige Version der App mit ember.js

Auf der linken Seite sehen Sie eine Liste mit Künstlern und auf der rechten Seite eine Liste mit Songs des ausgewählten Künstlers (Sie können auch sehen, dass ich einen guten Musikgeschmack habe, aber ich schweife ab). Neue Künstler und Songs können einfach hinzugefügt werden, indem Sie sie in das Textfeld eingeben und die benachbarte Schaltfläche drücken. Die Sterne neben jedem Song dienen der Bewertung, a la iTunes.

Wir könnten die rudimentäre Funktionalität der App in die folgenden Schritte unterteilen:

  1. Durch Klicken auf „Hinzufügen“ wird der Liste ein neuer Künstler hinzugefügt, dessen Name im Feld „Neuer Künstler“ angegeben ist (dasselbe gilt für Songs eines bestimmten Künstlers).
  2. Das Leeren des Felds „Neuer Künstler“ deaktiviert die Schaltfläche „Hinzufügen“ (dasselbe gilt für Songs eines bestimmten Künstlers).
  3. Wenn Sie auf den Namen eines Künstlers klicken, werden seine Songs auf der rechten Seite aufgelistet.
  4. Durch Klicken auf die Sterne wird ein bestimmter Song bewertet.

Wir haben noch einen langen Weg vor uns, um das zum Laufen zu bringen, also fangen wir an.

Routen: der Schlüssel zur Ember.js-App

Eines der Unterscheidungsmerkmale von Ember ist die starke Betonung, die es auf URLs legt. In vielen anderen Frameworks fehlt es entweder an separaten URLs für separate Bildschirme oder wird nachträglich hinzugefügt. In Ember ist der Router – die Komponente, die URLs und Übergänge zwischen ihnen verwaltet – das zentrale Element, das die Arbeit zwischen den Bausteinen koordiniert. Folglich ist es auch der Schlüssel zum Verständnis des Innenlebens von Ember-Anwendungen.

Hier sind die Routen für unsere Bewerbung:

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

Wir definieren eine Ressourcenroute, artists und eine darin verschachtelte songs . Diese Definition gibt uns die folgenden Routen:

Routen

Ich habe das großartige Ember Inspector-Plugin verwendet (es existiert sowohl für Chrome als auch für Firefox), um Ihnen die generierten Routen auf leicht lesbare Weise anzuzeigen. Hier sind die Grundregeln für Ember-Routen, die Sie anhand der obigen Tabelle für unseren speziellen Fall überprüfen können:

  1. Es gibt einen impliziten application .

    Dies wird für alle Anfragen (Übergänge) aktiviert.

  2. Es gibt eine implizite index .

    Dies wird eingegeben, wenn der Benutzer zum Stammverzeichnis der Anwendung navigiert.

  3. Jede Ressourcenroute erstellt eine Route mit demselben Namen und implizit darunter eine Indexroute.

    Diese Indexroute wird aktiviert, wenn der Benutzer zu der Route navigiert. In unserem Fall wird artists.index ausgelöst, wenn der Benutzer zu /artists navigiert.

  4. Eine einfache (keine Ressource), verschachtelte Route hat den Namen der übergeordneten Route als Präfix.

    Die Route, die wir als this.route('songs', ...) definiert haben, hat den Namen artists.songs . Es wird ausgelöst, wenn der Benutzer zu /artists/pearl-jam oder /artists/radiohead navigiert.

  5. Wenn der Pfad nicht angegeben ist, wird davon ausgegangen, dass er gleich dem Routennamen ist.

  6. Wenn der Pfad ein : enthält, wird er als dynamisches Segment betrachtet .

    Der ihm zugewiesene Name (in unserem Fall slug ) stimmt mit dem Wert im entsprechenden Segment der URL überein. Das obige slug -Segment hat den Wert pearl-jam , radiohead oder einen anderen Wert, der aus der URL extrahiert wurde.

Zeigen Sie die Liste der Künstler an

Als ersten Schritt bauen wir den Bildschirm, der links die Liste der Künstler anzeigt. Dieser Bildschirm sollte den Benutzern angezeigt werden, wenn sie zu /artists/ navigieren:

Künstler

Um zu verstehen, wie dieser Bildschirm gerendert wird, ist es an der Zeit, ein weiteres übergreifendes Ember-Designprinzip einzuführen: Konvention vor Konfiguration . Im obigen Abschnitt haben wir gesehen, dass /artists die artists aktiviert. Per Konvention lautet der Name dieses Routenobjekts ArtistsRoute . Es liegt in der Verantwortung dieses Routenobjekts, Daten abzurufen, die die App rendern soll. Das passiert im Modell-Hook der Route:

 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 diesem Snippet werden die Daten über einen XHR-Aufruf aus dem Backend geholt und – nach der Konvertierung in ein Modellobjekt – in ein Array gepusht, das wir anschließend anzeigen können. Die Verantwortlichkeiten der Route erstrecken sich jedoch nicht auf die Bereitstellung von Anzeigelogik, die von der Steuerung gehandhabt wird. Lass uns einen Blick darauf werfen.

Hmmm – tatsächlich müssen wir den Controller an dieser Stelle nicht definieren! Ember ist schlau genug, den Controller bei Bedarf zu generieren und das M.odel -Attribut des Controllers auf den Rückgabewert des Model-Hooks selbst zu setzen, nämlich die Liste der Künstler. (Auch dies ist ein Ergebnis des Paradigmas „Konvention über Konfiguration“.) Wir können eine Ebene nach unten gehen und eine Vorlage erstellen, um die Liste anzuzeigen:

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

Wenn Ihnen das bekannt vorkommt, liegt das daran, dass Ember.js Handlebars-Vorlagen verwendet, die eine sehr einfache Syntax und Helfer haben, aber keine nicht-triviale Logik zulassen (z. B. ODER- oder UND-Verknüpfung von Begriffen in einer Bedingung).

In der obigen Vorlage iterieren wir durch das Modell (das zuvor durch die Route zu einem Array eingerichtet wurde, das alle Künstler enthält) und rendern für jedes darin enthaltene Element einen Link, der uns zur artists.songs Route für diesen Künstler führt. Der Link enthält den Künstlernamen. Der #each Helfer in Handlebars ändert den Bereich darin auf das aktuelle Element, sodass sich {{name}} immer auf den Namen des Künstlers bezieht, der gerade iteriert wird.

Verschachtelte Routen für verschachtelte Ansichten

Ein weiterer interessanter Punkt im obigen Snippet: {{outlet}} , das Slots in der Vorlage angibt, in denen Inhalte gerendert werden können. Beim Verschachteln von Routen wird zuerst die Vorlage für die äußere Ressourcenroute gerendert, gefolgt von der inneren Route, die ihren Vorlageninhalt in das von der äußeren Route definierte {{outlet}} rendert. Genau das passiert hier.

Per Konvention rendern alle Routen ihren Inhalt in die gleichnamige Vorlage. Oben ist das Attribut data-template-name der obigen Vorlage artists , was bedeutet, dass es für die äußere Route, artists , gerendert wird. Es gibt einen Auslass für den Inhalt des rechten Panels an, in das die innere Route, artists.index , seinen Inhalt rendert:

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

Zusammenfassend gibt eine Route ( artists ) ihren Inhalt in der linken Seitenleiste wieder, wobei ihr Modell die Liste der Künstler ist. Ein anderer Weg, artists.index , rendert seinen eigenen Inhalt in den Slot, der von der artists -Vorlage bereitgestellt wird. Es könnte einige Daten abrufen, die als Modell dienen, aber in diesem Fall möchten wir nur statischen Text anzeigen, also müssen wir das nicht.

Verwandt: 8 wichtige Ember.js-Interviewfragen

Erstellen Sie einen Künstler

Teil 1: Datenbindung

Als nächstes wollen wir in der Lage sein, Künstler zu erstellen und nicht nur auf eine langweilige Liste zu schauen.

Als ich diese artists zeigte, die die Künstlerliste darstellt, habe ich ein bisschen geschummelt. Ich schneide den oberen Teil aus, um mich auf das Wesentliche zu konzentrieren. Jetzt füge ich das wieder hinzu:

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

Wir verwenden einen Ember-Helfer, input , mit dem Typ text, um eine einfache Texteingabe zu rendern. Darin binden wir den Wert der Texteingabe an die Eigenschaft newName des Controllers, der diese Vorlage unterstützt, ArtistsController . Wenn sich also die value-Eigenschaft der Eingabe ändert (mit anderen Worten, wenn der Benutzer Text eingibt), wird die newName Eigenschaft auf dem Controller synchron gehalten.

Wir machen auch bekannt, dass die Aktion createArtist werden soll, wenn auf die Schaltfläche geklickt wird. Schließlich binden wir die Eigenschaft „disabled“ der Schaltfläche an die Eigenschaft „ disabled “ des Controllers. Wie sieht denn die Steuerung aus?

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

newName wird zu Beginn auf leer gesetzt, was bedeutet, dass die Texteingabe leer sein wird. (Erinnern Sie sich, was ich über Bindungen gesagt habe? Versuchen Sie, newName zu ändern, und sehen Sie, wie es als Text im Eingabefeld wiedergegeben wird.)

disabled ist so implementiert, dass wenn kein Text im Eingabefeld vorhanden ist, es true und somit die Schaltfläche deaktiviert wird. Der .property -Aufruf am Ende macht daraus eine „berechnete Eigenschaft“, ein weiteres leckeres Stück vom Ember-Kuchen.

Berechnete Eigenschaften sind Eigenschaften, die von anderen Eigenschaften abhängen, die selbst „normal“ oder berechnet sein können. Ember speichert den Wert davon, bis sich eine der abhängigen Eigenschaften ändert. Anschließend wird der Wert der berechneten Eigenschaft neu berechnet und erneut zwischengespeichert.

Hier ist eine visuelle Darstellung des obigen Prozesses. Zusammenfassend: Wenn der Benutzer den Namen eines Künstlers eingibt, wird die Eigenschaft newName aktualisiert, gefolgt von der Eigenschaft disabled , und schließlich wird der Name des Künstlers zur Liste hinzugefügt.

Umweg: Eine einzige Quelle der Wahrheit

Denken Sie einen Moment darüber nach. Mit Hilfe von Bindungen und berechneten Eigenschaften können wir (Modell-)Daten als Single Source of Truth etablieren. Oben löst eine Änderung des Namens des neuen Künstlers eine Änderung der Controller-Eigenschaft aus, die wiederum eine Änderung der deaktivierten Eigenschaft auslöst. Wenn der Benutzer beginnt, den Namen des neuen Künstlers einzugeben, wird die Schaltfläche wie von Zauberhand aktiviert.

Je größer das System, desto größer ist der Einfluss des „Single Source of Truth“-Prinzips. Es hält unseren Code sauber und robust und unsere Eigenschaftsdefinitionen deklarativer.

Einige andere Frameworks legen ebenfalls Wert darauf, dass Modelldaten die einzige Quelle der Wahrheit sind, gehen aber entweder nicht so weit wie Ember oder erledigen ihre Arbeit nicht so gründlich. Angular hat zum Beispiel bidirektionale Bindungen – aber keine berechneten Eigenschaften. Es kann berechnete Eigenschaften durch einfache Funktionen „emulieren“; Das Problem dabei ist, dass es keine Möglichkeit hat zu wissen, wann eine „berechnete Eigenschaft“ aktualisiert werden muss, und daher auf Dirty Checking zurückgreift, was wiederum zu einem Leistungsverlust führt, der sich besonders in größeren Anwendungen bemerkbar macht.

Wenn Sie mehr über das Thema erfahren möchten, empfehle ich Ihnen, den Blogbeitrag von eviltrout für eine kürzere Version oder diese Quora-Frage für eine längere Diskussion zu lesen, in der sich Kernentwickler von beiden Seiten einbringen.

Teil 2: Aktionshandler

Gehen wir zurück, um zu sehen, wie die createArtist Aktion erstellt wird, nachdem sie ausgelöst wurde (nach dem Drücken der Schaltfläche):

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

Aktionshandler müssen in ein actions eingeschlossen werden und können auf der Route, dem Controller oder der Ansicht definiert werden. Ich habe mich dafür entschieden, es hier auf der Route zu definieren, weil das Ergebnis der Aktion nicht auf den Controller beschränkt ist, sondern „global“.

Hier ist nichts Besonderes los. Nachdem uns das Back-End mitgeteilt hat, dass der Speichervorgang erfolgreich abgeschlossen wurde, führen wir der Reihe nach drei Dinge aus:

  1. Fügen Sie den neuen Künstler zum Modell der Vorlage (alle Künstler) hinzu, damit es neu gerendert wird und der neue Künstler als letztes Element der Liste erscheint.
  2. Leeren Sie das Eingabefeld über das newName Binding, was uns die direkte Manipulation des DOM erspart.
  3. Übergang zu einer neuen Route ( artists.songs ), wobei der frisch erstellte Künstler als Modell für diese Route übergeben wird. transitionTo ist die Möglichkeit, intern zwischen Routen zu wechseln. (Der link-to Helfer dient dazu, dies über eine Benutzeraktion zu tun.)

Songs für einen Künstler anzeigen

Wir können die Songs für einen Künstler anzeigen, indem Sie entweder auf den Namen des Künstlers klicken. Wir übergeben auch den Künstler, der das Modell der neuen Route werden soll. Wenn das Modellobjekt auf diese Weise übergeben wird, wird der model Hook der Route nicht aufgerufen, da das Modell nicht aufgelöst werden muss.

Die aktive Route hier ist artists.songs und somit werden der Controller und die Vorlage ArtistsSongsController bzw. artist artists/songs sein. Wir haben bereits gesehen, wie die Vorlage in das von der artists bereitgestellte Outlet gerendert wird, sodass wir uns nur auf die vorliegende Vorlage konzentrieren können:

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

Beachten Sie, dass ich den Code zum Erstellen eines neuen Songs entfernt habe, da er genau derselbe wäre wie beim Erstellen eines neuen Künstlers.

Die Eigenschaft songs wird in allen Künstlerobjekten aus den vom Server zurückgegebenen Daten eingerichtet. Der genaue Mechanismus, durch den dies geschieht, ist für die aktuelle Diskussion wenig interessant. Fürs erste reicht es uns zu wissen, dass jeder Song einen Titel und eine Bewertung hat.

Der Titel wird direkt in der Vorlage angezeigt und die Bewertung wird über die StarRating Ansicht mit Sternen dargestellt. Lass uns das jetzt sehen.

Sternbewertungs-Widget

Die Bewertung eines Songs liegt zwischen 1 und 5 und wird dem Benutzer über eine Ansicht, App.StarRating , angezeigt. Ansichten haben Zugriff auf ihren Kontext (in diesem Fall das Lied) und ihren Controller. Das bedeutet, dass sie seine Eigenschaften lesen und ändern können. Dies steht im Gegensatz zu einem anderen Ember-Baustein, Komponenten, die isolierte, wiederverwendbare Steuerelemente sind, die nur Zugriff auf das haben, was ihnen übergeben wurde. (Wir könnten in diesem Beispiel auch eine Bewertungskomponente mit Sternen verwenden.)

Sehen wir uns an, wie die Ansicht die Anzahl der Sterne anzeigt und die Bewertung des Songs festlegt, wenn der Benutzer auf einen der Sterne klickt:

 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 und numStars sind berechnete Eigenschaften , die wir zuvor mit der disabled Eigenschaft des ArtistsController besprochen haben . Oben habe ich ein sogenanntes berechnetes Eigenschaftsmakro verwendet, von dem etwa ein Dutzend in Ember definiert sind. Sie machen typische berechnete Eigenschaften prägnanter und weniger fehleranfällig (zum Schreiben). Ich habe rating auf die Bewertung des Kontexts (und damit des Songs) gesetzt, während ich sowohl die Eigenschaften fullStars als numStars so definiert habe, dass sie im Kontext des Sternbewertungs-Widgets besser lesbar sind.

Die stars -Methode ist die Hauptattraktion. Es gibt ein Array von Daten für die Sterne zurück, in denen jedes Element eine rating (von 1 bis 5) und ein Flag ( full ) enthält, um anzugeben, ob der Stern voll ist. Dies macht es außerordentlich einfach, sie in der Vorlage durchzugehen:

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

Dieses Snippet enthält mehrere Anmerkungen:

  1. Erstens gibt der each -Helfer an, dass er eine Ansichtseigenschaft (im Gegensatz zu einer Eigenschaft auf dem Controller) verwendet, indem er dem Eigenschaftsnamen view voranstellt.
  2. Zweitens sind dem class Attribut des span-Tags gemischte dynamische und statische Klassen zugeordnet. Alles, dem ein : vorangestellt ist, wird zu einer statischen Klasse, während die Notation full:glyphicon-star:glyphicon-star-empty wie ein ternärer Operator in JavaScript ist: Wenn die Eigenschaft full wahr ist, sollte die erste Klasse zugewiesen werden; wenn nicht, die zweite.
  3. Wenn schließlich auf das Tag geklickt wird, sollte die setRating -Aktion ausgelöst werden – aber Ember wird es in der Ansicht nachschlagen, nicht in der Route oder im Controller, wie beim Erstellen eines neuen Künstlers.

Die Aktion wird somit auf der Ansicht definiert:

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

Wir erhalten die Bewertung aus dem rating , das wir in der Vorlage zugewiesen haben, und legen dies dann als rating für den Song fest. Beachten Sie, dass die neue Bewertung nicht im Back-End gespeichert wird. Es wäre nicht schwierig, diese Funktionalität basierend darauf zu implementieren, wie wir einen Künstler erstellt haben, und bleibt dem motivierten Leser als Übung überlassen.

Alles einpacken

Wir haben mehrere Zutaten des oben erwähnten Glutkuchens probiert:

  • Wir haben gesehen, wie Routen der Kern von Ember-Anwendungen sind und wie sie als Grundlage für Namenskonventionen dienen.
  • Wir haben gesehen, wie bidirektionale Datenbindungen und berechnete Eigenschaften unsere Modelldaten zur Single Source of Truth machen und es uns ermöglichen, eine direkte DOM-Manipulation zu vermeiden.
  • Und wir haben gesehen, wie man Aktionen auf verschiedene Arten auslöst und handhabt und eine benutzerdefinierte Ansicht erstellt, um ein Steuerelement zu erstellen, das nicht Teil unseres HTML ist.

Schön, nicht wahr?

Weiterlesen (und anschauen)

Ember hat viel mehr zu bieten, als ich allein in diesen Beitrag packen könnte. Wenn Sie eine Screencast-Serie darüber sehen möchten, wie ich eine etwas weiter entwickelte Version der obigen Anwendung erstellt habe, und/oder mehr über Ember erfahren möchten, können Sie sich in meine Mailingliste eintragen, um wöchentlich Artikel oder Tipps zu erhalten.

Ich hoffe, ich habe Ihren Appetit darauf geweckt, mehr über Ember.js zu erfahren, und dass Sie weit über die Beispielanwendung hinausgehen, die ich in diesem Beitrag verwendet habe. Wenn Sie sich weiter über Ember.js informieren, sollten Sie unbedingt einen Blick auf unseren Artikel zu Ember Data werfen, um zu erfahren, wie Sie die Ember-Data-Bibliothek verwenden. Viel Spaß beim Bauen!

Siehe auch : Ember.js und die 8 häufigsten Fehler, die Entwickler machen