Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 13 de noviembre de 2012
KnockoutHa pasado ya tiempo desde el último (y primer ;-)) post que escribí sobre Knockout, en el que hicimos una pequeña introducción al patrón Model-View-ViewModel y a Knockout.js, e incluso llegamos a implementar un pequeño ejemplo de código para ver cómo funcionaba el invento.

También presentamos en aquél momento dos conceptos que resultaban básicos para Knockout.js:
  • El binding, que es el mecanismo que permite mapear datos y acciones de la vista a propiedades y métodos del View-Model.
  • Las propiedades observables, que son aquellas a las que el framework realiza seguimiento de cambios con objeto de actualizar, cuando sus valores cambien, los elementos que dependan de ellas.
Estos dos conceptos están muy relacionados, y es conveniente tener claro de qué forma.

Como ya sabemos, podemos enlazar un elemento de la vista, en la práctica una etiqueta HTML, con una propiedad del View-Model especificándolo en el atributo data-bind de la misma. Por ejemplo, el siguiente código enlaza el valor de un <input> y el texto interior de un <span> la propiedad name de nuestro modelo-vista:
    <div>
        <input data-bind="value: name" type="text" name="name" />  
        <p>Hi, <span data-bind="text: name"></span></p>
    </div>
    <script type="text/javascript">
        var viewModel = {
            name: "John Smith"
        };
        ko.applyBindings(viewModel);
    </script>
Conceptualmente, en la vista lo que hemos hecho es enlazar el cuadro de texto a la propiedad name del modelo-vista. En principio, cuando éste sea editado y cambie, el valor de la propiedad debería ser automáticamente actualizado al introducido por el usuario, de forma que ambos elementos estarían siempre sincronizados. Y de la misma forma, el binding en el tag <span> indica que cuando la propiedad name cambie, el contenido de la etiqueta deberá también actualizarse automáticamente.

Saludemos a John. ¡Hola, John!Por tanto, lo que podemos de esperar de este código, en primer lugar, es que al aplicar el binding (en la llamada a ko.applyBindings()) ambos elementos quedarán poblados con el valor que encontramos en la propiedad name del objeto viewModel. Y así ocurre, tal y como se muestra en la captura de pantalla adjunta.

Sin embargo, si modificamos el valor del cuadro de texto, la etiqueta vemos que no se actualizará de forma automática. ¿Y esto a qué se debe? Pues básicamente a que Knockout no tiene ninguna manera de detectar que el valor de la propiedad ha cambiado.

Propiedades observables

Una propiedad observable es aquella capaz de notificar a Knockout que su valor ha sido modificado con objeto de que éste pueda propagar el cambio hacia elementos visuales u otros elementos que dependan de ellas. Simplemente eso.

En este post vamos a centrarnos exclusivamente en propiedades simples, es decir, aquellas que no contienen colecciones de elementos, a las que dedicaremos un artículo más adelante.
Las propiedades observables se definen sobre el view-model utilizando el método ko.observable() suministrado por Knockout, en cuyo primer parámetro especificaremos el valor inicial para las mismas. La forma correcta de implementar el ejemplo anterior habría sido, por tanto:
        var viewModel = {
            name: ko.observable("John Smith")
        };
El método ko.observable() retorna una función, lo que realmente introducimos en la propiedad, que permite tanto obtener como establecer sus valores, a modo de getter y setter. Si la invocamos sin parámetros retornará el valor actual de la propiedad, mientras que si le suministramos un parámetro estaremos estableciendo su valor.

John Smith en mayúsculasEs importante tener esto en cuenta, pues cambia la forma en que la utilizaremos las propiedades. En el siguiente ejemplo hemos insertando un botón para convertir en mayúsculas el nombre introducido por el usuario, en cuyo clic hacemos la conversión:
    <div>
        <input data-bind="value: name" type="text" />
        <input type="button" data-bind="click: toUpper" value="Upper!" />
        <p>
            Hi, <span data-bind="text: name"></span>
        </p>
    </div>
    <script type="text/javascript">
        var ViewModel = function () {
            var self = this;
            self.name = ko.observable("John Smith");
            self.toUpper = function() {
                var currentName = self.name(); // Get value                
                var upperName = currentName.toUpperCase();
                self.name(upperName);         // Set value   
            };
        };
        ko.applyBindings(new ViewModel());    
    </script>
Observad que obtenemos el valor actual de name invocándolo como un método sin parámetros, y no usándola como una propiedad, y que para establecer el valor invocamos otra vez a name() suministrándole su nuevo contenido. Básicamente, estamos usando la propiedad como un método getter/setter.

Fijaos también que hemos aprovechado la ocasión para cambiar ligeramente la forma de crear el objeto View-Model. De hecho, esta es una fórmula recomendada si no queremos complicarnos mucho con el “this” de javascript, que para los que estamos acostumbrados a lenguajes estáticos como C# es un poco lioso. Por ello, lo que hacemos en el constructor para evitar problemas es obtener una referencia a la instancia actual y guardarla en la variable interna self, creando un closure que podemos utilizar después para acceder a los miembros.

Y eso es todo lo que necesitamos para obtener la actualización bidireccional entre la vista y el modelo-vista. Una vez definidas las propiedades del view-model como observables ya podemos enlazarlas desde la vista con la tranquilidad de que los elementos “bindeados” serán actualizados conforme sus valores cambien, y viceversa.

Por último, aunque normalmente no vamos a necesitar hacerlo puesto que el binding es suficiente en la mayoría de los casos, podemos suscribirnos a los cambios realizados a un observable de forma muy sencilla:
        self.name.subscribe(function (newVal) {
            // Do something really interesting
        });
En este caso, también es posible eliminar la suscripción, tal y como se describe en la documentación de Knockout.

Propiedades calculadas

Knockout permite la creación de propiedades calculadas observables. Estas propiedades normalmente realizan cálculos o transformaciones sobre otros observables, y se actualizan de forma automática cuando cambian los valores de los que dependen.

Euros y dólares
Por ejemplo, imaginad un cuadro de edición en el que queremos que el usuario teclee un importe en euros, y una etiqueta donde vamos a mostrar el importe en dólares cuando el valor cambie. Asumiendo una tasa de cambio constante para simplificar, el código quedaría como sigue:
    <div>
        <input data-bind="value: euros" type="text"  /> €
        <p>
            Dollars: <span data-bind="text: dollars"></span> $
        </p>
    </div>
    <script type="text/javascript">
        var ViewModel = function () {
            var self = this;
            self.euros = ko.observable(0);
            self.dollars = ko.computed(function () {
                return self.euros() * 1.3;
            });
        };
        ko.applyBindings(new ViewModel());    
    </script>
El <span> donde vamos a mostrar el importe está enlazado con la propiedad dollars, que es inicializada usando el método ko.computed() al que suministramos la función de cálculo de su valor. Knockout detectará que el valor de ésta propiedad depende a su vez de la propiedad euros, y cada vez que ésta última cambie la recalculará y actualizará los elementos bindeados a ella.

O sea, que si el usuario modifica el cuadro de edición su nuevo valor pasará a la propiedad euros; al depender de ella, la propiedad dollars será reevaluada, y como consecuencia, su valor será mostrado como contenido de la etiqueta <span>. Y todo de forma automática, esa es la magia de Knockout :-)

Hasta ahora hemos visto propiedades calculadas de sólo lectura. Sin embargo, también existen propiedades calculadas de lectura y escritura. En este caso, al método ko.computed() debemos pasar un objeto que defina un método read(), que será usado para obtener el valor de la propiedad, y write(), para escribir sobre ella. Esta flexibilidad permite un uso muy creativo de las propiedades calculadas, pues al fin y al cabo se trata de una vía para introducir lógica personalizada en la lectura y escritura de propiedades.
Conversor de moneda
En el siguiente ejemplo utilizamos esta característica para implementar un sencillo conversor de monedas (dólares/euros) bidireccional. Utilizaremos dos cuadros de texto, uno para cada divisa, y haremos que al cambiar cualquiera de ellos se actualice de forma automática su contrario, para lo que usaremos una propiedad calculada:
    <div>
        <input data-bind="value: euros" type="text"  /> €
        <br/>
        <input data-bind="value: dollars" type="text"  /> $
    </div>
    <script type="text/javascript">
        var ViewModel = function () {
            var dollarsPerEuro = 1.3;
            var self = this;
            self.euros = ko.observable(0);
            self.dollars = ko.computed({
                read: function () {
                    return self.euros() * dollarsPerEuro;
                },
                write: function (value) {
                    self.euros(value / dollarsPerEuro);
                }
            });
        };
        ko.applyBindings(new ViewModel());    
    </script>
Veamos cómo funciona este código. Si el usuario modifica la casilla de euros, la secuencia de acciones es la siguiente:
  • Knockout actualizará la propiedad euros del view-model, puesto que el cuadro de texto está asociado a ella.
  • Durante la definición del objeto view-model, Kocknout ha detectado que la propiedad dollars depende del valor de euros, y como éste ha cambiado, infiere que es necesario reevaluar la propiedad. Esto lo hace invocando el método read(), que retornará la conversión a dólares de los euros que el usuario introdujo.
  • Seguidamente, dado que el valor de la propiedad dollars ha cambiado, Knockout notifica a los elementos suscritos a la misma de este hecho para que se actualicen. En este caso, el cuadro de texto correspondiente a los dólares mostrará el valor actualizado.
Comencemos ahora desde el otro extremo. Si el usuario introduce un valor en dólares, la secuencia será:
  • Dado que el cuadro de texto correspondiente a los dólares está asociado a la propiedad dollars, intenta escribir el valor en ella. Como es una propiedad calculada, llama al método write(), al que suministra el nuevo valor para la propiedad.
  • En el cuerpo de write() se calcula la conversión a euros y se actualiza la propiedad euros del modelo-vista.
Y con esto creo que podemos dejarlo por hoy…

Resumen

En este post nos hemos centrado en la capa view-model, y hemos visto el uso de las propiedades observables, pero no en su totalidad. Más adelante continuaremos estudiando otro tipo de propiedades que podemos emplear en nuestros view-models y enlazar desde las vistas: los arrays observables.
También en artículos posteriores saltaremos a la capa vista y profundizaremos en el mecanismo del binding, para ver qué herramientas nos ofrece Knockout para enlazar los componentes visuales con las propiedades o acciones definidas en el objeto modelo-vista.

He colgado un proyecto en mi Skydrive (HTML+JS exclusivamente) con algunos ejemplos, por si queréis echar un vistazo y practicar un poco. Uno de ellos es el conversor que hemos visto en este post, mientras que el segundo, algo más avanzado, muestra un formulario completo con validaciones usando computed observables. ¡Que aproveche! ;-)

Publicado en Variable not found.

2 Comentarios:

juanlao dijo...

Impresionante, me ha tocado desarrollar apps para Windows 8 en html5 y JavaScript y es exactamente igual (o tremendamente parecido) hacer apps para W8 con JavaScript.

josé M. Aguilar dijo...

Hola, amigo!

Sí, es que al final todo confluye :-D

Desde el punto de vista del desarrollador, es estupendo que podamos aplicar conocimientos ya adquiridos a mundos tan distintos como la web y w8.

Gracias por comentar!

Saludos.