Extender módulos propios con backbone
En mi mini aplicación hay tres tipos de vistas que se repiten: elementos, listas y formularios.
La idea es sencilla, empiezas a escribir código y cuando te das cuenta de que te estás repitiendo refactorizas hasta encontrar una estructura común, creas una pseudo clase para esa estructura en la que estén todos los elementos comunes y así luego utilizas este módulo para que sea extendido por las vistas de los módulos concretos.
Elementos
Son el reflejo de los modelos, en un principio simplemente se encargaban de completar la plantilla de ese modelo, pero en mi aplicación en cada lista existe la posibilidad de eliminar el modelo desde allí, así que añado también esa funcionalidad, el código queda así:
// Extiendo la vista base de Backbone
var Element = Backbone.View.extend({
initialize: function() {
// Cuando elimino el modelo lo quito de la lista
this.model.on('destroy', this.unrender, this);
},
events: {
// Cuando hago clic en el elemento .delete ejecuto removeModel
'click .delete': 'removeModel'
},
render: function() {
// Creo el elemento con los datos del modelo
this.setElement(this.template(this.model.toJSON()));
return this;
},
removeModel: function(e) {
e.preventDefault();
// Espero a que el modelo sea borrado de la base de datos
this.model.destroy({wait: true});
// Mientras tanto le añado una clase para poder «estilarlo» con CSS
this.$el.addClass('pending');
},
unrender: function() {
this.remove();
}
});
Con lo cual, cada vez que tengo que crear la vista para un modelo que sea de ese tipo, quedaría así:
// Extiendo mi «clase» en lugar de Backbone.View
var Expense = Element.extend({
// Con definir la plantilla es suficiente
template: _.template(expenseTemplate)
});
// Y otro ejemplo
var Category = Element.extend({
template: _.template(catTemplate)
});
Listas
Estas serían las vistas de las colecciones, la funcionalidad que necesito para mi aplicación es sencilla:
var List = Backbone.View.extend({
tagName: 'ul',
className: 'list-items',
initialize: function() {
// Cuando recupero del servidor la colección creo la estructura del elemento
this.collection.on('sync', this.render, this);
this.collection.fetch();
},
render: function() {
// Vacío el elemento, esto ya depende mucho de como sea la aplicación,
// en mi caso es lo que quiero
this.$el.html('');
// Y voy añadiendo cada elemento
this.collection.each(this.addOne, this);
return this;
},
addOne: function(model) {
// modelView se lo he pasado como parámetro al llamar a este módulo
this.$el.append(new this.options.modelView({model: model}).render().el);
}
});
Y cada lista luego:
// No necesito nada concreto, así que simplemente extiendo List
var Categories = List.extend({ });
var Expenses = List.extend({ });
Formularios
Y por último están los formularios, como es una aplicación de andar por casa no me he metido con validaciones ni nada (en el cliente, en el lado del servidor siempre porque ...).
var Form = Backbone.View.extend({
// Evento por defecto cuando el formulario ha sido enviado
formcomplete: 'formcomplete',
// Y cuando se ha cancelado
cancelform: 'cancelForm',
events: {
'submit':'addOne',
'click #cancel': 'cancelForm'
},
render: function() {
this.beforeRender();
this.$el.html(_.template(this.template));
this.afterRender();
return this;
},
// Helpers porque en algún caso los necesitaba, aunque por defecto no hacen nada
beforeRender: function() {},
afterRender: function() {},
addOne: function(e) {
e.preventDefault();
// Aplicación de andar por casa = chapucilla. Ni me preocupo de definir campos,
// me fio de saber que meter en cada campo y que cada campo se corresponda con los de la base de datos
// y directamente serializo el formulario.
// Corrijo la estructura del objeto conforme lo que espero recibir en el servidor
var model = this.correctJSON(this.$('form').serializeArray());
var formcomplete = this.formcomplete;
new this.model(model).save().then(function() {
// Y cuando lo envío lo anuncio
Events.trigger(formcomplete);
});
},
// Helper para formatear el objeto recogido del formulario
correctJSON: function(arr) {
var data = {};
_.each(arr, function(e, i, l) {
data[e.name] = e.value;
});
return data;
},
cancelForm: function(e) {
e.preventDefault();
Events.trigger(this.cancelform);
}
});
Y los ejemplos particulares:
var newCategory = Form.extend({
formcomplete: 'form:category',
model: catModel,
template: catformtemplate
});
var newExpense = Form.extend({
formcomplete: 'form:expense',
model: expenseModel,
template: mainFormTemplate,
afterRender: function() {
if (!this.catOptions) {
this.catOptions = new Categories({
collection: new catCollection(),
modelView: catModelView
});
}
this.$('#categories').html(this.catOptions.render().el);
}
});
Y así es como se puede usar backbone sin tener que repetir el mismo código todo el rato pero permitiéndote decidir a ti como se implementa la funcionalidad de la aplicación.
Sobra decir que este es un ejemplo de una aplicación muy pequeña (y sólo para mí) y que soy bastante nuevo en esto así que nada de esto es aplicable directamente en producción y/o en una aplicación de verdad.