Una sinfonía en C#

Un pequeño aporte a la comunidad de habla hispana.

Automatic html binding con Rivetsjs

Muchos frameworks nos ofrecen binding automático, two-way binding y cosas así out-of-the-box, en ocasiones no tenemos la necesidad de agregar un framework gigante a nuestra aplicación o simplemente no queremos o es impracticable, en tal caso existe una pequeña pero poderosa utilidad para binding: Rivetsjs.

¿Qué es Rivetsjs?

Es una pequeña utilidad para solucionar un único problema: binding automático, es simple de usar pero tal vez la documentación sea su punto más débil, así que vamos a los ejemplos directamente:

Primero ejemplo, binding simple:

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="js/vendor/rivets/dist/rivets.js"></script>
<script type="text/javascript">
	$(function(){
		//definimos un modelo simple, con algunas propiedades
		model = {
			name: "Ariel",
			lastname: "Ortega",
			age: 34
		};
		//indicamos el elemento DOM donde rivets debe bindear el modelo
		rivets.bind($("#view"), model);
	});
</script>
</head>
<body>
<div id="view" >
	<!--los atributos rv-* indican una relación directa entre el modelo y el elemento HTML -->
	<div rv-text="name" ></div>
	<!-- rv-text indica que la propiedad que rivets debe setear es text -->
	<div rv-text="lastname" ></div>
	<!-- en este caso rivets colocará el valor de la propiedad age del model en el text de este elemento-->
	<div rv-text="age" ></div>
</div>
</body>
</html>

Esto no es una gran cosa se puede lograr lo mismo con cualquier motor de templating como underscore por ejemplo (si quisiéramos acceder a un objeto dentro del modelo podemos usar la notación modelo.subobjeto.propiedad), en caso de querer imprimir directamente sin usar un elemento html usamos la siguiente notación:

{propiedad}

(el prefijo de los atributos rv y los delimitadores {} son configurables)

Esto está muy bien pero la parte más interesante es la interesante es lo siguiente.

Escuchar cambios en el modelo

No hay que hacer nada, por defecto rivetsjs ya está escuchando los cambios en el modelo, para comprobarlo vamos a la consola del navegador y comprobamos

image

Como vemos con cualquier cambio que hagamos en el modelo rivetsjs se encarga de actualizar el texto de cada div asociado, estos atributos especiales que usamos para vincular propiedades del modelo con el html se llaman binders en rivertsjs y tenemos varios por defecto:

  • text
  • html
  • enabled
  • disabled
  • checked
  • unchecked

Estos “setean” propiedades en el html, hay otros que sirven para controlar si un elemento se muestra o no, por ejemplo

<div rv-if="show">La propiedad show es true</div>

En este caso si la propiedad show del modelo es true se muestra el elemento, en otro caso no, existen un par de atributos para hacer cosas similares

  • show
  • hide
  • unless

Listas con rivetsjs

Si tenemos un listado y queremos mostrarlo y que, por supuesto, se actualice hacemos:

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="js/vendor/rivets/dist/rivets.js"></script>
<script type="text/javascript">
	$(function(){
		//definimos un modelo simple, con algunas propiedades
		model = {
			name: "Ariel",
			lastname: "Ortega",
			age: 40,
			show: false,
			clubes:[{nombre: "River Plate", goles: 80},
				{nombre: "Valencia Club", goles: 9},
				{nombre: "UC Sampdoria", goles: 8},
				{nombre: "Parma FC", goles:3},
				{nombre: "Fenerbahçe SK", goles: 5},
				{nombre: "Newell's Old Boys", goles: 11},
				{nombre: "Independiente Rivadavia", goles: 4},
				{nombre: "All Boys", goles: 0},
				{nombre: "Defensores de Belgrano", goles: 5}
			]
		};
		//indicamos el elemento DOM donde rivets debe bindear el modelo
		rivets.bind($("#view"), model);
	});
</script>
</head>
<body>
<div id="view" >
	<!--los atributos rv-* indican una relación directa entre el modelo y el elemento HTML -->
	<div rv-text="name" ></div>
	<!-- rv-text indica que la propiedad que rivets debe setear es text -->
	<div rv-text="lastname" ></div>
	<!-- en este caso rivets colocará el valor de la propiedad age del model en el text de este elemento-->
	<div rv-text="age" ></div>
	<div rv-if="show">La propiedad show es true</div>
	<div>Clubes y goles:</div>
	<ul rv-each-club="clubes">
		<li><span>{club.nombre}</span><span>:{club.goles}</span></li>
	</ul>	
</div>
</body>
</html>

de este modos, si vamos a la consola y hacemos

var club = model.clubes.pop();

Se actualiza el html, lo mismo si hacemos ahora

model.clubes.push(club);

image

Excelente.

Eventos y two-way binding

La otra mitad del poder de rivertsjs está dada por la capacidad de actualizar el modelo, algo así:

<input type="text" rv-value="age" />

en este caso si cambiamos el valor del input por otro vamos a ver que el resultado se refleja en el modelo.

La otra parte es poder asociar eventos a funciones en el modelo, del siguiente modo

<input type="button" rv-on-click="gritar" value="probar" />

sencillo, con el atributo rv-on-click asociamos el evento click a un método en el modelo, entonces agregamos en nuestro modelo el método gritar y éste será invocado al presionar el botón

Formatters

Los formatters sirven para manipular el modo en que mostramos en el html los valores del modelo, por ejemplo, un valor numérico con decimales, vemos un ejemplo.

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="js/vendor/rivets/dist/rivets.js"></script>
<script type="text/javascript">
	$(function(){
		//definimos un modelo simple, con algunas propiedades
		model = {
			name: "Ariel",
			lastname: "Ortega",
			age: 40,
			clubes:[{nombre: "River Plate", goles: 80},
				{nombre: "Valencia Club", goles: 9},
				{nombre: "UC Sampdoria", goles: 8},
				{nombre: "Parma FC", goles:3},
				{nombre: "Fenerbahçe SK", goles: 5},
				{nombre: "Newell's Old Boys", goles: 11},
				{nombre: "Independiente Rivadavia", goles: 4},
				{nombre: "All Boys", goles: 0},
				{nombre: "Defensores de Belgrano", goles: 5}
			]
		};
		
		//agregamos el formatter como un nuevo método a los que ya tiene
		rivets.formatters.clubformatter = function(club){
			return club.nombre + ": " + club.goles;
		};

		rivets.bind($("#view"), model);
	});
</script>
</head>
<body>
<div id="view" >
	<div rv-text="name" ></div>
	<div rv-text="lastname" ></div>
	<input type="text" rv-value="age" />
	<div>Clubes y goles:</div>
	<ul rv-each-club="clubes">
		<li><span rv-text="club | clubformatter"></span></li>
	</ul>	
</div>
</body>
</html>

Básicamente definimos un función y con el pipe (|) dentro de la expresión rv-text indicamos, primero el nombre del modelo (en este caso club, el nombre de cada ítem dentro de la iteración) y a continuación el formatter, lo interesante es que podemos poner varios unos tras otro, agregando más pipies.

En caso de necesitarlo podemos pasar parámetros a los formatters del siguiente modo:

<span rv-text="club | clubformatter param1, param2">

Y el formatter lo recibe así:

clubformatter(param1, param2)

 

Usando propiedades calculadas

En lugar de usar propiedades comunes para asociarlas a elementos del DOM podemos utilizar funciones, por ejemplo:

fullName: function(){
	return this.name + " " + this.lastname;
},

y después lo referenciamos en el html

<!-- el nombre completo está asociado a una función -->
<div rv-text="fullName"></div>

y con eso es suficiente, ahora, hay un detalle, si actualizamos el modelo la función fullName no se volverá a ejecutar, nosotros debemos indicar a rivetsjs que esta función tiene que volver a ejecutarse cuando cambie algo del modelo, en nuestro caso como la función depende del nombre le vamos a indicar que cuando detecte cambios en el nombre la ejecute nuevamente

<!-- el nombre completo está asociado a una función -->
<!-- el símbolo < indica dependencia --->
<div rv-text="fullName < name"></div>

Ahora sí va a cambiar cuando el nombre cambie, en este caso dependemos de el nombre y el apellido así que podemos indicarle todas las dependencias que queramos

<!-- el nombre completo está asociado a una función -->
<!-- el símbolo < indica dependencia --->
<div rv-text="fullName < name fullName"></div>

Como vemos rivetsjs es una linda utilidad para tener en nuestra caja de herramientas, dejo el ejemplo completo acá, nos leemos.

Comments (4) -

  • Rodrigo

    4/27/2014 11:07:10 PM | Reply

    Que tecnica usa para detectar los cambios en los modelos? Porque a primera vista parece que usa dirty checking.

  • leonardo

    4/28/2014 8:44:48 AM | Reply

    Buena pregunta, usa Object.defineProperty, por eso mismo no funciona en IE<9. Qué sería "dirty checking"?

  • Rodrigo

    4/28/2014 9:53:56 AM | Reply

    "dirty checking" vendría a ser una comparación propiedad por propiedad del modelo por cambios por una copia en memoria, o contra un hash del objeto. Para evitar getters/setters (como backbone.js y ember), angular por ejemplo usa esta práctica con lo que ellos llaman un "digest cycle", que no es más que un loop glorificado que está constantemente mirando si los modelos en el scope cambiaron. Muy malo para la performance. Otro punto por el que Angular apesta Tong

    habría que ver si rivet se puede extender para usar algun otro microframework que implemente observers, o un shim para observers de ES6.

  • leonardo

    4/28/2014 12:23:32 PM | Reply

    Según leí en github, están esperando que observers esté implementado masivamente para cambiar a observers, por el momento si querés que funciones en legacy tenés que implementar un apadpter tuyo que use setters.
    ¿Qué otras cosas apestan el Angular según vos? a mí particularmente no me gusta el framework que te limita la arquitectura.

Loading