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.

Backbonejs: Rutas

Dijimos que Backbone es especialmente útil para aplicaciones SPA (single page applications) así que vamos a ver un componente que nos ayuda con este tipo de aplicaciones.

¿Qué es una SPA?

Son aplicaciones que transcurren en la misma página, sin ir de una página a otra, un ejemplo típico de Gmail, cuando estamos en la bandeja de entrada vemos un listado de mails, hacemos click en uno de ellos y seguimos en la misma página pero si mirando con atención vamos a notar que parte de la URL ha cambiado, es decir, permanecimos dentro de la misma página pero, de algún modo, hemos ido a un lugar dentro de ella.

Cuando estamos en la bandeja de entrada vemos esta URL

https://mail.google.com/mail/u/0/?pli=1#inbox

Cuando vemos un mail vemos la siguiente

https://mail.google.com/mail/u/0/?pli=1#inbox/145511f0ec63a6d4

Lo que ocurre que por más que cambiemos algo de la URL posterior al numeral ( # ) no se ejecutará la navegación pero nosotros podemos saberlo desde Javascript (y decidir hacer algo), llamamos a esto navegación por anclas.

Navegación por anclas

Un ejemplo muy sencillo de navegación podemos verlo con referencias dentro de la misma página, vamos a Wikipedia para comprobarlo. Ingresamos a la URL del artículo sobre Backbonejs escribiendo esto en el navegador

http://en.wikipedia.org/wiki/Backbonejs

Hasta ahora todo normal, pero si agregamos al final de la URL el numeral y una referencia vemos que el navegador cambia de sitio dentro de la página

http://en.wikipedia.org/wiki/Backbonejs#References

Lo que hicimos no fue más que ir hasta un elemento dentro de la página llamado References, esto es automático, lo interesante es que podemos poner cualquier cosa después del numeral y además si presionamos el botón volver del navegador volvemos a la misma URL tal como era antes, con lo cual podemos navegar estando siempre en la misma página.

Nosotros desde Javascript podemos saber de estos cambios en la URL y hacer cosas, en esto se basa el modelo de rutas de Backbone, vamos a ver un ejemplo:

<html>
<head>
	<script type="text/javascript" src="js/vendor/jquery/jquery.js"></script>
	<script type="text/javascript" src="js/vendor/underscore/underscore.js"></script>
	<script type="text/javascript" src="js/vendor/backbone/backbone.js"></script>
	<script type="text/javascript">
		//creamos una clase Router extendiendo la clase base de Backbonejs
		var Router = Backbone.Router.extend({
			//definimos las rutas
			routes:{
				"": "home", // la url por defecto va a llamar a la función home
				"accion1": "funcion1" //la url #accion1 va a llamar a funcion1
			},
			home: function(){
				console.log("estamos en casa");
			},
			funcion1: function(){
				console.log("la acción 1 ha sido llamada");
			}			
		});
		
		//creamos un router
		var router = new Router();	
		//iniciamos el motor de navegación
		Backbone.history.start();	
	</script>
</head>
<body>
<a href="#accion1">Ir a la acción 1</a>
</body>
</html>

Básicamente cargamos este html en el navegador se escribe en la consola “estamos en casa” y cuando presionamos el link (que apunta a #acccion1) “la acción 1 ha sido llamada”, entonces con Backbone es muy simple escuchar estos cambios en la URL y llamar funciones a partir de ello.

Pasar parámetros a las rutas

Hay muchas cosas que se pueden hacer con las rutas, por ejemplo pasar parámetros, del siguiente modo:

<html>
<head>
	<script type="text/javascript" src="js/vendor/jquery/jquery.js"></script>
	<script type="text/javascript" src="js/vendor/underscore/underscore.js"></script>
	<script type="text/javascript" src="js/vendor/backbone/backbone.js"></script>
	<script type="text/javascript">
		//creamos una clase Router extendiendo la clase base de Backbonejs
		var Router = Backbone.Router.extend({
			//definimos las rutas
			routes:{
				"": "home", // la url por defecto va a llamar a la función home
				"accion1": "funcion1", //la url #accion1 va a llamar a funcion1
				"accion1/:parametro1": "funcion2" //la url #accion1 va a llamar a funcion1
			},
			home: function(){
				console.log("estamos en casa");
			},
			funcion1: function(){
				console.log("la acción 1 ha sido llamada");
			},
			funcion2: function(parametro1){
				console.log("se llama a la acción con el valor de parámetro: " + parametro1);
			}
		});
		
		//creamos un router
		var router = new Router();	
		//iniciamos el motor de navegación
		Backbone.history.start();	
	</script>
</head>
<body>
<a href="#accion1">Ir a la acción 1</a> </br>
<a href="#accion1/valor1">Ir a la acción 1 y pasar un parámetro</a>
</body>
</html>

Mágico, para no extenderme más con detalles que se pueden ver en la documentación los dejo por ahora con un link a un gist de este código, nos leemos.

Backbonejs: Colecciones

 

Qué son las colecciones?

Básicamente es un array de objetos del mismo tipo, aunque no necesariamente deberían serlo, la idea es tener una un lugar desde donde tomar nuestros datos y guardarlos (al menos temporalmente) y tener un conjunto de métodos para filtrar y demás, adicionalmente podemos interactuar con el backend de la aplicación.

Ejemplo de una colección

Como primer ejemplo vamos a crear un modelo y luego una colección de ese modelo, sería así:

//declaramos el modelo
var Tarea = Backbone.Model.extend({
	defaults:{
		done: false
	}
});
//declaramos la colección
var Tareas = Backbone.Collection.extend({
	model: Tarea //indicar el tipo de modelo a la colección
				 //permite agregar elementos como json
});

//creamos una colección y le pasamos un array de modelos
//como declaramos el tipo de modelo backbonejs se encarga
//de generar cada uno sin nuestra ayuda
var tareas = new Tareas(
	[
		{"name": "comprar lechuga"},
		{"name": "llamar a Roger"},
		{"name": "ir a nadar"}
	]
);

console.log(tareas.toJSON());

Como vemos podemos indicar el tipo de modelo que va a manipular la colección a través del parámetro model esto nos permite agregar elementos luego pasando simplemente un json, la colección se va a encargar de instanciar la clase

image

Agregando elementos a la colección

Para agregar un elemento simplemente invocamos el método add, como hemos definido la propiedad model podemos agregar un json simplemente y la colección se encargará de generar un modelo

tareas.add({name: "ir al chino"});

Recuperando elementos de la colección

En este caso pasa algo interesante, backbone va a agregar un atributo cid a cada elemento de manera automática (client id) que podemos usarlo para manipular los elementos, en este caso para recuperar el primer elemento de la colección haríamos:

tareas.get("c1");

esto está bien, pero no tiene mucha relación con lo que nosotros definimos para nuestro modelo, podemos buscar por un atributo en particular

tareas.get({cid: "c3"});

sin embargo en el caso que nuestro modelo tuviera un atributo llamado id, cuando hacemos

tareas.get("1");

backbone busca por id en lugar de cid, bien.

Agregando métodos útiles a la colección

Una cosa interesante es agregar métodos para acceder a la colección interna, por ejemplo, con un filtro, algo así:

var Tareas = Backbone.Collection.extend({
	model: Tarea, //indicar el tipo de modelo a la colección
				 //permite agregar elementos como json
	getDone: function(){ //agregamos un método que retorne
						 //las tareas terminadas
		return this.where({done: true}); //el método where es de la clase base
	}
});

Ok, si lo invocamos retorna los elementos de la colección que tiene la propiedad done en true.

Eventos en las colecciones

Del mismo modo que podemos escuchar eventos del modelo podemos hacerlo con las colecciones, por ejemplo cuando se agrega o quita un elemento del siguiente modo:

//declaramos el modelo
var Tarea = Backbone.Model.extend({
	defaults:{
		done: false
	}
});
//declaramos la colección
var Tareas = Backbone.Collection.extend({
	model: Tarea, //indicar el tipo de modelo a la colección
				 //permite agregar elementos como json
	getDone: function(){ //agregamos un método que retorne
						 //las tareas terminadas
		return this.where({done: true}); //el método where es de la clase base
	}
});

//creamos una colección y le pasamos un array de modelos
//como declaramos el tipo de modelo backbonejs se encarga
//de generar cada uno sin nuestra ayuda
var tareas = new Tareas(
	[
		{"name": "comprar lechuga"},
		{"name": "llamar a Roger"},
		{"name": "ir a nadar"}
	]
);

tareas.on("add", function(){
	console.log("se ha agregador algo");
});

image

También podemos escuchar cambios en elemento dentro de la colección, es decir, en modelos

tareas.on("change", function(){
	console.log("algo ha cambiado");
});

 

Existen varios métodos más en las colecciones, por ejemplo para volver el estado al principio con reset, para sincronizar con el servidor con sync, etc. para no extendernos lo dejamos acá e iremos viendo eventualmente en otros ejemplos lo que falta.

Dejo el ejemplo completo acá. La próxima vamos a hablar sobre ruteo y después vamos a  ver el tema de interacción con el servidor.

Nos leemos.

Backbonejs: La vista

Qué es la vista?

En ella vamos a mostrar nuestro modelo, en este caso al tratarse de un navegador web será en HTML, y responderá a la interacción del usuario.

Cómo creo una vista con Backbone?

Primero que nada utilizamos el mismo patrón que para crear un modelo, del siguiente modo:

var Vista = Backbone.View.extend({});

y luego para utilizarla

var vista = new Vista();

Definiciones y cosas que tenemos por defecto

Si no hacemos más nada que lo anterior nuestra vista no hace gran cosa, de hecho, no hace nada, lo que acabamos de lograr es extender la clase de Backbone para tener algunos métodos y propiedades (que vamos a usar después) pero en sí, por ahora, no hace mucho

El método render

Este método nos permitirá imprimir nuestra vista sobre algún otro elemento de la página, sin embargo backbone no nos da ninguna definición especial de este método (es decir, nada nos impide ponerle otro nombre) pero es lo más común hacerlo de este modo, vamos a ver una primera implementación

		var Vista = Backbone.View.extend({
			render: function(){
				return this;
			}
		});
				
		var vista = new Vista();
		
		$("#container").append(vista.render().el);	

Como vemos no hay nada especial, simplemente retornamos el mismo objeto vista en el método render (claro, es un ejemplo) la parte más importante del ejemplo es ver la línea final, luego de llamar al método render accedemos al objeto el, este objeto es parte de la clase base y va a contener el HTML que vamos a imprimir en la vista, entonces, siempre vamos a retornar this en nuestro método render para poder acceder a el y poner en la página. Si corremos este código vemos lo siguiente:

image

Por defecto el contenido de la vista es un simple div, el mismo se encuentra contenido en la propiedad el

La propiedad el de la vista

Como vimos en esta propiedad se guarda el contenido de nuestra vista, existe otra propiedad (muy similar) que se llama $el, que es igual que el pero tiene un wrapper de jQuery (o Zepto, o la biblioteca que hayamos usado para manipular el DOM), vamos a interactuar con esta propiedad cuando queramos manipular el contenido de nuestra vista.

El tagName

La propiedad tagName de la vista nos permite cambiar el tipo de elemento que genera, por defecto es un div pero si configuramos la propiedad como un strong por ejemplo es lo que obtendremos.

var Vista = Backbone.View.extend({
	tagName: "strong", //cambiamos el elemento de la vista
	render: function(){
		return this;
	}
});
		
var vista = new Vista();

$("#container").append(vista.render().el);

image

Usando un template en la vista

Como dije al principio backbone no nos dice mucho de la vista así que muchas de las cosas que hacen son como convenciones, una de ellas es asignar a la vista una propiedad template y colocar dentro código HTML que vamos a renderizar y con esto reemplazar totalmente el contenido del elemento el, por ejemplo:

var Vista = Backbone.View.extend({
	template: "<strong>hola</strong>",
	render: function(){
		this.el = this.template;
		return this;
	}
});
		
var vista = new Vista();

$("#container").append(vista.render().el);

image

Agregando el modelo a la vista

Bien, ahora vamos a intentar mostrar el contenido del modelo en la vista, para hacer vamos a crear un modelo como hicimos antes y pasarlo a la vista para que ésta lo muestre, el código sería así:

//definimos un modelo sin nada en especial
var Modelo = Backbone.Model.extend({
});	
//definimos una vista, vamos a usar el div por defecto
//en "el" para agregarle datos del modelo
var Vista = Backbone.View.extend({
	render: function(){
		//utilizamos $el para agregar el dato del modelo
		this.$el.append(this.model.get("texto"));
		return this;
	}
});
//creamos una instancia del modelo y asignamos la propiedad texto		
var modelo = new Modelo({texto: "hola mundo"});
//instanciamos una vista y pasamos el modelo en el constructor
//utilizamos la propiedad model
var vista = new Vista({model: modelo});
//renderizamos la vista
$("#container").append(vista.render().el);

image

Eventos, escuchando y cambiando el modelo

Una cosa interesante de backbone es que la comunicación entre componentes la podemos hacer totalmente a través de eventos, vamos a ver cómo hacemos para que la vista sepa que el modelo cambió.

Existe un método en la vista el cual es ejecutado al crearse la misma, que sirve las veces de constructor, aquí es donde nos “atachamos” a los eventos del modelo para saber que cambió, del siguiente modo:

//definimos un modelo sin nada en especial
var Modelo = Backbone.Model.extend({
});	
//definimos una vista, vamos a usar el div por defecto
//en "el" para agregarle datos del modelo
var Vista = Backbone.View.extend({
	initialize: function(){ //este método se ejecuta el crear una instancia
		this.model.on("change", this.render, this); 
		//escuchamos el evento "change" de nuestro modelo
		//y indicamos que cuando ocurra se ejecute el método
		//"render" (el último parámetro es el contexto)
	},
	render: function(){
		//utilizamos $el para agregar el dato del modelo
		this.$el.html(this.model.get("texto"));
		return this;
	}
});
//creamos una instancia del modelo y asignamos la propiedad texto		
var modelo = new Modelo({texto: "hola mundo"});
//instanciamos una vista y pasamos el modelo en el constructor
//utilizamos la propiedad model
var vista = new Vista({model: modelo});
//renderizamos la vista
$("#container").append(vista.render().el);

Si ahora ejecutamos la aplicación y desde la consola hacemos:

model.set("texto", "chau")

La vista se vuelve a dibujar.

Acá tenemos una lista completa de los eventos a los que nos podemos “atachar” entre ellos está, por supuesto”, el evento “invalid”.

Cambiar el contenido del modelo cuando algo cambia en la vista

Backbone tiene una forma de escuchar los eventos dentro de una vista con un sintaxis similar a la de jQuery, el único detalle a tener en cuenta es que el contexto sobre el que se trabaja es siempre el elemento $el, veamos un ejemplo

//definimos un modelo sin nada en especial
var Modelo = Backbone.Model.extend({
});	
//definimos una vista, vamos a usar el div por defecto
//en "el" para agregarle datos del modelo
var Vista = Backbone.View.extend({
	initialize: function(){ //este método se ejecuta el crear una instancia
		this.model.on("change", this.render, this); 
		//escuchamos el evento "change" de nuestro modelo
		//y indicamos que cuando ocurra se ejecute el método
		//"render" (el último parámetro es el contexto)
	},
	render: function(){
		//utilizamos $el para agregar el dato del modelo
		this.$el.html(this.model.get("texto"));
		return this;
	},
	events:{
		"click": function(){ //escuchamos el evento click
							 //sobre todo $el
			this.model.set("texto", "click!!"); //modificamos el modelo
		}
	}
});
//creamos una instancia del modelo y asignamos la propiedad texto		
var modelo = new Modelo({texto: "hola mundo"});
//instanciamos una vista y pasamos el modelo en el constructor
//utilizamos la propiedad model
var vista = new Vista({model: modelo});
//renderizamos la vista
$("#container").append(vista.render().el);

En este caso escuchamos el evento “click” sobre todo $el (porque no definimos agregamos un selector después del nombre del evento click) y cuando ocurre el evento modificamos el modelo, después, ya que estamos escuchando los cambios del modelo, automáticamente se vuelve a dibujar con el nuevo valor de la propiedad texto, para resumir dejo un link a un ejemplo un poco más completo, con un template.

Nos leemos.

Backbonejs: el modelo

Qué es el modelo?

El modelo es la abstracción que representa nuestro sistema, con sus datos y comportamiento, en principio debería ser totalmente independiente de los demás componentes de la aplicación (por ejemplo, debería ser independiente de la UI)

Creando un modelo con backbone

Para crear un modelo en backbone tenemos que hacer dos cosas:

  • Declarar una clase
  • Instanciarla

Para crear la clase de nuestro modelo necesitamos extender una clase de backbone, del siguiente modo:

var Task = Backbone.Model.extend();

con esto generamos una clase Task que extiende la clase Model de backbone, esto nos permite utilizar su funcionalidad, por ejemplo podemos hacer:

var task = new Task({name: "hacer algo"});
console.log(task.toJSON());

y obtenemos una representación JSON de nuestro modelo, esta funcionalidad la heredamos de la clase base de backbone.

Otra cosa que podemos hacer es tener propiedades por defecto, por ejemplo:

var Task = Backbone.Model.extend({
	defaults: {
		done: false
	}
});

de esta manera indicamos que la propiedad done, tiene un valor por defecto en false, entonces si hacemos

var task = new Task({name: "hacer algo"});
console.log(task.toJSON());

Obtenemos:

image

Leer y escribir propiedades

Si queremos leer o escribir alguna propiedad del modelo no podemos simplemente acceder a ella con el  (puntro)., es decir, no podemos hacer

	console.log(task.name);

Lo que necesitamos es acceder a los métodos get y set respectivamente

	task.set("name", "hablar con el dentista");
			
			
	console.log( task.get("name") );

No olvidemos que ahora estamos accediendo a un clase que hereda de otra, y además backbone necesita controlar el acceso a las propiedades del modelo para manejar las validaciones y notificaciones.

Validaciones

Para agregar validaciones a nuestro modelo tenemos que definir un método validate dentro de la declaración del mismo, por ejemplo

var Task = Backbone.Model.extend({
	defaults:{
		done: false
	},
	validate: function(attributes){
		if(attributes.name.length===0) return "the name is required";
	}
});

simplemente definimos el método validate, el cual recibe los atributos de la clase como parámetro y ponemos nuestro código de validación, por convención siempre que el método validate retorne algo distinto de void indicará un error de validación.

Para probarlo debemos hacer:

	
var Task = Backbone.Model.extend({				
		defaults:{
			done: false
			},				
		validate: function(attributes){					
			if(attributes.name.length===0) return "the name is required";				
		}			
	});
					
var task = new Task({name: "hacer algo"});			
task.set("name", "", {validate: true});			
console.log(task.toJSON());

Lo corremos y obtenemos:

image

La propiedad no cambió a causa del error de validación (el nombre deber existir), notemos que al hacer el set de la propiedad agregamos el parámetro {validate:true} esto es para que las validaciones se ejecuten al establecer el valor de la propiedad, podríamos obviarlo, dejar que el usuario establezca todos los valores y luego consultar el método isValid().

Eventos

Por último en esta escueta introducción al modelo vamos a hablar de eventos, podemos saber varias cosas relacionadas con el modelo

  • Cuándo cambió alguna propiedad
  • Cuándo cambia una propiedad particular
  • Cuándo hay un error de validación

Hay más pero por ahora está bien, un ejemplo completo de los visto hasta ahora más los eventos sería:

<html>
	<head>
		<script type="text/javascript" src="js/vendor/jquery/jquery.min.js"></script>
		<script type="text/javascript" src="js/vendor/underscore/underscore.js"></script>
		<script type="text/javascript" src="js/vendor/backbone/backbone.js"></script>			
	</head>
	<body>
		<div id="container" ></div>

		<script type="text/javascript">
			//creamos el modelo extendiendo la clase Model de backbone
			var Task = Backbone.Model.extend({
				//valores por defecto de algunas propiedades
				defaults:{ 
					done: false
				},
				//método de validación, si retorna algo indica que ocurrió un error
				validate: function(attributes){
					if(attributes.name.length===0) return "the name is required";
				}
			});
		
			//creamos un objeto a partir de la definición del modelo
			var task = new Task({name: "hacer algo"});
			
			//le asignamos un valor a la propiedad name
			task.set("name", "");
			
			//escuchamos el evento change del modelo
			task.on("change", function(){
				console.log("algo cambió");
				console.log("el valor anterior fue:");
				console.log(this.changed);
			});

			//agregamos un filtro al evento para indicar que nos interesa el name
			task.on("change:name", function(){
				console.log("el nombre cambió");
			});
			
			//si hay un error de validación cuando
			//hacemos set("propiead", "valor", {validate: true} se ejecuta este evento
			//también se ejecuta consultamos el método isValid() y existen errores
			task.on("invalid", function(){
				console.log("ocurrió un error de validación");
				console.log(this.validationError);
			});
		</script>
		
	</body>
</html>
		
	

Nos leemos, la próxima.