Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Introducción a Knockout
Descargar el ejemplo de código
Hoy en día el enlace de datos es una de las características más populares en el desarrollo, y la biblioteca Knockout en JavaScript trae estas características al desarrollo en HTML y JavaScript. La simplicidad de la sintaxis del enlace declarativo y la integración completa con patrones de separación tales como Modelo-Vista-Vista de modelo (MVVM) facilitan las tareas de conexión comunes de inserción y extracción, al mismo tiempo que el código se puede mantener y leer mejor. En esta columna inaugural de Perspectivas sobre el cliente examinaré los escenarios para los cuales Knockout es ideal, explicaré cómo comenzar a usarlo y enseñaré el uso de sus características más fundamentales. Los ejemplos de código, que puede descargar de archive.msdn.microsoft.com/mag201202Client, ilustran cómo usar el enlace declarativo, crear diferentes tipos de objetos de enlace y escribir código centrado en datos en JavaScript que se rige por patrones de separación estricta, tales como MVVM.
Para comenzar
Knockout es una pequeña biblioteca de código abierto desarrollada en JavaScript por Steve Sanderson con una licencia MIT. Knockoutjs.com mantiene una lista actualizada de los exploradores con los que funciona Knockout (actualmente esto son todos los exploradores más importantes, como Internet Explorer 6+, Firefox 2+, Chrome, Opera y Safari). Antes de comenzar a desarrollar con Knockout necesita algunos recursos importantes. Comience por descargar la versión más reciente de Knockout (actualmente 2.0.0) de bit.ly/scmtAi y haga referencia a ella en su proyecto. Si está usando Visual Studio 2010, sin embargo, le recomiendo encarecidamente que instale y use la extensión NuGet de Visual Studio para administrar paquetes para descargar Knockout (y todas las bibliotecas que pudiera necesitar), ya que es capaz de administrar las versiones y lo informará cuando se encuentren disponibles versiones nuevas. NuGet descargará Knockout e instalará dos archivos JavaScript en la carpeta de scripts del proyecto. Se recomienda el archivo minimizado para la producción y este se rige por la convención de nombres knockout-x.y.z.js, donde x.y.z son los números de la versión principal, secundaria y revisión. También se instala el archivo knockout-x.y.x-debug.js que contiene el código fuente de Knockout en forma humanamente legible. Recomiendo que se remita a este archivo durante el aprendizaje de Knockout y durante la depuración.
Una vez que tenga los archivos, abra su página HTML (o el archivo Razor si está usando ASP.NET MVC), cree un script y haga referencia a la biblioteca knockout:
<script src="../scripts/knockout-2.0.0.js" type="text/javascript"></script>
Sin enlaces
Para explicar Knockout, lo mejor es examinar primero cómo escribiríamos el código para insertar datos de un objeto de origen en los elementos HTML sin usar Knockout (el código fuente pertinente se encuentra en can en la página de ejemplo 01-without-knockout.html en el código descargable). Luego mostraré cómo hacer esto mismo con Knockout. Comenzaré con algunos elementos HTML de destino e insertaré algunos valores de un objeto de origen en estos elementos HTML:
<h2>Without Knockout</h2>
<span>Item number:</span><span id="guitarItemNumber"></span>
<br/>
<span>Guitar model:</span><input id="guitarModel"/>
<span>Sales price:</span><input id="guitarSalesPrice"/>
Si tenemos un objeto del cual quiere insertar los datos en unos elementos HTML estándar, podríamos usar jQuery:
$(document).ready(function () {
var product = {
itemNumber: "T314CE",
model: "Taylor 314ce",
salePrice: 1199.95
};
$("#guitarItemNumber").text(product.itemNumber);
$("#guitarModel").val(product.model);
$("#guitarSalesPrice").val(product.salePrice);
});
Este ejemplo emplea jQuery para ubicar los elementos HTML con los identificadores correspondientes y establecer sus valores en la propiedad debida de cada objeto.
Debemos fijarnos en tres puntos importantes en este código. Primero, los valores se insertan del objeto de origen en los elementos HTML, por lo que necesitamos una línea de código por cada asignación del valor de origen al elemento de destino. Si tuviéramos muchas más propiedades (o en el caso de matrices y grafos de objetos), el código rápidamente se podría volver incontrolable. Segundo, si cambian los valores en el objeto de origen, los elementos HTML no reflejarán esos cambios, a menos que volvamos a llamar el código que inserta los valores. Tercero, si los valores cambian en el elemento HTML (el destino), los cambios no se verán reflejados en el objeto de origen subyacente. Por supuesto, todo esto se podría solucionar con una gran cantidad de código adicional, pero usaré el enlace de datos para solucionarlo.
El mismo código se podría reescribir con Knockout:
<h2>With Knockout</h2>
<span Item number</span><span data-bind="text: itemNumber"></span>
<br/>
<span>Guitar model:</span><input data-bind="value: model"/>
<span>Sales price:</span><input data-bind="value: salePrice"/>
Observe cómo reemplazamos los atributos id con atributos data-bind. Una vez que llamamos la función applyBindings, Knockout enlaza el objeto dado (“product” en este ejemplo) con la página. Esto establece el contexto de datos en el objeto product, por lo que los elementos de destino ahora pueden identificar la propiedad del contexto de datos con el cual se quieren enlazar:
ko.applyBindings(product);
Los valores del objeto de origen se insertarán en los elementos de destino en esta página. (Todas las funciones de Knockout se encuentran dentro de su propio espacio de nombres: ko.) La vinculación entre los elementos de destino y el objeto de origen está definida por el atributo data-bind. En el ejemplo anterior, Knockout veía que el atributo data-bind de la primera etiqueta span especificaba que el valor de su texto se debía enlazar con la propiedad itemNumber. Knockout luego insertaba el valor de la propiedad product.itemNumber en el elemento.
Aquí puede observar cómo Knockout permite reducir fácilmente la cantidad de código. A medida que crece el número de propiedades y elementos, el único código JavaScript necesario es la función applyBindings. Sin embargo, esto no soluciona todos los problemas. Este ejemplo no actualiza los destinos de HTML cuando cambia el origen, ni se actualiza el objeto de origen cuando cambian los destinos en HTML. Para esto vamos a necesitar observables.
Observables
Knockout agrega el seguimiento de las dependencias mediante los observables, que son objetos que pueden notificar unos agentes de escucha cuando los valores subyacentes cambiaron (este concepto es similar a la interfaz INotifyPropertyChanged de la tecnología XAML). Knockout implementa las propiedades observables al encapsular las propiedades de los objetos con una función personalizada llamada observable. Por ejemplo, en vez de establecer una propiedad en un objeto de este modo:
var product = {
model: "Taylor 314ce"
}
podemos definir que la propiedad sea observable con Knockout:
var product = {
model: ko.observable("Taylor 314ce")
}
Una vez que las propiedades se definieron como observables, comienza a tomar forma el enlace de datos. El código en JavaScript que vemos en la figura 1 ilustra dos objetos que son enlazados a elementos HTML por Knockout. El primer objeto (data.product1) define sus propiedades con un objeto literal, mientras que el segundo objeto (data.product2) define las propiedades como observables de Knockout.
Figura 1 Con y sin observables
$(document).ready(function () {
var data = {
product1: {
id: 1002,
itemNumber: "T110",
model: "Taylor 110",
salePrice: 699.75
},
product2: {
id: ko.observable(1001),
itemNumber: ko.observable("T314CE"),
model: ko.observable("Taylor 314ce"),
salePrice: ko.observable(1199.95)
}
};
ko.applyBindings(data);
});
El HTML de este ejemplo, que se entrega en la figura 2, muestra cuatro conjuntos de enlaces de elementos. La primera y la segunda etiqueta div contienen elementos HTML enlazados a objetos que no son observables. Observe que cuando cambian los valores del primer div, no cambia nada más. La tercera y cuarta etiqueta div contienen los elementos HTML enlazados a las propiedades observables. Preste atención a que cuando cambian los valores del tercer div, se actualizan los valores del cuarto div. Puede probar este demo con el ejemplo 02-observable.html.
Figura 2 Enlace a observables y no observables
<div>
<h2>Object Literal</h2>
<span>Item number</span><span data-bind="text: product1.itemNumber"></span>
<br/>
<span>Guitar model:</span><input data-bind="value: product1.model"/>
<span>Sales price:</span><input data-bind="value: product1.salePrice"/>
</div>
<div>
<h2>Underlying Source Object for Object Literal</h2>
<span>Item number</span><span data-bind="text: product1.itemNumber"></span>
<br/>
<span>Guitar model:</span><span data-bind="text: product1.model"></span>
<span>Sales price:</span><span data-bind="text: product1.salePrice"></span>
</div>
<div>
<h2>Observables</h2>
<span>Item number</span><span data-bind="text: product2.itemNumber"></span>
<br/>
<span>Guitar model:</span><input data-bind="value: product2.model"/>
<span>Sales price:</span><input data-bind="value: product2.salePrice"/>
</div>
<div>
<h2>Underlying Source Object for Observable Object</h2>
<span>Item number</span><span data-bind="text: product2.itemNumber"></span>
<br/>
<span>Guitar model:</span><span data-bind="text: product2.model"></span>
<span>Sales price:</span><span data-bind="text: product2.salePrice"></span>
</div>
(Observación: Knockout no exige el uso de propiedades observables. Si quiere usar elementos DOM para recibir los valores una vez sin actualizarlos una vez que cambien en el objeto de origen, basta con usar objetos simples. Pero si quiere que los elementos DOM de origen y de destino permanezcan sincronizados (enlace bidireccional), entonces deberá plantearse la posibilidad de usar propiedades observables.)
Enlaces integrados
Los ejemplos hasta este punto han ilustrado cómo enlazar con los enlaces de texto y de valor de Knockout. Knockout cuenta con varios enlaces integrados que facilitan el enlace de las propiedades de los objetos a los elementos DOM de destino. Por ejemplo, cuando Knockout se encuentra con un enlace de texto, establecerá la propiedad innerText (con Internet Explorer) o la propiedad equivalente en otros exploradores. Cuando se usa el enlace de texto, se sobrescribe cualquier texto previo. Aunque existen muchos enlaces integrados, los más comunes para la presentación se enumeran en la figura 3. La documentación de Knockout en línea contiene un listado completo en el panel izquierdo de navegación (bit.ly/ajRyPj).
Figura 3 Enlaces comunes de Knockout
Ejemplo | Escenario |
text: model | Enlaza la propiedad (model) con el valor del texto del elemento de destino. Se usa frecuentemente para los elementos de solo lectura, como por ejemplo span. |
visible: isInStock | Enlaza la propiedad de valor (isInStock) con la visibilidad del elemento de destino. El valor de la propiedad se evalúa en verdadero o en falso. |
value: price | Enlaza el valor de la propiedad (price) con el elemento de destino. Se usa frecuentemente con los elementos input, select y textarea. |
css: className | Enlaza el valor de la propiedad (className) con el elemento de destino. Se usa frecuentemente para intercambiar los nombres de las clases de css por elementos DOM. |
checked: isInCart | Enlaza el valor de la propiedad (isInCart) con el elemento de destino. Se usa para los elementos checkbox. |
click: saveData | Agrega un controlador de eventos para la función JavaScript enlazada (saveData) cuando el usuario hace clic en el elemento DOM. Funciona con cualquier elemento DOM, pero se usa frecuentemente con los elementos button, input y a. |
attr: {src: photoUrl, alt: name} | Enlaza cualquier atributo especificado para el elemento DOM con el objeto de origen. Se usa frecuentemente cuando los otros enlaces integrados no son capaces de abarcar la situación, como por ejemplo con el atributo src de la etiqueta img. |
Matrices observables
Ahora que ya se mojó los pies con Knockout en los ejemplos previos, llegó el momento de continuar con un ejemplo más práctico, aunque fundamental, con datos jerárquicos. Knockout permite varios tipos de enlaces de datos, tales como los enlaces a propiedades sencillas (como vimos en los ejemplos anteriores), enlace a matrices de JavaScript, enlaces calculados y enlaces personalizados (que discutiré en un artículo futuro sobre Knockout). El siguiente ejemplo ilustra cómo usar Knockout para enlazar una matriz de objetos de productos con una lista (ver figura 4).
Figura 4 Enlace a una matriz observable
Al tratar con grafos de objetos y enlace de datos, conviene encapsular todos los datos y las funciones que necesita la página en un solo objeto. Esto se denomina frecuentemente como Modelo de vista del patrón MVVM. En este ejemplo la Vista es la página HTML con sus elementos DOM. El Modelo es la matriz con los productos. El Modelo de vista une el Modelo con la Vista y el pegamento usado es Knockout.
La matriz con los productos se prepara con la función observableArray. Esta es similar a ObservableCollection en las tecnologías XAML. Como la propiedad products es un observableArray, cada vez que se agrega o elimina un elemento de la matriz, se notifican los elementos de destino y el elemento se agrega o elimina del DOM, como vemos aquí:
var showroomViewModel = {
products: ko.observableArray()
};
El objeto showroomViewModel es la raíz que se enlazará por datos a los elementos de destino. Contiene una lista de productos que provienen de un servicio de datos en forma de JSON. La función que carga la lista de los productos es la función showroomViewModel.load, que aparece en la figura 5 junto con el resto del JavaScript que configura el objeto showroomViewModel (el código fuente completo de este ejemplo se encuentra en 03-observableArrays.html). La función load itera por los datos de productos del ejemplo y usa la función Product para crear objetos product nuevos antes de insertarlos en observableArray.
Figura 5 Definición de los datos que se enlazan
var photoPath = "/images/";
function Product () {
this.id = ko.observable();
this.salePrice = ko.observable();
this.listPrice = ko.observable();
this.rating = ko.observable();
this.photo = ko.observable();
this.itemNumber = ko.observable();
this.description = ko.observable();
this.photoUrl = ko.computed(function () {
return photoPath + this.photo();
}, this);
};
var showroomViewModel = {
products: ko.observableArray()
};
showroomViewModel.load = function () {
this.products([]); // reset
$.each(data.Products, function (i, p) {
showroomViewModel.products.push(new Product()
.id(p.Id)
.salePrice(p.SalePrice)
.listPrice(p.ListPrice)
.rating(p.Rating)
.photo(p.Photo)
.itemNumber(p.ItemNumber)
.description(p.Description)
);
});
};
ko.applyBindings(showroomViewModel);
Aunque todas las propiedades de los productos están definidas con observables, no necesariamente tienen que ser observables. Por ejemplo, podrían ser propiedades llanas si son de solo lectura y si (al cambiar el origen) no se espera que se actualice el destino o se deba actualizar todo el contenedor. Sin embargo, si hay que actualizar las propiedades en cuanto cambia el origen o este se edite en el DOM, entonces los observables son la opción correcta.
La función Product define todas sus propiedades como observables de Knockout (con excepción de photoUrl). Cuando Knockout enlaza los datos, se puede acceder a la propiedad con la matriz de los productos; esto permite usar fácilmente funciones estándar tales como length para mostrar cuántos elementos están enlazados actualmente por datos:
<span data-bind="text: products().length"></span>
Enlaces de control del flujo
La matriz con los productos luego se puede enlazar por datos a un elemento DOM, donde se puede usar como una plantilla anónima para mostrar la lista. El siguiente HTML muestra que la etiqueta ul usa el enlace de control del flujo foreach para enlazarse a los productos:
<ul data-bind="foreach:products">
<li class="guitarListCompact">
<div class="photoContainer">
<img data-bind="visible: photoUrl, attr: { src: photoUrl }"
class="photoThumbnail"></img>
</div>
<div data-bind="text: salePrice"></div>
</li>
</ul>
Los elementos dentro de la etiqueta ul se usarán para crear plantillas de cada producto. Dentro del bucle foreach, el contexto de los datos también cambia del objeto raíz showroomViewModel a cada producto determinado. Esta es la razón por la cual los elementos internos de DOM se pueden enlazar directamente a las propiedades photoUrl y salePrice de un producto.
Hay cuatro enlaces del control del flujo principales: foreach, if, ifnot y with. Estos enlaces de control nos permiten definir en forma declarativa el flujo de la lógica de control sin crear una plantilla con nombre. Cuando el enlace de control del flujo if está seguido por una condición que se evalúa en verdadero o el enlace ifnot está seguido por una condición que se evalúa en false, los contenidos dentro del bloque se enlazan y se muestran, como vemos aquí:
<div data-bind="if:onSale">
<span data-bind="text: salePrice"></span>
</div>
El enlace with cambia el contexto de los datos en cualquier objeto que especifiquemos. Esto es especialmente útil al explorar grafos de objetos con varias relaciones entre elementos principales y secundarios o diferentes ViewModel dentro de una misma página. Por ejemplo, si hay un objeto ViewModel llamado sale que está enlazado a la página y tiene los objetos secundarios customer y salesPerson, se podría usar el enlace with para que los enlaces sean más legibles y más fáciles de mantener, como vemos aquí:
<div data-bind="with:customer">
<span data-bind="text: name"></span><br/>
<span data-bind="text: orderTotal"></span>
</div>
<div data-bind="with:salesPerson">
<span data-bind="text: employeeNum"></span><br/>
<span data-bind="text: name"></span>
</div>
Observables calculados
Puede que se haya percatado de que la función Product definió photoUrl como un tipo especial de una propiedad calculada. La función ko.computed define una función enlazante que evalúa el valor de la operación de enlace de datos. La propiedad calculada se actualiza automáticamente cuando cambia cualquiera de los observables de los que depende para su evaluación. Esto es particularmente útil cuando el valor no está representado claramente en un valor concreto del objeto de origen. Otro ejemplo común aparte de crear una dirección URL es crear una propiedad fullName a partir de las propiedades firstName y lastName.
(Observación: Las versiones anteriores de Knockout denominaban las propiedades calculadas dependentObservable. Knockout 2.0.0 permite las dos versiones, pero recomiendo usar la función calculada más nueva.)
Las propiedades calculadas aceptan una función para evaluar el valor y el objeto que representará el objeto al cual estamos enlazando el enlace. El segundo parámetro es importante, ya que los objetos literales de JavaScript no tienen cómo referirse a sí mismos. En la figura 5 se pasa la palabra clave this (que representa showroomViewModel) para que la pueda usar la función del observable dependiente para obtener la propiedad photo. Si no se pasara esto, la función photo quedaría indefinida y la evaluación no podría producir la dirección prevista:
this.photoUrl = ko.computed(function () {
return photoPath + photo(); // photo() will be undefined
});
Entender las propiedades fundamentales del enlace
Esta columna lo inició en el enlace de datos con la biblioteca Knockout para JavaScript. El aspecto más importante de Knockout es entender las propiedades de enlace fundamentales: observable, observableArray y computed. Con estos tres observables puede crear aplicaciones robustas en HTML con patrones de separación sólidos. También abordé los tipos de enlaces integrados más comunes e ilustré los enlaces de control del flujo. Pero Knockout tiene muchas más funciones. La próxima vez exploraré los enlaces integrados con mayor profundidad.
John Papa fue un evangelizador de Microsoft en los equipos de Silverlight Windows 8, donde animó el popular programa de televisión Silverlight. También presentó como conferencias magistrales y sesiones en BUILD, MIX, PDC, Tech•Ed, Visual Studio Live! y DevConnections. Papa también es columnista de Visual Studio Magazine (Papa’s Perspective) y autor de vídeos de entrenamiento con Pluralsight. Puede seguirlo por Twitter en twitter.com/john_papa.
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Steve Sanderson