Una introducción completa a Backbone.Marionette (Parte 2)
En la primera parte de esta serie, analizamos la aplicación de Backbone.Marionette. Esta vez, discutiremos el sistema de módulos que se incluye en Backbone.Marionette. Se puede acceder a los módulos a través de la Aplicación, pero los módulos son un tema muy amplio y merecen un artículo dedicado a ellos.
En la primera parte de esta serie, hablamos de Backbone.MarionetteApplication
. Esta vez, discutiremos el sistema de módulos que se incluye en Backbone.Marionette. Se puede acceder a los módulos a través de Application
, pero los módulos son un tema muy amplio y merecen un artículo dedicado a ellos.
¿Qué son los módulos?
Antes de entrar en detalles sobre cómo usar el sistema de módulos de Marionette, debemos asegurarnos de que todos tenemos una definición decente de módulo. Un módulo es una unidad de código independiente que idealmente hace una cosa . Se puede utilizar junto con otros módulos para crear un sistema completo. Cuanto más independiente sea una unidad de código, más fácilmente podrá intercambiarse o modificarse internamente sin afectar otras partes del sistema.
Lecturas adicionales sobre SmashingMag:
- Una introducción completa a Backbone.Marionette (Parte 1)
- Una introducción completa a Backbone.Marionette (Parte 3)
- Consejos y patrones de Backbone.js
- Una introducción a JavaScript de pila completa
Para este artículo, eso es todo lo que necesitamos para definir módulos, pero si desea obtener más información sobre cómo escribir código modular, hay muchos recursos en Internet, de los cuales el capítulo "La mantenibilidad depende de la modularidad " de Aplicaciones de una sola página en La profundidad es una de las mejores que existen.
Actualmente, el lenguaje JavaScript no tiene ningún método integrado para definir módulos (la próxima versión debería cambiar eso ), pero han surgido muchas bibliotecas para brindar soporte para definir y cargar módulos. Lamentablemente, el sistema de módulos de Marionette no brinda soporte para cargar módulos desde otros archivos, pero ofrece algunas funciones que otros sistemas de módulos no tienen, como la capacidad de iniciar y detener un módulo. Cubriremos más de eso más adelante. Ahora mismo, comenzaremos con la definición de un módulo.
Definición del módulo
Comencemos con las definiciones de módulos más básicas. Como se mencionó, se puede acceder a los módulos a través de Application
, por lo que debemos crear una instancia de uno de ellos. Entonces podemos usar su module
método para definir un módulo.
var App = new Backbone.Marionette.Application();var myModule = App.module(‘myModule’);
Eso es bastante simple, ¿verdad? Bueno, lo es, pero ese es el módulo más simple que podemos crear. Pero ¿qué creamos exactamente? Esencialmente, le dijimos a la aplicación que queremos un módulo básico, sin funcionalidad agregada por nosotros, y que tendrá un nombre myModule
(de acuerdo con el argumento que le pasamos module
). Pero, ¿qué es un módulo básico? Es una instanciación de un Marionette.Module
objeto.
Module
viene con un poco de funcionalidad integrada, como eventos (a través de EventAggregator
, que discutiremos detalladamente en un artículo posterior), comenzando con inicializadores (como lo Application
ha hecho) y terminando con finalizadores (repasaremos eso en el " Sección Iniciar y detener módulos”).
Definición del módulo estándar
Ahora veamos cómo definir un módulo con algunas de nuestras propias funciones.
App.module("myModule", function(myModule, App, Backbone, Marionette, $, _){ // Private Data And Functions var privateData = "this is private data"; var privateFunction = function(){ console.log(privateData); } // Public Data And Functions myModule.someData = "public data"; myModule.someFunction = function(){ privateFunction(); console.log(myModule.someData); }});
Como puedes ver, hay muchas cosas ahí. Miremos la línea superior y avancemos hacia abajo. Al igual que antes, llamamos App.module
y proporcionamos un nombre para el módulo. Pero ahora también estamos pasando una función. La función recibe varios argumentos. Apuesto a que puedes descubrir cuáles son, según los nombres que les he dado, pero aún así los explicaré todos:
- myModule es el mismo módulo que estás intentando crear. Recuerde, ya está creado para usted y es una nueva instancia de
Module
. Probablemente querrás ampliar esto con algunas propiedades o métodos nuevos; de lo contrario, también puedes seguir con la sintaxis corta que no requiere que pases una función. App
es elApplication
objeto que llamastemodule
.Backbone
es, por supuesto, la referencia a la biblioteca Backbone.Marionette
es la referencia a la biblioteca Backbone.Marionette. En realidad, está disponible a través deBackbone
, pero esto le permite asignarle un alias y darle un nombre más corto.$
es su biblioteca DOM, que será jQuery o Zepto (o posiblemente algo más en el futuro)._
es una referencia a Underscore o Lodash, cualquiera que esté usando.
Después de eso, puedes pasar y usar argumentos personalizados. Repasaremos esto en un momento.
Normalmente diría que la mayoría de estos argumentos son innecesarios; después de todo, ¿por qué no tendrías acceso a estas referencias? Sin embargo, pude verlos útiles en un par de situaciones:
- Un minificador puede acortar los nombres de los argumentos, ahorrando algunos bytes.
- Si está utilizando RequireJS o algún otro cargador de módulos, solo necesita incorporar el
Application
objeto como una dependencia. El resto estará disponible a través de los argumentos que te proporcioneModule
.
De todos modos, volvamos a explicar el resto de lo que sucede en el código anterior. Dentro de la función, puedes utilizar el cierre para crear variables y funciones privadas, que es lo que hemos hecho. También puede exponer datos y funciones públicamente agregándolos como propiedades de myModule
. Así es como creamos y ampliamos nuestro módulo. No es necesario devolver nada porque se podrá acceder al módulo directamente a través de App
, como explicaré en la sección "Acceso a un módulo" a continuación.
Nota: Asegúrese de intentar solo agregar propiedades a su module
variable y no establecerla igual a algo (por ejemplo, myModule = {…}
), porque cuando configura su module
variable a algo, eso cambia a qué hace referencia el nombre de la variable, y ninguno de los Los cambios que especifique se mostrarán en su módulo más adelante.
Anteriormente, señalé que puedes enviar argumentos personalizados. De hecho, puedes enviar tantos argumentos personalizados como quieras. Eche un vistazo al código siguiente para ver cómo se hace.
App.module("myModule", function(myModule, App, Backbone, Marionette, $, _, customArg1, customArg2){ // Create Your Module}, customArg1, customArg2);
Como puede ver, si pasa argumentos adicionales a module
, los pasará a la función en la que está definiendo su módulo. Una vez más, el mayor beneficio que veo de esto es ahorrar algunos bytes después de la minificación ; Aparte de eso, no le veo mucho valor.
Otra cosa a tener en cuenta es que la this
palabra clave está disponible dentro de la función y en realidad se refiere al módulo. Esto significa que ni siquiera necesitas el primer argumento, pero perderías la ventaja de la minificación si no usaras el argumento. Reescribamos ese primer código usando this
para que puedas ver que es exactamente igual a myModule
.
App.module("myModule", function(){ // Private Data And Functions var privateData = "this is private data"; var privateFunction = function(){ console.log(privateData); } // Public Data And Functions this.someData = "public data"; this.someFunction = function(){ privateFunction(); console.log(this.someData); }});
Como puede ver, como no estoy usando ninguno de los argumentos, decidí no enumerar ninguno de ellos esta vez. También debería ser obvio que puedes omitir el primer argumento y simplemente usar this
.
Definiciones divididas
Lo último que mencionaré sobre la definición de módulos es que podemos dividir las definiciones. No sé exactamente por qué querrías hacer esto, pero es posible que alguien quiera ampliar tus módulos más adelante, por lo que dividir las definiciones podría ayudarles a evitar tocar tu código original. A continuación se muestra un ejemplo de definiciones divididas:
// File 1App.module("myModule", function(){ this.someData = "public data";});// File 2App.module("myModule", function(){ // Private Data And Functions var privateData = "this is private data"; var privateFunction = function(){ console.log(privateData); } this.someFunction = function(){ privateFunction(); console.log(this.someData); }});
Esto nos da el mismo resultado que la definición anterior, pero está dividido. Esto funciona porque en , se nos proporciona File 2
el módulo que definimos (suponiendo que se ejecutó antes ). Por supuesto, si intenta acceder a una variable o función privada, debe definirse en la definición del módulo donde se usa porque solo está disponible dentro del cierre donde se define.File 1
File 1
File 2
Accediendo a un módulo
¿De qué sirve crear módulos si no podemos acceder a ellos? Necesitamos poder acceder a ellos para poder utilizarlos. Bueno, en el primer fragmento de código de este artículo, viste que cuando llamé module
, asigné su valor de retorno a una variable. Esto se debe a que utilizamos el mismo método para definir y recuperar módulos.
var myModule = App.module("myModule");
Normalmente, si solo está intentando recuperar el módulo, pasará el primer argumento y module
saldrá y tomará ese módulo por usted. Pero si pasa una función como segundo argumento, el módulo se aumentará con su nueva funcionalidad y aún devolverá su módulo recién creado o modificado. Esto significa que puede definir su módulo y recuperarlo todo con una sola llamada al método.
Sin embargo, esta no es la única forma de recuperar módulos. Cuando se crea un módulo, se adjunta directamente al Application
objeto con el que se construyó. Esto significa que también puedes usar la notación de puntos normal para acceder a tu módulo; pero esta vez debe definirse de antemano, de lo contrario volverás undefined
.
// Works but I don't recommend itvar myModule = App.myModule;
Si bien esta sintaxis es más corta, no transmite el mismo significado a otros desarrolladores. Recomendaría usar module
para acceder a sus módulos para que sea obvio que está accediendo a un módulo y no a alguna otra propiedad de App
. La conveniencia y el peligro aquí es que creará el módulo si aún no existe. El peligro viene si escribes mal el nombre del módulo; no tendrá forma de saber que no obtuvo el módulo correcto hasta que intente acceder a una propiedad que no existe.
Submódulos
Los módulos también pueden tener submódulos. Lamentablemente, Module
no tiene su propio module
método, por lo que no puede agregarle submódulos directamente, pero eso no nos detendrá. En cambio, para crear submódulos, llama module
a App
, tal como solía hacerlo; pero para el nombre del módulo, debe colocar un punto ( .
) después del nombre del módulo principal y luego colocar el nombre del submódulo.
App.module('myModule.newModule', function(){ ...});
Al usar el separador de puntos en el nombre del módulo, Marionette sabe que debería crear un módulo como un submódulo del módulo antes del punto. Lo interesante (y potencialmente peligroso) es que si el módulo principal no se crea en el momento en que llamas a esto, lo creará junto con su submódulo. Esto puede ser peligroso debido al mismo potencial de errores ortográficos que mencioné anteriormente. Podría terminar creando un módulo que no tenía intención de crear y el submódulo se le adjuntaría, en lugar del módulo que pretendía.
Accediendo a submódulos
Como antes, se puede acceder a los submódulos de la misma forma en que están definidos, o puede acceder a ellos como propiedades del módulo.
// These all work. The first example is recommendedvar newModule = App.module('myModule.newModule');var newModule = App.module('myModule').newModule;var newModule = App.myModule.newModule;// These don't work. Modules don't have a 'module' functionvar newModule = App.myModule.module('newModule');var newModule = App.module('myModule').module('newModule');
Cualquiera de estos métodos para acceder al submódulo funcionará igualmente bien si ya se han creado tanto el módulo como el submódulo.
Iniciar y detener módulos
Si leíste el artículo anterior de la serie sobre Application
, sabrás que puedes comenzar Application
con start
. Bueno, iniciar módulos es lo mismo y también se pueden detener con stop
.
Si recuerda (suponiendo que haya leído el artículo anterior), puede agregar inicializadores con addInitializer
a un Application
, y se ejecutarán cuando se inicie (o se ejecutarán inmediatamente si Application
ya se inició). Algunas otras cosas suceden cuando inicias un Application
. Aquí están todos los eventos, en orden:
- dispara el
initialize:before
evento, - inicia todos los módulos definidos,
- ejecuta todos los inicializadores en el orden en que fueron agregados,
- dispara el
initialize:after
evento, - desencadena el
start
evento.
A Module
se comporta de manera muy similar. La cantidad de eventos y algunos de los nombres de los eventos son diferentes, pero en general es el mismo proceso. Cuando se inicia un módulo,:
- dispara el
before:start
evento, - inicia todos sus submódulos definidos,
- ejecuta todos sus inicializadores en el orden en que fueron agregados,
- desencadena el
start
evento.
El stop
método también es muy similar. Sin embargo, en lugar de agregar inicializadores, debe agregar finalizadores. Esto se hace con addFinalizer
y pasando una función para que se ejecute cuando stop
se llame. A diferencia de los inicializadores, no se pasan datos ni opciones a cada una de las funciones. Cuando stop
se llama,:
- dispara el
before:stop
evento, - detiene sus submódulos,
- ejecuta sus finalizadores en el orden en que fueron agregados,
- desencadena el
stop
evento.
Los inicializadores y finalizadores no están pensados únicamente para que los utilicen otros. De hecho, son bastante útiles cuando se usan dentro de la definición del módulo. De esta manera, puede definir un módulo dentro de la definición sin crear ningún objeto para su uso, pero luego escribir sus inicializadores para comenzar a crear los objetos y configurarlos, como en el ejemplo siguiente.
App.module("myModule", function(myModule){ myModule.startWithParent = false; var UsefulClass = function() {...}; // Constructor definition UsefulClass.prototype ... // Finish defining UsefulClass ... myModule.addInitializer(function() { myModule.useful = new UsefulClass(); // More setup }); myModule.addFinalizer(function() { myModule.useful = null; // More tear down });});
Arranque automático y manual
Cuando se define un módulo, de forma predeterminada se iniciará automáticamente al mismo tiempo que se inicia su padre (ya sea el Application
objeto raíz o un módulo padre). Si un módulo está definido en un padre que ya se inició, se iniciará inmediatamente.
Puede configurar un módulo para que no se inicie automáticamente cambiando su definición de dos maneras. Dentro de la definición, puede establecer la startWithParent
propiedad de un módulo en false
, o puede pasar un objeto (en lugar de una función) que module
tenga una startWithParent
propiedad establecida en false
y una define
propiedad para reemplazar la función normal.
// Set 'startWithParent' inside functionApp.module("myModule", function(){ // Assign 'startWithParent' to false this.startWithParent = false;});// -- or --// Pass in objectApp.module("myModule", { startWithParent: false, define: function(){ // Define module here }});App.start();// myModule wasn't started, so we need to do it manuallyApp.module('myModule').start("Data that will be passed along");
Ahora el módulo no se iniciará automáticamente. Debes llamar start
manualmente para iniciarlo, como hice yo en el ejemplo anterior. Los datos que se pasan start
pueden ser de cualquier tipo y se pasarán a los submódulos cuando se inicien, a los inicializadores y a los eventos before:start
y .start
Como dije, los datos no se transmiten de esta manera cuando llamas stop
. Además, stop
debe llamarse manualmente y siempre llamará stop
a submódulos; No hay forma de evitar esto. Esto tiene sentido porque un submódulo no debería estar ejecutándose cuando su padre no se está ejecutando, aunque hay casos en los que un submódulo debería estar apagado cuando su padre se está ejecutando.
Otros eventos y funcionalidad integrada
Mencioné que Module
viene con algunas funciones integradas, como EventAggregator
. Como se mencionó, podemos usar el on
método en un módulo para observar eventos relacionados con el inicio y la detención. Eso no es todo. No hay otros eventos integrados, pero un módulo puede definir y desencadenar sus propios eventos. Echar un vistazo:
App.module('myModule', function(myModule) { myModule.doSomething = function() { // Do some stuff myModule.trigger('something happened', randomData); }});
Ahora, cada vez que llamemos doSomething
al módulo, se activará el something happened
evento al que puedes suscribirte:
App.module('myModule').on('something happened', function(data) { // Whatever arguments were passed to `trigger` after the name of the event will show up as arguments to this function // Do something with `data`});
Esto es muy similar a la forma en que hacemos las cosas con eventos en colecciones, modelos y vistas en el código Backbone normal.
Cómo podríamos utilizar realmente un módulo
Los módulos en Marionette definitivamente se pueden usar para definir módulos de manera muy similar a cualquier otra biblioteca de definición de módulos, pero en realidad no fue así como fue diseñado para usarse. Los métodos integrados start
y stop
son una indicación de esto. Los módulos que incluye Marionette están destinados a representar subsistemas algo grandes de una aplicación. Por ejemplo, veamos Gmail .
Gmail es una aplicación única que en realidad contiene varias aplicaciones más pequeñas: cliente de correo electrónico, cliente de chat, cliente de teléfono y administrador de contactos. Cada uno de ellos es independiente (puede existir por sí solo), pero todos están dentro de la misma aplicación y pueden interactuar entre sí. Cuando iniciamos Gmail por primera vez, el administrador de contactos no está activo, ni tampoco la ventana de chat. Si tuviéramos que representar esto con una aplicación Marionette, cada una de esas subaplicaciones sería un módulo. Cuando un usuario hace clic en el botón para abrir el administrador de contactos, detendríamos la aplicación de correo electrónico (porque queda oculta, aunque, para acelerar, podríamos mantenerla en funcionamiento y simplemente asegurarnos de que no se muestre en el DOM) e iniciar el administrador de contactos.
Otro ejemplo sería una aplicación construida en gran medida a partir de widgets. Cada widget sería un módulo que puede iniciar y detener para mostrarlo u ocultarlo. Esto sería como un panel personalizable como iGoogle o el panel en la parte posterior de WordPress.
Por supuesto, no estamos limitados a usar los módulos de Marionette de esta manera, aunque es difícil usarlo en el sentido tradicional. Esto se debe a que los módulos de Marionette son objetos totalmente instanciados, mientras que los módulos tradicionales son "clases" que están destinadas a la instanciación posterior.
Conclusión
¡Uf! Esa es mucha información. Si has llegado hasta aquí, te felicito (aunque fue mucho más fácil para ti leer esto que para mí escribirlo). De todos modos, espero que hayas aprendido mucho sobre la forma en que Marionette maneja la definición, el acceso, el inicio y la parada de módulos y submódulos. Puede que le resulte una herramienta muy útil o puede optar por ignorar por completo su existencia. Esa es una de las mejores cosas de Backbone y Marionette: la mayoría de sus funciones son en gran medida independientes, por lo que puedes elegir lo que quieres usar.
Créditos de la imagen en portada: ruiwen
(al) (ea)
Explora más en
- Codificación
- Marcos
- javascript
Deja un comentario