Composants Ember une plongée profonde

Ember.js est un framework JavaScript MVC qui permet aux développeurs de créer des applications Web ambitieuses. Bien que MVC pur permette à un développeur de séparer les problèmes, il ne vous fournit pas tous les outils et votre application aura besoin d'autres constructions. Aujourd'hui, je vais parler de l'un de ces concepts. Les composants Ember sont essentiellement des fragments d'interface utilisateur réutilisables en bac à sable. Si vous n'êtes pas familier avec Ember, veuillez vous familiariser avec Getting Started with Ember.js ou le cours Let's Learn Ember. Dans ce didacticiel, nous allons couvrir la spécification des composants Web, apprendre à écrire un composant dans Ember, parler de composition, expliquer la différence entre une vue Ember et un composant Ember et apprendre à intégrer des plugins avec des composants Ember..


Un mot sur les composants Web

Les composants Ember sont basés sur la spécification W3C Web Components. La spécification est composée de quatre spécifications plus petites; modèles, décorateurs, DOM shadow et éléments personnalisés. Parmi ces quatre concepts, seuls trois d'entre eux ont des spécifications strictes, les décorateurs étant l'exception. Grâce à la mise en place des spécifications, les développeurs d’infrastructure ont pu remplir ces nouvelles API avant de les mettre en œuvre par les éditeurs de navigateurs..

Il y a plusieurs concepts importants à saisir lorsque l'on parle de composants:

  • Les composants ne connaissent rien du monde extérieur à moins d'être explicitement transmis
  • Les composants doivent avoir une interface bien définie avec le monde extérieur
  • Les composants ne peuvent manipuler aucun code JavaScript en dehors du composant
  • Les composants peuvent diffuser des événements
  • Les éléments personnalisés doivent être nommés avec un trait d'union
  • En dehors de JavaScript ne peut pas manipuler de composants

Les composants Web fournissent une véritable encapsulation pour les widgets d'interface utilisateur. Vous trouverez ci-dessous un diagramme du fonctionnement d'un composant au niveau le plus élémentaire..

Tandis qu'Ember a réussi à remplir de nombreuses spécifications, les frameworks comme AngularJS, Dart, Polymer et Xtags proposent des solutions similaires. La seule mise en garde ici est qu'Ember et Angular n'affectent pas les styles au composant. Avec le temps, ces solutions multifonctionnelles disparaîtront et les frameworks adopteront la mise en œuvre du fournisseur de navigateurs. Il s'agit d'une approche de développement fondamentalement différente, car nous pouvons tirer parti des spécifications futures sans nous attacher aux fonctionnalités expérimentales des navigateurs..


Le composant Ember le plus basique

Avec notre connaissance des composants Web, implémentons le composant très basique my-name d’en haut, mais dans Ember. Commençons par télécharger le kit de démarrage Ember à partir du site Web Ember. Au moment de ce tutoriel, la version de Ember est 1.3.0. Une fois que vous avez téléchargé, ouvrez les fichiers dans votre éditeur favori, supprimez tous les modèles index.html (noté avec data-template-name) et tout dans app.js.

La première chose que nous allons vouloir faire est de créer notre modèle de composant. Pour les besoins de ce didacticiel, nous allons utiliser des modèles en ligne. Vous faites cela en écrivant ce qui suit dans votre index.html fichier. Nous devons également créer une nouvelle application Ember dans notre code JavaScript..

   var App = Ember.Application.create ();

Vous remarquerez que le nom de modèle de données a un nom de chemin au lieu d'une simple chaîne. La raison pour laquelle nous préfixons le nom de notre composant avec "Composants/" est de dire à Ember que nous avons affaire à un modèle de composant et non à un modèle d’application standard. Vous remarquerez également que le nom du composant contient le trait d'union. Il s'agit de l'espacement de nom que j'ai mentionné dans la spécification des composants Web. L'espacement de noms est fait pour éviter les conflits de noms avec les balises existantes.

Si nous ouvrons le navigateur, nous ne devrions rien voir de différent. La raison en est que nous n'avons encore rien placé dans notre modèle mon nom. Prenons soin de ça.

Maintenant, dans le navigateur, vous devriez voir quelque chose comme l'image ci-dessus. Nous n'avons toujours pas terminé, car vous voyez que nous n'imprimons pas de nom. Comme je l'ai mentionné dans la première section, les composants doivent présenter une interface bien définie avec le monde extérieur. Dans ce cas, nous nous intéressons au nom. Passons donc le nom en plaçant un attribut name sur le composant my-name.

Lorsque vous actualisez la page, vous devriez voir "Bonjour, je m'appelle Chad". Tout cela avec l'écriture d'une ligne de JavaScript. Maintenant que nous avons la sensation d'écrire un composant de base, parlons de la différence entre les composants Ember et les vues Ember..


Composants de braise et vues de braise

Ember est un MVC, alors certains pensent peut-être: "Pourquoi ne pas simplement utiliser une vue pour cela?" C'est une question légitime. Les composants sont en fait une sous-classe de Ember.View, la plus grande différence ici est que les vues se trouvent généralement dans le contexte d'un contrôleur. Prenons l'exemple ci-dessous.

 App.IndexController = Ember.Controller.extend (myState: 'on'); App.IndexView = Ember.View.extend (click: function () var controller = this.get ('contrôleur'), ​​myState = controller.get ('monState'); console.log (contrôleur) // le contrôleur instance console.log (myState) // La chaîne "on");
 

Les vues se trouvent normalement derrière un modèle et transforment une entrée brute (clic, mouseEnter, mouseMove, etc.) en une action sémantique (openMenu, editName, hideModal, etc.) dans un contrôleur ou une route. Une autre chose à souligner est que les modèles ont également besoin d’un contexte. Donc ce qui finit par arriver, c’est que Ember infère le contexte à travers les conventions de nommage et l’URL. Voir le schéma ci-dessous.

Comme vous pouvez le constater, il existe un niveau de hiérarchie basé sur l'URL et chaque niveau de cette hiérarchie a son propre contexte qui est dérivé des conventions de dénomination..

Les composants Ember n'ont pas de contexte, ils ne connaissent que l'interface qu'ils définissent. Cela permet de rendre un composant dans n'importe quel contexte, ce qui le rend découplé et réutilisable. Si le composant expose une interface, le travail du contexte consiste à remplir cette interface. En d'autres termes, si vous souhaitez que le composant soit correctement rendu, vous devez lui fournir les données qu'il attend. Il est important de noter que ces valeurs transmises peuvent être des chaînes ou des propriétés liées..

Lorsque des propriétés liées sont manipulées dans un composant, ces modifications sont toujours propagées où qu'elles soient référencées dans votre application. Cela rend les composants extrêmement puissants. Maintenant que nous comprenons bien en quoi les composants diffèrent des vues, examinons un exemple plus complexe illustrant comment un développeur peut composer plusieurs composants..


Composition des composants

L’un des aspects les plus intéressants d’Ember est qu’il repose sur des concepts de hiérarchie d’interface utilisateur, ce qui est très évident dans la composition des composants. Vous trouverez ci-dessous un exemple de ce que nous allons faire. C'est une interface utilisateur de discussion de groupe simple. Évidemment, je ne vais pas écrire tout un service de chat pour alimenter l'interface utilisateur, mais nous pouvons voir comment nous pouvons le décomposer en composants réutilisables et composables..

Voyons d'abord comment nous allons diviser l'interface utilisateur en parties plus petites et plus digestibles. Tout ce que nous pouvons dessiner est un composant, à l'exception des entrées de texte et de bouton situées au bas de l'interface utilisateur. Notre objectif est de pouvoir configurer uniquement le composant sur la couche externe, tout le reste devrait fonctionner.

Commençons par créer un nouveau fichier html appelé chat.html et mettre en place toutes les dépendances pour Ember. Suivant créer tous les modèles.

       

Vous verrez que les composants peuvent être imbriqués à l'intérieur d'autres composants. Cela crée des composants comme des legos que nous pouvons assembler comme bon nous semble. Nous avons juste besoin d'écrire sur l'interface du composant.

Si nous allons maintenant dans le navigateur, nous ne devrions pas en voir beaucoup car nous n’avons aucune donnée dans le composant. Vous remarquerez également que même s’il n’ya pas de données, les composants ne génèrent pas d’erreur. La seule chose qui est réellement rendue ici est la zone de saisie et le bouton d’envoi. C'est parce qu'ils ne dépendent pas de ce qui est transmis.

En regardant de plus près les modèles, vous remarquerez que nous avons attribué quelques éléments à la composante de discussion de groupe..

 

Dans ce cas, nous passons le modèle du contexte de la IndexRoute comme "messages" et nous avons défini la chaîne de envoyer le message comme l'action sur le composant. L'action sera utilisée pour diffuser lorsque l'utilisateur veut envoyer un nouveau message. Nous couvrirons cela plus tard dans le tutoriel. Vous remarquerez également que nous configurons des interfaces strictes avec les composants imbriqués, qui utilisent tous les données transmises par l’interface de discussion en groupe..

    #chaque message dans les messages
  • chat-message username = message.twitterUserName message = message.text time = message.timeStamp
  • /chaque

Comme indiqué précédemment, vous pouvez transmettre des chaînes ou des propriétés liées à des composants. En règle générale, utilisez des guillemets pour passer une chaîne, n'utilisez pas de guillemets pour transmettre une propriété liée. Maintenant que nos modèles sont en place, jetons-y quelques données factices.

 App = Ember.Application.create (); App.IndexRoute = Ember.Route.extend (modèle: function () return [id: 1, prénom: 'Tom', dernier nom: 'Dale', twitterUserName: 'tomdale', texte: 'Je pense que nous devrions revenir le vieux Tomster. Il était génial. ', timeStamp: Date.now () - 400000,, id: 2, prénom:' Yehuda ', nom:' Katz ', twitterUserName:' wycats ', texte:' Cela \ ' C'est une bonne idée. ', timeStamp: Date.now () - 300000,];);

Si nous examinons cela maintenant dans le navigateur, nous devrions voir un peu de progrès. Mais il reste encore du travail à faire, notamment obtenir l'affichage des images, formater la date et pouvoir envoyer un nouveau message. Prenons soin de ça.

Avec notre composant user-avatar, nous souhaitons utiliser un service appelé Avatars.io pour récupérer l'avatar Twitter d'un utilisateur en fonction de son nom d'utilisateur Twitter. Voyons comment le composant image de l'utilisateur est utilisé dans le modèle.

  

C'est un composant assez simple mais vous remarquerez que nous avons une propriété liée appelée avatarUrl. Nous allons avoir besoin de créer cette propriété dans notre code JavaScript pour ce composant. Vous remarquerez également que nous spécifions le service à partir duquel nous voulons récupérer l'avatar. Avatars.io vous permet de récupérer des avatars sociaux sur Twitter, Facebook et Instagram. Nous pouvons rendre ce composant extrêmement flexible. Ecrivons le composant.

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

Comme vous pouvez le constater, pour créer un nouveau composant, nous suivons simplement la convention de nommage de NAMEOFCOMPONENTComponent et étendre Ember.Component. Maintenant, si nous revenons au navigateur, nous devrions maintenant voir nos avatars.

Pour prendre en charge le formatage de la date, utilisons moment.js et écrivons un assistant Handlebars pour formater la date à notre intention..

 Ember.Handlebars.helper ('format-date', fonction (date) moment de retour (date) .fromNow (););

Il ne nous reste plus qu'à appliquer l'aide à notre composant d'horodatage.

 

Nous devrions maintenant avoir un composant qui formate les dates au lieu des horodatages de l'époque Unix.

Nous pouvons faire mieux cependant. Ces horodatages devraient se mettre à jour automatiquement au fil du temps, alors faisons en sorte que notre composant horodatage le fasse exactement..

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

Quelques points à noter ici. L'un est le sur() syntaxe déclarative du gestionnaire d'événements. Cela a été introduit dans Ember avant la version 1.0. Il fait exactement ce que vous pensez faire, lorsque le composant d'horodatage est inséré dans le DOM, scheduleStartTime est appelé. Lorsque l'élément est sur le point d'être détruit et nettoyé, le killTimer la méthode sera appelée. Le reste du composant indique simplement le temps de mise à jour chaque minute.

L’autre chose que vous remarquerez est qu’il y a plusieurs appels à Ember.run. Dans Ember, il existe un système de mise en file d'attente, généralement appelé boucle d'exécution, qui est vidé lorsque les données sont modifiées. Ceci est fait essentiellement pour fusionner les changements et faire le changement une fois. Dans notre exemple, nous allons utiliser Ember.run.later pour exécuter le startTimer méthode chaque minute. Nous allons aussi utiliser Ember.run.cancel pour retirer la minuterie. Il s'agit essentiellement des méthodes d'intervalle de démarrage et d'arrêt propres à Ember. Ils sont nécessaires pour que le système de files d'attente reste synchronisé. Pour plus d'informations sur la boucle d'exécution, je suggère de lire l'article d'Alex Matchneer "Tout ce que vous n'avez jamais voulu savoir sur la boucle de braise Ember".

La prochaine chose à faire est de configurer l'action de manière à ce qu'un nouveau message soit créé lorsque l'utilisateur clique sur Soumettre. Notre composant ne devrait pas se soucier de la manière dont les données sont créées; il devrait simplement indiquer que l’utilisateur a essayé d’envoyer un message. Notre IndexRoute sera responsable de prendre cette action et de se transformer en quelque chose de significatif.

 App.GroupChatComponent = Ember.Component.extend (message: ", actions: submit: function () var message = this.get ('message') .trim (), conversation = this. $ ('Ul') [0]; // Récupère la valeur de 'action' // et envoie l'action avec le message this.sendAction ('action', message); // Lorsque la boucle d'exécution Ember est terminée // défile vers le bas Ember. run.schedule ('afterRender', function () conversation.scrollTop = conversation.scrollHeight;); // Réinitialise le champ du message texte this.set ('message', "););
 
input type = "text" placeholder = "Envoyer un nouveau message" value = message input type = "submit" value = "Send"

Étant donné que le composant de discussion de groupe possède les boutons d'entrée et d'envoi, nous devons réagir lorsque l'utilisateur clique sur envoyer à ce niveau d'abstraction. Lorsque l'utilisateur clique sur le bouton d'envoi, il exécute l'action d'envoi dans notre implémentation de composant. Dans le gestionnaire d'actions d'envoi, nous allons obtenir la valeur de message, qui est définie par l'entrée de texte. Nous enverrons ensuite l'action avec le message. Enfin, nous allons réinitialiser le message à une chaîne vide.

L’autre chose étrange que vous voyez ici est la Ember.run.schedule méthode étant appelée. Encore une fois, c'est la boucle d'exécution d'Ember en action. Vous remarquerez que la planification prend une chaîne comme premier argument, dans ce cas "afterRender". Ember gère en fait plusieurs files d'attente, l'une d'entre elles étant rendue. Donc, dans notre cas, nous disons que, lorsque l'envoi du message est terminé, que des manipulations sont effectuées et que la file d'attente de rendu a été vidée, appelez notre rappel. Cela fera défiler notre ul en bas pour que l’utilisateur puisse voir le nouveau message après les manipulations. Pour plus d'informations sur la boucle d'analyse, je suggère de lire l'article d'Alex Matchneer "Tout ce que vous n'avez jamais voulu savoir sur la boucle Run Ember".

Si nous passons sur le navigateur et que nous cliquons sur le bouton d’envoi, nous obtiendrons une très belle erreur de la part de Ember: "Erreur non capturée: rien ne gère l’événement 'sendMessage'. C’est ce à quoi nous nous attendons, car nous n’avons pas expliqué à notre application comment réagir à ce genre d’événements..

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

Maintenant, si nous revenons au navigateur, tapez quelque chose dans l'entrée du message et cliquez sur Envoyer, nous devrions voir le message dans la console. Donc, à ce stade, notre composant est faiblement couplé et parle au reste de notre application. Faisons quelque chose de plus intéressant avec ça. D'abord, créons un nouveau Ember.Object travailler comme modèle pour un nouveau message.

 App.Message = Ember.Object.extend (id: 3, prenom: 'Chad', nom: 'Hietala', twitterUserName: 'chadhietala', texte: null, timeStamp: null);

Alors quand le envoyer le message action se produit, nous allons vouloir remplir le texte et TimeStamp champ de notre modèle Message, créez-en une nouvelle instance, puis poussez cette instance dans la collection de messages existante.

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

Lorsque nous revenons au navigateur, nous devrions maintenant pouvoir créer de nouveaux messages..

Nous avons maintenant plusieurs morceaux d’interface utilisateur réutilisables que nous pouvons placer n’importe où. Par exemple, si vous aviez besoin d'utiliser un avatar ailleurs dans votre application Ember, nous pouvons réutiliser le composant user-avatar..

 

Emballer des plugins jQuery

À ce stade, vous vous demandez probablement "Et si je veux utiliser un plugin jQuery dans mon composant?" Aucun problème. Par souci de brièveté, modifions notre composant user-avatar pour afficher une info-bulle lorsque nous survolons l'avatar. J'ai choisi d'utiliser le tooltipster du plugin jQuery pour gérer l'info-bulle. Modifions le code existant pour utiliser tooltipster.

Premièrement, ajoutons les fichiers corrects à notre chat.html et modifier le composant avatar d'utilisateur existant.

 

Et puis notre JavaScript:

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

Une fois encore, nous voyons la syntaxe déclarative du listener d’événement, mais pour la première fois, nous voyons ceci. $. Si vous connaissez jQuery, attendez-vous à ce que nous interrogions tous les éléments avec la classe de 'avatar'. Ce n'est pas le cas dans Ember car le contexte est appliqué. Dans notre cas, nous recherchons uniquement des éléments de la classe "avatar" dans le composant utilisateur-avatar. C'est comparable à la méthode de recherche de jQuery. Lors de la destruction de l'élément, nous devrions dissocier l'événement de survol sur l'avatar et nettoyer toutes les fonctionnalités. Pour ce faire, passez à 'détruire' au pointeur de l'outil. Si nous allons dans le navigateur, actualisons et survolons une image, nous devrions voir le nom d'utilisateur de l'utilisateur.


Conclusion

Dans ce didacticiel, nous avons exploré de près les composants Ember et avons montré comment utiliser des fragments d’UI réutilisables pour générer des composites plus volumineux et intégrer les plugins jQuery. Nous avons examiné la différence entre les composants et les vues dans Ember. Nous avons également abordé l’idée de la programmation par interface en ce qui concerne les composants. J'espère avoir pu mettre en lumière non seulement les composants Ember, mais également les composants Web et l'orientation du Web..