4Trabes Historias de una empresa en 100 metros cuadrados

El blog de Trabe Soluciones

Cerrando modales: ¿Quién cierra a quién?

| | Comentarios

Nota introductoria

Los conceptos que vamos a tratar en este post los ejemplificamos con Backbone. Por si no lo conoces o no tienes muy claro que es, aquí te dejamos una brevisima introducción:

Backbone es uno de los múltiples frameworks Javascript (1 y 2) que implementan el paradigma MVC en el navegador. En Backbone el rol de controlador lo juegan los componentes que sus desarrolladores denominan Views (Backbone.View) y el papel de vistas se implementa con cualquiera de las librerías de templating existentes (Eco, Moustache, etc.). Por lo tanto, una vez dicho esto, ten en cuenta que cuando hablemos de vista probablemente nos refiramos a controladores. Sea como sea, intentaremos ser lo más claros posible :___ )

Actualización: Realmente no está tan claro que Backbone siga el paradigma MVC (1, 2 y 3) pero durante el post seguiremos la convención de Vista =~ Plantilla HTML y Controlador =~ Backbone.View.

Al tomate

En Trabe estamos utilizando/probando Backbone en una aplicación en la que hay vistas que se van a mostrar tanto de manera modal como no modal. Como la lógica de estas vistas es independiente de si se están mostrando de manera modal o no, queremos separar la gestión de la ventana modal de su propio contenido y así facilitar la reutilización/refactorización/mantenimiento del código. Para explicarnos mejor vamos a utilizar el siguiente gráfico:

"Vista modal y vista modalizada"

Habrá una vista de Backbone (ModalView en el gráfico) encargada de gestionar la ventana modal (abrir, cerrar, pelearse con el plugin que “pinta” el modal) y una vista modalizada (ModalizedView) que en el caso del gráfico gestiona un formulario.

Hasta aquí todo va muy bien, pero existe un punto en el que, digamos, chocan las responsibilidades de la ModalView y de la ModalizedView. ¿Quién cierra la vista modal? Si es ModalizedView la que cierra el modal, estamos asignándole lógica que no le corresponde con lo que luego será más difícil de mantener o reutilizar. Así que será ModalView la que se cierre a sí misma; pero ¿cómo le indicamos a la ventana modal que puede cerrarse? Podemos hacerlo utilizando:

  • Una referencia a la vista modal
  • Eventos o promesas

El código que utilizaríamos para mostrar la ventana modal sería algo así como:

1
2
3
4
modal = new ModalView(...)
modalized = new ModalizedView(...)

modal.modalize(modalized)

Ahora veremos cómo implementar ModalizedView y ModalView utilizando cada opción.

Referencia a la vista modal

La vista a modalizar tiene una referencia a la vista modal. Cuando quiere cerrarla simplemente invoca un método en dicha vista.

Usando una referencia a la vista modal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ModalizedView extends Backbone.View
  handleDone : ->
    # The user is done, close the modal view!
    @get('modal_view').close()

class ModalView extends Backbone.View
  modalize : (view) ->
    # Show the view inside the modal
    # ...
    # Set a reference to the modal view
    view.set('modal_view') = modal_view

  close : ->
    # Close modal window

modal = new ModalView(...)
modalized = new ModalizedView(...)

modal.modalize(modalized)

En este caso los problemos que nos encontramos son los siguientes:

  • Si cambiamos el API de la vista modal podemos afectar a las vistas que la utilicen.
  • Estamos añadiendo lógica que no le corresponde a la vista modalizada.
  • En el caso del ejemplo, el método handleDone no podría ser reutilizado, directamente, si la vista no es modal (no tendríamos una referencia al modal_view correspondiente).

Eventos o promesas

La utilización de eventos y promesas comparte ventajas e inconvenientes. Entre las ventajas están:

  • Desacoplan las dos vistas manteniéndolas independientes. O casi ;)
  • Tanto las promesas como los eventos pueden ser utilizadas no solo por la vista modal sino por otros componentes para tener constancia del estado de la vista modalizada.

Y entre las desventajas:

  • Ambas vistas tienen que adaptar un “protocolo” común (ya sea a través de los eventos o la promesa).
  • En ambos casos dependeremos de una librería que facilite la utilización de promesas o eventos (por ejemplo, jQuery).
  • La lógica correspondiente a lanzar un evento o resolver una promesa no está directamente relacionada con el contexto en el que se encuentra. Claro que este es el precio a pagar por las ventajas que ofrecen.

Eventos

La vista a “modalizar” lanza un evento que la vista modal captura y como consecuencia se cierra.

Usando eventos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ModalizedView extends Backbone.View
  handleDone : ->
    # The user is done, signal it using an event
    @trigger "modal:close"

class ModalView extends Backbone.View
  modalize : (view) ->
    # Show the view inside the modal
    view.on "modal:close", ->
      # Close modal window

modal = new ModalView(...)
modalized = new ModalizedView(...)

modal.modalize(modalized)

Promesas

Por promesas nos referimos a los Deferred/Promise de jQuery. Una promesa es un objeto que “represents the eventual value returned from the single completion of an operation” (CommonJS/Promises).

Las vistas a modalizar tienen disponible una promesa que utilizan para indicar que ya han cumplido con su cometido. Esta promesa será utilizada por la vista modal para determinar cuándo tiene que cerrarse.

Usando promesas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ModalizedView extends Backbone.View
  initialize : ->
    @_deferred  = $.Deferred()
    @promise    = @_deferred().promise()

  handleDone : ->
    @_deferred.resolve()

class ModalView extends Backbone.View
  modalize : (view) ->
    # Show the view inside the modal
    # Attach a done callback which will close the modal window
    view.promise.done ->
      # close modal window

modal = new ModalView(...)
modalized = new ModalizedView(...)

modal.modalize(modalized)

¿Conclusiones?

Las mejores soluciones son la del evento y la de la promesa. ¿Y entre estas dos? Nos gusta más la promesa porque lleva “de serie” la distinción entre una finalización “exitosa” (resolve/done) y una “fallida” (reject/fail), que en el caso de los modales es bastante común. Además, es un estándar de facto – al menos en jQuery – lo que nos permite aprovechar todas las herramientas asociadas.

Comments