Introducción
En este segundo tema vamos a seguir conociendo la librería Backbone explicando uno de sus componentes más importantes: los Modelos.
Los modelos son los encargados de almacenar los datos de tu aplicación, así como proporcionar un conjunto de funcionalidades comunes y conectarlos al sistema de eventos Backbone para notificarnos cuando un modelo ha sido creado, modificado o borrado.
El método extend
Para crear un en modelo Backbone utilizaremos el método Backbone.Model.extend():
var Cliente = Backbone.Model.extend({ initialize: function(){ alert('Esta funcion se llamará en la creación de cada instancia') } }); var cliente = new Cliente(); |
Este método espera recibir como primer parámetro un objeto hash donde se definen las propiedades de cada instancia del modelo. Podremos así sobreescribir algunas propiedades predefinidas, como la función initialize que se invocará a la hora de crear cada una de las instancias del modelo.
Opcionalmente el método extend puede aceptar un segundo hash con propiedades que se definirán a nivel de clase, es decir, propiedades estáticas:
var Modelo = Backbone.Model.extend({ propiedadDeInstancia: "Valor de instancia" },{ propiedadDeClase: "Valor de clase" }); var modelo = new Modelo(); alert(modelo.propiedadDeInstancia); // Valor de instancia alert(Modelo.propiedadDeClase); // Valor de clase (date cuenta de la mayúscula) |
Siempre que utilizemos extend, los objetos Backbone se definirán a través de la cadena de prototipado de objetos JavaScript, lo que significa que podemos extender y crear subclases de modelos ya definidos:
Persona = Backbone.Model.extend({ // Definicion de persona }); Cliente = Persona.extend({ }); |
Trabajando con atributos
Una vez definido un modelo, podremos crear una instancia utilizando new y pasarle como primer parámetro los atributos que queremos almacenar en el modelo:
var cliente = new Cliente({nombre: 'Antonio', apellidos: 'García Ros'}); |
Podemos especificar los atributos que queramos, y éstos pasarán a formar parte de los atributos del modelo. Además de los atributos, podemos pasar un segundo parámetro que contendrá otro objeto hash de opciones. Tanto el hash de atributos como el hash de opciones se pasarán como parámetros en la función initialize.
A la hora de crear una instancia de un modelo, podemos asociarle cualquier conjunto de atributos que deseemos, pero si queremos asegurarnos de que toda instancia de nuestro modelo contenga al menos un conjunto predefinido de atributos, podemos definir una sección default en las propiedades del método extend:
Cliente = Backbone.Model.extend({ defaults: { empresa: 'Unknow', telefonos: [] }, initialize: function(attrs, opts){ } }); |
De esta forma nos aseguraremos de que cada instancia de nuestro modelo contenga un conjunto predefinido de atributos, así como unos valores por defecto.
Cada instancia de modelo almacena los atributos en una variable interna llamada attributes, pero no se recomienda trabajar con ella directamente. En vez de eso utilizaremos los métodos get y set, garantizando así que se realicen las validaciones y disparado de eventos oportunos de modificación. set(attrs, [options]) permite asignar un hash de atributos a la instancia, y get(attr) devuelve el valor del atributo pasado como parámetro:
cliente.set({edad: 31, soltero: false}); alert(cliente.get('edad')); // 31 |
También podemos incluir nuestras propias funciones de manipulación a la hora de definir nuestro modelo:
Cliente = Backbone.Model.extend({ defaults: { empresa: 'Unknow', telefonos: [] }, nuevoTelefono: function( n_tel ){ var telefonos_array = this.get("telefonos"); telefonos_array.push( n_tel ); this.set({ telefonos: telefonos_array }); } }); var cliente = new Cliente({name: 'Alfonso Marin', company:'Universidad de murcia', telefonos:['968000000']}); cliente.nuevoTelefono('687000000'); var telefonos = cliente.get('telefonos'); // ['968000000', '687000000'] |
Cada vez que se establece o modifica un atributo, ya sea en la creación de la instancia o a través de la función set, Backbone invoca a la función validate del modelo. Por defecto esta función no está definida, y podemos establecerla en el momento de la definición del modelo:
Cliente = Backbone.Model.extend({ validate: function(attrs){ if (attrs.edad < 18){ return "Demasiado joven para ser cliente"; } } }); |
Si el proceso de validación se pasa con éxito, la función no devolverá nada. En caso contrario, esta función deberá devolver una cadena de caracteres o una instancia de la clase Error. Además provocará que no se finalice la ejecución del método set generando un evento error. Para capturar dicho error, podremos definir la propiedad error del objeto hash de opciones del método set con la función que deseemos ejecutar en caso de error:
cliente.set({edad:15, {error:function(model, error){ //... }}); |
obviamente esto es bastante engorroso, pues cada vez que utilicemos set deberíamos asociarle la función de error. En vez de eso, lo que se suele hacer es enlazar los eventos error de un determinado modelo a una función callback, como ya vimos en el tema anterior:
cliente.bind('error', function(model, error){ alert('Error de validación: ' + error); }); |
Capturando eventos de modificación en un modelo
Cada vez que se modifica el valor de un atributo a través del método set, se generan dos eventos: uno llamado change y otro llamado change:nombre_de_atributo. Utilizando el mecanismo de enlazado (bind) podremos detectar cuando cambia alguno de los atributos del modelo o algún atributo en particular:
Cliente = Backbone.Model.extend(); var cliente = new Cliente({nombre:'Alfonso',apellidos:'Marín Marín'}); cliente.bind('change',function(target, options){ //options es el mismo objeto hash que se pasa desde el comando set(attrs, [options]) alert('Atributo modificado'); }); cliente.bind('change:nombre', function(target, valor, options){ //Podemos acceder a la variable interna var old = this.previousAttributes().nombre; alert('Modificado nombre a ' + valor + '. Antiguo valor:' + old); }); cliente.set({nombre:'Antonio'}); |
Como podemos ver en el ejemplo, podemos hacer uso del método previousAttributes para acceder al valor de todos los atributos del modelo en su estado previo al cambio, o utilizando previous(attr) para conocer el antiguo valor de un determinado atributo. Si por el contrario solo quisiéramos el subconjunto de atributos modificados, podremos hacer uso de changedAttributes:
var cliente = new Cliente({nombre:'Alfonso',apellidos:'Marín Marín'}); cliente.bind('change', function(){ alert(JSON.stringify(this.changedAttributes())); }); cliente.set({nombre:'Antonio', edad:'31'});// Alerta imprimiría: {"nombre":"Antonio","edad":"31"} |
Existen otros métodos de un modelo que disparan el evento change:
- unset(attr, [options]): elimina un atributo del modelo.
- clear([options]): elimina todos los atributos del modelo
Identificadores de instancias: id y cid
Cada instancia de podrá tener un atributo identificador que corresponda con su identificador en el servidor. Por defecto dicho atributo se llama id, aunque podemos especificar el que nosotros queramos a través de la propiedad idAttribute:
Cliente = Backbone.Model.extend({ idAttribute: 'dni' }); |
Como veremos en futuros temas, dicho identificador será importante para las operaciones de sincronización, y debería ser único entre las instancias de un mismo modelo.
Por otra parte, cada vez que creamos una instancia internamente se le asignará otro identificador llamado cid, que es el identificador de cliente y será único entre todas las instancias de todos los modelos backbone creados en el cliente. Es especialmente útil cuando creamos instancias que todavía no se han guardado en el servidor y no tienen su propio identificador oficial. Este identificador seguirá el siguiente patrón de numeración: c0, c1, c2, … Podemos acceder a él a través de la propiedad cid:
Cliente = Backbone.Model.extend(); var cliente = new Cliente(); alert (cliente.cid); // c0 |
Resumen
Hemos visto cómo se pueden definir modelos y crear instancias de ellos. También hemos aprendido cómo podemos crearlos, validarlos y modificarlos, así como saber en todo momento qué atributos se están modificando.
Existen otros métodos interesantes relacionados con los modelos, pero la mayoría de ellos tienen que ver con su tratamiento dentro de las colecciones de modelos y con la sincronización y guardado de datos con el servidor. Los iremos viendo en sucesivos temas según vayamos necesitándolos.
En el siguiente tema abordaremos las colecciones de objetos.
Cristopher says
Me parece muy interesante tu tutorial,
a como dijiste no existe buena documentación de Backbone,
excelente aporte (Y)
Wilson Rivera says
Hola que tal, está muy interesante tu tutorial. Tengo una inquietud, esta
var cliente = Cliente({name: ‘Alfonso Marin’, company:’Universidad de murcia’, telefonos:[‘968000000’]}) también es una forma de instancia un modelo?
esta se encuentra en la caja #7 de código fuente
Gracias
Alfonso Marín says
Efectivamente, así estás creando una instancia
Matías Damonte says
Faltaria agregarle el new sino no corre, quedaria de esta manera la linea 7.
var cliente = new Cliente({name: ‘Alfonso Marin’, company:’Universidad de murcia’, telefonos:[‘968000000’]})
Alfonso Marín says
Corregido, gracias
Wilson Rivera says
Otra pregunta con respecto al id y el cid, tu explicas que el cid es un valor incremental con el patrón c(contador), ahora si el id yo no lo defino con la propiedad idAttribute, qué valor le pone por defecto al crear una instancia del modelo?
Alfonso Marín says
Backbone espera que todo modelo tenga un campo ‘id’ que corresponda con el identificador único que proporciona el servidor. Si por cualquier cosa el identificador no se llamara ‘id’, podremos redefinirlo con idAttribute.
Si las instancias las creamos desde el cliente, hasta que no sincronicemos no tendremos un valor en dicho campo, pues es el servidor quien se lo asigna. Pero siempre, siempre, siempre, nuestras instancias tendrán un valor cid, que es un identificador único para cada una de las instancias de cada uno de los modelos de nuestra aplicación
Luis Lomeli says
En el caso de los array defaults:
defaults: {
empresa: ‘Unknow’,
telefonos: []
},
no es recomendable asignar como default el array vacio [] a ‘telefonos’ ya que cuando ejecutamos la funcion «nuevoTel» se agregan a cuantas instancias del modelo tengamos. Es mejor inicializar a «undefined» y en la funcion validar:
var tels_arr = this.get(«telefonos») || [];
tels_arr.push(tel);
this.set({telefonos: tels_arr});
Saludos!!
Jeeba says
Estimado, hay un ligero typo debajo del subtitulo Identificadores de instancias: id y cid, creo que falta una palabra en la frase «Cada instancia de _____ podrá».
Esta muy genial el tuto, me muero de ganas de conocer lo que siga 🙂
Ale Prieto says
¡excelente explicación!
¡muy bueno!
¡gracias!
Ricardo Ortega Avalo says
Excelentes aportes, muchas gracias por el tiempo invertido 😉
juan says
Excelente el tutorial, muy fácil de leer y entenderlo. Felicitaciones!
jose says
muy bueno!!! la verdad excelente !!
Franco says
estoy usando backbone con require, y creo una instancia de modelo con la informacion que me devuelve una llamada AJAX a un server , mi problema es como accedo a esa instancia para manipularla desde otro script ?, es decir creo una instacia de modelo en un script A , y necesito manipularla desde un script B, se puede hacer eso ?
Geyser says
que chido que compartas tu conocimiento sobre este framework. yo lo estoy aprendiendo y me parece un excelente herramienta. Gracias por la info.
Geyser says
Hola, tengo un problema. Cuando hago un save a un modelo backbone manda un método OPTION, algo que no creo que este bien. Necesito enviar un PUT porque ese metodo espera la api a la que envia los datos. Podrias darme algún consejo al respecto?
Magarin says
Hola Alfonso,
Soy totalmente nuevo en el mundo javascript en general y backbone en particular, así que en primer lugar me gustaría felicitarte. Tu tutorial me parece una excelente toma de contacto con backbone. Muy conciso y claro. En segundo plantearte una duda. En uno de los ejemplos he visto este código:
nuevoTelefono: function( n_tel ){
var telefonos_array = this.get(«telefonos»);
telefonos_array.push( n_tel );
this.set({ telefonos: telefonos_array });
}
Por lo que he visto en otros tutoriales parece una práctica común en el mundo javascript/backbone, sin embargo, mi tendencia (vengo del mundo java) sería hacerlo así:
nuevoTelefono: function( n_tel ){
this.get(«telefonos»).push( n_tel );
}
Por lo poquito que he probado parece que el funcionamiento es equivalente. ¿Dónde está la diferencia/as?
Gracias de antemano
Alfonso Marín says
Efectivamente se puede hacer como indicas, pues está mas optimizado. En el ejemplo usé variables intermedias por claridad y con fines didácticos.