Y el combate se decidió por KO (vi)–Validaciones

Bueno… sigamos con otro post sobre esta serie en la que vamos hablando de cosas sobre knockout. Y en esta ocasión toca hablar de como validar los campos que tengamos enlazados en un formulario.

Dado que estamos moviendo toda nuestra lógica al viewmodel de cliente, es lógico asumir que las validaciones irán también en él, en lugar de estar “ancladas” al DOM como ocurre cuando usamos jQuery validate, p.ej. Si usamos knockout lo normal es tener los campos de nuestro formulario enlazados con propiedades de nuestro viewmodel.

Extendiendo observables

Lo primero que necesitamos para poder aplicar una validación en un viewmodel de knockout es poder colocar código en las propiedades del viewmodel. De esa forma podremos poner el código que queramos cuando se establezca el valor de dicha propiedad. Pues bien ello es posible, pero con una condición: que dichas propiedades sean observables. Para ello se usa la técnica de extender un observable a través del objeto ko.extenders.

Retomemos el ejemplo del post anterior y añadamos una vista de edición de cervezas. Para ello, creamos un método en el controlador de WebApi para que nos devuelva una cerveza:

public Beer GetById(int id)

{

    return beers[id];

}

Y luego creamos una vista para el controlador HomeController que llamaremos Edit. El código inicial puede ser algo como:

@model int

 

@{

    ViewBag.Title = "Edit Beer";

}

 

@section scripts

{

    <script type="text/javascript">

        $(document).ready(function() {

            var getUri = "@Url.RouteUrl("DefaultApi", new {httproute="", controller="Beers", id=Model.ToString()})";

            $.getJSON(getUri, function (data)

            {

                createViewModel(data);

            });

        });

 

        function createViewModel(jsonData)

        {

            var vm = {

                name: ko.observable(jsonData.Name),

                brewery: ko.observable(jsonData.Brewery),

                ibu: ko.observable(jsonData.Ibu)

            };

            ko.applyBindings(vm);

        }

    </script>

 

}

 

@using (Html.BeginForm())

{

    <fieldset>

        <legend>Edit Beer</legend>

        <div class="editor-label">

            <label for="name">Name:</label>

        </div>

        <div class="editor-field">

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

        </div>

 

        <div class="editor-label">

           <label for="brewery">Brewery</label>

        </div>

        <div class="editor-field">

            <input type="text" id="brewery" data-bind="value: brewery"/>

        </div>

 

        <div class="editor-label">

            <label for="ibu">Ibu</label>

        </div>

        <div class="editor-field">

            <input type="text" id="ibu" data-bind="value: ibu"/>

        </div>

        <p>

            <input type="button" value="Save" id="cmdSave"/>

        </p>

    </fieldset>

}

La vista recibe el ID de la cerveza a editar, hace la llamada via Ajax para obtener esos datos, crea el viewmodel de knockout y muestra los campos de la UI enlazados.

Vamos a validar los IBUs. Los IBUs son una medida que indican la amargura de una cerveza. Una lager normal suele estar alrededor de los 30-35 IBUs mientras que una IPA puede alcanzar los 150. Así que vamos a meter una validación para que el valor de los IBUs sea numérico 🙂

Para ello vamos a extender el observable ibu. Para ello debemos añadir la función que extiende el observable en ko.extenders. Extender un observable significa declarar una función que se ejecutará cuando se acceda a dicho observable. Dicha función puede hacer algo y debe devolver un observable (que es el que se terminará enlazando al campo enlazado al observable inicial). Usualmente se devuelve el propio observable original, pero se puede devolver un observable calculado que use el original de cualquier manera.

Empecemos pues por declarar una función que valide si ibu es un número. Para ello añadimos al principio de la función createViewModel:

ko.extenders.numericval = function (target, params) {

    function validate(value) {

        return  value % 1 == 0;

    }

    validate(target());

    target.subscribe(validate);

 

    return target;

};

En este código hacemos cuatro cosas:

  1. Definimos y añadimos el campo numericval dentro de ko.extenders. Dicho campo es una función.
  2. Dentro de dicha función definimos otra función interna que es la que realmente comprueba si un valor es entero o no.
  3. Realizamos una primera validación (del valor inicial del observable)
  4. Suscribimos la función validate al observable. Cada vez que el observable se modifique la función validate se ejecutará.

Si ahora ejecutáramos el código veríamos que no ocurre nada. El código dentro de ko.extenders.numericval no se ejecuta nunca. La razón es que hemos definido la extensión del observable pero nos falta extender el observable en sí. Eso se consigue con el método extend. Este método recibe un objeto json con una propiedad. El nombre de la propiedad es la extensión a aplicar y el valor de la propiedad es el valor que tomará el segundo parámetro (params) de la extensión que hemos creado (el primer parámetro target es el observable que extendemos):

var vm = {

    name: ko.observable(jsonData.Name),

    brewery: ko.observable(jsonData.Brewery),

    ibu: ko.observable(jsonData.Ibu).extend({ numericval: "" })

};

Ahora sí! Hemos definido una extensión (numericval) y la hemos aplicado al observable ibu. Le pasamos “” como parámetros (que tampoco usamos para nada).

Pero esta extensión todavía NO hace nada. Es decir, si depuráis el código javascript veréis que se efectivamente cada vez que se modifica el observable ibu se ejecuta el método validate, pero este método no hace nada salvo devolver true o false.

¿Como podemos informar al usuario de que el valor es incorrecto?

Subobservables

Pues la solución a la pregunta anterior pasa por usar un “subobservable”. Un subobservable es un observable definido dentro de otro observable. Vamos pues a crear un subobservable, que llamaremos hasError. Este subobservable lo definiremos dentro del observable que extendamos, es decir dentro de target. Luego modificaremos validate() para que en lugar de devolver true o false, establezca el valor del subobservable hasError:

ko.extenders.numericval = function (target, params) {

    target.hasError = ko.observable();

    function validate(value) {

        target.hasError(!(value % 1 == 0));

    }

    validate(target());

    target.subscribe(validate);

    return target;

};

Y ya casi estamos! Ahora nos queda un punto que es… enlazar un elemento de la UI al subobservable. ¿Y como enlazamos a un subobservable? Pues del mismo modo que enlazamos a un observable normal, salvo que ahora el nombre del observable es “observable.subobservable”. Así si queremos enlazar algo al valor del subobservable hasError, deberemos usar “ibu.hasError” como nombre:

<div class="editor-field">

    <input type="text" id="ibu" data-bind="value: ibu, css: {‘input-validation-error’: ibu.hasError, ‘field-validation-error’ : ibu.hasError}"/>

</div>

Fijaos en el atributo data-bind del <input>. Ahora estoy usando dos bindings:

  1. value (que ya usábamos) para enlazar el valor
  2. css para aplicar clases css en función del valor del observable. El binding css toma un objeto json, en el cual los nombres de las propiedades son las clases que se aplicarán y el valor de dichar propiedades es un campo del viewmodel. Si vale true se aplica la clase y si vale false no. En este caso:
    1. Se aplicará input-validation-error si ibu.hasError vale true
    2. Se aplicará field-validation-error si ibu.hasError vale true

Nota: En este caso he puesto los nombres de las clases entre comillas simples porque esos nombres no son identificadores javascript válidos. Si los nombres de las clases a aplicar son identificadores javascript válidos, no es necesario entrecomillarlos.

Por supuesto podríamos añadir un mensaje de error, p.ej. en un <span> que se visualice si ibu.hasError vale true. Para ello podemos usar el binding visible que tiene knockout:

<div class="editor-field">

    <input type="text" id="ibu" data-bind="value: ibu, css: {‘input-validation-error’: ibu.hasError, ‘field-validation-error’ : ibu.hasError}"/>

    <span data-bind="visible: ibu.hasError" class="message-error">Error: Debe ser numérico</span>

</div>

¡Y listos!

Aquí tenéis el resultado:

image

Un tema que se observa si se ejecuta el código es que el observable ibu no se modifica cada vez que tecleamos, si no tan solo cuando el textbox pierde el foco. Pero como ya vimos en el post anterior, eso tiene fácil solución.  Basta con usar valueUpdate:

<input type="text" id="ibu" data-bind="value: ibu, valueUpdate: ‘afterkeydown’ ,

    css: {‘input-validation-error’: ibu.hasError, ‘field-validation-error’ : ibu.hasError}"/>

Y con eso hemos terminado. Apuntar solamente que las extensiones se “almacenan” en ko.extenders y son independientes del viewmodel (recordad que debemos “aplicarlas” usando extend). Por lo tanto podemos tener nuestras librerías de “validaciones” con knockout!

Espero que os haya resultado interesante!

Os dejo el ejemplo completo en my skydrive: https://skydrive.live.com/redir?resid=6521C259E9B1BEC6!174 (Nota el zip es KoDemoVI.zip, aunque la solución y la carpeta luego se llamen KoDemoV). Para probar la edición basta con ir a /Home/Edit/{id}, pasando el id de la cerveza.

Saludos!

[Kata]- Cambio con monedas

Muy buenas! Puro divertimento 🙂

Os propongo un kata por si quereis entrenar las neuronas. Por supuesto no es un kata original mío (soy muy malo para eso), de hecho es un kata creo que bastante famosillo, pero es uno con el que me he enfrentado no hace mucho…

El enunciado es muy simple… Crear una función que reciba dos parámetros: Un entero que represente una cantidad de dinero, y una colección que represente los tipos de monedas de los que disponemos. Dicha función debe devolver de cuantas maneras diferentes podemos repartir las mondedas para conseguir el valor total de dinero (asumiendo que tenemos infinitas monedas de cada tipo).

P.ej. si el dinero es 4 y los tipos de monedas son 1 y 2, la función debe devolver “3”, ya que hay 3  maneras de sumar 4 usando 1 y 2:

  • 1+1+1+1
  • 2+2
  • 1+1+2

Si los tipos de monedas son 5,10,20,50,100,200 y 500 y la cantidad de dinero es 300 el valor devuelto debe ser 1022. Con las mismas monedas y dinero 500 el resultado asciende a 6149. Y finalmente con las mismas monedas y la cantidad de dinero 301 el valor es 0, ya que no hay combinación alguna.

El kata es sencillo, simplemente rellenar la función:

int TiposCambio(int money, IEnumerable<int> coins)

{

}

Como “pista” diré que usando C# 3.0 puede conseguirse una respuesta de 3 líneas de código y que no requiere ninguna variable local.

Podéis ir proponiendo soluciones, sugerencias, pistas… en los comentarios! 😉

Saludos!

PD: Si ya conoces la respuesta (como digo es un kata muy conocido) deja que los demás la busquen (aunque nada te impide dar sútiles pistas en los comentarios :p)

PD 2: No, no hay premio alguno… tan solo el placer de haber solucionado el acertijo… os parece poco? 😛 😛 😛

Y el combate se decició por KO (v): Filtrando colecciones

Muy buenas! Tras un parón, volvemos a la carga con la serie que trata sobre knockoutjs. Recuerda que puedes ver todos los artículos de esta serie.

En este post vamos a ver como filtrar colecciones con knockout.

Como siempre comenzamos con una clase Beer y un controlador de WebApi que devuelva una colección de elementos Beer. La clase Beer es tal como sigue:

public class Beer

{

    public string Name { get; set; }

    public string Brewery { get; set; }

    public int Ibu { get; set; }

}

Bien, ahora vamos a realizar una vista (Home/Index) que me muestre el listado de cervezas. Para ello creamos una vista como la que sigue:

<script type=»text/javascript»>

     $(document).ready(function() {

         $.getJSON(«@Url.RouteUrl(«DefaultApi», new {httproute=«», controller=«Beers»})«, function(data) {

             CreateViewModel(data);

         });

 

     });

 

     function CreateViewModel(data) {

         var vm = { beers:data };

         window.vm = vm;

         ko.applyBindings(vm);

     }

 </script>

<table>

    <thead>

        <th>Nombre</th>

        <th>Ibus</th>

        <th>Cervecería</th>

    </thead>

    <tbody data-bind=»foreach: beers»>

        <tr>

            <td data-bind=»text: Name»></td>

            <td data-bind=»text: Ibu»></td>

            <td data-bind=»text: Brewery»></td>

        </tr>

    </tbody>

</table>

Nada nuevo hasta ahora, pero con eso ya vemos nuestra tabla de cervezas:

image

Ahora vamos a jugar un poco con esos datos en cliente usando knockout. Para ello vamos a implementar unos filtros para filtrar los datos directamente desde cliente.

Así pues, vamos a introducir un texbox donde se introducirá el nombre de la cervecería y nos filtrará los datos. El código del textbox es:

<input type=»text» data-bind=»value: brewerySelected, valueUpdate: ‘afterkeydown'» />

Aquí enlazamos la propiedad value del textbox con la propiedad “bewerySelected” del viewmodel (que deberemos crear) y además le indicamos cuando debe modificarse el valor de la propiedad del viewmodel. El valor de valueUpdate es el nombre del evento que knockout usará para modificar la propiedad del viewmodel. En este caso le indicamos ‘afterkeydown’ para que nos actualice la propiedad del viewmodel cada vez que pulsemos una tecla. Si no ponemos valueUpdate el evento usado es ‘change’ (en el caso de los textboxs se lanza cuando pierden el foco).

Ahora añadimos esta propiedad a nuestrov viewmodel:

var vm = {

    beers: data,

    brewerySelected : ko.observable(«»)

};

Es importante destacar que brewerySelected se declara como un observable. Pero ¡ojo! eso NO es para que desde el textbox pueda modificarse dicha propiedad (eso viene de serie). Necesitamos que sea un observable porque vamos a necesitar que la propia knockout sepa cuando se ha modificado dicha propiedad. La razón es que vamos a crear un observable calculado que depende de dicha propiedad y nos interesa que knockout refresque el valor del observable calculado cuando el valor de brewerySelected cambie. De ahí que necesitemos que sea un observable.

¿Y cual va a ser el valor calculado? Pues la lista de cervezas cuya cervecería empieza por el valor que haya en brewerySelected:

vm.filteredByBrewery = ko.computed(function() {

    var filter = this.brewerySelected().toLowerCase();

    if (!filter) return this.beers;

    return ko.utils.arrayFilter(this.beers, function (item) {

        return ko.utils.stringStartsWith(item.Brewery.toLowerCase(), filter);

    });

}, vm);

Fijaos que usamos ko.computed (que ya vimos) para definir el observable calculado. En este caso dicho observable es la colección de elementos cuya propiedad Brewery empieza por el valor que haya en brewerySelected. Para implementarla hacemos uso de dos métodos propios de knockout:

  1. ko.utils.arrayFilter: Filtra los elementos de un array por aquellos que cumplan el predicado que se le pasa. Dicho predicado es una función que se llama por todos elementos del array y debe devolver true o false según el elemento deba ser incluído o no.
  2. ko.utils.stringStartsWith: Devuelve si una cadena empieza por otra indicada.

Y casi listos! Tan solo nos queda hacer una pequeña modificación, que es enlazar el <tbody> a la propiedad filteredByBrewery (en lugar de a la propiedad beers):

<tbody data-bind=»foreach: filteredByBrewery»>

¡Y listos, podemos ver que a medida que vayamos escribiendo la lista se va filtrando!

imageimage

La clave es, recordad, en declarar selectedBrewery como observable. Eso hace que cuando se modifique knockout recalcule el observable calculado filteredByBrewery. Y este último al ser un observable cuando es recalculado forza una modificación de la UI.

Ahora a lo mejor alguien se está haciendo la pregunta: ¿Como sabe knockout que debe recalcular el observable calculado filterByBrewery al modificarse el observable selectedBrewery? Pues simple y llanamente porque desde el código de la función de filterByBrewery se llama a selectedBrewery. A mi personalmente que sea capaz de llegar a ese nivel me parece brutal! Si no te lo crees prueba de añadir otro observable, enlázalo a un cuadro de texto y observa que al modificar este segundo observable el observable calculado filterByBrewery no es recalculado (puedes comprobarlo poniendo un breakpoint con cualquiera de las herramientas de debug de javascript). Es decir knockout sabe de que observables depende cada observable calculado y así solo recalcularlos cuando es necesario… ¡Simplemente fantástico!

Os dejo el código fuente del proyecto en mi skydrive: https://skydrive.live.com/redir?resid=6521C259E9B1BEC6!314 (es el fichero KoDemoV.zip).

Saludos!