Ember Components A Deep Dive

Ember.js is een JavaScript MVC-raamwerk waarmee ontwikkelaars ambitieuze webtoepassingen kunnen maken. Hoewel pure MVC een ontwikkelaar toestaat om problemen te scheiden, biedt het niet alle tools en heeft uw toepassing andere constructies nodig. Vandaag ga ik het hebben over een van die constructies. Ember-componenten zijn in essentie sandboxed herbruikbare brokken van UI. Als u niet bekend bent met Ember, raadpleeg dan Aan de slag met Ember.js of de cursus Let's Learn Ember. In deze zelfstudie bespreken we de Web Components-specificatie, leren we hoe we een component in Ember schrijven, praten we over compositie, leggen we het verschil uit tussen een Sintel-weergave en een Sintel-component en oefenen we integratie van plug-ins met Ember-componenten.


Een woord over webcomponenten

Ember-componenten zijn gebaseerd op de W3C Web Components-specificatie. De specificatie bestaat uit vier kleinere specificaties; sjablonen, decorateurs, schaduw-DOM en aangepaste elementen. Van deze vier concepten hebben er slechts drie een harden specificaties, decorateurs vormen de uitzondering. Door de specificaties aanwezig te hebben, hebben framework-ontwikkelaars deze nieuwe API's kunnen polyfilleren voordat ze door browserleveranciers worden geïmplementeerd.

Er zijn verschillende belangrijke concepten om te begrijpen wanneer je over componenten praat:

  • Componenten weten niets van de buitenwereld tenzij ze expliciet worden ingeleverd
  • Componenten moeten een goed gedefinieerde interface naar de buitenwereld hebben
  • Componenten kunnen geen JavaScript buiten het onderdeel manipuleren
  • Componenten kunnen evenementen uitzenden
  • Aangepaste elementen moeten met een koppelteken worden weergegeven
  • JavaScript buiten kan geen componenten manipuleren

Webcomponenten bieden ware inkapseling voor UI-widgets. Hieronder is een diagram van hoe een component op het meest basale niveau werkt.

Hoewel Ember met succes een groot aantal specificaties heeft gepolyfilled, hebben frameworks zoals AngularJS, Dart, Polymer en Xtags vergelijkbare oplossingen. Het enige voorbehoud hier is dat Ember en Angular momenteel geen scopestijlen toepassen op de component. Na verloop van tijd verdwijnen deze polyfill-oplossingen en nemen frameworks de implementatie van de browser-leverancier over. Dit is een fundamenteel andere benadering van ontwikkeling, omdat we kunnen profiteren van toekomstige specificaties zonder onszelf te verbinden met experimentele functies in browsers.


De meest elementaire component van de balsem

Laten we, met onze kennis van Web Components, de zeer elementaire naamcomponent van boven, maar dan in Sintel implementeren. Laten we beginnen met het downloaden van de Ember Starter Kit van de Ember-website. Op het moment van deze tutorial is de versie van Ember 1.3.0. Zodra u het hebt gedownload, opent u de bestanden in uw favoriete editor en verwijdert u alle sjablonen index.html (aangegeven met data-template-naam) en alles erin app.js.

Het eerste dat we willen doen is onze componentsjabloon maken. In het belang van deze tutorial gaan we inline sjablonen gebruiken. U doet dit door het volgende te schrijven in uw index.html het dossier. We moeten ook een nieuwe Ember-applicatie maken in onze JavaScript.

   var App = Ember.Application.create ();

U zult merken dat de data-template-naam een ​​padnaam heeft in plaats van alleen een gewone string. De reden waarom we onze componentnaam als voorvoegsel gebruiken "Componenten /" is om tegen Ember te zeggen dat we te maken hebben met een componentsjabloon en niet met een standaard applicatiesjabloon. U zult ook opmerken dat de componentnaam het koppelteken bevat. Dit is de namespacing die ik had genoemd in de Web Components-specificatie. Namespacing wordt gedaan, zodat we geen botsingen met bestaande tags hebben.

Als we de browser openen, zouden we niets anders moeten zien. De reden hiervoor is dat we nog iets in onze naamsjabloon moeten plaatsen. Laten we ervoor zorgen.

... 

Nu zou je in de browser iets moeten zien zoals de afbeelding hierboven. We zijn nog steeds niet klaar, zoals je ziet, we printen eigenlijk geen naam uit. Zoals ik in het eerste gedeelte al zei, moeten componenten een goed gedefinieerde interface aan de buitenwereld blootstellen. In dit geval houden we ons bezig met de naam. Dus laten we de naam doorgeven door een naamattribuut op de component met de naam te plaatsen.

... 

Wanneer u de pagina vernieuwt die u zou moeten zien "Hallo, mijn naam is Chad". Dit alles met het schrijven van een regel JavaScript. Nu we een gevoel hebben voor het schrijven van een basiscomponent, laten we het hebben over het verschil tussen componenten van de Sintel en Sintel-weergaven.


Ember Components vs. Ember Views

Ember is een MVC, dus sommigen denken misschien: "Waarom niet gewoon hier een weergave voor gebruiken?" Dit is een legitieme vraag. Componenten zijn eigenlijk een subklasse van Ember.View, het grootste verschil is dat views meestal worden gevonden in de context van een controller. Neem het onderstaande voorbeeld.

 App.IndexController = Ember.Controller.extend (myState: 'on'); App.IndexView = Ember.View.extend (klik: function () var controller = this.get ('controller'), myState = controller.get ('myState'); console.log (controller) // De controller instance console.log (myState) // De string "aan");
 

Views zitten normaal gesproken achter een template en veranderen raw input (click, mouseEnter, mouseMove, etc.) in een semantische actie (openMenu, editName, hideModal, etc.) in een controller of route. Een ander ding om op te wijzen is dat sjablonen ook een context nodig hebben. Dus wat er gebeurt, is dat Ember de context afleidt door conventies en de URL te benoemen. Zie het diagram hieronder.

Zoals je kunt zien, is er een niveau van hiërarchie op basis van de URL en elk niveau van die hiërarchie heeft zijn eigen context die is afgeleid door naamgevingsconventies.

Ember-componenten hebben geen context, ze weten alleen over de interface die ze definiëren. Hierdoor kan een component in elke context worden weergegeven, waardoor het wordt ontkoppeld en opnieuw kan worden gebruikt. Als de component een interface blootstelt, is het de taak van de context om die interface te vervullen. Met andere woorden, als u wilt dat het onderdeel correct wordt weergegeven, moet u het gegevens leveren die het verwacht. Het is belangrijk op te merken dat deze doorgegeven waarden zowel tekenreeksen als gebonden eigenschappen kunnen zijn.

Wanneer gebonden eigenschappen binnen een component worden gemanipuleerd, worden deze wijzigingen nog steeds doorgegeven waar ze ook worden vermeld in uw toepassing. Dit maakt componenten extreem krachtig. Nu we goed begrijpen hoe componenten verschillen van weergaven, laten we eens kijken naar een complexer voorbeeld dat illustreert hoe een ontwikkelaar meerdere componenten kan samenstellen.


Samenstelling van componenten

Een heel leuk iets van Ember is dat het is gebouwd op concepten van UI-hiërarchie en dit is heel duidelijk met de samenstelling van componenten. Hieronder is een voorbeeld van wat we gaan maken. Het is een eenvoudige gebruikers-interface voor groepschats. Vanzelfsprekend ga ik geen hele chatservice schrijven om de gebruikersinterface van stroom te voorzien, maar we kunnen zien hoe we de gebruikersinterface kunnen onderbreken in herbruikbare en samengestelde componenten.

Laten we eerst kijken hoe we de gebruikersinterface zullen opdelen in kleinere en beter verteerbare delen. Alles waar we een kader rond kunnen tekenen is een component, met uitzondering van de tekst- en knopinvoer onderaan de gebruikersinterface. Ons doel is om alleen de component op de buitenste laag te kunnen configureren, al het andere zou gewoon moeten werken.

Laten we beginnen met het maken van een nieuw html-bestand met de naam chat.html en het instellen van alle afhankelijkheden voor Sintel. Maak vervolgens alle sjablonen.

       

U zult zien dat componenten kunnen worden ingebed in andere componenten. Dit maakt componenten net als lego's die we kunnen samenstellen zoals we willen. We hoeven alleen maar naar de interface van de component te schrijven.

Als we nu in de browser gaan kijken, zouden we niet veel moeten zien omdat we geen gegevens in de component laten stromen. U zult ook merken dat, hoewel er geen gegevens zijn, de componenten geen fouten veroorzaken. Het enige dat hier daadwerkelijk wordt weergegeven, is het invoergebied en de verzendknop. Dit komt omdat ze niet afhankelijk zijn van wat wordt doorgegeven.

Als je wat meer kijkt naar de sjablonen, merk je dat we een aantal dingen hebben toegewezen aan de groep-chatcomponent.

 

In dit geval geven we het model door uit de context van de IndexRoute als "berichten" en we hebben de reeks ingesteld bericht versturen als de actie op het onderdeel. De actie wordt gebruikt om uit te zenden wanneer de gebruiker een nieuw bericht wil verzenden. We zullen dit later in de tutorial bespreken. Het andere dat je zal opvallen, is dat we strikte interfaces instellen voor de geneste componenten die allemaal de gegevens gebruiken die zijn doorgegeven via de interface voor groeps-chat.

... 
    #each message in messages
  • chat-bericht username = message.twitterUserName message = message.text time = message.timeStamp
  • /elk
...

Zoals eerder vermeld, kunt u tekenreeksen of gebonden eigenschappen doorgeven aan componenten. Vuistregel zijn, gebruik citaten bij het passeren van een string, gebruik geen aanhalingstekens bij het passeren van een gebonden eigenschap. Nu we onze sjablonen op hun plaats hebben, laten we wat nepgegevens erop plaatsen.

 App = Ember.Application.create (); App.IndexRoute = Ember.Route.extend (model: function () return [id: 1, firstName: 'Tom', lastName: 'Dale', twitterUserName: 'tomdale', tekst: 'Ik denk dat we terug moeten oude Tomster. Hij was geweldig. ', timeStamp: Date.now () - 400000,, id: 2, firstName:' Yehuda ', lastName:' Katz ', twitterUserName:' wycats ', tekst:' That \ ' een goed idee. ', timeStamp: Date.now () - 300000,];);

Als we dit nu in de browser bekijken, zouden we een beetje vooruitgang moeten zien. Maar er moet nog wat werk worden verzet, vooral om de afbeeldingen weer te geven, de datum te formatteren en een nieuw bericht te verzenden. Laten we ervoor zorgen.

Met onze user-avatar-component willen we een service genaamd Avatars.io gebruiken om de Twitter-avatar van een gebruiker op te halen op basis van hun Twitter-gebruikersnaam. Laten we eens kijken naar hoe het gebruikersbeeldcomponent in de sjabloon wordt gebruikt.

  

Het is een vrij eenvoudig onderdeel maar je zult merken dat we een ingebonden eigenschap hebben genoemd avatarUrl. We zullen deze eigenschap binnen ons JavaScript voor deze component moeten maken. Een ander ding dat je zal opmerken is dat we de service specificeren waartoe we de avatar willen ophalen. Met Avatars.io kun je sociale avatars van Twitter, Facebook en Instagram ophalen. We kunnen dit onderdeel extreem flexibel maken. Laten we het onderdeel schrijven.

 App.UserAvatarComponent = Ember.Component.extend (avatarUrl: function () var gebruikersnaam = this.get ('gebruikersnaam'), service = this.get ('service'), availableServices = ['twitter', 'facebook' , 'instagram']; if (availableServices.indexOf (service)> -1) return 'http://avatars.io/' + service + '/' + gebruikersnaam; return 'images / cat.png'; .property ('gebruikersnaam', 'service'));

Zoals je kunt zien, om een ​​nieuwe component te maken, volgen we gewoon de naamconventie van NAMEOFCOMPONENTComponent en verlengen Ember.Component. Als we nu teruggaan naar de browser, zouden we nu onze avatars moeten zien.

Om te zorgen voor de datumnotatie, gebruiken we moment.js en schrijven we een stuurhulp om de datum voor ons op te stellen.

 Ember.Handlebars.helper ('format-date', functie (datum) return moment (date) .fromNow (););

Nu hoeven we alleen maar de helper toe te passen op onze tijdstempelcomponent.

 

We zouden nu een component moeten hebben die datums opmaakt in plaats van de tijdstempels van Unix-tijdperken.

We kunnen het echter beter doen. Deze tijdstempels moeten automatisch worden bijgewerkt in de loop der tijd, dus laten we ervoor zorgen dat onze component voor tijdstempels precies dat doet.

 App.TimeStampComponent = Ember.Component.extend (startTimer: function () var currentTime = this.get ('time'); this.set ('time', currentTime - 6000); this.scheduleStartTimer ();, scheduleStartTimer: function () this._timer = Ember.run.later (this, 'startTimer', 6000); .on ('didInsertElement'), killTimer: function () Ember.run.cancel (this._timer) ; .on ('willDestroyElement'));

Een paar punten om op te merken hier. Een daarvan is de op() declaratieve gebeurtenishandler-syntaxis. Dit werd geïntroduceerd in Ember voorafgaand aan de 1.0-release. Het doet precies wat u denkt dat het doet, wanneer de tijdstempelcomponent in de DOM wordt ingevoegd, scheduleStartTime wordt genoemd. Wanneer het element op het punt staat te worden vernietigd en de KillTimer methode wordt aangeroepen. De rest van het onderdeel vertelt gewoon hoe lang het is om elke minuut bij te werken.

Het andere wat je zult opvallen, is dat er verschillende oproepen naar zijn Ember.run. In Ember is er een wachtlijnsysteem, gewoonlijk de run-loop genoemd, dat wordt leeggemaakt wanneer gegevens worden gewijzigd. Dit wordt gedaan om wijzigingen in essentie samen te voegen en één keer de wijziging aan te brengen. In ons voorbeeld gaan we gebruiken Ember.run.later om de startTimer methode elke minuut. We zullen ook gebruiken Ember.run.cancel om de timer te verwijderen. Dit zijn in essentie de eigen start- en stopintervalmethoden van Ember. Ze zijn nodig om het wachtrijsysteem gesynchroniseerd te houden. Voor meer informatie over de run loop raad ik aan het artikel van Alex Matchneer te lezen: "Alles wat je nooit wilde weten over de loop van de loop van de Sintel".

Het volgende dat we moeten doen, is de actie zo instellen dat wanneer de gebruiker inspringt, er een nieuw bericht wordt aangemaakt. Ons component zou het niet moeten schelen hoe de gegevens worden gemaakt, het moet gewoon uitzenden dat de gebruiker heeft geprobeerd een bericht te verzenden. Onze IndexRoute is verantwoordelijk voor het nemen van deze actie en wordt iets zinvols.

 App.GroupChatComponent = Ember.Component.extend (message: ", actions: submit: function () var message = this.get ('message') .trim (), conversation = this. $ ('Ul') [0]; // Haalt de waarde van 'actie' // op en verzendt de actie met het bericht this.sendAction ('action', message); // Wanneer de loop van de Sintel is voltooid // scrol naar de onderste Sintel. run.schedule ('afterRender', function () conversation.scrollTop = conversation.scrollHeight;); // Reset het tekstberichtveld this.set ('message', "););
 
input type = "text" placeholder = "Verstuur nieuw bericht" value = message input type = "submit" value = "Send"

Omdat de groep-chat-component de invoer- en verzendknop bezit, moeten we reageren op de gebruiker door op verzenden op dit abstractieniveau te klikken. Wanneer de gebruiker op de knop Verzenden klikt, voert deze de verzendactie uit in onze componentimplementatie. Binnen de submit action handler krijgen we de waarde van het bericht, dat wordt ingesteld door de tekstinvoer. We sturen de actie dan samen met het bericht. Ten slotte zullen we het bericht resetten naar een lege string.

Het andere vreemde dat je hier ziet, is de Ember.run.schedule methode wordt aangeroepen. Dit is wederom de runloop van Sintel in actie. Je zult opmerken dat het schema een string neemt als zijn eerste argument, in dit geval "afterRender". Ember heeft eigenlijk verschillende wachtrijen die het beheert, waardoor het een van hen is. Dus in ons geval zeggen we wanneer het verzenden van het bericht is voltooid en alle manipulaties zijn uitgevoerd en nadat de wachtrij voor het genereren van gegevens is weggevaagd, bel ons terug. Dit zal door ons scrollen ul naar de onderkant zodat de gebruiker het nieuwe bericht kan zien na elke manipulatie. Voor meer informatie over de looploop, raad ik aan het artikel van Alex Matchneer te lezen "Alles wat je nooit wilde weten over de loop van de loop van de Sintel".

Als we naar de browser gaan en we klikken op de verzendknop, krijgen we een hele mooie fout van Ember die zegt: "Niet-afgevangen fout: niets heeft de gebeurtenis afgehandeld 'sendMessage'. Dit is wat we verwachten omdat we onze applicatie niet hebben verteld hoe om te reageren op dit soort evenementen. Laten we dat oplossen.

 App.IndexRoute = Ember.Route.extend (/ * ... * / actions: sendMessage: function (message) if (message! == ") console.log (message););

Als we nu teruggaan naar de browser, typ dan iets in de berichtinvoer en druk op verzenden, we zouden het bericht in de console moeten zien. Dus op dit punt is onze component los gekoppeld en praat met de rest onze applicatie. Laten we hier iets interessanters mee doen. Laten we eerst een nieuw maken Ember.Object om te werken als een model voor een nieuw bericht.

 App.Message = Ember.Object.extend (id: 3, firstName: 'Chad', lastName: 'Hietala', twitterUserName: 'chadhietala', text: null, timeStamp: null);

Dus toen de bericht versturen actie gebeurt, we willen de tekst en tijdstempel veld van ons berichtmodel, maak er een nieuw exemplaar van aan en druk dat exemplaar vervolgens in de bestaande verzameling berichten.

 App.IndexRoute = Ember.Route.extend (/ * ... * / actions: sendMessage: function (message) var user, messages, newMessage; if (message! == ") messages = this.modelFor ('index '), newMessage = App.Message.create (text: message, timeStamp: Date.now ()) messages.pushObject (newMessage););

Wanneer we teruggaan naar de browser, moeten we nu nieuwe berichten kunnen maken.

We hebben nu verschillende herbruikbare brokken gebruikersinterfaces die we overal kunnen plaatsen. Als u bijvoorbeeld een avatar ergens anders in uw Ember-toepassing zou moeten gebruiken, kunnen we de component User-avatar opnieuw gebruiken.

 

JQuery-invoegtoepassingen inpakken

Op dit punt vraag je je waarschijnlijk af: "Wat als ik een jQuery-plug-in in mijn component wil gebruiken?" Geen probleem. Kortheidshalve, laten we onze user-avatar component aanpassen om een ​​tooltip te tonen wanneer we over de avatar zweven. Ik heb ervoor gekozen om de tooltipster voor jQuery-plug-ins te gebruiken om de knopinfo te verwerken. Laten we de bestaande code aanpassen om tooltipster te gebruiken.

Laten we eerst de juiste bestanden aan onze toevoegen chat.html en wijzig de bestaande gebruikersavatarcomponent.

... ...  ... 

En dan onze JavaScript:

 App.UserAvatarComponent = Ember.Component.extend (/ * ... * / SetupTooltip: function () this. $ ('.Avatar') .tooltipster (animation: 'fade'); .on ('didInsertElement' ), destroyTooltip: function () this. $ ('.avatar') .tooltipster ('destroy'); .on ('willDestroyElement'));

Nogmaals, we zien de syntaxis van de declaratieve gebeurtenislistener, maar we zien het voor het eerst dit. $. Als je bekend bent met jQuery, zou je verwachten dat we alle elementen met klasse van 'avatar' zouden ondervragen. Dit is niet het geval in Sintel, omdat context wordt toegepast. In ons geval zijn we alleen op zoek naar elementen met de klasse 'avatar' in de component gebruiker-avatar. Het is vergelijkbaar met de zoekmethode van jQuery. Bij vernietiging van het element moeten we de zweefgebeurtenis op de avatar losmaken en eventuele functionaliteit opruimen, dit gebeurt door 'vernietigen' door te geven aan tool-tipgever. Als we naar de browser gaan, een afbeelding verversen en zweven, moeten we de gebruikersnaam van de gebruiker zien.


Conclusie

In deze zelfstudie hebben we een diepe duik genomen in de componenten van de Ember en hebben laten zien hoe je herbruikbare stukjes UI kunt gebruiken om grotere composieten te genereren en jQuery-plug-ins te integreren. We hebben gekeken hoe componenten verschillen van weergaven in Sintel. We hebben ook het idee van interface-gebaseerd programmeren behandeld als het gaat om componenten. Hopelijk kon ik niet alleen licht laten schijnen op de componenten van de Sintel, maar ook op de webcomponenten en waar het internet naartoe gaat.