CommonJS para Titanium
CommonJS es un estandar de facto que permite encapsular código JavaScript en “módulos” que utilizaremos para crear nuestros propios componentes y librerías.
Con encapsular me refiero a la habilidad de crear una “burbuja” con código que tiene su propio ámbito de variables, de manera que ninguna de las que definamos en su interior contamina el ámbito global. En otro artículo hago una breve explicación de porqué es malo contaminar el ámbito global y como evitarlo. commonJS supone un nivel más de control sobre el ámbito de las variables.
El programador define el interfaz del módulo (las funciones y variables “públicas” a las que se podrá acceder desde fuera), de modo que para usarlo sólo tendremos que preocuparnos por conocer este interfaz y no lo que ocurre en su interior.
El buen uso de los módulos CommonJS facilita la reutilización de componentes entre proyectos, hace la trazabilidad más sencilla y obliga a estudiar mejor la interdependencia entre partes del proyecto.
Antes de continuar, si no sabes cómo se crean los objetos en JavaScript o quieres dar un repaso a las principales peculiaridades del lenguaje, echa un vistazo a los dos tutoriales de introducción a JavaScript para Titanium. Están aquí: 1 y 2
¿Que pinta tiene un modulo commonJS?
Se trata de un fichero JavaScript (.js) en el que encontraremos una serie de funciones y variables y un objeto especial llamado exports
(no hay que definirlo, ya existe per sé).
Dentro del módulo pondremos todo nuestro código, pero sólo el que sea añadido a exports
será accesible desde fuera.
TestModule.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //Estas variables están disponibles en el módulo, pero no fuera de él var variablePrivada1, varPrivada2, varPrivada3; //este método sólo es visible dentro del fichero .js function metodoPrivado(){ } //este método es visible dentro y fuera del fichero //por ser declarado como parte de exports exports.metodoPublico = function(){ } exports.saluda = function(){ alert('hola'); } //Podemos convertir un método de privado a público añadiéndolo a exports //Ojo, no ponemos parentesis, ya que lo que devuelve no es el resultado de metodoPrivado, si no la función en sí exports.metodoYaNoEsPrivado = metodoPrivado; //Y lo mismo con una propiedad exports.propiedadPublica = variablePrivada1; |
Ciclo de ejecución
Un aspecto importante a tener en cuenta es que un módulo commonJS sólo se interpreta la primera vez que es invocado. Esto implica varias cosas:
- Primero, que la carga de los distintos módulos se retrasa hasta que son necesarios, lo que hace que el arranque inicial suela ser más rápido
- Segundo y más importante: las variables que se declaren fuera de cualquier función del módulo tendrán un comportamiento estático (en el ejemplo, anterior, las declaradas en la línea 2). Solo se inicializan la primera vez y el resto de veces que invocamos al módulo no se inicializan de nuevo, conservando su último valor asignado. Aunque esto puede ser confuso al principio, es tremendamente útil a la hora de compartir información entre distintas partes del proyecto sin necesidad de usar variables globales.
Llamada desde Titanium
Para cargar un módulo commonJS desde Titanium utilizamos la orden require()
Dado un fichero TestModule.js
que se encuentre en el raíz de nuestro proyecto (en la carpeta resources), lo cargaremos así (ojo, al hacer require no se pone la extensión del fichero)
1 2 3 4 5 6 7 8 9 | var myTestModule = require('/TestModule'); //Podemos consultar una propiedad del módulo Ti.API.info(myTestModule.propiedadPublica); //y ejecutar sus métodos públicos myTestModule.saluda(); |
Esta orden carga el módulo en una variable cuyo nombre, en este caso ‘myTestModule’, lo asignamos a nuestra conveniencia. Una vez cargado, myTestModule contiene el objeto exports
que hemos ido implementando en la definición del módulo.
Patrones de uso
Existen varios patrones de uso de los módulos commonJS que se repiten confrecuencia en Titanium y conviene conocer desde un principio. Comentaré dos de ellos que usaré a menudo en próximos artículos.
Un Componente instanciable (o sea, reusable)
Una instancia es una copia de un objeto que tiene su propio ciclo de vida. Esto permite que varias instancias puedan sobrevivir simultáneamente con ciclos de vida diferentes e incluso con comportamientos diferentes sin depender necesariamente una de otra. Ejemplos de instancias son la mayoría de componentes UI de Titanium: un botón, un label, una ventana… En este caso, querremos que cada instancia del objeto no colisiones con el resto.
Supongamos que queremos crear un componente propio que extienda de uno existente. Lo “envolveremos” en un módulo commonJS, añadiéndole las nuevas funcionalidades. Como ejemplo, vamos a crear un botón especial con dos estados, de manera que su color cambie cada vez que se pulsa el botón:
SwitchButton.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | module.exports = function(args){ //inicializa args para asegurarse que existe var args = args || {}; args.colorOn = args.colorOn || ''; //si no se ha definido colorOn, asigna '' args.colorOff = args.colorOff || ''; //Creamos el botón nativo de Titanium var btn = Ti.UI.createButton(args); //quitamos la imagen de fondo, en iOS es necesario para ver bien el color btn.backgroundImage = 'transparent'; //método privado que determina la imagen de fondo del botón function setColor(){ btn.backgroundColor = btn.value ? btn.colorOn : btn.colorOff; } //al hacer click, cambia la imagen btn.addEventListener('click', function(e){ btn.value = !btn.value; setColor(); }); //inicializa la imagen del botón setColor(); return btn; }; |
Para utilizar nuestro componente en Titanium, deberemos crearlo con el operador new
app.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var win = Ti.UI.createWindow(); //Cargamos el módulo en un objeto "Clase" var SwitchButton = require('SwitchButton'); //creamos nuestra instancia de la clase SwitchButton: var btn = new SwitchButton({ width:180, height:35, title:'hazme click!', colorOn: '#fff', colorOff:'#999' }); win.add(btn); win.open(); |
Con esta técnica, podríamos añadir tantos botones como fueran necesarios, cada uno con sus características de tamaño y color y cada uno con sus propios addEventListeners.
Tienes un ejemplo más avanzado de SwitchButton usando imágenes de fondo y disparando un evento propio en mi proyecto TitanTricks
Patrón de configuración
A menudo ocurre que distintas partes de la aplicación deben compartir una serie de datos “estáticos” sobre la aplicación. Cosas como el nombre de la app, la url de un servicio web al que debe acceder o distintas configuraciones de estilo.
Config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var config = { name: 'miApp', //nombre de la aplicacion autoPlay: false, //iniciar video automáticamente //Start and finish paths with a slash '/' photosPath: '/content/photos/', imagesPath: '/content/imgs/', songsPath: '/content/audio/', serverUrl: 'http://www.myserver.com/webservice.php' }; //decimos al módulo que el objeto exports es igual al objeto config module.exports = config; |
Como puedes ver, en vez de extender exports
con una propiedad config, en este caso directamente asignamos config al módulo. Esta técnica es igual de válida que la de extender exports
. Lo único que no debes hacer nunca dentro de un mismo módulo es mezclar ambas técnicas: o extiendes exports
o igualas module.exports
a un objeto tuyo. La experiencia te dirá cuando usar una u otra, te he mostrado un ejemplo de cada caso para que te suene a la hora de diseñar tu propio módulo.
Ahora, en nuestra aplicación, podremos invocar al módulo Config sin necesidad de instanciarlo:
App.js
1 2 3 4 5 6 7 | var Config = require('Config'); //Podemos usar Config sin necesidad de instanciarlo, porque exports ya contiene las propiedades que esperamos alert('Bienvenido a ' + Config.name); Ti.API.info('Las fotos están en ' + Config.photosPath); |
Este tipo de patrón también es muy útil para separar los estilos de la programación pura. Podemos tener un fichero de estilos fácilmente reutilizables y modificables al que invocar desde cualquiera de nuestros módulos. Podríamos, por ejemplo, cambiar los colores de todas las ventanas sólo con cambiar el color en este fichero.
Styles.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //Cargamos el módulo Config en nuestro módulo Styles var Config = require('Config'); module.exports = { Window: { backgroundColor:'#fff' }, LabelTitle: { top:0, width:Ti.UI.FILL, height: 40, text: Config.name, backgroundColor:'#000', color:'#fff', font:{fontSize:24} } } |
Y ahora en App.js…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //Cargamos los módulos necesarios var $$ = require('Styles'); var LabelTitle = require('LabelTitle'); var SwitchButton = require('SwitchButton'); var win = Ti.UI.createWindow($$.Window); var lblTile = new LabelTitle($$.LabelTitle); //creamos nuestra instancia de la clase SwitchButton: var btn = new SwitchButton({ width:180, height:35, title:'hazme click!', colorOn: '#fff', colorOff:'#999' }); win.add(lblTitle); win.add(btn); win.open(); |
Me gusta usar $$
siempre como nombre del módulo de estilos. Es fácil de recordar, rápido de escribir y recordar (manía que adquirí del estilo de programación de tweetanium, una app que hizo escuela en los principios de Titanium). El hecho de que sean dos símbolos $$ no tiene ninguna otra implicación como nombre de variable.
Esto es to, esto es todo amigos
Estos dos patrones son suficientes para empezar a montar la arquitectura de la aplicación. Por supuesto que hay más e intentaré mostrarlos en sucesivos tutoriales.
Mi objetivo, una vez superados los conceptos básicos de JavaScript y sabiendo qué son y cómo funcionan los módulos commonJS, es mostraros como crear una arquitectura sólida, escalable y segura para una aplicación móvil con Titanium. En la próxima entrega crearemos una plantilla para comenzar todas nuestras aplicaciones desde una base e iremos viendo como superar los distintos obstáculos que nos encontraremos en la comunicación entre componentes. Siempre, por supuesto, intentando seguir el manual de buenas prácticas.
En entregas posteriores, desarrollaré algunos problemas típicos de programación, como la llamada a métodos asíncronos, componentes UI avanzados, optimización de código y lo que se me vaya ocurriendo (se aceptan retos, ruegos y sugerencias).
Hasta entonces, os recomiendo probar y desmenuzar la app TitanTricks, un proyecto iOS+Android de demostración/experimentación con Titanium. También existe TitanTricksMobileWeb, para su versión web.
¿Te interesa Titanium y JavaScript? Sígueme en @jrayon
Rodrigo Costa says
Hola,
Gracias por escribir este tutorial. Muy bueno para entender mejor sobre la estructura.
Ahora su blog queda entre los mis favoritos.
Saludos,
Rodrigo.
@RSCdev – Twitter.
J. Rayon says
Gracias, Rodrigo
Jorge says
Excelente explicación te felicito, esta nota me abrió un poco mas el panorama de como usar CommonJS con Titanium.
Te queria preguntar si existe forma de realizar una aplicación con procesos concurrentes, deseo realizar una aplicación en donde por ejemplo este trabajando con la aplicación normalmente (viendo productos, agregandolos , modificandolo etc. ) y por el otro lado este mandando mi posicion cada 5 minutos a un servidor externo mediante un web service.
Quisiera saber si es posible realizar esto?, y de ser asi como se aria?. Espero y me puedas ayudar.
J. Rayon says
Si la aplicación está abierta, no hay ningún problema en hacer esto.
Puedes ejecutar una tarea repetitiva en Titanium con función setInterval(). Por ejemplo,
2
3
4
5
6
//actualiza aquí la posición
}
//5*60*1000 = 5 minutos en milisegundos
var timerPosition = setInterval(updateUserPosition, 5*60*1000);
en este ejemplo, la función updateUserPosition se ejecutará cada 5 minutos. Para detener el reloj, basta con hacer
timerPosition = null;
Ojo, que esto realmente no son tareas concurrentes, se ejecutarán en el mismo hilo, pero en principio si la carga de lo que está haciendo la app no es muy grande, debería funcionarte. Para tareas realmente en paralelo, existe un módulo para iOS experimental que permite crear “workers” que hacen tareas en un segundo plano. Para usar el módulo hay dominar el uso de threads, pero cualquier interesado puede encontrarlo en https://github.com/appcelerator-modules/ti.worker
Jorge says
Gracias por tu respuesta tratare de hacerlo como comentas y tambien le dare uan leida al modulo se ve interesante. Saludos!!!
Antonio says
Excelentes artículos, espero y realices mas publicaciones, quisiera hacerte una pregunta que me surgió estoy intentando hacer una aplicacion que reciba notificaciones push, este no es problema si no que quiero gestionar el envio de estas a travez de mi propio portal construido en ASP.NET , y es aqui donde surge mi duda puedo consumir el servicio que brinda Appcelerator desde mi aplicacion Web y si asi como lo podria implementar?, espeor y me puedas dar alguna referencia. Muchas gracias por todo
J. Rayon says
sí, puedes hacerlo mediante llamadas REST. Mira la documentación aquí http://docs.appcelerator.com/cloud/latest/#!/api/PushNotifications