Mostrando entradas con la etiqueta knockout. Mostrar todas las entradas
Mostrando entradas con la etiqueta knockout. Mostrar todas las entradas

lunes, 6 de agosto de 2012

Introducción a Knockout (VIII) - obsersable computed y binding event

Con los binding que trae knockout de fábrica debería ser más que suficiente para la mayoría de los proyectos en los que usemos knockout. Pese a ésto knockout nos brinda la forma de poder extender estos comportamientos predefinidos para poder tener un control total sobre nuestra vista y así no tener prácticamente ninguna limitación.

Computed Observables
Dentro de los observables que nos da knockout tenemos los computed observables (prefiero no traducirlo). Son funciones, que depende de uno o más observables y que se actualizan automáticamente cuando una de sus dependencias cambia. Típicamente se usan para información que se muestra en solo lectura aunque como veremos más adelante también los computed observables pueden ser de escritura y ellos a su vez puede actualizar valores.

Supongamos esta vista
<p>Nombre: <input type="text" data-bind="value: firstname" /></p>
<p>Apellidos: <input type="text" data-bind="value: lastname" /></p>
<p>Nombre completo: <label data-bind="text: name"></p>
Y este modelo
function ViewModel(model) {  
    var self = this;  
  
    self.firstname = ko.observable('');  
    self.lastname = ko.observable('');  
    self.name = ko.computed(function() {
        if (self.firstname() == '' || self.lastname() == '') return '';
         return self.lastname() + ', ' + self.firstname();
    });
}  

var viewModel = new ViewModel();  
ko.applyBindings(viewModel);​
Como vemos establecemos dos observables sobre firstname y lastname. También definimos observable computed que actualiza la propiedad name. Esta función se lanzará cada vez que la propiedades lastname y firstname cambien. Lo realmente maravilloso es que la función detecta a que propiedades debe detectar los cambios sin que nosotros debamos hacer nada más que usarlas en el cuerpo de la función.

Podemos probar este ejemplo en el siguiente enlace.

Como comentamos antes, los computed observables también pueden ser de escritura y ellos actualizar determinados valores. He de reconocer que esto no suele ser muy común pero puede que en algún momento nos sea útil. Para hacer un computed observables de lectura y escritura en el ejemplo anterior cambiaremos la vista
<p>Nombre: <input type="text" data-bind="value: firstname" /></p>
<p>Apellidos: <input type="text" data-bind="value: lastname" /></p>
<p>Nombre completo: <input type="text" data-bind="value: name"></p>
y el modelo de la siguiente manera
function ViewModel(model) {  
    var self = this;  
  
    self.firstname = ko.observable('');  
    self.lastname = ko.observable('');  
    self.name = ko.computed({
        read: function() {
            if (self.firstname() == '' || self.lastname() == '') return '';
                return self.lastname() + ', ' + self.firstname();
        },
        write: function(value) {
            var index = value.lastIndexOf(", ");
            if (index > 0) 
            { 
                self.firstname(value.substring(index + 2));
                self.lastname(value.substring(0, index));
            }
        }
    });
}  

var viewModel = new ViewModel();  
ko.applyBindings(viewModel);​
Tal y como hicimos antes, si actualizamos el nombre y los apellidos automáticamente se actualizará el campo del nombre del completo. La salvedad en este caso, es que si actualizamos el nombre completo nosotros con el formato apellidos, nombre, se actualizarán los campos nombre y apellidos.

Podemos probar este ejemplo en el siguiente enlace.

Binding Event
Otra de las posibilidades que nos da knockout es trabajar directamente con los eventos que tienes javascript, pese a que ya tenemos algunos bindings que trabajan con ellos como puede ser el binding click.
Por ejemplo, si definimos la siguiente vista
<div>
    <input type="text" data-bind="value: message, event: { keyup: onkeyup }" />
    <label data-bind="text: status" ></label>
</div>​
y el siguiente modelo
function ViewModel(model) {  
    var self = this; 

    self.message = ko.observable('');
    self.status = ko.observable('Has escrito 0 caracteres...');
  
    self.onkeyup = function(data, event) {
        self.status('Has escrito ' + event.target.value.length + ' caracteres...');           
        return true;
    }
}  
ko.applyBindings(new ViewModel());
En este ejemplo estamos enganchándonos al evento keyup de un textbox y actualizamos un label con la información del número de caracteres que hemos introducido en el textbox.

Podemos probar este ejemplo en el siguiente enlace.

Como vemos, knoutout es bastante versátil y prácticamente podemos hacer lo que queramos. En el próximo artículo veremos como hacer nuestros propios bindings que también nos pueden ser útiles en algunos contextos.

Happy coding!

jueves, 26 de julio de 2012

Introducción a Knockout (VII) - La propiedad $index

El otro día estaba revisando la documentación de binding foreach de knockout cuando recordé que una de las novedades que había traído la versión 2.1.0 era la inclusión de la propiedad $index dentro del contexto foreach. Pensando que utilidad podría tener dentro de una aplicación real y que no fuera la de simplemente mostrar un índice ya que eso lo podemos tener usando <ol>.

Pensando un poco se me ocurrió un ejemplo donde nos podría ser útil esta nueva propiedad, así que supongamos que tenemos una colección de elementos de este tipo
public class Item
{
 public int IdItem { get; set; }
 public string Description { get; set; }
 public int Quantity { get; set; }
}
Esta colección de elementos se la tenemos que presentar al usuario y este debe escribir las cantidades que quiere para cada Item, y enviarla de nuevo al servidor. Se podría ver como un versión muy reducida de un carrito de la compra, donde tenemos los elementos que queremos comprar y podemos modificar la cantidad de elementos. Bien, para mostrar esta información y para simplificar el ejemplo he creado la siguiente modelo y acciones

Modelo
public class Model
{
 public List<Item> Items { get; set; }
}
Acciones
public ActionResult Index()
{
 Model model = new Model();
 FillModel(model);

 return View(model);
}

[HttpPost()]
public ActionResult Index(Model model)
{
 return RedirectToAction("Index");
}

private void FillModel(Model model)
{
 model.Items = new List<Item>();
 for (int i = 1; i <= 10; i++)
  model.Items.Add(new Item() { IdItem = i, Description = string.Format("Item{0}", i), Quantity = 0 });
}
Vista
<h2>Items</h2>
@using (Html.BeginForm())
{ 
 for(int i = 0; i < Model.Items.Count; i++)
 {
  <div>
   @Html.HiddenFor(m => Model.Items[i].IdItem)
   @Html.HiddenFor(m => Model.Items[i].Description)
   @Model.Items[i].Description
   @Html.TextBoxFor(m => Model.Items[i].Quantity)
  </div>
 }
 <input type="submit" value="Enviar" />
}
El aspecto de este formulario sería el siguiente

Vista

Bien, si modificamos alguna de las cantidades y le damos a enviar, obtendremos esto en la controladora

Post

Como vemos el valor introducido por el usuario (10)  ha sido "bindeado" en nuestro modelo sin problemas. Bien, ahora supongamos que queremos hacer lo mismo pero usando knockout. Para eso, debemos modificar ligeramente la vista de la siguiente manera.
@using (Html.BeginForm())
{ 
 <!-- ko foreach: items -->
 <div>
  <input type="hidden" data-bind="value: IdItem"  />
  <input type="hidden" data-bind="value: Description"  />
  <span data-bind="text: Description"></span>
  <input type="text" data-bind="value: Quantity"  />
 </div>
 <!-- /ko -->
 <input type="submit" value="Enviar" />
}

<script type="text/javascript">
 $(document).ready(function () {
  function ViewModel(model) {
   var self = this;

   self.items = ko.observableArray(model.Items);
  }
  var viewModel = new ViewModel(@Html.Raw(Json.Encode(Model)));
  ko.applyBindings(viewModel);
 });
</script>
Con esto veremos que nuestra vista es igual a la obtenida anteriormente, pero al enviar el form nos daremos cuenta que nuestro modelo no se "bindea" correctamente.


Si sabemos como funciona el bind de ASP.NET MVC veremos que este error es lógico ya que los elementos que estamos poniendo en nuestra vista no tienen el atributo name definido, pero además hay que tener en cuenta que estamos intentando "bindear" una colección por lo que tendremos que especificar el índice que ocupa el elemento en la colección. Para esto es para lo que viene de maravilla el uso de la propiedad $index dentro del contexto foreach (no es que antes no se pudiera hacer, sino que era más complicado). Con esto la vista quedaría de la siguiente manera
<!-- ko foreach: items -->
<div>
 <input type="hidden" data-bind="value: IdItem, attr: {name: 'Items['+$index()+'].IdItem'}"  />
 <input type="hidden" data-bind="value: Description, attr: {name: 'Items['+$index()+'].Description'}"  />
 <span data-bind="text: Description"></span>
 <input type="text" data-bind="value: Quantity, attr: {name: 'Items['+$index()+'].Quantity'}"  />
</div>
<!-- /ko -->
Esto generaría el siguiente código html que vemos que cumple con lo dicho anteriormente

Vista HTML

Y si hacemos el post veremos que el modelo se ha "bindeado" de nuevo correctamente



NOTA
Hay otra forma de generar la vista de una manera más "elegante" y evitando así tener que usar un bucle y teniendo a nuestra alcance el Intellisense es la siguiente. Creamos una página parcial (Partial Page) llamada Item.cshtml  (mismo nombre que la clase que contiene la colección) en el directorio View/Home/RenderPartials o Shared/RenderPartials y en ella pondremos lo siguiente
@model test.web.mvc.cs.Models.Item

<div>
 @Html.HiddenFor(m => m.IdItem)
 @Html.HiddenFor(m => m.Description)
 @Model.Description
 @Html.TextBoxFor(m => m.Quantity)
</div>
Luego en la vista tan solo deberemos poner lo siguiente
@using (Html.BeginForm())
{ 
 @Html.EditorFor(x => x.Items)
 <input type="submit" value="Enviar" />
}

Happy coding!

miércoles, 13 de junio de 2012

Introducción a Knockout (VI) - Insertando datos con ASP.NET MVC4 WebApi

En el anterior artículo de knockout vimos como podíamos llamadas AJAX para cargar nuestra UI. Hoy vamos a ver como podemos hacer el mantenimiento de nuestras entidades también de una forma sencilla. Lo primero será extender nuestro repositorio de datos para añadir cierta lógica para el almacenamiento de nuestros datos.

IRepository
public interface IBookRepository
{
  IEnumerable<Book> Get();
  IEnumerable<Book> GetList(string search);
  Book Add(Book item);
  void Remove(int id);
}
BookRepository
public class BookRepository : IBookRepository
{
    private List<Book> products = new List<Book>();
    private int _nextId = 1;

    public BookRepository()
    {
        Add(new Book
        {
            Id = 1,
            Title = "Introducing Microsoft Sql Server 2012"
        });
        Add(new Book
        {
            Id = 2,
            Title = "Introducing Windows Server 2008 R2"
        });
        Add(new Book
        {
            Id = 3,
            Title = "Visual Studio 2010"
        });
        Add(new Book
        {
            Id = 4,
            Title = "Programming Windows Phone 7"
        });
        Add(new Book
        {
            Id = 5,
            Title = "Visual Studio 2008"
        });
        Add(new Book
        {
            Id = 6,
            Title = "Microsoft .NET 4.0"
        });
        Add(new Book
        {
            Id = 7,
            Title = "ASP.NET 4.0"
        });
    }

    public IEnumerable<Book> Get()
    {
        return products;
    }

    public IEnumerable<Book> GetList(string search)
    {
        return products.Where(m => m.Title.Contains(search));
    }

    public Book Add(Book item)
    {
        item.Id = _nextId++;
        products.Add(item);
        return item;
    }

    public void Remove(int id)
    {
        products.RemoveAll(p => p.Id == id);
    }
}
BookController
public class BookController : ApiController
{
    static IBookRepository repository = new BookRepository();

    // GET /api/book
    public IEnumerable Get(string search)
    {
        return (string.IsNullOrEmpty(search) ? repository.Get() : repository.GetList(search));
    }

    // POST /api/book/
    public Book Post(Book model)
    {
        return repository.Add(new Book() { Title = model.Title });
    }

    // DELETE /api/values/5
    public void Delete(int id)
    {
        repository.Remove(id);
    }
}

Bien, con esto ya podemos irnos a la parte de cliente para ver como haríamos. La verdad es que es bastante fácil y lo único que debemos hacer es extraer los valores de las propiedades de nuestro modelo para formar un objeto JSON que pasaremos vía POST a nuestra API.

De esa manera nuestra interfaz y nuestro modelo quedarían así
<h2>Ejemplo de uso de Knockout</h2>
<h3>Insertar un libro</h3>
<div>
  Título del libro <input type="text" data-bind="value: title" /><input type="button" data-bind="click: onsave" value="Save" />
</div>

<h3>Listado de libros</h3>
<div id="search">
  <input type="text" data-bind="value: search" />
  <input type="button" data-bind="click: onsearch" value="Search" />
</div>

<!-- ko if: books().length == 0 -->
<div data-bind="if: books().length == 0">
  No hay resultados que mostrar
</div>
<!-- /ko -->

<!-- ko ifnot: books().length == 0 -->
<ul data-bind="foreach: books">
  <li ><span data-bind="text: $data.Title"></span> | <span data-bind="click: $parent.ondelete" style="cursor: pointer">[x]</span></li>
</ul>
<!-- /ko -->
Vemos que hemos añadido una pequeña sección para añadir el título de un nuevo libro, mientras que el resto de la interfaz permanece igual respecto al ejemplo del artículo anterior.
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.title = ko.observable('');
            self.search = ko.observable('');
     self.books = ko.observableArray([]);

     self.onsave = function () {
      var title = self.title();
   
      if (title == '')
      {
       alert('El título es obligatorio');
       return; 
      }
   
      $.ajax({
       type: 'POST',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { Title: title },
       success: function (pReturn) {
        self.books.push(pReturn);
       }
      });
     }

     self.onsearch = function () {
      var search = self.search();
      $.ajax({
       type: 'GET',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")" + "/get/" + search,
       data: { },
       success: function (pReturn) {
        self.books(pReturn);
       }
      });
     }

     self.ondelete = function (e) {   
      $.ajax({
       type: 'DELETE',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { id: e.Id },
       success: function (pReturn) {
        self.books.remove(e);
       }
      });
     }

     self.onsearch();
    }
    ko.applyBindings(new ViewModel()); 
</script>
Vemos que el método onsave extraemos el valor de la propiedad observable que luego usaremos para formar el JSON que pasaremos en la llamada. Para modelos más complejos podríamos serializar con el método toJSON todo el modelo de la siguiente manera:
self.onsave = function () { 
        var data = ko.toJSON(self);
   
 if (data.title == '')
 {
  alert('El título es obligatorio');
  return; 
 }
   
 $.ajax({
  type: 'POST',
  dataType: "json",
  contextType: "application/json; charset=utf-8",
  url: "@Url.Action("book", "api")",  
                data: data,
  success: function (pReturn) {
   self.books.push(pReturn);
  }
 });
}
OJO, porque este ejemplo "no funciona" porque en la serialización a JSON del modelo también se está incluyendo la propiedad books, que incluye la lista de libros que se está mostrando y esto provoca que el mapeo del modelo se haga incorrectamente en el método POST de la API. Lo he comentado para que sirva de referencia en modelos más complejos.

Otra opción es usar dos modelos diferentes en la misma vista, pero eso lo dejaremos para otro artículo que este con tanto código se ha extendido un poco.

Happy coding!

miércoles, 23 de mayo de 2012

Introducción a Knockout (V) - Cargando datos dinámicamente con ASP.NET MVC4 WebApi

Hasta ahora hemos visto varios ejemplo con knockout pero usando datos estáticos. En este artículo haremos uso de datos "dinámicos" cargados a través de Web Api (una de las funcionalidades que nos ha traído la beta de ASP.NET MVC4).

No es el objetivo de este artículo hacer explicar el funcionamiento de esta nueva funcionalidad, pero para nuestro ejemplo nos basta con lo siguiente
Controller
public class BookController : ApiController
{
  private IBookRepository repository = new BookRepository();

  // GET /api/book
  public IEnumerable<book> Get(string search)
  {
    return (string.IsNullOrEmpty(search) ? repository.Get() : repository.GetList(search));
  }
}
BookRepository
public class BookRepository : IBookRepository
{
  private List<book> books = new List<book>(); 

  public BookRepository()
  {
    Add(new Book
    {
      Id = 1,
      Title = "Introducing Microsoft Sql Server 2012"
    });
    Add(new Book
    {
      Id = 2,
      Title = "Introducing Windows Server 2008 R2"
    });
    Add(new Book
    {
      Id = 3,
      Title = "Visual Studio 2010"
    });
    Add(new Book
    {
      Id = 4,
      Title = "Programming Windows Phone 7"
    });
    Add(new Book
    {
      Id = 5,
      Title = "Visual Studio 2008"
    });
    Add(new Book
    {
      Id = 6,
      Title = "Microsoft .NET 4.0"
    });
    Add(new Book
    {
      Id = 7,
      Title = "ASP.NET 4.0"
    });
  }

  public IEnumerable<book> Get()
  {
    return products;
  }

  public IEnumerable<book> GetList(string search)
  {
    return products.Where(m => m.Title.Contains(search));
  }

  public Book Add(Book item)
  {    
    books.Add(item);
    return item;
  }
}
y finalmente IBookRepository
public interface IBookRepository
{
  IEnumerable<book> Get();
  IEnumerable<book> GetList(string search);
}
Como vemos nada complicado, una simple controladora que accede a un origen de datos un tanto especial, pero con la cual conseguimos simular el acceso a datos, y vemos como podemos consumir estos datos desde cliente.
Nuestro html está formado por los siguiente elementos, un buscador
<h2>Ejemplo de uso de Knockout</h2>
<h3>Listado de libros</h3>
<div id="search">
  <input type="text" data-bind="value: search" />
  <input type="button" data-bind="click: onsearch" value="Search" />
</div>
Un elemento que muestra un mensaje si no hay resultado
<!-- ko if: books().length == 0 -->
  No hay resultados que mostrar
<!-- /ko -->
Una lista para mostrar nuestros resultados
<!-- ko ifnot: books().length == 0 -->
<ul data-bind="foreach: books">
  <li data-bind="text: $data.Title"></li>
</ul>
<!-- /ko -->
Y nuestro modelo
<script type="text/javascript" language="javascript">
  function ViewModel() {
    var self = this;

    self.search = ko.observable('');
    self.books = ko.observableArray([]);

    self.onsearch = function () {
      var search = self.search();
      $.ajax({
        type: 'GET',
        dataType: "json",
        contextType: "application/json; charset=utf-8",
        url: "@Url.Action("book", "api")" + "/get/" + search,
        data: {},
        success: function (pReturn) {
          self.books(pReturn);
        }
      });
    }

    // Búsqueda inicial
    self.onsearch();
  }
  ko.applyBindings(new ViewModel()); 
</script>
En nuestro modelo definimos dos observadores, uno sobre el texto a buscar, y otro sobre la colección de libros que mostraremos. Es importante tener en cuenta que el observador sobre la colección de libro solo hace falta definirlo una vez y no es necesario redefinirlo cada que vez que actualicemos la colección. Es un fallo muy común y que puede darnos algún que otro quebradero de cabeza.

Inicialmente se llama al método onsearch de nuestro modelo, al igual que cada que hagamos click en el botón de buscar. Para la llamada si usaremos jQuery, y simplemente atacaremos nuestra controladora.

Otro detalle, es que para ocultar ya no es necesario poner div innecesarios, ya que he encotnrado una sintaxis más elegante que nos evita crear elementos innecesarios. En vez de poner esto
<div data-bind="if: books().length == 0">
  No hay resultados que mostrar
</div>
ponemos esto
<!-- ko if: books().length == 0 -->
  No hay resultados que mostrar
<!-- /ko -->
Vemos lo simple que queda nuestro código incluso cuando tenemos que cargar dinámicamente datos desde por ejemplo una Api.

En el próximo artículo veremos como podemos crear elementos, en nuestro caso libros, y veremos que lo podemos hacer también de una forma muy simple...

Happy coding!

martes, 22 de mayo de 2012

Introducción a Knockout (IV) - El uso de if, ifnot y visible

Hoy seguiremos con la explicación del funcionamiento básico de knockout. Está vez le tocará el turno al binding visible y veremos como controlar el flujo de la presentación mediante el uso de if o ifnot.

Bien, el propósito del binding visible es mostrar u ocultar un elemento DOM en base a un valor, y como veremos a continuación su funcionamiento es muy simple:
<h3>Listado de libros</h3>
<ul data-bind="foreach: libros, visible: mostrar">
 <li data-bind="text: $data"></li>
</ul>
<input type="checkbox" data-bind="checked: mostrar" /> Mostrar

<script type="text/javascript" language="javascript">
 function ViewModel() {
  var self = this;

  self.libros = ko.observableArray(["Introducing Microsoft Sql Server 2012",
   "Introducing Windows Server 2008 R2",
   "Visual Studio 2010", 
   "Programming Windows Phone7"]);

  self.mostrar = ko.observable(true)
 }
 ko.applyBindings(new ViewModel()); 
</script>
Podemos ver su funcionamiento aquí.

Como vemos, establecemos un checkbox que nos indicará si debemos mostrar la lista de elementos o no. Mediante el dababind checked actualizamos el valor del función observable mostrar, que a su vez, cambiará el estado del listado (ul). Si vemos el código html veremos que se añade display:none al estilo del ul cuando desmarcamos el checkbox.


El funcionamiento de if es similar al del databind visible. La principal diferencia es que mientras visible muestra u oculta en base al css, el databind if elimina los nodos que no serán visible del árbol html.

Modificando un poco nuestro ejemplo anterior ya que no podemos usar el databind forech e if en el mismo elemento veremos como funciona el databind if
<h3>Listado de libros</h3>
<div data-bind="if: mostrar">
 <ul data-bind="foreach: libros">
  <li data-bind="text: $data"></li>
 </ul>
</div>
<input type="checkbox" data-bind="checked: mostrar" /> Mostrar

<script type="text/javascript" language="javascript">
 function ViewModel() {
  var self = this;

  self.libros = ko.observableArray(["Introducing Microsoft Sql Server 2012",
   "Introducing Windows Server 2008 R2",
   "Visual Studio 2010", 
   "Programming Windows Phone7"]);

  self.mostrar = ko.observable(true)
 }
 ko.applyBindings(new ViewModel()); 
</script>
Podemos ver el ejemplo anterior funcionando aquí

Si vemos el código html del ejemplo anterior veremos esto cuando el checkbox está desmarcado


Como vemos es muy fácil modificar el aspecto de nuestra presentación usando los databind visible e if dotando a nuestra interfaz de una mayor dinamismo.

Happy coding!

lunes, 14 de mayo de 2012

Introducción a Knockout (III) - El uso del data-bind options

A raíz de una pregunta sobre como podríamos detectar la selección de un elemento en un combobox o dropdown me he puesto a mirar a fondo como funciona el data-bind options de knockout. Mirando su documentación, este data-bind, se usa en los dropdown-list o en los multiselect-list. Fuera de estos elementos el elementos este data-bind no realiza ninguna acción.

Para usarlo tan sólo debemos hacer poner esto en nuestro html
<h2>Ejemplo de uso de Knockout</h2>
<h3>Seleccione una opción</h3>
<select data-bind="options: optionValues, value: selectedOptionValue"></select>
y definir nuestro modelo MVVM de la siguiente manera
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.optionValues = ['Alpha', 'Beta', 'Gamma'];
     self.selectedOptionValue = ko.observable("Gamma");
     });
    }
    ko.applyBindings(new ViewModel());
</script>
Como vemos, usamos un select html donde definiremos el data-bind options, asignándole los datos que tengamos en la propiedad optionValues de nuestro modelo y asignando el observador selectionOptionValue para detectar los cambios. En caso que queramos detectar el cambio de valores, podemos usar la función suscribe de la siguiente manera
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.optionValues = ['Alpha', 'Beta', 'Gamma'];
     self.selectedOptionValue = ko.observable("Gamma");
     self.selectedOptionValue.subscribe(function (value) {
      alert("El valor seleccionado es " + value);
     });
    }
    ko.applyBindings(new ViewModel());
</script>
Podemos complicar un poco nuestro modelo para asignar un objeto en vez de una lista plana de elementos. Esto se acerca más a la realidad donde probablemente mostremos algún objeto del tipo clave / valor. También podemos hacer que el dropdown-list presente el clásico mensaje "Seleccione un valor...". Para esto definiremos nuestro select html de la siguiente manera
<h2>Ejemplo de uso de Knockout</h2>
<h3>Seleccione una opción</h3>
<select data-bind="options: optionValues, optionsText: 'value', optionsValue: 'id', value: selectedOptionValue, optionsCaption: 'Seleccione una opción...'"></select>
y nuestro modelo así
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.optionValues = [{ id: "1", value: "valor1" }, { id: "2", value: "valor2"}];
     self.selectedOptionValue = ko.observable("2");
     self.selectedOptionValue.subscribe(function (value) {
      alert("El valor seleccionado es " + value.id);
     });
    }
    ko.applyBindings(new ViewModel());
</script>
Como vemos definimos un objeto simple con una propiedad id y otra propiedad valor, que asignamos a la propiedad optionValues que será usada para rellenar el dropdown-list. También definimos el optionsText, que indica la propiedad usada como texto en el select, y el optionsValue que no indica el value. El mensaje "Seleccione una opción..." es definido mediante la opción optionsCaption. Vemos que al igual que en las lista planas también podemos hacer uso de la función suscribe para manejar nosotros la selección de un elemento.

Vemos como con knockout es bastante fácil manejar los dropdown-list.

Happy coding!

martes, 8 de mayo de 2012

Introducción a Knockout (II) - El uso de for-each y click

En mi anterior artículo hice una pequeña introducción a knockout. Hoy vamos a ver como nos puede ayudar a crear elementos en nuestra interfaz y a su vez interactuar con estos elementos de una manera muy fácil y "elegante".

Para esto tan solo debemos usar el data-bind foreach dado por knockout.Veamos su funcionamiento con el siguiente ejemplo:
<h3>Listado de libros</h3>

<ul data-bind="foreach: libros">
  <li data-bind="text: $data"></li>
</ul>

<script type="text/javascript" language="javascript">
  function ViewModel() {
    var self = this;
  
    self.libros = ko.observableArray([]);

    // Creamos nuestra colección de libros
    var t = ["Introducing Microsoft Sql Server 2012",
      "Introducing Windows Server 2008 R2",
      "Visual Studio 2010", 
      "Programming Windows Phone7"];
    self.libros(t);
  }
  ko.applyBindings(new ViewModel()); 
</script>
Vemos que creamos nuestro <ul> y ahí establecemos el data-bind foreach, sobre una propiedad de nuestro modelo. Esta propiedad está definida como un array sobre el que establecemos un observador para que cada vez que cambie se rehaga nuestro listado. Para ver este comportamiento vamos a utilizar otro data-bind, en este caso usarmos el data-bind click que nos permite interactuar sobre un click en un elemento determinado.
<h3>Listado de libros</h3>
<ul data-bind="foreach: libros">
  <li data-bind="text: $data"></li>
</ul>
<span data-bind="click: recargar" style="cursor: pointer">Recargar lista</span>

<script type="text/javascript" language="javascript">
  function ViewModel() {
    var self = this;
  
    self.libros = ko.observableArray([]);

    // Creamos nuestra colección de libros
    var t = ["Introducing Microsoft Sql Server 2012",
      "Introducing Windows Server 2008 R2",
      "Visual Studio 2010", 
      "Programming Windows Phone7"];
    self.libros(t);

    self.recargar = function () {
      t = ["Visual Studio 2008",
        "Microsoft .NET 4.0",
        "ASP.NET 4.0"];

      self.libros(t);
    }
  }
  ko.applyBindings(new ViewModel());
</script>
En este ejemplo vemos como inicialmente cargamos la lista con 4 títulos y tras hacer click sobre el recargar, ésta se recarga con los tres nuevo libros definidos en el método recargar. Vemos que el código es muy limpio sobre todo en la parte html, y con poco esfuerzo podemos cambiar la función recargar de estática a dinámica haciendo una llamada ajax para obtener datos de nuestro servicio. Puedes probar el ejemplo haciendo click aquí.

Antes la sencillez del código, la pregunta sería, ¿cómo podríamos hacer esto con jQuery? Pues sin ser un experto en jQuery sería algo así
<h3>Listado de libros</h3>
<ul id="libros"></ul>
<span id="recargar" style="cursor: pointer">Recargar lista</span>​
$(document).ready(function() {
    var t = ["Introducing Microsoft Sql Server 2012",
      "Introducing Windows Server 2008 R2",
      "Visual Studio 2010", 
      "Programming Windows Phone7"];
    bind(t);    
    
    $('#recargar').click(function() {
        t = ["Visual Studio 2008",
          "Microsoft .NET 4.0",
          "ASP.NET 4.0"];
        bind(t);
    });
    
    function bind(t)
    {
        $('#libros').empty();
        $.each(t, function(index, item) {
            $('#libros').append('<li>'+item+'</li>');
        });
    }
});​
Puedes probar el ejemplo aquí

Happy codding!

lunes, 7 de mayo de 2012

Introducción a Knockout (I)

Desde hace tiempo cada vez programo más en cliente. Para esto jQuery nos ofrece una gran ayuda, sobre todo con su gran cantidad de selectores que nos permite realizar tareas comunes como mostrar u ocultar información o crear elementos html "al vuelo" de una manera fácil.

Pues bien, una de las cosas que más odio de jQuery es tener que recorrer un div (o elemento html al gusto) para extraer toda la información que tienen los inputs para montar un JSON, y éste enviarlo al servidor. De igual forma es tedioso tener que mapear a mano cada valor recibido de "nuestro servidor" en los diferentes elementos html que tengamos en nuestra página. Tengo hecho un pequeño framework que me ayuda en este tipo de tareas (marcando los inputs, checkboxs o radiobuttons en los que deseo insertar o extraer la información) pero nunca me ha terminado de convencer.

Para resolver todo esto he descubierto, gracias a la plantilla de ASP.NET MVC4, un script que se llama Knockout. Knockout nos ayuda a interactuar de una manera fácil con la interfaz mediante el patrón MVVM (poniendo cada cosa en su sitio).

Y como todo se entiende mejor con código vamos a poner un ejemplo.
Nombre: <input data-bind="value: Nombre" type="text" />
Primer apellido <input data-bind="value: PrimerApellido" type="text" />
function ViewModel() {
    var self = this;
    
    self.Nombre = "John";
    self.PrimerApellido= "Doe";
}

// Activates knockout.js
ko.applyBindings(new ViewModel());​
Bien, ¿que hemos hecho en el ejemplo? Pues hemos vinculado (bindeado en ese idioma que tenemos los informáticos) a través del atributo data-bind la propiedad value del input (o sea, lo que se muestra) con el valor de Nombre o PrimerApellido que se definan en nuestro modelo (ViewModel).

Pero no solo estamos atados a datos estáticos sino que podemos quedarnos a la espera a que ese datos cambien para actualizar el valor en el otro lado. Veamos el siguiente ejemplo
Nombre: <input type="text" data-bind="value: Nombre" /><br />
Primer apellido <input type="text" data-bind="value: PrimerApellido" /><br />
Nombre: <span data-bind="text: Nombre"></span>
Primer apellido: <span data-bind="text: PrimerApellido"></span ><br />
function ViewModel() {
    var self = this;
    
    self.Nombre= ko.observable("John");
    self.PrimerApellido= ko.observable("Doe");
}

// Activates knockout.js
ko.applyBindings(new ViewModel());​
Vemos que si cambiamos el valor de uno de los input, éste al perder el foco actualiza el valor del span, ya que ambos se han enlazado (incluido los cambios) al valor que tenga el modelo. Para esto hemos definido un observador que nos da knockout. Como vemos con poco código hemos vinculado la actualización de un campo con nuestro modelo (y viceversa). Lo cual es ideal, para el caso de recibir valores de nuestra API a través de llamadas ajax.

A veces nos puede venir bien combinar dos valores, y establecerlos en una propiedad, pero a su vez estar atentos a su cambios. Este ejemplo lo vemos con el siguiente código
Nombre: <input type="text" data-bind="value: Nombre" /><br />
Primer apellido <input type="text" data-bind="value: PrimerApellido" /><br />
Hola <span data-bind="text: Mensaje"></span ><br />
function ViewModel() {
    var self = this;
    
    self.Nombre= ko.observable("John");
    self.PrimerApellido= ko.observable("Doe");
    self.Mensaje= ko.computed(function() {
        return this.Nombre() + " " + this.PrimerApellido();    
    }, this); 
}

// Activates knockout.js
ko.applyBindings(new ViewModel());​
Como vemos el uso de un "modelo" con estos observadores hacen que mejore la legibilidad del código que escribimos, aunque la potencia de Knockout no queda aquí, ya que también nos permite:
  • Engancharnos a eventos como el click o el checked. 
  • Establecer observadores sobre colecciones (útil para mantener una lista de los checks marcados para un filtrado).
  • Mostrar u ocultar información en base a criterios (por ejemplo una lista vacia) que podemos definir en nuestro modelo.
  • Iterar sobre colecciones para crear elementos en la UI.