Una guía para crear su primera aplicación Ember.js
Publicado: 2022-03-11A medida que las aplicaciones web modernas hacen cada vez más del lado del cliente (el hecho mismo de que ahora nos referimos a ellas como "aplicaciones web" en lugar de "sitios web" es bastante revelador), ha aumentado el interés en los marcos del lado del cliente. . Hay muchos jugadores en este campo, pero para aplicaciones con mucha funcionalidad y muchas partes móviles, dos de ellos se destacan en particular: Angular.js y Ember.js.
Ya publicamos un completo [tutorial de Angular.js][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], así que En esta publicación, nos centraremos en Ember.js, en el que crearemos una aplicación Ember simple para catalogar su colección de música. Se le presentarán los principales bloques de construcción del marco y obtendrá una idea de sus principios de diseño. Si desea ver el código fuente mientras lee, está disponible como rock-and-roll en Github.
¿Qué vamos a construir?
Así es como se verá nuestra aplicación Rock & Roll en su versión final:
A la izquierda, verás que tenemos una lista de artistas y, a la derecha, una lista de canciones del artista seleccionado (también puedes ver que tengo buen gusto musical, pero me estoy desviando). Se pueden agregar nuevos artistas y canciones simplemente escribiendo en el cuadro de texto y presionando el botón adyacente. Las estrellas al lado de cada canción sirven para puntuarla, al estilo iTunes.
Podríamos dividir la funcionalidad rudimentaria de la aplicación en los siguientes pasos:
- Al hacer clic en 'Agregar', se agrega un nuevo artista a la lista, con un nombre especificado en el campo 'Nuevo artista' (lo mismo ocurre con las canciones de un artista determinado).
- Vaciar el campo 'Nuevo artista' desactiva el botón 'Agregar' (lo mismo ocurre con las canciones de un artista determinado).
- Al hacer clic en el nombre de un artista, aparecen sus canciones a la derecha.
- Al hacer clic en las estrellas, se califica una canción determinada.
Tenemos un largo camino por recorrer para que esto funcione, así que comencemos.
Rutas: la clave de la app Ember.js
Una de las características distintivas de Ember es el gran énfasis que pone en las URL. En muchos otros marcos, falta tener URL separadas para pantallas separadas o se agrega como una ocurrencia tardía. En Ember, el enrutador, el componente que administra las URL y las transiciones entre ellas, es la pieza central que coordina el trabajo entre los bloques de construcción. En consecuencia, también es la clave para comprender el funcionamiento interno de las aplicaciones de Ember.
Aquí están las rutas para nuestra aplicación:
App.Router.map(function() { this.resource('artists', function() { this.route('songs', { path: ':slug' }); }); });
Definimos una ruta de recursos, artists
y una ruta de songs
anidadas dentro de ella. Esa definición nos dará las siguientes rutas:
Utilicé el excelente complemento Ember Inspector (existe tanto para Chrome como para Firefox) para mostrarle las rutas generadas de una manera fácil de leer. Estas son las reglas básicas para las rutas de Ember, que puedes verificar para nuestro caso particular con la ayuda de la tabla anterior:
Hay una ruta de
application
implícita.Esto se activa para todas las solicitudes (transiciones).
Hay una ruta
index
implícita.Esto se ingresa cuando el usuario navega a la raíz de la aplicación.
Cada ruta de recursos crea una ruta con el mismo nombre e implícitamente crea una ruta de índice debajo de ella.
Esta ruta de índice se activa cuando el usuario navega a la ruta. En nuestro caso,
artists.index
se activa cuando el usuario navega a/artists
.Una ruta anidada simple (sin recursos) tendrá su nombre de ruta principal como prefijo.
La ruta que definimos como
this.route('songs', ...)
tendráartists.songs
como su nombre. Se activa cuando el usuario navega a/artists/pearl-jam
o/artists/radiohead
.Si no se proporciona la ruta, se supone que es igual al nombre de la ruta.
Si la ruta contiene un
:
se considera un segmento dinámico .El nombre que se le asigne (en nuestro caso,
slug
) coincidirá con el valor en el segmento apropiado de la URL. El segmento deslug
anterior tendrá el valorpearl-jam
,radiohead
o cualquier otro valor extraído de la URL.
Mostrar la lista de artistas
Como primer paso, construiremos la pantalla que muestra la lista de artistas a la izquierda. Esta pantalla debe mostrarse a los usuarios cuando navegan a /artists/
:
Para comprender cómo se representa esa pantalla, es hora de presentar otro principio general de diseño de Ember: la convención sobre la configuración . En la sección anterior, vimos que /artists
activa la ruta de los artists
. Por convención, el nombre de ese objeto de ruta es ArtistsRoute
. Es responsabilidad de este objeto de ruta obtener datos para que la aplicación los represente. Eso sucede en el gancho del modelo de la ruta:
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; } });
En este fragmento, los datos se obtienen a través de una llamada XHR desde el back-end y, después de la conversión a un objeto modelo, se envían a una matriz que luego podemos mostrar. Sin embargo, las responsabilidades de la ruta no se extienden a proporcionar lógica de visualización, que es manejada por el controlador. Vamos a ver.
Hmmm, de hecho, ¡no necesitamos definir el controlador en este punto! Ember es lo suficientemente inteligente como para generar el controlador cuando sea necesario y establecer el atributo M.odel
del controlador en el valor de retorno del enlace del modelo en sí, es decir, la lista de artistas. (Nuevamente, esto es el resultado del paradigma de 'convención sobre configuración'). Podemos bajar una capa y crear una plantilla para mostrar la 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>
Si esto le parece familiar, es porque Ember.js usa plantillas de Handlebars, que tienen una sintaxis y ayudantes muy simples, pero no permiten una lógica no trivial (por ejemplo, términos OR o AND en un condicional).
En la plantilla anterior, iteramos a través del modelo (configurado anteriormente por la ruta a una matriz que contiene todos los artistas) y para cada elemento en él, generamos un enlace que nos lleva a la artists.songs
artistas.canciones para ese artista. El enlace contiene el nombre del artista. El ayudante #each
en Handlebars cambia el alcance dentro de él al elemento actual, por lo que {{name}}
siempre se referirá al nombre del artista que se encuentra actualmente en iteración.
Rutas anidadas para vistas anidadas
Otro punto de interés en el fragmento anterior: {{outlet}}
, que especifica los espacios en la plantilla donde se puede representar el contenido. Al anidar rutas, la plantilla para la ruta de recursos externa se representa primero, seguida de la ruta interna, que representa el contenido de su plantilla en el {{outlet}}
definido por la ruta externa. Esto es exactamente lo que sucede aquí.
Por convención, todas las rutas representan su contenido en la plantilla del mismo nombre. Arriba, el atributo data-template-name
de la plantilla anterior es artists
, lo que significa que se representará para la ruta externa, artists
. Especifica una salida para el contenido del panel derecho, en el que la ruta interna, artists.index
, presenta su contenido:
<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>
En resumen, una ruta ( artists
) presenta su contenido en la barra lateral izquierda, siendo su modelo la lista de artistas. Otra ruta, artists.index
, presenta su propio contenido en la ranura proporcionada por la plantilla de artists
. Podría obtener algunos datos para servir como su modelo, pero en este caso todo lo que queremos mostrar es texto estático, por lo que no es necesario.
crear un artista
Parte 1: enlace de datos
A continuación, queremos poder crear artistas, no solo mirar una lista aburrida.
Cuando mostré la plantilla de artists
que muestra la lista de artistas, hice un poco de trampa. Recorté la parte superior para centrarme en lo importante. Ahora, agregaré eso de nuevo:
<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>
Usamos un asistente de Ember, input
, con type text para representar una entrada de texto simple. En él, vinculamos el valor de la entrada de texto a la propiedad newName
del controlador que respalda esta plantilla, ArtistsController
. En consecuencia, cuando la propiedad de valor de la entrada cambia (en otras palabras, cuando el usuario escribe texto en ella), la propiedad newName
en el controlador se mantendrá sincronizada.
También informamos que la acción createArtist
debe activarse cuando se hace clic en el botón. Finalmente, vinculamos la propiedad deshabilitada del botón a la propiedad disabled
del controlador. Entonces, ¿cómo se ve el controlador?
App.ArtistsController = Ember.ArrayController.extend({ newName: '', disabled: function() { return Ember.isEmpty(this.get('newName')); }.property('newName') });
newName
se establece en vacío al principio, lo que significa que la entrada de texto estará en blanco. (¿Recuerda lo que dije sobre los enlaces? Intente cambiar newName
y vea cómo se refleja como texto en el campo de entrada).

disabled
se implementa de tal manera que cuando no hay texto en el cuadro de entrada, devolverá true
y, por lo tanto, el botón se desactivará. La llamada .property
al final hace que esta sea una "propiedad calculada", otra porción deliciosa del pastel Ember.
Las propiedades calculadas son propiedades que dependen de otras propiedades, que pueden ser "normales" o calculadas. Ember almacena en caché el valor de estos hasta que cambia una de las propiedades dependientes. Luego vuelve a calcular el valor de la propiedad calculada y lo vuelve a almacenar en caché.
Aquí hay una representación visual del proceso anterior. Para resumir: cuando el usuario ingresa el nombre de un artista, la propiedad newName
se actualiza, seguida de la propiedad disabled
y, finalmente, el nombre del artista se agrega a la lista.
Desvío: una única fuente de verdad
Piense en eso por un momento. Con la ayuda de enlaces y propiedades calculadas, podemos establecer datos (modelo) como la única fuente de verdad . Arriba, un cambio en el nombre del nuevo artista desencadena un cambio en la propiedad del controlador, que a su vez desencadena un cambio en la propiedad deshabilitada. A medida que el usuario comienza a escribir el nombre del nuevo artista, el botón se habilita, como por arte de magia.
Cuanto más grande es el sistema, más influencia obtenemos del principio de 'fuente única de la verdad'. Mantiene nuestro código limpio y robusto, y nuestras definiciones de propiedades, más declarativas.
Algunos otros marcos también ponen énfasis en que los datos del modelo sean la única fuente de verdad, pero no van tan lejos como Ember o no logran hacer un trabajo tan completo. Angular, por ejemplo, tiene enlaces bidireccionales, pero no tiene propiedades calculadas. Puede "emular" propiedades calculadas a través de funciones simples; el problema aquí es que no tiene forma de saber cuándo actualizar una "propiedad calculada" y, por lo tanto, recurre a la verificación sucia y, a su vez, conduce a una pérdida de rendimiento, especialmente notable en aplicaciones más grandes.
Si desea obtener más información sobre el tema, le recomiendo que lea la publicación de blog de eviltrout para obtener una versión más corta o esta pregunta de Quora para una discusión más larga en la que intervienen los principales desarrolladores de ambos lados.
Parte 2: Controladores de acciones
Volvamos a ver cómo se crea la acción createArtist
después de que se activa (después de presionar el botón):
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'); } }); } } });
Los controladores de acciones deben incluirse en un objeto de actions
y se pueden definir en la ruta, el controlador o la vista. Elegí definirlo en la ruta aquí porque el resultado de la acción no se limita al controlador sino que es "global".
No hay nada lujoso pasando aquí. Después de que el back-end nos informara que la operación de guardado se completó con éxito, hacemos tres cosas, en orden:
- Agregue el nuevo artista al modelo de la plantilla (todos los artistas) para que se vuelva a renderizar y el nuevo artista aparezca como el último elemento de la lista.
- Borre el campo de entrada a través del enlace
newName
, lo que nos evita tener que manipular el DOM directamente. - Transición a una nueva ruta (
artists.songs
), pasando al artista recién creado como modelo para esa ruta.transitionTo
es la forma de moverse entre rutas internamente. (El asistentelink-to
sirve para hacer eso a través de la acción del usuario).
Mostrar canciones para un artista
Podemos mostrar las canciones de un artista haciendo clic en el nombre del artista. También pasamos en el artista que se va a convertir en el modelo de la nueva ruta. Si el objeto del modelo se pasa así, no se llamará al enlace del model
de la ruta, ya que no es necesario resolver el modelo.
La ruta activa aquí es artists.songs
y, por lo tanto, el controlador y la plantilla serán ArtistsSongsController
y artists/songs
, respectivamente. Ya vimos cómo la plantilla se procesa en la salida proporcionada por la plantilla de los artists
para que podamos centrarnos solo en la plantilla en cuestió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>
Tenga en cuenta que eliminé el código para crear una nueva canción, ya que sería exactamente lo mismo que para crear un nuevo artista.
La propiedad de songs
se configura en todos los objetos de artista a partir de los datos devueltos por el servidor. El mecanismo exacto por el cual se hace tiene poco interés para la discusión actual. Por ahora, es suficiente para nosotros saber que cada canción tiene un título y una calificación.
El título se muestra directamente en la plantilla y la calificación se representa mediante estrellas, a través de la vista StarRating
. Veamos eso ahora.
Widget de calificación de estrellas
La calificación de una canción está entre 1 y 5 y se muestra al usuario a través de una vista, App.StarRating
. Las vistas tienen acceso a su contexto (en este caso, la canción) y su controlador. Esto significa que pueden leer y modificar sus propiedades. Esto contrasta con otro bloque de construcción de Ember, los componentes, que son controles aislados y reutilizables con acceso solo a lo que se les ha pasado. (También podríamos usar un componente de calificación de estrellas en este ejemplo).
Veamos cómo la vista muestra la cantidad de estrellas y establece la calificación de la canción cuando el usuario hace clic en una de las estrellas:
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
y numStars
son propiedades calculadas que discutimos previamente con la propiedad disabled
de ArtistsController
. Arriba, utilicé la llamada macro de propiedad calculada, una docena de las cuales están definidas en Ember. Hacen que las propiedades calculadas típicas sean más sucintas y menos propensas a errores (para escribir). Configuré rating
para que sea la calificación del contexto (y, por lo tanto, de la canción), mientras que definí las propiedades fullStars
y numStars
para que se leyeran mejor en el contexto del widget de calificación de estrellas.
El método de las stars
es el principal atractivo. Devuelve una matriz de datos para las estrellas en la que cada elemento contiene una propiedad de rating
(de 1 a 5) y una bandera ( full
) para indicar si la estrella está llena. Esto hace que sea extremadamente simple recorrerlos en la plantilla:
<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>
Este fragmento contiene varios puntos de nota:
- Primero,
each
ayudante designa que usa una propiedad de vista (a diferencia de una propiedad en el controlador) al anteponer el nombre de la propiedad conview
. - En segundo lugar, el atributo de
class
de la etiqueta span tiene asignadas clases estáticas y dinámicas mixtas. Todo lo que tenga el prefijo:
se convierte en una clase estática, mientras que lafull:glyphicon-star:glyphicon-star-empty
es como un operador ternario en JavaScript: si la propiedad full es verdadera, se debe asignar la primera clase; si no, el segundo. - Finalmente, cuando se hace clic en la etiqueta, se debe activar la acción
setRating
, pero Ember la buscará en la vista, no en la ruta o el controlador, como en el caso de crear un nuevo artista.
La acción se define así en la vista:
App.StarRating = Ember.View.extend({ (...) actions: { setRating: function() { var newRating = $(event.target).data('rating'); this.set('rating', newRating); } } });
Obtenemos la calificación del atributo de datos de rating
que asignamos en la plantilla y luego la configuramos como la rating
de la canción. Tenga en cuenta que la nueva calificación no se mantiene en el back-end. No sería difícil implementar esta funcionalidad basándonos en cómo creamos un artista y se deja como ejercicio para el lector motivado.
Envolviéndolo todo
Hemos probado varios ingredientes de la mencionada tarta Ember:
- Hemos visto cómo las rutas son el quid de las aplicaciones de Ember y cómo sirven como base para las convenciones de nomenclatura.
- Hemos visto cómo los enlaces de datos bidireccionales y las propiedades calculadas hacen que los datos de nuestro modelo sean la única fuente de verdad y nos permiten evitar la manipulación directa del DOM.
- Y hemos visto cómo disparar y manejar acciones de varias maneras y construir una vista personalizada para crear un control que no sea parte de nuestro HTML.
Hermoso, ¿no?
Más lecturas (y observación)
Hay mucho más en Ember de lo que podría incluir solo en esta publicación. Si desea ver una serie de screencasts sobre cómo construí una versión algo más desarrollada de la aplicación anterior u obtener más información sobre Ember, puede suscribirse a mi lista de correo para recibir artículos o consejos semanalmente.
Espero haber abierto su apetito para aprender más sobre Ember.js y que vaya mucho más allá de la aplicación de muestra que he usado en esta publicación. A medida que continúe aprendiendo sobre Ember.js, asegúrese de consultar nuestro artículo sobre Ember Data para aprender a usar la biblioteca de ember-data. ¡Diviértete construyendo!