1. Code
  2. JavaScript

En el Ring con knockout.js

En la esquina roja, con un peso de solo 29Kb (sin comprimir), está knockout.js; una biblioteca de JavaScript pura que simplifica la creación de interfaces de usuario dinámicas. Knockout es independiente de la biblioteca, por lo que se puede usar fácilmente con cualquiera de las bibliotecas de JavaScript más populares que ya están disponibles, pero funciona especialmente bien con jQuery, y usa jQuery.tmpl como su motor de plantillas predeterminado.
Scroll to top
12 min read
This post is part of a series called Into the Ring with Knockout.js.

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

En la esquina roja, con un peso de solo 29Kb (sin comprimir), está knockout.js; una biblioteca de JavaScript pura que simplifica la creación de interfaces de usuario dinámicas. Knockout es independiente de la biblioteca, por lo que se puede usar fácilmente con cualquiera de las bibliotecas de JavaScript más populares que ya están disponibles, pero funciona especialmente bien con jQuery, y usa jQuery.tmpl como su motor de plantillas predeterminado.

Knockout no pretende ser un reemplazo para jQuery.

Knockout no pretende ser un reemplazo para jQuery; jQuery es muy popular, ya que todos ustedes saben que yo también soy un gran fanático, y es muy bueno en lo que hace. Pero es difícil crear interfaces de usuario complejas utilizando solo jQuery; cuanto más grande sea la aplicación detrás de la interfaz, y cuanto más pueda interactuar el usuario con ella, más difícil será mantener cierta apariencia de orden. Los controladores de eventos abundan, y rápidamente terminas con literalmente cientos de líneas de código.

Es perfectamente posible crear interfaces de usuario complejas y altamente dinámicas solo con jQuery, pero ¿el presupuesto de su proyecto tiene el tiempo necesario para escribir y depurar más de 800 líneas de código? ¿Qué ocurre dentro de 6 meses cuando algo necesita cambiar o agregarse? Aquí es donde entra knockout.

Visión general

En este tutorial construiremos una interfaz simple que muestra una lista de contactos y luego permite que el visitante interactúe con la interfaz de usuario para cambiar la forma en que se muestran los datos, como filtrar la lista o clasificarla. Usaremos knockout como una capa entre nuestros datos y la página para simplificar la creación y la administración o nuestra interfaz de usuario.


Ronda 1 - Comenzando

Knockout utiliza una arquitectura de modelo Vista-modelo-vista. La lista visible de contactos que usamos en este ejemplo y los elementos en la página en que se componen, se pueden considerar como una vista. Los datos que se muestran en la página es el modelo. El modelo de vista es una representación del estado actual de la interfaz de usuario, una combinación de los datos y la vista que también contiene el comportamiento utilizado para interactuar con el modelo y actualizar la vista.

Comencemos por crear la estructura de carpetas que necesitaremos y la página básica con la que trabajaremos. Cree una nueva carpeta llamada knockout en alguna parte de su sistema, luego dentro de esta carpeta cree tres nuevas carpetas llamadas css, img y js. La carpeta css se usará para guardar la hoja de estilo simple que usaremos, y la carpeta img la imagen única. La carpeta js contendrá el archivo de script que creamos, así como las bibliotecas de las que dependemos. Inicialmente, esta carpeta deberá contener los siguientes archivos:

  • jquery.tmpl.js
  • jquery-1.6.2.js
  • knockout-1.2.1.js

Ahora, en tu editor de texto, crea la siguiente página básica:

1
<!DOCTYPE html>
2
<html>
3
    <head>
4
        <title>Knockout</title>
5
        <link rel="stylesheet" href="css/styles.css" />
6
    </head>
7
    <body>
8
        <script src="js/jquery-1.6.2.min.js"></script>
9
        <script src="js/jquery.tmpl.js"></script>
10
        <script src="js/knockout-1.2.1.js"></script>
11
        <script src="js/behavior.js"></script>
12
    </body>
13
</html>

Guarde esta página como index.html en la carpeta knockout de raíz. Hasta ahora, no hay nada digno de mención aquí que no sea el uso de HTML5. Aunque knockout.js es compatible con versiones anteriores de HTML, los atributos que agregaremos a nuestros elementos no forman parte del estándar HTML 4.01 estándar y, por lo tanto, la página no será válida. Este no es el caso de HTML5, que define atributos data-* para incrustar datos personalizados.

También usamos una hoja de estilo básica para este ejemplo, pero solo se usa para este ejemplo en particular y es completamente arbitraria. Como este no es un tutorial de CSS, evitaré mostrarlo aquí, pero si tienes curiosidad, mira el archivo en la demostración.

El archivo de comportamiento

A continuación, podemos crear nuestro archivo de comportamiento; en una nueva página en su editor de texto agregue el siguiente código:

1
(function ($) { var model = [{  name: "John",  address: "1, a road, a town, a county, a postcode",  tel: "1234567890",  site: "www.aurl.com", pic: "/img/john.jpg",  deleteMe: function () { viewModel.people.remove(this); } 
2
    }, {  name: "Jane",  address: "2, a street, a city, a county, a postcode",  tel: "1234567890",  site: "www.aurl.com",  pic: "/img/jane.jpg",  deleteMe: function () { viewModel.people.remove(this); } 
3
    }, {  name: "Fred",  address: "3, an avenue, a village, a county, a postcode",  tel: "1234567890",  site: "www.aurl.com",  pic: "/img/fred.jpg",  deleteMe: function () { viewModel.people.remove(this); } 
4
    }, {  name: "Freda",  address: "4, a street, a suburb, a county, a postcode",  tel: "1234567890",  site: "www.aurl.com",  pic: "/img/jane.jpg",  deleteMe: function () { viewModel.people.remove(this); } 
5
    }], viewModel = { people: ko.observableArray(model),
6
    }
7
  }; ko.applyBindings(viewModel);
8
    
9
})(jQuery);

Guarde este archivo como behavior.js en la carpeta js. Comenzamos definiendo una función de autoinvención, a la que pasamos jQuery para asignar un alias al carácter $.

Luego definimos el modelo que usaremos. En este ejemplo, es una matriz local, pero podríamos obtener exactamente el mismo formato de datos de un servicio web con la suficiente facilidad. Nuestra array contiene una serie de objetos object de personas, que corresponden a entradas individuales en una base de datos contacts. Principalmente, nuestros datos consisten en cadenas simples, pero cada object también contiene un method deleteMe, que se utiliza para eliminar el object de viewModel.

Recuerde, el viewModel se refiere al estado actual de la interfaz de usuario. Es un objeto, y el primer elemento que le agregamos es nuestra array que contiene los objetos de personas. Usamos el ko.observableArray() method para agregar nuestra array al viewModel object. Los observables son un aspecto fundamental de knockout.js; Estamos dando instrucciones para que las otras entidades observen estos elementos y reaccionen cuando cambian.

Este es todo lo que nuestro modelo de vista contiene en este momento, aunque hemos dejado una coma detrás del valor de la propiedad de personas para cuando agregamos más propiedades.

Después del object object, usamos el ko.applyBindings() method para aplicar cualquier enlace que hayamos creado y comenzar a administrar el viewModel. En este punto del ejemplo, aún no hemos agregado ningún enlace. Para crear enlaces entre nuestra view y viewModel, necesitamos agregar un poco más de HTML.


Ronda 2 - Creando una vista

Knockout funciona brillantemente con las plantillas de jQuery.

Ahora tenemos nuestro model y un simple viewModel en su lugar. Lo siguiente que debemos hacer es mostrar los datos de viewModel en la página. Knockout funciona brillantemente con las plantillas de jQuery. Esto nos permite usar el complemento tmpl para construir el HTML requerido. Agregue el siguiente código al elemento <body> de la página, directamente antes de los elementos <script>:

1
<div id="people" data-bind="template: { name: 'personTemplate', foreach: people }">
2
</div>
3
<script id="personTemplate" type="text/x-jquery-tmpl">
4
    <section class="person">
5
        <img src="../img/person.png" alt="${ name }" />
6
        <h1>${ name }</h1>

7
        <address>${ address }</address>

8
        <span class="tel">${ tel }</span>

9
        <a href="http://${ site }" title="Visit site">${ site }</a>

10
        <div class="tools">
11
            <button data-bind="click: deleteMe">Delete</button>

12
        </div>

13
    </section>

14
</script>

Primero agregamos un elemento <div> vacío con un ID, principalmente para propósitos de estilo. Este elemento también tiene un atributo especial - data-bind. Este atributo le dice a los knockout que el elemento almacena sus datos en viewModel. Cuando llamamos a ko.applyBindings() en nuestro JS, este es un enlace que se aplica. En este caso, usamos el enlace de plantilla, que nos permite especificar el nombre de una plantilla que nos gustaría usar en un objeto de configuración pasado al enlace.

También usamos la propiedad foreach en este objeto de configuración y especificamos el nombre de nuestra gente observableArray como la fuente de nuestros datos. Podríamos usar la sintaxis tmpl estándar, {{each}}, para iterar sobre los datos de nuestra gente, pero es más eficiente usar la sintaxis de eliminación directa. Debido a que la información de nuestra gente está contenida dentro de una array, observable, knockout controlara el array para detectar cambios y, cuando ocurra, actualizará automáticamente cualquier plantilla que muestre los datos. Si usamos la sintaxis de tmpl, nuestra plantilla completa se representará cada vez que cambien los datos, pero cuando usamos la propiedad foreach de knockout, solo se representará la única instancia correspondiente al elemento que ha cambiado.

Siguiendo el contenedor <div> definimos nuestra plantilla. Esto se hace de la misma manera que una plantilla tmpl normal. Dentro de la plantilla, especificamos los elementos que nos gustaría repetir para cada objeto en nuestra fuente de datos. Tenemos un elemento <section> como contenedor, seguido de un elemento apropiado para cada elemento dentro del person object. Una cosa a tener en cuenta es que podemos suministrar enlaces en nuestro código de plantilla. Agregamos un atributo data-bind a un botón de eliminar; esta vez usamos el enlace click y especificamos el nombre de la person que se encuentra dentro de cada person object.

Cuando ejecutamos la página en un navegador, debemos encontrar que nuestra página contiene los datos de nuestro viewModel,

Así que eso está bien, ¿no? Pero no es tan diferente a usar el plugin tmpl.

Lo realmente genial es que view no solo se actualiza en consecuencia cuando cambia viewModel, también se actualiza viewModel cuando cambia la vista. Entonces, si hacemos clic en uno de los botones de eliminar de nuestra página, ¡la people array también tendrá el person object correspondiente eliminado!

La array original que pasamos al ko.observable() method no está realmente actualizada, pero normalmente, probablemente obtendremos nuestros datos de una solicitud AJAX en lugar de codificarla en la página, así que todo lo que necesitaríamos Lo que hay que hacer es volver a enviar los datos, con person eliminada.


Ronda 3 - Agregando nuevos datos

Tenemos la capacidad de eliminar un person object; a continuación, podemos agregar la capacidad de agregar una nueva persona a nuestro dataModel; Actualice el contenedor <div> que agregamos a la página anterior para que contenga los siguientes elementos nuevos:

1
<a href="#" title="Add new person" data-bind="click: showForm, visible: displayButton">Add person</a>
2
<fieldset data-bind="visible: displayForm">
3
    <div class="details">
4
        <label>Name: <input id="name" /></label>
5
        <label>Address: <input id="address" /></label>
6
        <label>Tel: <input id="tel" /></label>
7
        <label>Site: <input id="site" /></label>
8
    <div>
9
    <div class="img">
10
        <label>Picture: <input id="pic" type="file" /></label>
11
    </div>
12
    <div class="tools">
13
        <button data-bind="click: addPerson">Add</button>
14
        <button data-bind="click: hideForm">Cancel</button>
15
    </div>
16
</fieldset>

El primer elemento nuevo que agregamos es una etiqueta <a>, que se utiliza para abrir el formulario que aceptará los nuevos datos. Esto es similar a cómo lo haríamos en una implementación jQuery regular, excepto que también tendríamos que agregar un controlador de eventos para escuchar los clics en el elemento y hacer cosas como detener el evento. Con nocauts, no tenemos que preocuparnos por nada de eso. Todo lo que necesitamos hacer es especificar el nombre de un method dentro de nuestro viewModel, que nos gustaría ejecutar cada vez que se hace clic en el elemento. Knockout adjuntará el controlador y detendrá el enlace que se está siguiendo.

Como puede ver, podemos especificar varios enlaces en un elemento. Nuestro elemento <a> también utiliza el enlace visible. De nuevo, especificamos una propiedad de nuestro viewModel, excepto que esta vez, no es una función sino una variable simple que contiene un valor booleano; verá cómo funciona esto cuando lleguemos a agregar el JS para nuestra nueva funcionalidad en un momento.

Después del enlace, también agregamos un <fieldset> que contiene etiquetas y entradas que podemos usar para agregar los datos relevantes para crear un nuevo object en nuestra people array. Al final de nuestro nuevo HTML, agregamos dos nuevos elementos <button>; ambos tienen enlaces de clic añadido a ellos. Los primeros enlaces al addPerson method, el segundo al hideForm method. La carga de imágenes en realidad no funciona en este ejemplo, solo está ahí para mostrar.

Ahora echemos un vistazo al nuevo JavaScript que necesitamos; agregue el siguiente código directamente después de la propiedad people de nuestro viewModel (dejamos una coma colgante lista para agregar estas nuevas propiedades y métodos):

1
displayButton: ko.observable(true), displayForm: ko.observable(false), showForm: function () { viewModel.displayForm(true).displayButton(false);
2
}, hideForm: function () { viewModel.displayForm(false).displayButton(true);
3
}, addPerson: function () { viewModel.displayForm(false).displayButton(true).people.push({ name: $("#name").val(), address: $("#address").val(), tel: $("#tel").val(), site: $("#site").val(), pic: "", deleteMe: function () { viewModel.people.remove(this); }
4
    });
5
}

La primera propiedad es displayButton, que es una propiedad observable (su valor puede ser observado) por otras entidades. La entidad que observa su valor es nuestro elemento <a> en la vista. Inicialmente lo establecimos en true, por lo que cuando se carga la página (o más bien cuando se llama al método applyBindings()), el enlace estará visible.

La siguiente propiedad se llama displayForm, que también es observable, excepto que, esta vez, la configuramos como false, por lo que el elemento en nuestra vista que lo está observando (el fieldset) estará inicialmente oculto.

Luego agregamos dos métodos: showForm() y hideForm(). Estos dos métodos simples se usan, obviamente, para mostrar u ocultar el formulario respectivamente, y para hacerlo, todo lo que necesitan hacer es establecer la propiedad observable displayForm en true o false. Debido a que se está observando el valor, cada vez que cambie su valor, nuestra vista se actualizará automáticamente.

También ajustamos la propiedad showButton cada vez que cambia el estado del formulario. Si el fieldset es visible, ocultamos el enlace, y si ocultamos el fieldset, el botón se vuelve visible nuevamente. Como puede ver, el knockout admite el encadenamiento, lo que facilita enormemente la actualización de múltiples propiedades en nuestro viewModel. La vista debería aparecer así cuando el formulario es visible:

El último método que agregamos es el addPerson() method, que se utiliza para actualizar nuestro viewModel con los detalles de la nueva persona. Todo lo que hacemos en este método es ocultar el formulario y mostrar el botón, y crear un objeto literal que contenga los valores ingresados ​​en los campos de texto y luego empujar este object en nuestra people array.

Para recuperar la people array actualizada de nuestro viewModel, podemos usar el serializador JSON incorporado para eliminar la array observable en un object JSON. Normalmente haríamos esto para devolver los datos al servidor, pero, para probarlo, podríamos agregar esta línea de código al final del addPerson() method:

1
console.log(ko.toJSON(viewModel.people));

El ko.toJSON() method genera un object JSON que contiene el contenido actual de la people array, que podemos ver en Firebug (hay otros exploradores DOM disponibles):


Revisión posterior a la pelea

En este tutorial, cubrimos dos aspectos principales de knockout.js: enlaces declarativos y observables.

Los enlaces se aplican en nuestro HTML y especifican propiedades y matrices de datos cuyos valores deben observarse para los cambios. Cuando estos valores cambian, los elementos de la vista que los observan se actualizarán automáticamente, ya sea aplicando una nueva iteración de una plantilla o mostrando u ocultando un elemento, como en este ejemplo.

Hay otros enlaces que podemos utilizar para realizar diferentes acciones cuando la vista interactúa o cuando se actualizan los datos en viewModel.

Knockout.js es una capa extremadamente útil que se encuentra entre la interfaz de nuestra interfaz de usuario y los datos subyacentes, y administra las interacciones y los cambios de estado para nosotros. Hace mucho trabajo para nosotros, aunque en realidad solo hemos arañado la superficie de lo que es capaz en este ejemplo básico. ¿Qué piensas de knockout.js?