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!

Deja un comentario

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