Una de las grandes virtudes de Adobe AIR es ser multiplataforma. Sin embargo, esto hace que cosas como el manejo de los diálogos del sistema de archivos tengan que adaptarse a Windows, Mac y Linux, y no ofrezcan tantas funciones como sería de esperar. Por ejemplo, cada vez que abrimos un diálogo para abrir o guardar un archivo, por defecto se abre en la localización en que dejamos la ventana anterior (si le damos un objeto File nuevo, y sin ruta definida), aunque eso no siempre sea lo más conveniente.
Por ejemplo, imaginemos que estamos haciendo algún tipo de editor, que cargue archivos fuente (digamos, por ejemplo, archivos txt), y genere algún tipo de archivo de salida (como un jpg en el que insertamos el texto que hemos cargado). No nos interesa que al abrir el diálogo de guardar archivo tengamos que encontrar la carpeta donde dejamos la salida (pongamos D:/trabajos/jpgtxt/ ), y cada vez que queremos cargar tengamos que buscar el directorio de las fuentes (que puede ser C:\Documents and Settings\usuario\Mis documentos\fuentes), desde el de las salidas.
Para solucionar esto, haremos una clase que se encargue de memorizar mediante SharedObjects las rutas en las que guardamos los diferentes archivos, y de paso, un par de clases que encapsulen el proceso de cargar y guardar datos desde los diálogos del sistema, ya que realmente lo único que nos interesa es cargar un ByteArray y guardar un ByteArray, ya que al fin y al cabo, son estos objetos los que contienen la información útil de los archivos. De esta manera, podríamos recordar tanto la ruta del archivo que guardamos, como la del que cargamos, y tenerla disponible cada vez que abrimos el programa. Además nos ahorraríamos preocupaciones sobre cómo se cargan y guardan archivos, y trabajaríamos únicamente con los byteArrays que contiene los datos.
Así pues, la clase PathSaver, sería algo así:
Código :
package com.zigzah.utils { import flash.events.Event; import flash.filesystem.File; import flash.net.SharedObject; /**Class to manage the file paths in a more usable way.*/ public class PathSaver { private static var so:SharedObject; /** Returns a file from the location it was saved the last time. * @param id The id of the given file. * @param returnName if true, the function returns name of the file, else, it returns the folder. * @param newName if returnName is set to false, the new name of the file. * @return A File object, with the path it had the last time it was saved. */ public static function getFile (id:String , returnName:Boolean = false , newName:String = ""):File { /*Obtenemos el SharedObject, y sacamos la ruta del archivo y la del directorio.*/ so = SharedObject.getLocal(id + "$file"); var path: String = so.data.path; var parent: String = so.data.parent; var file:File = new File (); /*Devolvemos un nuevo archivo según si existía el so, y según los parámetros.*/ if (path && parent) { if (returnName) { file = file.resolvePath(path); }else if (newName != "") { file = file.resolvePath(parent).resolvePath(newName); }else { file = file.resolvePath(parent) } } else { file = File.desktopDirectory.resolvePath(newName); } /*Indicamos que cada vez que el archivo se guarde o abra, su nueva ruta quede almacenada en el so.*/ file.addEventListener(Event.SELECT, savePath); return file; } /** This function saves the file path in a sharedObject. */ private static function savePath (event:Event) :void { var file:File = event.target as File; so.data.path = file.nativePath; so.data.parent = file.parent.nativePath; so.flush(); } } }
Con esta clase, cada vez que necesitamos llamar a una archivo cuya ruta almacenó el usuario, en vez de crear el objeto File con new File (); usamos PathSaver.getFile ("IDDelArchivo");. Además, jugando con los otros parámetros, podemos conseguir distintas funcionalidades, como conseguir la ruta absoluta del archivo, o la de la carpeta que lo contiene, y obtener un nuevo nombre por defecto, que puede ser útil para guardar (como veremos en el ejemplo de abajo).
Veamos ahora una clase para cargar archivos desde el diálogo de Abrir, que hace uso de esta clase, y devuelve simplemente un ByteArray con los datos cargados:
Código :
package com.zigzah.utils { import flash.events.Event; import flash.events.EventDispatcher; import flash.filesystem.File; import flash.filesystem.FileMode; import flash.filesystem.FileStream; import flash.net.FileFilter; import flash.utils.ByteArray; /**Dispatched when the user selects a file from the fileSystem, * and the bytearray from the file is ready.*/ [Event(name="bytesloaded", type="com.zigzah.utils.LoadFileEvent")] /** A utility class to get files easily from the filesystem */ public class FileLoader extends EventDispatcher { private var file :File private var filter:FileFilter /**The title of the open dialog window*/ public var openDialog : String = "Open File"; /**Creates a new FileLoader object, with the specified filefilter and * default path, in the open dialog * @param filter the filefilter object that will be used to browse for files in the filesystem. * @param fileID the id that PathSaver will to get a file.*/ public function FileLoader(filter : FileFilter , fileID : String = "$defaultSaveFile") { file = PathSaver.getFile(fileID); this.filter = filter; file.addEventListener(Event.SELECT, makeBytes); } /** Opens the browse dialog. When the user chooses a file, a * LoadFileEvent will be dispatched, and it will contain the bytearray * retrieved by the file. * @see LoadFileEvent*/ public function getBytes ():void { file.browseForOpen(openDialog ,[filter]); } /**Returns a ByteArray, for the LoadFileEvent.*/ private function makeBytes (event:Event) :void { var fs:FileStream = new FileStream (); fs.open(event.target as File, FileMode.READ); var ba:ByteArray = new ByteArray (); fs.readBytes(ba, 0, fs.bytesAvailable); fs.close(); var ev:LoadFileEvent = new LoadFileEvent(); ev.bytes = ba; ev.fileName = file.name; this.dispatchEvent (ev); } } }
Ésta clase también requiere LoadFileEvent, que no es más que:
Código :
package com.zigzah.utils { import flash.events.Event; import flash.utils.ByteArray; public class LoadFileEvent extends Event { public var bytes : ByteArray; public var fileName : String; public static const BYTES_LOADED: String = "bytesloaded"; public function LoadFileEvent(/*type:String, bubbles:Boolean=false, cancelable:Boolean=false*/) { super(LoadFileEvent.BYTES_LOADED, false, false); } } }
Y ahora, otra clase para guardar ByteArrays, mediante el diálogo:
Código :
package com.zigzah.utils { import flash.events.Event; import flash.filesystem.File; import flash.filesystem.FileMode; import flash.filesystem.FileStream; import flash.utils.ByteArray; /**A simple class that lets the user save a byteArray in a file, * and uses PathSaver to save the file location.*/ public class FileSaver { private var ba:ByteArray /**The title of the save dialog window.*/ public var dialog : String = "Save File"; /**Constructor. * @param bytes The byteArray that will be saved.*/ public function FileSaver(bytes:ByteArray) { this.ba = bytes; } /**Opens a save dialog. * @param name the default name of the file. * @param fileID the id that will be used by PathSaver.*/ public function save (name:String , fileID:String = "$defaultFile"):void { var file:File = PathSaver.getFile(fileID , false , name); file.addEventListener(Event.SELECT,saveBytes); file.browseForSave(dialog); } /**Writes the given bytes in the fileSystem.*/ private function saveBytes (event:Event):void { var file:File = event.target as File; var fs:FileStream = new FileStream(); fs.open(file , FileMode.WRITE) fs.writeBytes(ba); fs.close(); } } }
Para probarlo, podríamos hacer un simple editor de texto en Flex/AIR. Fíjense en que recuerda la ruta tanto de los archivos que cargan como de los que guardan (para eso, pruébenlo varias veces):
Código :
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ import com.zigzah.utils.*; [Bindable] private var text:String = ""; private function load (event:Event):void { var ff:FileFilter = new FileFilter ("txt" , "*.txt"); var fldr:FileLoader = new FileLoader (ff , "loadedTXT"); fldr.getBytes(); fldr.addEventListener(LoadFileEvent.BYTES_LOADED,loadHandler); } private function loadHandler (event:LoadFileEvent):void { var ba:ByteArray = event.bytes; text = ba.readUTFBytes(ba.length); } private function save (event:Event):void { var ba:ByteArray = new ByteArray(); ba.writeUTFBytes(ta.text); var fsaver:FileSaver = new FileSaver (ba); fsaver.save("newtxt.txt","savedTXT"); } ]]></mx:Script> <mx:Button label="Load txt" click="load(event)" top="80" left="30"/> <mx:Button label="Save txt" click="save(event)" right="30" top="80"/> <mx:TextArea id="ta" text="{text}" right="30" left="30" bottom="10" top="120"/> </mx:WindowedApplication>
Aquí pueden descargar los archivos del proyecto:
[Archivos]
¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.
Por eldervaz el 12 de Octubre de 2008
Por selecters75 el 12 de Octubre de 2008
Por selecters75 el 12 de Octubre de 2008
Por Zah el 12 de Octubre de 2008
selecters75-blog :
Es una clase estática, que es casi un Singleton, solo que un singleton es más indicado para contener algún tipo de variables de la aplicación, y mejor una clase estática, si realiza funciones. Una cosa no tiene nada que ver con la otra. Si usa SharedObjects es para que los datos de las rutas se guarden para la próxima vez que se encienda la aplicación.
selecters75-blog :
Es un ejemplo sencillo que lee y escribe texto de manera sencilla, para mostrar cómo funciona la clase. De todas formas, la codificación (o decodificación) en formato AMF, tiene sentido para objetos, no para texto. Para texto se usan readUTF (cuando hay bytes delante que indican la longitud del texto), readUTFBytes (cuando tú especificas la longitud del texto) y readMultiByte (cuando quieres leer texto con alguna codificación rara), con sus correspondientes funciones write...
Saludos.
Por selecters75 el 13 de Octubre de 2008
Por cierto, con write/readobject tengo este problema: http://www.cristalab.com/foros/t62538_error-1063-argument-count-mismatch-usando-readobject-air.html
Saludos.
Por Zguillez el 13 de Octubre de 2008
Por Anselmo Prada León el 14 de Octubre de 2008
Saludos afectuosos,Anselmo Prada
Por Kárites el 20 de Mayo de 2009
Si alguien lo consigue, staré esperando respuesta! yo seguiré investigando...
Gracias!!
Salu2!
Por Fede el 24 de Septiembre de 2011