Skip to content
Christian Gastrell edited this page May 26, 2015 · 4 revisions

Guardar las fotos que sacamos desde la aplicacion de Galeria utilizando los metodos anteriores. Trabajen sobre prueba12.

HTML

El archivo index.html queda asi (los botones que usamos para probar los archivos de texto no molestan, asi que los dejamos). No hay desafios aca.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="format-detection" content="telephone=no" />
    <meta name="msapplication-tap-highlight" content="no" />
    <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
    <link rel="stylesheet" type="text/css" href="css/jquery.mobile-1.4.5.min.css" />
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <title>Galeria</title>
    <style>
      .picThumb {
        max-width: 20%;
      }
      img.foto {
        max-width: 100%;
      }
    </style>
  </head>
  <body>

    <div data-role="page" id="home">
      <div data-role="header">
        <h1>Galeria de fotos</h1>
      </div>
      <div data-role="content" class="galeria">
        <p class="fileContent"></p>
        <a href="#" id="grabarArchivo" class="ui-btn ui-btn-b">Grabar</a>
        <a href="#" id="leerArchivo" class="ui-btn ui-btn-b">Leer</a>
      </div>
      <div data-role="footer" data-position="fixed">
        <a href="#" id="sacarFoto" style="display:block" class="ui-btn ui-btn-b ui-icon-camera ui-btn-icon-top">Foto!</a>
      </div>
    </div>

    <div data-role="page" id="fotoView">
      <div data-role="header">
        <a href="#" class="ui-btn" data-rel="back">Volver</a>
        <h1>Foto</h1>
      </div>
      <div data-role="content">
        <img class="foto" src="" />
      </div>
    </div>

    <script type="text/javascript" src="cordova.js"></script>
    <script type="text/javascript" src="js/index.js"></script>
    <script type="text/javascript" src="js/jquery.mobile-1.4.5.min.js"></script>
    <script type="text/javascript">

    </script>
  </body>
</html>

Codigo

En el archivo js/index.js tenemos la inicializacion con Deferred's (pueden ir copiando y pegando los bloques de codigo, deberian terminar armando todo el archivo):

var deviceReady = $.Deferred();
var jqmReady = $.Deferred();
var documentReady = $.Deferred();

$(document).on("ready", function() {
  documentReady.resolve();
});

$(document).on("deviceready", function() {
  deviceReady.resolve();
});

$(document).on("mobileinit", function () {
  jqmReady.resolve();
});
//cuando los 3 se resuelvan, ejecutar init()
$.when(deviceReady, jqmReady, documentReady).then(init);

Inicializacion

La funcion init() inicializa la configuracion de jQuery Mobile, la funcionalidad de la camara y el fileApi que hicimos:

function init() {
  // Configuracion de JQM para phonegap
  $.mobile.allowCrossDomainPages = true;
  $.support.cors = true;
  $.mobile.buttonMarkup.hoverDelay = 0;
  $.mobile.pushStateEnabled = false;
  $.mobile.defaultPageTransition = "none";

  //inicializamos la funcion de la camara y el fileApi
  camara.initialize();
  fileApi.initialize();
}

NOTA: el objeto camara y fileApi pueden ubicarse en cualquier orden en el codigo mientras mantengan la integridad del objeto (estos bloques de codigo son demostrativos, no van en el archivo index.js):

  • el objeto se declara como una variable: var camara = {}
  • el objeto puede tener metodos dentro, separados por coma:
var camara = {
  metodo1: function() { alert('Hola'); },
  metodo2: function() { alert('Chau'); }
}
  • el ultimo metodo no debe llevar una coma cuando se cierra
  • los metodos son como variables dentro del objeto, pero se asignan con dos puntos (:), no con el signo igual (=)
var camara = {
  metodo1: function() { alert('metodo correcto'); },
  metodo2 = function() { alert('metodo erroneo, eleva un error y detiente la ejecucion') }
//^^^^^^^^^^^^^^^^^^^^
}

FIN DE NOTA


Camara

Luego tenemos este objeto que nos sirve para inicializar la camara de manera ordenada y reusable (voy a partir el objeto en cada metodo, pero pueden seguir copiando y pegando cada bloque en orden y deberia quedar bien):

Declaracion del objeto y metodo para inicializacion

var camara = {
  initialize: function() {
    if(localStorage.galeria) {
      $.each(JSON.parse(localStorage.galeria), function(i,e){
        camara.addPictureToGallery(e.path);
      });
    }else{
      localStorage.galeria = JSON.stringify([]);
    }
    $('#sacarFoto').on('click', camara.sacarFoto);
  },

Al inicializar, verificamos si existe localStorage.galeria. De ser asi lo parseamos y asumimos que es un array. Iteramos sobre este array y (este es uno de los cambios) por cada item del array sabemos/suponemos que tendremos un objeto {name: "nombreDelArchivo", path: "rutaAlArchivo"}. Sabiendo esto, llamamos a camara.addPictureToGallery(e.path) (donde e es el valor del item iterado).

Si localStorage.galeria no existe, le asignamos un array vacio convertido en texto con el metodo JSON.stringify.

Por ultimo, asignamos al boton $('#sacarFoto') la funcion camara.sacarFoto cuando surja el evento click.


sacarFoto

  sacarFoto: function() {
    var options = {
      destinationType : Camera.DestinationType.DATA_URL,
      sourceType : Camera.PictureSourceType.CAMERA,
      saveToPhotoAlbum: true
    };

    navigator.camera.getPicture(camara.onDataUrlSuccess, camara.onError, options);
  },

El metodo sacarFoto llama a navigator.camera.getPicture con un set simple de opciones. Como callback pasa camara.onDataUrlSuccess


onDataUrlSuccess

  onDataUrlSuccess: function(imageData) {
    //en cuanto empieza esta funcion
    //mostramos un "loading" o spinner
    $.mobile.loading('show');

    //traigo la galeria del localStorage y la parseo
    var fotos = JSON.parse(localStorage.galeria);

    //genero un id para la imagen, sirve de filename tambien
    var id = 'img_' + parseInt(Math.random() * 1000000);

    //escribo la imagen y a la vuelta agrego el id:path al storage
    fileApi.writeJpeg(id + '.jpg', imageData, function(jpegPath){
      //con el id y el path, hago un objeto y lo agregamos al array
      fotos.push({name:id, path: jpegPath});
      
      //por ultimo convertimos el array en string
      //y lo volvemos a almacenar en localStorage
      localStorage.galeria = JSON.stringify(fotos);

      //agregamos la imagen a la galeria (HTML)
      camara.addPictureToGallery(jpegPath);
    });
  },

El metodo onDataUrlSuccess es el callback asignado para cuando sacamos la foto. Recibe como parametro el archivo JPG codificado en base64.

Luego actualiza el registro de fotos en localStorage.galeria, guarda la foto y la agrega a la galeria (HTML):

  • Parsea el valor de localStorage.galeria en un array fotos
  • Genera un id img_ con un numero al azar concatenado
  • Llama a fileApi.writeJpeg pasandole
    • El id generado (ej: img_823391) y la extension ".jpg" como nombre de archivo
    • El archivo codificado en base64 como contenido
    • Una funcion para el callback que
      • Recibe el path donde queda guardado el archivo
      • Agrega un objeto al array fotos con:
        • name: id generado
        • path: la ruta recibida por la funcion
      • Vuelve a convertir el array fotos con JSON.stringify y lo almacena en localStorage.galeria
      • Llama a camara.addPictureToGallery pasandole el path donde queda guardado el archivo

addPictureToGallery y onError

  addPictureToGallery: function(image) {
    var pic = $('<img />')
      .addClass('picThumb')
      .attr('src', image)
      .appendTo('.galeria');

    pic.click(function(e){
      $('#fotoView img').first().attr('src',image);
      $('body').pagecontainer('change','#fotoView');
    });
    $.mobile.loading('hide');
  },
  onError: function(message){
    console.log(arguments);
    alert('Error: ' + message);
  }
}

addPictureToGallery genera un tag/elemento HTML <img />, le agrega la clase picThumb, le agrega el attributo src con el path a la imagen (recibida como parametro en la funcion) y agrega el elemento al elemento que tiene clase .galeria.

A este mismo <img /> le asigna una funcion al evento click:

  • Buscar un img dentro de #fotoView (la pagina que usamos para ver las fotos)
  • Como puede devolver mas de uno, pedimos el primero con .first()
  • A ese img le cambia el valor del attributo src por el del path de la foto (insertando efectivamente la imagen en la pagina #fotoView)
  • Navega a la pagina #fotoView (por codigo)

Y por ultimo esconde el loading/spinner que mostramos cuando iniciamos el proceso, alla por onDataUrlSuccess.


fileApi

El objeto que generamos para ayudarnos con la lectura y grabacion de archivos. Este lo tienen mas fresquito, asi que no voy tan detallado.

var fileApi = {
  initialize: function(){
    window.resolveLocalFileSystemURL(cordova.file.externalDataDirectory, fileApi.onDir, fileApi.onError);

    $('#grabarArchivo').click(function(){
      fileApi.writeTextFile('test.txt', 'texto de prueba', function(data){
        alert('Escribi '+data+' en test.txt');
      });
    });

    $('#leerArchivo').click(function(){
      fileApi.readTextFile('test.txt', function(data){
        $('.fileContent').text('Contenido del archivo: '+data);
      });
    });
  },
  onDir: function(directoryEntry) {
    fileApi.dir = directoryEntry;
  },
  onError: function(err) {
    alert(err.code);
  },
  writeTextFile: function(file, content, callback) {
    var onFile = function(fileEntry) {
      fileEntry.createWriter(function(fileWriter){
        fileWriter.write(content);
        callback && callback(content);
      }, fileApi.onError);
    }
    fileApi.dir.getFile(file, {create: true}, onFile, fileApi.onError);
  },
  readTextFile: function(file, callback) {
    var onFile = function(fileEntry) {
      //convierte el fileEntry en un fileObject
      fileEntry.file(function(fileObject){
        var reader = new FileReader();
        reader.onloadend = function(){
          callback && callback(this.result);
        }
        reader.readAsText(fileObject);
      });
    }
    fileApi.dir.getFile(file, {create:false}, onFile, fileApi.onError);
  },
  writeJpeg: function(fileName, base64content, callback) {
    var onFile = function(fileEntry) {
      fileEntry.createWriter(function(fileWriter){
        var blob = b64toBlob(base64content,'image/jpeg');
        fileWriter.onwriteend = function(){
          console.log(this);
          callback && callback(fileEntry.nativeURL);
        }
        fileWriter.write(blob);
      }, fileApi.onError);
    }
    fileApi.dir.getFile(fileName, {create: true}, onFile, fileApi.onError);
  }
}

b64toBlob

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

Esta es la funcion que convierte el archivo codificado en base64 a Blob. Deberian tenerla al menos al final del archivo

Consigna

Entender el codigo e implementarlo en la aplicacion. Copien y peguen, drive safe. Traten de ver las imagenes/archivos de texto a traves del explorador de windows (como para verificar).

Para entretenerse:

https://github.com/AllThingsSmitty/jquery-your-mom-should-know

  1. Repaso y conceptos basicos
  2. Ejercicios para repasar
  3. Problematica de desarrollo mobile
  4. Phonegap
  5. Requerimientos e instalacion
  6. Phonegap CLI
  7. [Primeros ejercicios con Phonegap](Ejercicio Phonegap)
  8. Plugins
  9. Hello world
  10. Estructura de un proyecto Phonegap
  11. Plugin Device
  12. Debugging
  13. Incorporando un framework CSS
  14. Plugin Vibration
  15. Sumando partes
  16. Plugin Battery Status
  17. Un poco de jQuery
  18. jQuery Mobile
  19. Navegacion
  20. Plugin Dashboard
  21. Revision de Plugin Dashboard
  22. Paginas extra
  23. ToDo
  24. Persistencia
  25. ToDo Persistente
  26. Plugin Camera
  27. Opciones de camara
  28. App Mi Galeria
  29. Revision de Galeria
  30. Plugin File
  31. Escribir y leer texto
  32. Escribir archivos binarios
  33. Guardando fotos
  34. Debugger: weinre
  35. Inspeccionar con weinre
  36. Phonegap Developer App
  37. Refactor de apps
  38. Plugin Media
  39. Pruebas con audio
  40. Control y monitoreo de audio
  41. Encapsular play/pausa
  42. Hacks
  43. Formato de tiempos
  44. Grabar y reproducir
  45. Proyecto Integrador
  46. Revision de interfaz
  47. Interfaz basica
  48. Entrevista: modelo
  49. Funcionalidad: revision
  50. Seleccion de framework
  51. iRec: navegacion
  52. iRec: fileApi
  53. iRec: guias
  54. iRec: entrevistas
  55. iRec: recordApi
  56. iRec: helpers
  57. iRec: mediaApi
  58. iRec: inicializar paginas
  59. iRec: refactor
  60. iRec: mediaApi refactor
  61. iRec: recordApi refactor
  62. iRec: helpers
  63. iRec: guias.js
  64. iRec: entrevistas.js
  65. iRec: crear guias
  66. iRec: re-inicializando
  67. iRec: re-ordenando
  68. iRec: pendientes
  69. iRec: reporte ux
  70. iRec:revision: volver
  71. iRec:entrevista: volver
  72. iRec:revision: cambios
  73. iRec:revision: stop
  74. iRec:revision: Rew 10"
  75. iRec:revision: tag Go
  76. iRec: limpieza
  77. Firma de apps
  78. Android
  79. Generacion de key
  80. Firma de apk
  81. Alineacion de zip
  82. Firmar con Phonegap
  83. iOS
  84. Detalles finales
  85. Config.xml
  86. Iconos
  87. Splash
Clone this wiki locally