Titanium Mobile: Crea un cargador de imágenes
Spanish (Español) translation by Steven (you can also view the original English article)
Este tutorial demostrará cómo crear una barra de progreso personalizada mediante la creación de un cargador de imágenes con Titanium Mobile. Específicamente, el proyecto de demostración te permitirá seleccionar una imagen de la galería de fotos del dispositivo y cargarla en un servidor remoto para su almacenamiento. Se explicarán tanto el código de Titanium Mobile como el código del lado del servidor. ¡Ahora, comencemos!
Vista previa del proyecto
La siguiente es una vista previa de la aplicación multiplataforma que crearemos en esta serie:






Requisitos previos...
Este tutorial asume que estás ejecutando Titanium Studio y tienes instaladas las últimas actualizaciones de SDK, que eran 1.7.5 en el momento de escribir este artículo. Como este tutorial se centra en la plataforma iOS, el lector también se beneficiaría de tener todos los requisitos para el desarrollo en la plataforma iOS.
Paso 1: Crear un nuevo proyecto
Abre Titanium Studio y crea un nuevo proyecto, seleccionando solo los objetivos de dispositivo Android, iPhone y iPad. Puedes nombrar tu nuevo proyecto como quieras, sin embargo, llamaremos al nuestro "ProgressBar". Proporciona a tu nuevo proyecto un ID de aplicación y una URL, luego selecciona la última versión del SDK de Titanium. Cuando hayas terminado, haz clic en "Siguiente" y luego selecciona la plantilla "Aplicación de ventana única" antes de hacer clic en "Finalizar".






Si la opción de plantilla "Aplicación de ventana única" no está disponible en tu versión de Titanium, puedes importar el código fuente del proyecto adjunto a este tutorial. Descarga los archivos del proyecto y descomprímelos en el directorio desde el que deseas trabajar. Luego sigue estos pasos:
- En el menú Archivo, selecciona "Importar" para abrir la ventana emergente de "Importar proyecto".
- En la lista de fuentes de importación, selecciona "Titanium" y luego "Importar proyecto existente de Titanium".
- En el campo de texto 'Directorio', busca la carpeta que contiene los archivos del proyecto que descargaste y descomprimiste.
- Haz clic en el botón Finalizar y se creará un nuevo proyecto llamado "ProgressBar" y estará disponible en el panel Explorador de proyectos.
Si aún no lo has hecho, ahora es un buen momento para descargar los archivos del proyecto y descomprimir todas las imágenes en el directorio "Resources/assets/images" en tu propio directorio "Resources/assets/imagess" bajo el nuevo proyecto que acabas de crear. De forma predeterminada, Titanium también incluye dos archivos de iconos en la raíz de tu directorio "Resources" llamados KS_nav_ui.png y KS_nav_views.png; no los necesitamos, así que simplemente mueve ambos a la papelera. Puedes omitir este paso si ya has importado todo el proyecto mediante la función "Importar".
Paso 2: Creación de la interfaz
Como la plantilla de la aplicación de ventana única sigue la estructura de CommonJS, puede ser un poco diferente a lo que estás acostumbrado. Aquí, el archivo app.js extrae el archivo ApplicationWindow.js usando 'require', que a su vez crea una instancia y devuelve nuestro objeto Window predeterminado para la aplicación. El archivo ApplicationWindow crea una instancia de una nueva vista llamada FirstView, que está codificada dentro del archivo FirstView.js en la carpeta 'Resources/ui'. Este objeto de vista FirstView es donde codificaremos casi todo este tutorial. Elimina cualquier código pregenerado dentro del constructor export.FirstView = function() y reemplázalo con lo siguiente:
//FirstView Component Constructor exports.FirstView = function() { // Let's hide the status bar on the iphone/ipad for neatness if(Ti.Platform.osname == 'iphone' || Ti.Platform.osname == 'ipad'){ Titanium.UI.iPhone.statusBarHidden = true; } // Create object instance, a parasitic subclass of Observable var self = Ti.UI.createView({ backgroundColor: '#232323' }); // The view below is the background of the slider var progressBackgroundView = Ti.UI.createView({ width: 300, height: 27, left: ((Ti.Platform.displayCaps.platformWidth - 300) / 2), top: (Ti.Platform.displayCaps.platformHeight / 2), visible: false, backgroundImage: 'assets/images/track-complete.png' }); self.add(progressBackgroundView); //the slider will show a graphical representation of the upload progress //backgroundImage will reduce flicker as it doesn't redraw every width change like 'image' will var progressView = Ti.UI.createImageView({ width: 0, height: 25, left: 1, top: 1, backgroundImage: 'assets/images/bar.jpg', borderRadius: 3 }); progressBackgroundView.add(progressView); //this label will show the upload progress as a percentage (i.e. 25%) var lblSending = Ti.UI.createLabel({ width: 'auto', right: ((Ti.Platform.displayCaps.platformWidth - 300) / 2), top: ((Ti.Platform.displayCaps.platformHeight / 2) + 30), text: '', height: 20, font: {fontFamily: 'Arial', fontSize: 14, fontWeight: 'bold'}, color: '#fff', textAlign: 'right', visible: false }); self.add(lblSending); //this button will appear initially and allow the //user to choose a photo from their gallery var btnChoosePhoto = Ti.UI.createButton({ width: 220, height: 35, title: 'Select photo for upload.', font: {fontFamily: 'Arial'}, color: '#000000', top: (Ti.Platform.displayCaps.platformHeight / 2), visible: true }); self.add(btnChoosePhoto); return self; };
Arriba hemos creado la estructura básica de nuestra barra de progreso, y un botón que permitirá al usuario abrir la Galería de fotos en su dispositivo y seleccionar una imagen que desee cargar. Ejecuta la aplicación en el simulador ahora, y deberías ver un solo botón en el medio de la pantalla. Algo similar a esto:



Paso 3: Elegir una imagen de la galería
Ahora que tenemos nuestra configuración de interfaz básica, agreguemos un detector y controlador de eventos a btnChoosePhoto que permitirá a nuestro usuario elegir una foto de su galería. Escribe el siguiente código directamente antes de la línea 'self.add(btnChoosePhoto);'.
btnChoosePhoto.addEventListener('click', function(e){ Titanium.Media.openPhotoGallery({ success:function(event) { Ti.API.debug('Our type was: '+event.mediaType); if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO) { UploadPhotoToServer(event.media); } }, cancel:function() { }, error:function(err) { Ti.API.error(err); }, mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO] }); });
En este controlador de eventos "clic", estamos abriendo la galería y comprobando dentro de la sección "success" del código si el tipo de medio elegido fue una foto comparando el objeto con el valor constante Ti.Media.MEDIA_TYPE_PHOTO. Si esta verificación pasa, a continuación pasamos nuestro objeto event.media a una nueva función llamada UploadPhotoToServer. Es aquí donde realizaremos la carga del archivo y mostraremos al usuario nuestra barra de progreso a medida que se realiza la carga.
Paso 4: Crear el código del servidor PHP para aceptar y guardar nuestra foto cargada
Vamos a crear nuestro código del servidor para guardar de forma remota nuestra foto y devolver su URL usando PHP. PHP es un lenguaje web común y bien conocido; compatible también con muchos servicios de alojamiento web. Crea un nuevo archivo llamado upload.php y escribe en ese archivo el siguiente código:
<?php //this function returns a random 5-char filename with the jpg extension function randomFileName() { $length = 5; $characters = 'abcdefghijklmnopqrstuvwxyz'; $string = ''; for ($p = 0; $p < $length; $p++) { $string .= $characters[mt_rand(0, strlen($characters))]; } return $string . '.jpg'; } //create the random filename string and uploads target variables $randomString = randomFileName(); $target = 'uploads/'; $target = $target . $randomString; if(move_uploaded_file($_FILES['media']['tmp_name'], $target)) { //output the location to our image echo 'https://mobiletuts.example.com/progressbar/uploads/' . $randomString; } else { echo "false"; } ?>
Este script PHP básico simplemente toma un archivo POSTED llamado 'media' y luego lo guarda con un nombre de archivo aleatorio de cinco caracteres de la extensión JPG, que es generado por la función randomFileName(). Si logramos guardar la foto con éxito, el script repetirá la ubicación remota de la URL de nuestra nueva foto; de lo contrario, se repetirá "falso" si falla.
Ahora debes guardar y transferir el archivo upload.php a tu servidor. Además, también necesitarás crear una nueva carpeta en el mismo directorio que tu archivo upload.php llamado 'uploads'. Esta es la carpeta donde se guardarán nuestras imágenes. Asegúrate de que la carpeta de imágenes tenga los permisos de escritura adecuados (generalmente CHMOD 770) o nuestro script PHP no funcionará.
Ten en cuenta que si no tienes acceso a un servidor PHP, siempre puedes escribir un script en el idioma de tu elección (.NET, Ruby, etc.) que realice la misma tarea.
Paso 5: Cargar una foto y mostrar el progreso a través de nuestra barra de progreso personalizada
Antes de crear nuestra función, hay un par de cosas pequeñas que debemos hacer. La primera es crear una variable llamada androidUploadProgress en la parte superior de nuestras exportaciones de la declaración de FirstView. Esto hará un seguimiento del progreso de carga aproximado de nuestra carga de archivos en dispositivos Android. Desafortunadamente, en el momento de escribir este artículo, Titanium Mobile para Android no es compatible con la variable de progreso durante el evento onsendstream, lo que significa que no podemos calcular exactamente qué tan lejos estamos de la carga en los dispositivos Android y, por lo tanto, debemos utilizar un enfoque de mejor estimación.
//this variable is for android to calculate the approx upload progress var androidUploadProgress = 0;
Además, necesitamos crear una nueva variable de tipo string en el archivo "i18n/en/strings.xml" que contendrá la ubicación de nuestro script de carga del servidor. Agrega el siguiente elemento de tipo string después del existente de 'bienvenida', reemplazando la URL con la ubicación de tu propio script PHP.
<string name="server">http://mobiletuts.example.com/progressbar/upload.php</string>
Creamos ahora nuestra función UploadPhotoToServer, escribiendo el siguiente código en tu archivo FirstView.js. El mejor lugar para esta función es debajo de la sección cerca de la parte superior que oculta la barra de estado en los dispositivos iOS. Esto comienza con if(Ti.Platform.osname == 'iphone'. Esta función comprobará si nuestro usuario está en línea y, de ser así, creará un nuevo HTTPClient que enviará los datos multimedia a nuestro servidor PHP donde se guardarán y se devolverá la URL completa de la imagen cargada.
Esta es una función larga y forma la "carne" de nuestra aplicación Titanium , así que vamos a analizarla pieza por pieza. En primer lugar, creemos la función en sí, junto con el código inicial que ocultará el botón "Elegir foto" y, en su lugar, mostrará al usuario nuestra barra de progreso:
//this function will take in a 'media' object (a photo from the gallery in this case) //and will upload it to our server via the PHP script. On a successful upload, our //server will return the new HTTP path of the image we uploaded, which we can then load //in the Safari/web browser so the user can view it. function UploadPhotoToServer(media){ if (Titanium.Network.online == true) { self.children[0].show(); //show the uploading slider progress bar self.children[0].children[0].width = 0; //make sure the default value is zero self.children[1].show(); //show the uploading label self.children[1].text = 'Uploading photo, please wait...'; //set the label to default value self.children[2].hide(); //hide the select photo button } else { alert('You must have a valid Internet connection in order to upload this photo.'); } }
Notarás que estamos accediendo a nuestros componentes de visualización haciendo referencia a su índice dentro del objeto secundario FirstView. También podríamos haber declarado simplemente nuestros componentes de visualización en la parte superior de nuestro archivo FirstView.js y luego crear una instancia de ellos, sin embargo, hemos elegido hacer las cosas de esta manera para que comprendas cómo acceder al elemento secundario de una vista principal. A continuación, necesitamos crear una instancia de nuestro objeto XHR HttpClient y crear todos los controladores de eventos necesarios para rastrear cuándo se está ejecutando nuestra carga de HttpClient y si falla o ha tenido éxito. Agrega el siguiente código debajo de lo que escribiste anteriormente. Observa cómo estamos accediendo a la propiedad string "server" establecida anteriormente en el tutorial desde nuestro archivo strings.xml usando L("server"), y cómo el objeto de parámetro que envía nuestra publicación HttpClient se llama "media", que coincide exactamente con el Objeto FILE "media" esperado por nuestro script PHP.
var xhr = Titanium.Network.createHTTPClient(); xhr.onerror = function(e){ Ti.API.info('IN ERROR ' + e.error); alert('Sorry, we could not upload your photo! Please try again.'); }; xhr.onload = function(){ Ti.API.info('IN ONLOAD ' + this.status + ' readyState ' + this.readyState); }; xhr.onsendstream = function(e){ Ti.API.info('ONSENDSTREAM - PROGRESS: ' + e.progress); }; // open the client xhr.open('POST', L('server')); //the server location comes from the 'strings.xml' file // send the data xhr.send({ media: media, });
Ahora editemos la función del controlador de eventos onsendstream. En esta función vamos a calcular el progreso de carga actual de nuestro archivo, y ampliaremos el ancho de la barra de progreso en consecuencia. Ten en cuenta que hacemos esto ligeramente diferente para Android que para iOS, ya que la propiedad "progress" de onsendstream no está actualmente implementada para Android.
xhr.onsendstream = function(e){ Ti.API.info('ONSENDSTREAM - PROGRESS: ' + e.progress); if(Ti.Platform.osname == 'android') { //android doesn't support the "progress" variable during onsendstream yet :( //we're going to dummy up a progress value for this based on each packet being about 2.5% of the total upload progress //it won't be totally accurate, but it will give the user a good indicator that the upload is working if(androidUploadProgress < 1) { androidUploadProgress += 0.025; self.children[1].text = 'Uploading photo, please wait... ' + (Math.round(androidUploadProgress * 100)).toString().replace(".","") + '%'; self.children[0].children[0].width = Math.round(298 * androidUploadProgress); } } else { //else on ios devices, calculate the progress of the upload using e.progress if(Math.round(e.progress * 100) <= 100) { self.children[1].text = 'Uploading photo, please wait... ' + (Math.round(e.progress * 100)).toString().replace(".","") + '%'; self.children[0].children[0].width = Math.round(298 * e.progress); //set the slider value to the nearest whole integer (ie 25%, not 24.95%) } } };
Intenta ejecutar tu aplicación en el simulador ahora, y después de elegir una foto de la Galería, tu barra de progreso debería aparecer y debería aumentar de tamaño a medida que se carga el archivo. Debería verse como lo siguiente:



Paso 6: Terminando
Finalmente, necesitamos extender la función descarga (unload) del controlador de eventos. En él, debemos asegurarnos de que la barra de progreso muestre el 100% (especialmente para Android), y debemos determinar si el archivo se cargó correctamente en el servidor o no verificando si la propiedad responseText está establecida en "false". Si no es así, podemos asumir que todo funcionó A-OK y presentar a nuestros usuarios un cuadro de diálogo de alerta de confirmación donde pueden elegir abrir su imagen recién cargada en el navegador web. Luego, restableceremos nuestro diseño y todas las propiedades de nuestros objetos cuando estemos listos para la siguiente carga de fotos.
xhr.onload = function(){ Ti.API.info('IN ONLOAD ' + this.status + ' readyState ' + this.readyState); if(this.responseText != 'false') { var url = this.responseText; //set our url variable to the response self.children[0].children[0].width = 298; //set the progress to 100% (298px based on our design) //if we successfully uploaded, then ask the user if they want to view the photo var confirm = Titanium.UI.createAlertDialog({ title: 'Upload complete!', message: 'Open your image in the browser?', buttonNames: ['Yes', 'No'] }); confirm.addEventListener('click', function(conEvt) { //if the index selected was 0 (yes) then open in safari Ti.API.info(conEvt.index); if(conEvt.index === 0){ //open our uploaded image in safari Ti.Platform.openURL(url); } //reset the upload button self.children[0].hide(); //hide the status bar self.children[1].hide(); //hide the status label self.children[2].show(); //show the upload button again androidUploadProgress = 0; //reset the android progress value }); confirm.show(); } else { alert('Whoops, something failed in your upload script.'); self.children[0].hide(); //hide the status bar self.children[1].hide(); //hide the status label self.children[2].show(); //show the upload button again androidUploadProgress = 0; //reset the android progress value } };
¡Eso es todo! Ejecuta la aplicación ahora en el simulador (Android o iPhone) o en tu dispositivo. Aún deberías ver el medidor de progreso de carga de archivos animado de 0 a 100%, y cuando se complete, debería aparecer un cuadro de diálogo de alerta como el que se muestra a continuación, dándole la opción de ver la foto recién cargada en el navegador.





