Backbone y Flask, primeras impresiones

Hacía tiempo que quería probar Backbone y en mi trabajo no había tenido la oportunidad de hacerlo,
ahora que tengo tiempo libre he decidido probar y poner en práctica todas estas cosas que creo se han convertido en estándar
de facto y no he tenido la oportunidad por falta de tiempo.

Hace dos semanas estuve con un proyecto de Android y practiqué los principios básicos de su API, la semana pasada le tocó
el turno a Backbone, ya que mi proyecto Android quedó en punto muerto y hay otro proyecto que quiero dejar listo pronto
ahora que tengo tiempo para ello.

Estuve probando el proyecto de to-dos, primer ejemplo básico construido con Backbone y Flask, y de ahí lo adapté a lo que yo
necesitaba para OpenBoe, y estoy muy contento con cómo ha quedado la estructura del
frontend y lo fácil que son las cosas con Backbone una vez que le has dedicado un tiempo. Todavía mi código no es muy bueno
(llevo pocas horas probando con las herramientas), pero ya hace lo que yo necesitaba, que no es poco, la segunda fase será optimizar el código y hacerlo más agradable.

Para poner en situación, OpenBOE es un servicio escrito en Flask con una tarea en background que hace scraping sobre la web oficial del BOE
y guarda los datos extraídos en una base de datos en Mongo para su posterior ordenación y filtrado en el servicio. La parte del frontend
ahora es más dinámica, no teniendo que cargar parte de la pantalla, ya que era innecesario completamente. Ahora sólo se refrescará la lista
en función de nuestros parámetros de búsqueda y a correr. :-)

Con esto quería mostrar un ejemplo sencillo de cómo lo he hecho para los que tengan ganas de empezar con Backbone y les haya dado pereza
meterse en la documentación oficial o en algún proyecto ejemplo.

La parte servidor podría ser:


    @app.route("/")
    def index():
        '''
            Cargamos la vista principal, en la que sólo cargaremos
            las secciones y los titulos para poder navegar por ellas
            posteriormente
        '''

        sections = Db().get_col('section')
        feeds = Db().get_col('feed')

        return render_template("index.html", sections=sections, feeds=feeds)


    @app.route("/links/", methods=['GET', 'POST'])
    def feed():
        '''
            Cargamos los elementos del feed rss
            tomado como parámetro, Esta función servirá
            para cargar todos los datos que necesitemos
            en cualquier sección de la web
        '''

        feed_slug = request.args.get('feed', '')
        feed = Db().find_one('feed', {"slug": feed_slug})
        if not feed:
            abort(404)

        links = Db().has('link', {"feed_id": feed['_id']}, sort_field="date")

        return  make_json_response(links)

        ...

    def make_json_response(body):
        '''
            Crea una respuesta en JSON para cuando lo solicite
            Backbone.
        '''

        resp = make_response(json.dumps(body, default=json_util.default))
        resp.mimetype = 'application/json'

        return resp

Ya tenemos todo lo necesario para empezar a jugar con Backbone, una primera vista index que se cargará
al inicio, y con los menús que hemos creado en ella iremos navegando por la web, de la navegación se
encargará en este caso el frontend, o mejor dicho Backbone.Router.

El método feed se encargará de dar los datos en JSON en función de en qué sección nos encontremos. Aquí
podríamos definir también filtrado de búsqueda para devolver sólo los datos en los que el usuario estuviese
interesado.

Vamos con la parte del frontend con Backbone.


    (function() {
        var TEMPLATE_URL = '/static';

        var Link = Backbone.Model.extend({
        });

        var LinkList = Backbone.Collection.extend({

            model: Link,

            url: '/links/',

            initialize: function(options) {
            },

            fetch: function(options) {
                return Backbone.Collection.prototype.fetch.call(this, options);
            }
        });

        var LinkView = Backbone.View.extend({
            tagName:  "div",

            initialize: function(options) {
            },

            render: function() {
                var self = this;

                $(self.el).template(TEMPLATE_URL + '/templates/item.html', self.model.toJSON(), function() {
                    self.setText();
                });

                return this;
            },

            setText: function() {
                var text = this.model.get('description');
                var title = this.model.get('title');
                var link_link = this.model.get('link');
                var guid = this.model.get('guid');

                this.$('.item-description').text(text);
                this.$('.item-link').text(title);
                this.$('.item-link').attr('href', link_link);
                this.$('.pdf-item-link').attr('href', guid);
            }
        });

        window.LinkApp = Backbone.View.extend({

            events: {
                "click #search-button": "performSearch"
            },

            initialize: function(options) {
                var self = this,
                    parentElt = options.appendTo || $('.body-container');

                TEMPLATE_URL = options.templateUrl || TEMPLATE_URL;

                parentElt.template(TEMPLATE_URL + '/templates/app.html', {}, function() {
                    self.el = $('#openboeapp');
                    self.delegateEvents();

                    self.links = new LinkList({feed: options.feed,
                                               query: options.query,
                                               params: options.params
                                              });
                    self.links.bind('add',   self.addOne, self);
                    self.links.bind('reset', self.addAll, self);
                    self.links.bind('all',   self.render, self);

                    self.section = options.section;
                    self.feed = options.feed;

                    self.links.fetch({feed: options.feed});
                });
            },

            render: function() {
                var self = this;
                return this;
            },

            addOne: function(link) {
                var view = new LinkView({model: link});
                this.$("#link-list").append(view.render().el);
            },

            addAll: function() {
                this.links.each(this.addOne);
            }
        });

        window.MyRouter = Backbone.Router.extend({
            routes: {
                "!/": "index",
                "!/:section/:feed": "feed_items",
            },

            index: function() {
                console.log("This is the index!");
            },

            feed_items: function(section, feed) {
                var myquery = query || "";
                var view = new LinkApp({
                                        feed: feed,
                                        section: section
                                       });
            }
        });
    }());

Hay varios elementos en Backbone, yo en este caso he utilizado Model, Collection, Router y View.

Lo más importante que hay que saber en este caso es que el Collection llamará a nuestro servidor cuando usemos
fetch para traerse los datos de tipo Link (Model), y usará la url definida en url. Añadiendo parámetros a esta url
en la inicialización del objeto o en fetch, podremos hacer consultas personalizadas.

La View se encargará de pintar su plantilla o el html asociada a ella donde nosotros le digamos (en este caso en #openboeapp),
y en su initialize podremos llamar al fetch de los objetos que necesitemos para cuando esta vista se inicie así como unir algún evento
a funciones de nuestra vista o casi hacer cualquier cosa que queramos.

En mi caso tengo dos views, una para pintar cada elemento y otra que se encarga de la colección, muy útil si queremos crear
eventos de edición eliminación de cada objeto manteniendo limpia la vista padre y haciendo más fácil el testeo de cada parte.
En LinkView tenemos un setText, que se encargará de inyectar nuestras variables en el template definido en esta parte,
pero es más limpio pasarle una lista de objetos al propio template y definir en éste donde va cada variable.

Para darle un poco de sentido y localización a cada vista y los posibles datos en ellas, podemos organizarlo todo en el Router.
En él definiremos la ruta y qué método se encargará de la petición entrante por ésta.

Por ahora poco más, puedes ver el ejemplo completo en el repositorio de OpenBOE y seguir otras referencias que he comentado en el artículo.

Javier Aguirre

Read more posts by this author.

comments powered by Disqus