martes, 17 de abril de 2012
La programación web tradicional, basada en el modelo de petición-respuesta de página completa, no requería demasiado código en cliente más allá que el necesario para realizar validaciones y lógica de presentación simple: ¿para qué complicarnos en cliente si podíamos hacerlo todo en servidor?
De hecho, durante la era Webforms, la célebre pareja formada por viewstate y postback llevaban a servidor prácticamente la totalidad de la lógica de presentación mediante el artificio de la programación por eventos, y el código cliente necesario para que todo funcionara era generado de forma automática por ASP.NET en su mayor parte.
Esta lógica de presentación podía estar (y de hecho muchas veces estaba) mezclada con tareas de control e incluso con lógica de negocio, y los desarrolladores empezamos a hacernos conscientes de los monstruos que estábamos creando. Esta preocupación provocó la aparición de soluciones que pretendían estructurar los sistemas de forma razonable, separando las responsabilidades y, en definitiva, poniendo un poco de orden en ese caos.
Implementaciones del patrón MVP, MVVM, o incluso el framework ASP.NET MVC son ejemplos del resultado de esta inquietud. Aunque con matices, todos estos enfoques permiten separar la lógica de negocio, control y presentación en “capas” relativamente independientes.
Sin embargo, la programación web está cambiando, o mejor dicho, lo ha hecho ya ;-). El pesado modelo tradicional petición-respuesta de página completa está siendo relegado en favor de interfaces más ágiles y dinámicos que se basan en el aprovechamiento de la capacidad del cliente para realizar una gran cantidad de tareas a través del uso intensivo de scripting y Ajax.
¿Y a dónde nos lleva este cambio de paradigma? Pues, entre otras cosas, a que el volumen de código script en nuestras páginas ha crecido de forma desmesurada y, debido a ello, se incremente exponencialmente la complejidad en el lado cliente.
Pero como ocurrió en el lado servidor, enseguida han comenzado a aparecer soluciones que nos ayudan a estructurar el código cliente usando patrones ya usados en el otro lado, como MVVM, MVP, o MVC.
En esta serie de posts estudiaremos el uso de Knockout.js, una biblioteca javascript que facilita la creación de interfaces ricas y dinámicas basándose en el patrón MVVM.
De forma muy similar a MVC, esta arquitectura divide los componentes en tres grupos.
El Modelo básicamente contiene datos de forma totalmente independiente de cómo vayan a ser presentados en el UI, y casi siempre sin comportamiento. Por ejemplo, podrían ser clases o entidades planas del dominio de la aplicación.
La Vista contiene los elementos del interfaz necesarios para comunicarse con el usuario, como etiquetas, cuadros de texto, botones, desplegables, etc. Por ir centrándonos en el contexto que nos interesa, la vista es puro lenguaje de marcado (HTML, XAML…), y no contiene lógica de ningún tipo. Tanto los datos que necesita para componer la UI como el comportamiento de los elementos en pantalla los toma del View-Model usando mecanismos como el binding (después entraremos en esto).
El Modelo-Vista, o View-Model, es una abstracción sobre la Vista que contiene tanto los datos como el comportamiento de ésta, pero sin conocer los detalles sobre cómo está implementada. En cierto sentido podríamos considerar que es similar a un controlador en MVC. Es el encargado de transformar y formatear la información del Modelo para que pueda ser representada en la vista y, de la misma forma, se encarga de recibir y gestionar las acciones del usuario y actualizar el Modelo cuando es necesario.
Para los desarrolladores ASP.NET MVC, el concepto podría asimilarse con una clase ViewModel, a la que además se han incorporado métodos para gestionar los eventos desencadenados por el usuario durante la interacción con la aplicación.
Pero sin duda, una de las piezas clave en este patrón es el binding, o enlace, tanto de datos como de acciones entre las capas View y View-Model.
En la práctica, este mecanismo permite indicar sobre la Vista, de forma declarativa, qué propiedades del Modelo-Vista se corresponden con cada uno de los componentes del interfaz de usuario, estableciendo un vínculo uni o bidireccional sobre ambos.
Por ejemplo, podríamos tener un cuadro de texto en la Vista vinculado a una propiedad llamada “Nombre” en el View-Model, de forma que cuando el valor de la propiedad cambiara automáticamente se reflejaría en el valor del cuadro de texto, y cuando el usuario modificara el cuadro de texto el valor de la propiedad también sería actualizado de forma automática. Con esto conseguimos mantener Vista y View-Model perfectamente sincronizados sin ningún tipo de esfuerzo por parte del desarrollador.
Y al igual que es posible enlazar elementos del interfaz con propiedades del View-Model, también podemos enlazar de forma declarativa acciones o eventos del usuario a comandos, o métodos definidos sobre el mismo View-Model. Por tanto, y valga como ejemplo, el tratamiento del clic sobre un botón lo implementaríamos en un método del Modelo-Vista, mientras que en la Vista lo único que haríamos es bindear (enlazar) el evento click del botón a dicho método.
Es bastante compacta (~40Kb sin comprimir), no depende de ninguna otra biblioteca para funcionar y funciona con todos los navegadores razonablemente actualizados. Es open source, se distribuye bajo licencia MIT y está muy bien documentada en su sitio web (incluyendo unos tutoriales interactivos bastante buenos).
Sus características principales, que conforman las ventajas que nos aporta a los desarrolladores, son:
Realmente se trata de una biblioteca pura de scripts que funciona en cliente sin dependencias hacia ninguna tecnología concreta en el lado servidor.
Observad en el código anterior que, aunque logramos una separación razonable entre la visualización (HTML) y la lógica de presentación (script), el código script es bastante farragoso y presenta una fuerte dependencia hacia la forma en que está implementada la vista. Estamos mezclando inicialización de valores, el registro de manejadores de eventos, lógica, accesos al DOM, mapeo entre valores y controles… demasiadas responsabilidades para un único punto.
Pero lo peor de todo es que esta forma de implementarlo no deja claro qué datos maneja la página, ni qué comportamiento se espera de ella, pues ambos aspectos están dispersos a lo largo del script. Esta forma de desarrollar, cuando nos encontremos ante un escenario más complejo, nos llevaría irremediablemente al caos que comentaba al principio.
¿Y cómo implementaríamos estas mismas funcionalidades con Knockout.js? Veamos primero el script que podríamos emplear, que conformaría la capa View-Model:
Bastante más simple y ordenado, ¿verdad? En el código podemos ver claramente los datos que necesita la vista para funcionar (
Pasemos ahora a los detalles.
Las propiedades nombre (
También podemos ver que sobre el propio View-Model se define el método
Por último, la llamada a
Lo interesante de este código es que, aparte de dejar bastante claro qué esperamos de la página, no hay referencia alguna desde el script hacia los elementos de la vista, ni accesos al DOM, simplemente se mantiene actualizado el View-Model. Knockout se encargará de actualizar el UI conforme a los cambios realizados gracias al binding y a los “observables” que hemos comentado anteriormente.
La implementación de la Vista, puro marcado HTML, podría ser la siguiente:
El atributo
Bueno, y con esto creo que podemos dejarlo por hoy :-) Obviamente, Knockout es mucho más que esto, y así lo iremos viendo en futuros posts, en los que iremos desmenuzando poco a poco este interesante framework y viendo cómo podemos utilizarlo para mejorar el código de nuestra capa de presentación.
Ah, he colgado en Skydrive el ejemplo desarrollado en este post. Es un zip de 15kb, con un único archivo .html y otro .js (knockout) con el que podéis jugar para entender mejor su funcionamiento.
Publicado en Variable not found.
De hecho, durante la era Webforms, la célebre pareja formada por viewstate y postback llevaban a servidor prácticamente la totalidad de la lógica de presentación mediante el artificio de la programación por eventos, y el código cliente necesario para que todo funcionara era generado de forma automática por ASP.NET en su mayor parte.
Esta lógica de presentación podía estar (y de hecho muchas veces estaba) mezclada con tareas de control e incluso con lógica de negocio, y los desarrolladores empezamos a hacernos conscientes de los monstruos que estábamos creando. Esta preocupación provocó la aparición de soluciones que pretendían estructurar los sistemas de forma razonable, separando las responsabilidades y, en definitiva, poniendo un poco de orden en ese caos.
Implementaciones del patrón MVP, MVVM, o incluso el framework ASP.NET MVC son ejemplos del resultado de esta inquietud. Aunque con matices, todos estos enfoques permiten separar la lógica de negocio, control y presentación en “capas” relativamente independientes.
Sin embargo, la programación web está cambiando, o mejor dicho, lo ha hecho ya ;-). El pesado modelo tradicional petición-respuesta de página completa está siendo relegado en favor de interfaces más ágiles y dinámicos que se basan en el aprovechamiento de la capacidad del cliente para realizar una gran cantidad de tareas a través del uso intensivo de scripting y Ajax.
¿Y a dónde nos lleva este cambio de paradigma? Pues, entre otras cosas, a que el volumen de código script en nuestras páginas ha crecido de forma desmesurada y, debido a ello, se incremente exponencialmente la complejidad en el lado cliente.
Pero como ocurrió en el lado servidor, enseguida han comenzado a aparecer soluciones que nos ayudan a estructurar el código cliente usando patrones ya usados en el otro lado, como MVVM, MVP, o MVC.
En esta serie de posts estudiaremos el uso de Knockout.js, una biblioteca javascript que facilita la creación de interfaces ricas y dinámicas basándose en el patrón MVVM.
¿El patrón MVVM?
MVVM, Model-View-ViewModel, es un patrón de diseño de la capa presentación definido en 2005 por John Gossman, de Microsoft, adaptando el Presentation Model de Fowler a las particularidades de las tecnologías en las que estaba trabajando (WPF).De forma muy similar a MVC, esta arquitectura divide los componentes en tres grupos.
El Modelo básicamente contiene datos de forma totalmente independiente de cómo vayan a ser presentados en el UI, y casi siempre sin comportamiento. Por ejemplo, podrían ser clases o entidades planas del dominio de la aplicación.
La Vista contiene los elementos del interfaz necesarios para comunicarse con el usuario, como etiquetas, cuadros de texto, botones, desplegables, etc. Por ir centrándonos en el contexto que nos interesa, la vista es puro lenguaje de marcado (HTML, XAML…), y no contiene lógica de ningún tipo. Tanto los datos que necesita para componer la UI como el comportamiento de los elementos en pantalla los toma del View-Model usando mecanismos como el binding (después entraremos en esto).
El Modelo-Vista, o View-Model, es una abstracción sobre la Vista que contiene tanto los datos como el comportamiento de ésta, pero sin conocer los detalles sobre cómo está implementada. En cierto sentido podríamos considerar que es similar a un controlador en MVC. Es el encargado de transformar y formatear la información del Modelo para que pueda ser representada en la vista y, de la misma forma, se encarga de recibir y gestionar las acciones del usuario y actualizar el Modelo cuando es necesario.
Para los desarrolladores ASP.NET MVC, el concepto podría asimilarse con una clase ViewModel, a la que además se han incorporado métodos para gestionar los eventos desencadenados por el usuario durante la interacción con la aplicación.
Pero sin duda, una de las piezas clave en este patrón es el binding, o enlace, tanto de datos como de acciones entre las capas View y View-Model.
En la práctica, este mecanismo permite indicar sobre la Vista, de forma declarativa, qué propiedades del Modelo-Vista se corresponden con cada uno de los componentes del interfaz de usuario, estableciendo un vínculo uni o bidireccional sobre ambos.
Por ejemplo, podríamos tener un cuadro de texto en la Vista vinculado a una propiedad llamada “Nombre” en el View-Model, de forma que cuando el valor de la propiedad cambiara automáticamente se reflejaría en el valor del cuadro de texto, y cuando el usuario modificara el cuadro de texto el valor de la propiedad también sería actualizado de forma automática. Con esto conseguimos mantener Vista y View-Model perfectamente sincronizados sin ningún tipo de esfuerzo por parte del desarrollador.
Y al igual que es posible enlazar elementos del interfaz con propiedades del View-Model, también podemos enlazar de forma declarativa acciones o eventos del usuario a comandos, o métodos definidos sobre el mismo View-Model. Por tanto, y valga como ejemplo, el tratamiento del clic sobre un botón lo implementaríamos en un método del Modelo-Vista, mientras que en la Vista lo único que haríamos es bindear (enlazar) el evento click del botón a dicho método.
¿Qué es Knockout?
Knockout.js es una biblioteca de scripts desarrollada por Steve Sanderson que implementa el patrón MVVM con el objetivo de facilitarnos la creación de interfaces de usuario de edición y visualización de datos, usando una estructura simple, limpia y mantenible en nuestro código.Es bastante compacta (~40Kb sin comprimir), no depende de ninguna otra biblioteca para funcionar y funciona con todos los navegadores razonablemente actualizados. Es open source, se distribuye bajo licencia MIT y está muy bien documentada en su sitio web (incluyendo unos tutoriales interactivos bastante buenos).
Sus características principales, que conforman las ventajas que nos aporta a los desarrolladores, son:
- Sistema de bindings declarativos, expresados sobre los propios tags HTML usando atributos data-bind, que permiten crear un vínculo uni o bidireccional entre elementos del interfaz de usuario y propiedades o acciones del View-Model, que se actualizarán de forma automática ante cambios.
- Seguimiento de dependencias, capaz de detectar los cambios realizados tanto en la Vista como en el Modelo-Vista y propagarlos hacia todos los objetos o elementos dependientes.
- Por supuesto, el interfaz de usuario se actualiza automáticamente para reflejar los cambios en el View-Model.
- Incluye un sistema de plantillas que facilita la creación porciones de vistas reutilizables.
¿En qué tipo de proyectos puedo utilizarlo?
En todos: PHP, Python, Perl, JSP, Ruby, ASP.NET MVC, Webforms, ASP clásico, HTML y en los que se os puedan ocurrir.Realmente se trata de una biblioteca pura de scripts que funciona en cliente sin dependencias hacia ninguna tecnología concreta en el lado servidor.
¡Un poco de código, por favor!
Veamos un ejemplo rápido que demuestre una pequeña parte de la potencia de Knockout. Nuestro objetivo es implementar una funcionalidad sencilla como la siguiente:- presentaremos un simple cuadro de texto en el que el usuario podrá introducir su nombre, junto con un botón.
- bajo ellos, un texto en el que se podrá ver el nombre del usuario y el número de veces que ha sido pulsado el botón; las porciones “dinámicas” de este texto (nombre y número de clics) se actualizarán desde script conforme el usuario actúe sobre el interfaz. Es decir, si el usuario modifica el nombre o pulsa el botón, se actualizará el mensaje para representar el nuevo estado.
- además, para complicar un poco más el escenario, como máximo se podrá hacer clic cinco veces sobre el botón. Al llegar a ese punto, el botón deberá deshabilitarse.
HTML | SCRIPT |
<div> <input type="text" id="name" /> <button id="button">Click me!</button> <p> <span id="nameLabel"></span>, you've clicked <span id="clicksLabel"></span> times </p> </div> | $(function () {
var numClicks = 0;
$("#name").val("John");
$("#clicksLabel").text(numClicks);
$("#nameLabel").text($("#name").val());
$("#name")
.change(function () {
$("#nameLabel").text($("#name").val());
});
$("#button").click(function () {
numClicks++;
if (numClicks >= 5) {
$("#button").attr("disabled", true);
}
$("#clicksLabel").text(numClicks);
});
});
|
Observad en el código anterior que, aunque logramos una separación razonable entre la visualización (HTML) y la lógica de presentación (script), el código script es bastante farragoso y presenta una fuerte dependencia hacia la forma en que está implementada la vista. Estamos mezclando inicialización de valores, el registro de manejadores de eventos, lógica, accesos al DOM, mapeo entre valores y controles… demasiadas responsabilidades para un único punto.
Pero lo peor de todo es que esta forma de implementarlo no deja claro qué datos maneja la página, ni qué comportamiento se espera de ella, pues ambos aspectos están dispersos a lo largo del script. Esta forma de desarrollar, cuando nos encontremos ante un escenario más complejo, nos llevaría irremediablemente al caos que comentaba al principio.
¿Y cómo implementaríamos estas mismas funcionalidades con Knockout.js? Veamos primero el script que podríamos emplear, que conformaría la capa View-Model:
var viewModel = {
numClicks: ko.observable(0),
name: ko.observable("Peter"),
canClick: function () {
return this.numClicks() < 5;
},
addClick: function () {
if (this.canClick()) {
var clicks = this.numClicks();
this.numClicks(clicks + 1);
}
}
};
ko.applyBindings(viewModel);
Bastante más simple y ordenado, ¿verdad? En el código podemos ver claramente los datos que necesita la vista para funcionar (
numClicks
y name
), y el comportamiento esperado en ella (los métodos canClick()
y addClick()
); ojo, y no existe ninguna referencia hacia la implementación de la vista ni accesos al DOM. Pasemos ahora a los detalles.
ko
es el objeto básico de Knockout; o en otras palabras, ko es a Knockout lo que $ a jQuery. name
) y número de clicks (numClicks
) las estamos inicializando a sus valores por defecto, “Peter” y “0” respectivamente. Sin embargo, como podéis observar, usamos para ello el método ko.Observable()
. Esto quiere decir básicamente que estas propiedades serán “vigiladas”, de forma que cuando cambie su valor Knockout deberá analizar qué elementos dependen de ellas y actualizarlos en consecuencia (volveremos a esto cuando veamos el código de la Vista).También podemos ver que sobre el propio View-Model se define el método
canClick()
, que determina si debemos permitir al usuario hacer más clicks sobre el botón, y addClick()
, que será la lógica a ejecutar cuando el usuario lo pulse: simplemente incrementamos el número de clicks, aunque dado que es un objeto “observable” tenemos que hacerlo usándolo como una función tanto a la hora de consultar su valor como de asignárselo.Por último, la llamada a
ko.applyBindings()
lo que hace es poner en marcha Knockout con el objeto View-Model que hemos definido.Lo interesante de este código es que, aparte de dejar bastante claro qué esperamos de la página, no hay referencia alguna desde el script hacia los elementos de la vista, ni accesos al DOM, simplemente se mantiene actualizado el View-Model. Knockout se encargará de actualizar el UI conforme a los cambios realizados gracias al binding y a los “observables” que hemos comentado anteriormente.
La implementación de la Vista, puro marcado HTML, podría ser la siguiente:
<div> <input data-bind="value: name" type="text" /> <button data-bind="click: addClick, enable: canClick()" type="button">Click me!</button> <p> <span data-bind="text: name"></span>, you've clicked <span data-bind="text: numClicks"></span> times </p> </div>
El atributo
data-bind
es el utilizado por Knockout para definir los enlaces, o bindings, desde los elementos del interfaz hacia las propiedades o métodos del View-Model.- en el primer caso, el cuadro de edición, estamos enlazando su valor al de la propiedad
name
del View-Model. Cuando cambie ésta, el cuadro de texto de texto cambiará y viceversa: Vista y View-Model estarán siempre sincronizados de forma automática. - en el segundo caso, el botón, enlazamos el evento
click
del ratón con el métodoaddClick
del View-Model, y su atributoenable
concanClick()
para deshabilitar el botón cuando hayamos llegado al máximo de veces permitidas. - el tercer y cuatro enlace se realiza entre los
<span>
y las propiedades del View-Model que queremos mostrar. De nuevo la magia de los “observables” hará que cuando éstas cambien el texto interior de estas etiquetas cambien de forma automática para mostrar su valor.
Bueno, y con esto creo que podemos dejarlo por hoy :-) Obviamente, Knockout es mucho más que esto, y así lo iremos viendo en futuros posts, en los que iremos desmenuzando poco a poco este interesante framework y viendo cómo podemos utilizarlo para mejorar el código de nuestra capa de presentación.
Ah, he colgado en Skydrive el ejemplo desarrollado en este post. Es un zip de 15kb, con un único archivo .html y otro .js (knockout) con el que podéis jugar para entender mejor su funcionamiento.
Publicado en Variable not found.
Publicado por José M. Aguilar a las 9:45 a. m.
Etiquetas: desarrollo, javascript, knockout, patrones
10 Comentarios:
Excelente José Maria,
una introducción muy clara y concisa a partir de la cual "apetece" trastear con Knocout.
Congrats!
Muchas gracias, Josep!
Muy interesante profe, habrá que seguir futuros posts.
Saludos!!!
Muchas gracias, Carlos :-)
Gracias. Llevaba tiempo oyendo hablar de Knockout.js y no pasaba de interesarme más allá de saber que es una librería para JavaScript que sigue el patrón MVVM. Al final estás tan saturado de información (nuevas tendencias en el desarrollo, nuevas APIs, etc) que no llegas a profundizar. Gracias a este artículo tan cercano y bien planteado me has convencido y habrá que valorar muy seriamente utilizar Knockout.
Interesante y verdaderamente útil.
Llevamos un tiempo trabajando con el y os recomiendo probarlo a todos.
El mapping plugin es una delicia ;) y aunque no estén muy bien documentadas el espacio ko.utils es muy interesante también.
Saludos!
@jacob La verdad es que lo menos que podemos hacer es probarlo y ya, con conocimiento de causa, ver si nos interesa usarlo o no. Así que: ¡adelante! :-)
@fer, gracias por la recomendación y las interesantes aportaciones. :-)
Y muchas gracias a ambos por comentar.
No se puede descargar
Muy buena guía. Es un placer encontrar a gente con conocimientos que sabe concentrarlos en una explicación sencilla y clara. Es mucho más frecuente ver a gente que hace guías que solo entiende la gente que no las necesita leer..
Un saludo y gracias.
Muy bien explicado.
Perfecto para principiantes como yo.!
Enviar un nuevo comentario