Y el combate se decidió por KO (iii)

Bueno, continuamos aquí nuestra serie explorando las maravillas de Knockout. Todos los posts de esta serie los podéis encontrar en: http://geeks.ms/blogs/etomas/archive/tags/knockout/default.aspx

Serializando viewmodels

En el post anterior, vimos los observables de knockout y como funcionaban. Vimos como crear un formulario, enlazarlo a un viewmodel que usara observables y como mandar el viewmodel serializado en json hacia un servicio REST.

Ciertamente, el tema de la serialización a JSON de nuestro viewmodel era un poco peliagudo. Dado que los observables son funciones debíamos “crear” un objeto adicional a partir de nuestro viewmodel, invocando a los observables de forma manual, ya que usar JSON.stringify sobre nuestro viewmodel no funcionaba (los observables no se serializaban). El código que usábamos era:

   1: var jsonBeer = JSON.stringify({

   2:    Name : this.Name(),

   3:    Ibu : this.Ibu(),

   4:    Abv : this.Abv()

   5: });

Tener que crear otro objeto “con la misma” estructura que nuestro viewmodel e ir llamando manualmente a los observables es posible para viewmodels sencillitos como este, pero para viewmodels más grandes es, como mínimo, un peñazo.

Como ya debes estar pensando, knockout ofrece una solución a ese problema, ya que como comentamos en el post anterior enviar objetos via JSON es bastante común hoy en día. Si vuestro viewmodel contiene observables entonces la mejor manera de serializarlos es usar uno de los métodos siguientes:

  1. ko.toJS: Convierte el viewmodel a un objeto “plano” javascript (es decir hace justo lo que hemos hecho nosotros a mano, es decir invocar los observables). El objeto devuelto puede serializarse a JSON de la forma que se prefiera.
  2. ko.toJSON: Llama a ko.toJS y serializa el objeto usando JSON.stringify.

Así, en lugar del código anterior podemos usar tranquilamente:

   1: var jsonBeer = ko.toJSON(this);

Para la deserialización (es decir, obtención del objeto viewmodel a partir de los datos JSON), Knockout no trae de serie nada. Esto implica que si nuestro viewmodel usa observables debemos manualmente crear nuestro viewmodel y llamar a los observables para inicializar el valor. Eso también puede ser un problema y para lidiar con ello existe un plugin de knockout. Hablaremos de él más adelante en esta serie de posts.

Observables calculados

Recuerda que los viewmodels están fuertemente atados a la vista. De hecho son la abstracción del modelo para una vista en particular y su tarea es “facilitar” al máximo el código de la vista.

Vamos a añadir en el formulario de modificación, el tipo de cerveza (si es una IPA, una stout o una pale ale, p. ej.). A nivel del servicio REST hemos de modificar la clase Beer:

   1: public class Beer

   2: {

   3:     public string Name { get; set; }

   4:     public decimal Abv { get; set; }

   5:     public int Ibu { get; set; }

   6:     public string Style { get; set; }

   7: }

Por el momento esto nos basta. Ahora, modificamos nuestra vista de Edicion (Edit.cshtml) para que tenga un campo adicional más donde entrar el tipo de cerveza:

   1: <label for="Name">Style</label>

   2: <input type="text" name="Name" id="Style" data-bind="value: Style" />

   3: <br />

Bien, fijaos en el uso del data-bind para enlazar este <input /> al valor de la propiedad Style del viewmodel.

Por supuesto debo modificar en la vista cuando creamos el viewmodel, para inicializar el observable Style, a partir de los datos JSON devueltos por el servicio:

   1: $.getJSON(uri, function(data) {

   2:     vm = {

   3:         Name  : ko.observable(data.Name),

   4:         Ibu  : ko.observable(data.Ibu),

   5:         Abv : ko.observable(data.Abv),

   6:         Style : ko.observable(data.Style),

   7:         editBeer : function() {

   8:     // Continua...

Finalmente modifico el método GetById del controlador ApiBeerController para que me informe del valor del campo Style:

   1: public Beer GetById(int id)

   2: {

   3:     return new Beer { Name = "Imperial Stout #" + id, Abv = 10.0M, Ibu = 120, Style="Imperial Stout"};

   4: }

Si ahora ejecuto obtengo lo esperado:

image

Hasta ahí no hemos hecho nada nuevo

Bien, ahora imaginad que queremos presentar en el título el nombre y el tipo de la cerveza, algo como “Heineken (American Lager)”. Una solución es componer esto en la vista. Para ello modifico el <h2>:

   1: <h2 id="title"></h2>

Y luego en el código javascript, cuando he cargado el viewmodel, justo antes (o después) de la llamada a ko.applyBindings coloco el siguiente código javascript:

   1: $("#title").text(vm.Name() + "(" + vm.Style() + ")");

El resultado es el esperado (se muestra el nombre y el tipo de cerveza dentro del <h2>. Pero… no os suena eso a un enlace? La función de un viewmodel es facilitar al máximo la creación de la vista, siendo una representación de lo que la vista muestra. No podría tener el viewmodel una propiedad que devolviese esta cadena? Es una propiedad especial cierto, ya que es de solo lectura y su valor está calculado a partir del valor de otras propiedades.

Esto en knockout se conoce como un observable calculado y se crean usando el método ko.computed. Este método recibe un parámetro que es la función que calcula el valor del observable:

   1: vm.Title = ko.computed(function() {

   2:     return vm.Name() + "(" + vm.Style() + ")";

   3: });

Aquí debo hacer un apunte importante sobre la sintaxis Javascript. Como quizá ya sabéis hay dos maneras en Javascript de inicializar objetos: usando un constructor o bien usando notación literal. Yo he usado en los ejemplos de esta serie he usado la notación literal (mientras que en los ejemplos de la propia web usan más la notación de constructor). Pues bien, los observables calculados son incompatibles con la notación literal. Eso significa que NO puedes declarar observables calculados a la vez que declaras el resto del viewmodel, y los tienes que añadir después. De hecho el código completo de la creación del viewmodel es:

   1: vm = {

   2:     Name  : ko.observable(data.Name),

   3:     Ibu  : ko.observable(data.Ibu),

   4:     Abv : ko.observable(data.Abv),

   5:     Style : ko.observable(data.Style),

   6:     editBeer : function() {

   7:        // Codigo...

   8:     }

   9: };

  10: vm.Title = ko.computed(function() {

  11:     return vm.Name() + "(" + vm.Style() + ")";

  12: });

Fijaos como las propiedades Name, Ibu, Abv, Style y editBeer son definidas en notación literal (propiedad : valor), pero el observable calculado lo he añadido luego.

Aclarado este aspecto “notacional”,  tan solo nos queda enlazar el <h2> con el valor del campo Title de nuestro viewmodel:

   1: <h2 id="title" data-bind="text: Title"></h2>

Y ahora podemos ver como todo funciona correctamente:

image

Pero recordad que los observables son “bidireccionales” no? Basta con modificar el textbox de Name o el de Style para que… ¡se cambie el título!

image

¿No os parece una pasada?

Bueno, lo dejamos aquí por hoy, en futuros posts seguiremos explorando las capacidades de knockout!

Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *