Previous Page Next Page

4.4. File API

4.4.1. Write Text to a File from a String

4.4.1.1. Problem

A user has made changes to textual content in the application, which need to be saved to the local disk for offline access.

4.4.1.2. Solution

You can write text through the File and FileStream classes that are part of Adobe AIR.

4.4.1.3. Discussion

Before any reading or writing to disk takes place, a reference to a file or directory must exist in the application. You can establish a file reference in a number of ways, including via programmatic manipulation and user selection. You accomplish both of these by using the File class. The File class also contains static properties that point to common locations on the operating system. These locations include the desktop directory, user directory, and documents directory:

var file =
air.File.applicationStorageDirectory.
resolvePath( 'myFile.txt' );

The call to File.resolvePath() creates a reference to a file named myFile.txt located in the application storage directory. Once a reference has been established, it can be used in file I/O operations. Note that this doesn't actually create the file at this point.

Physically reading and writing to disk is accomplished using the FileStream class. The FileStream class does not take any arguments in its constructor:

var stream = new air.FileStream();

With the file reference available and a FileStream object instantiated, the process of writing data to disk can take place. The type of data that can be written may be anything from binary, to text, to value objects. You can access all of these by using the respective FileStream method for that operation.

The first step in writing a file is to open it using the FileStream.open() method. The FileStream.open() method takes two arguments. The first argument is the file reference created earlier that will be the destination of the output. The second argument is the type of file access the application will need. In the case of writing data to a file, the FileMode.WRITE static property will be most common. A second possibility is FileMode.APPEND, depending on the application requirements:

stream.open( file, air.FileMode.WRITE );

When writing text, an application should use FileStream.writeMultiByte() to ensure that data is written with the correct encoding. The FileStream.writeMultiByte() method takes two arguments. The first argument is the String object (text) that will be written to disk. The second argument is the character set to be used. The most common character set is that which the operating system is using, which is available on the File class as File.systemCharset:

stream.writeMultiByte( document.getElementById
('txtNote' ).value, air.File.systemCharset );

Once the text has been written to the file, it is important to close the file to avoid any corruption or blocking of access from other applications. You close a file using the FileStream.close() method.

NOTE

XML data is already in textual format and, as such, can be written to disk using this same series of steps. If the application uses the XMLHttpRequest object, using the myXml.responseText property alone may be adequate for most situations.

<html>
<head>

<title>Writing a Text File</title>

<style type="text/css">
body {
    font-family: Verdana, Helvetica, Arial, sans-serif;
    font-size: 11px;
    color: #0B333C;
}

#save {
    position: absolute;
    right: 5px;
    bottom: 5px;
}

textarea {
    position: absolute;
    left: 5px;
    right: 5px;
    top: 5px;
    bottom: 32px;
}
</style>

<script type="text/javascript" src="airaliases.js"></script>

<script type="text/javascript">
function doLoad()
{
    document.getElementById( 'save' ).
    addEventListener( 'click', doSave );
}

function doSave()
{
    var file = air.File.desktopDirectory.
    resolvePath( 'note.txt' );
    var note = document.getElementById( 'note' ).value;
    var stream = new air.FileStream();

    stream.open( file, air.FileMode.WRITE );
    stream.writeMultiByte( note, air.File.systemCharset );
    stream.close();
}
</script>

</head>
<body onLoad="doLoad();">

<textarea id="note"></textarea>
<input id="save" type="button" value="Save" />

</body>
</html>


					  

4.4.2. Synchronously Read Text from a File

4.4.2.1. Problem

You want to read the contents of a small text file into your application.

4.4.2.2. Solution

Use the File and FileStream classes provided by Adobe AIR to locate, open, and operate on text files.

4.4.2.3. Discussion

You can read small files that contain text content using the FileStream.open() method. This method opens a file synchronously for reading. Synchronous access requires less code, but also blocks any additional user input until the data has been read. When using asynchronous access, additional user input is not blocked, but event handlers must be registered, which results in more development time.

NOTE

Although it is possible to access XML files as text, the result of this approach is a string of text that can't be readily manipulated. Accessing an XML file for use as a data source is often more easily handled using XMLHttpRequest or wrapper functionality offered by most JavaScript libraries.

The steps for synchronously reading a file are almost always the same:

  1. Get a File reference.

  2. Create a FileStream object.

  3. Open the stream for synchronous access.

  4. Call the appropriate FileStream read methods.

  5. Close the file.

The first step to reading a text file is to get a reference to the resource on disk. You can establish a reference by programmatically designating a path using the appropriate property on the File object, such as File.applicationStorageDirectory. You will also have to call File.resolvePath() when using this approach, as the static File class properties always return a directory:

var file =
air.File.applicationStorageDirectory.
resolvePath('myFile.txt' );

The FileStream class has an empty constructor and can be instantiated anywhere in your application. The file reference just established is used during the physical process of opening the file. The mode for which the file is going to be opened must also be specified.

The FileMode object serves no purpose other than to provide constants for the types of file access that can be performed. These operations are FileMode.READ, FileMode.WRITE, FileMode.UPDATE, and FileMode.APPEND:

var stream = new air.FileStream();
stream.open( file, air.FileMode.READ );

You can use three FileStream methods to read character data from a file. The FileStream.readUTF() and FileStream.readUTFBytes() methods are specifically tuned for UTF data.

If this is the target format of the data for the application, you should use these methods directly. In the case of other character sets, you can use the FileStream.readMultiByte() method to specify the target format. Additional character sets are specified in the form of a string, such as us-ascii. There is also a convenience property on the File object to use the default system character set, File.systemCharset.

You also need to specify the number of bytes to be read in the case of FileStream.readUTFBytes() and FileStream.readMultiByte(). This sizing will depend largely on the requirements of the application. When reading the entire file is required, you can find the number of bytes available to be read on the FileStream.bytesAvailable property:

var data = stream.readMultiByte( stream.bytesAvailable, 
air.File.systemCharset );

Once the contents of a file have been read, it is important to close the file. This operation will allow other applications to access the file:

stream.close();

Although a demonstrable amount of flexibility has been provided by Adobe AIR, the actual process in its entirety is considerably concise. This brevity is provided when performing synchronous data access operations. Synchronous file access should be reserved for smaller files regardless of reading or writing character or binary data:

<html>
<head>

<title>Synchronous File Access</title>

<style type="text/css">
body {
    font-family: Verdana, Helvetica, Arial, sans-serif;
    font-size: 11px;
    color: #0B333C;
}

textarea {
    position: absolute;
    left: 5px;
    right: 5px;
    top: 5px;
    bottom: 5px;
}
</style>

<script type="text/javascript" src="airaliases.js"></script>

<script>
function doLoad()
{
    var data = null;
    var file = new air.File();
    var stream = null;

    file = air.File.applicationDirectory.resolvePath(
     'the-raven.txt' );

    stream = new air.FileStream();
    stream.open( file, air.FileMode.READ );
    data = stream.readMultiByte( stream.bytesAvailable, 
    air.File.systemCharset );
    stream.close();

    document.getElementById( 'editor' ).value = data;
}
</script>

</head>
<body onLoad="doLoad();">

<textarea id="editor"></textarea>

</body>
</html>


					  

4.4.3. Asynchronously Read Text from a File

4.4.3.1. Problem

You want to read a large amount of text into your application without impacting the user interface.

4.4.3.2. Solution

Use the File and FileStream classes to asynchronously operate on the data; ensuring that the application execution is not blocked while the file is being processed.

4.4.3.3. Discussion

Files containing a large amount of data should be read using the FileStream.openAsync() method. This method opens a file asynchronously for reading or writing and will not block additional user input. Asynchronous file operations achieve this goal by raising events during processing. The result is that event listeners must be created and registered on the FileStream object.

The steps for asynchronously reading a file are almost always the same:

  1. Get a File reference.

  2. Create a FileStream object.

  3. Create event handlers for processing data.

  4. Add event listeners for asynchronous operations.

  5. Open the stream for asynchronous access.

  6. Close the file.

The first step to reading a text file is to get a reference to the resource on disk. You can establish a reference by programmatically designating a path using the appropriate property on the File object, such as File.applicationStorageDirectory:

var file =
air.File.applicationStorageDirectory.
resolvePath('myFile.txt' );

A FileStream instance is necessary to read or write to the file:

stream = new air.FileStream();

Before registering event handlers on a FileStream object, you must create those handlers. The events that are triggered by file I/O operations using the FileStream class will always pass an event object as an argument. The properties on the event object will depend on the type of event being raised. This object can be helpful in determining the target FileStream object, how much data is available for reading, how much data is waiting to be written, and more:

function doProgress( event )
{
    // Read all the data that is currently available
    var data = stream.readMultiByte( stream.bytesAvailable, 
    air.File.systemCharset );

    // Append the most recent content
    document.getElementById( "editor" ).value += data;

    // Close the file after the entire contents
    // have been read
    if( event.bytesLoaded == event.bytesTotal )
    {
        stream.close();
    }
}

Registering for events takes place using the addEventListener() method:

stream.addEventListener( air.ProgressEvent.PROGRESS, 
doProgress );

You can open a stream for asynchronous access using the FileStream.openAsync() method. The FileStream.openAsync() method takes two arguments that specify the file being accessed and the type of access being performed.

The FileMode object serves no purpose other than to provide constants for the types of file access that can be performed. These operations are FileMode.READ, FileMode.WRITE, FileMode.UPDATE, and FileMode.APPEND:

stream.openAsync( file, air.FileMode.READ );

As soon as the file is opened and new data is available in the stream, the ProgressEvent.PROGRESS event is triggered. Depending on the size of the file, as well as machine and network characteristics, not all of the bytes may be read in a single pass. In many cases, additional read operations take place, raising a ProgressEvent.PROGRESS event for each iteration.

Once all of the data has been read from the file, an Event.COMPLETE event is broadcast.

After the file has been read, it is important to close the file stream to ensure that other applications can access it:

stream.close();

This example provides a baseline for the various types of asynchronous access an application might choose to perform. In this case, the contents of the file are read and placed into an HTML text area each time more data is available. Asynchronous processing also provides the means for random file access (seek) without interrupting the user interface. An application should always use asynchronous access whenever the size of a file is in question.

<html>
<head>

<title>Asynchronous File Access</title>

<style type="text/css">
body {
    font-family: Verdana, Helvetica, Arial, sans-serif;
    font-size: 11px;
    color: #0B333C;
}

textarea {
    position: absolute;
    left: 5px;
    right: 5px;
    top: 5px;
    bottom: 5px;
}
</style>

<script type="text/javascript" src="airaliases.js"></script>

<script type="text/javascript">
var stream = null;

function doLoad()
{
    var file = air.File.applicationDirectory.resolvePath(
     'the-raven.txt' );

    stream = new air.FileStream();
    stream.addEventListener( air.ProgressEvent.PROGRESS, 
    doProgress );
    stream.openAsync( file, air.FileMode.READ );
}

function doProgress( event )
{
    var data = stream.readMultiByte( stream.bytesAvailable, 
    air.File.systemCharset );

    document.getElementById( 'editor' ).value += data;

    if( event.bytesLoaded == event.bytesTotal )
    {
         stream.close();
    }
}
</script>

</head>
<body onLoad="doLoad();">

<textarea id="editor"></textarea>

</body>
</html>


					  

4.4.4. Load Data from an XML File

4.4.4.1. Problem

You want to read XML data from a local file using common JavaScript techniques, and you want to manipulate the Document Object Model (DOM), not just the character data.

4.4.4.2. Solution

You can read a local XML document for its data using the XMLHttpRequest object, and by using a File object reference as the URI endpoint as opposed to a web address.

4.4.4.3. Discussion

Most JavaScript libraries, and virtually every data-oriented Ajax application, makes use of the XMLHttpRequest object to load data. This is a common means to accessing data from the client without refreshing the page, and it is core to Ajax development techniques. Adobe AIR includes support for the XMLHttpRequest object, which can be used for data access.

The XMLHttpRequest.open() method expects three arguments. The first argument is the HTTP method to be used for the call, which is commonly GET or POST. The third argument tells the object whether it should make the request asynchronously. The challenge in an Adobe AIR application is the second argument, which tells the object where to get its data:

var xml = new XMLHTTPRequest();

xml.open( 'GET', 'myData.xml', true );

This URI endpoint generally points to a remote server. This can still happen in an application that is online, but as Adobe AIR applications can also work offline, the endpoint needs to be pointed to a local resource. Rather than pass an endpoint to a remote server, a File reference can be provided:

var file = air.File.applicationStorageDirectory.resolve
('myData.xml' );
var xml = new XMLHttpRequest();

xml.onreadystatechange = function()
{
    if( xml.readystate == 4 )
    {
        // Work with data
    }
}

xml.open('GET', file.url, true );
xml.send( null );

The key distinction to make for this example is the use of the File.url property, which the XMLHttpRequest object understands and uses to access the appropriate data. Using this approach results in a traditional DOM that can be used to traverse and manipulate the XML data in the file. Additionally, you can use this approach with common JavaScript libraries.

4.4.4.4. Given
<rolodex>
    <contact>
        <first>Kevin</first>
        <last>Hoyt</last>
    </contact>
    ...
</rolodex>

4.4.4.5. Example
<html>
<head>

<title>Reading XML Data (using XMLHttpRequest)</title>

<style type="text/css">
body {
    font-family: Verdana, Helvetica, Arial, sans-serif;
    font-size: 11px;
    color: #0B333C;
}
</style>

<script type="text/javascript" src="airaliases.js"></script>

<script type="text/javascript">
var contacts = air.File.applicationDirectory.resolvePath
( 'rolodex.xml' );

function doLoad()
{
    var xml = new XMLHttpRequest();

    xml.onreadystatechange = function()
    {
         var elem = null;
         var first = null;
         var last = null;
         var rolodex = null;

         if( xml.readyState == 4 )
         {
              rolodex = xml.responseXML.documentElement.
              getElementsByTagName
( 'contact' );

              for( var c = 0; c < rolodex.length; c++ )
              {
                   first = rolodex[c].getElementsByTagName
                   ( 'first' )[0].textContent;
                   last = rolodex[c].getElementsByTagName
                   ( 'last' )[0].textContent;

                   elem = document.createElement( 'div' );
                   elem.innerText = first + " " + last;
                   document.body.appendChild( elem );
              }
         }
    }

    xml.open( 'GET', contacts.url, true );
    xml.send( null );
}
</script>

</head>
<body onLoad="doLoad();">



</body>
</html>


					  

4.4.5. Create a Temporary File

4.4.5.1. Problem

An application needs to store transient information during file processing, and cannot assume that adequate memory exists to store the data in memory.

4.4.5.2. Solution

Creating temporary files with File.createTempFile() is an ideal means to store transient information while relieving the overhead of additional memory.

4.4.5.3. Discussion

The File class contains a static File.createTempFile() method that you can use to establish a temporary file. The temporary file is created at a destination determined by the operating system. Temporary files are also automatically given a unique name to avoid collision with other files that may be present:

var temp = air.File.createTempFile();

Once a temporary file has been created, you can use the File and FileStream APIs to interact with the file as you would any other file.:

var stream = new air.FileStream();

stream.open( temp, air.FileMode.WRITE );
stream.writeMultiByte('Hello', air.File.systemCharset );
stream.close();

You can use the File.moveTo() and File.moveToAsync() methods after the fact, should you decide that it is necessary to keep the temporary file for later reference. Both move methods take two arguments. The first argument is a File reference to the destination location. The second argument is a Boolean value that controls overwriting any existing file. If the second argument is set to false, and a collision occurs, the application throws an error:

var move = air.File.desktopDirectory.resolve('temp.txt' );

try
{
    temp.moveTo( move, false );
} catch( ioe ) {
    alert('Can\'t move file:\n' + ioe.message );
}

The JavaScript try/catch block will receive an error object of type IOError. The IOError class has available numerous properties that you can use for further evaluation. The exception in the previous code snippet raises the error message that is generated by Adobe AIR:

<html>
<head>

<title>Creating a Temporary File</title>

<script type="text/javascript" src="airaliases.js"></script>

<script type="text/javascript">
function doLoad()
{
    var stream = new air.FileStream();
    var temp = air.File.createTempFile();
    var move = air.File.desktopDirectory.resolvePath
    ( 'temp.txt' );

    stream.open( temp, air.FileMode.WRITE );
    stream.writeMultiByte( 'Hello World!', air.File.
    systemCharset );
    stream.close();

    try
    {
         temp.moveTo( move, false );
    } catch( ioe ) {
         alert( 'Could not move temporary file:\n' + 
         ioe.message );
    }

}
</script>

</head>
<body onLoad="doLoad();">



</body>
</html>


					  

4.4.6. Iterate the Contents of a Directory

4.4.6.1. Problem

The application is required to display information about a directory as part of the user interface.

4.4.6.2. Solution

Use the File.browseForDirectory() method to prompt the user to select a directory, and then use the File.getDirectoryListing() method to iterate through the contents of the directory.

4.4.6.3. Discussion

The File class provides numerous properties that you can use to get specific information about files on disk. Also, various methods on the File class pertain to getting a directory listing. Although an application can specify a directory programmatically, you can use File.browseForDirectory() to prompt the user to select a directory using the native dialog. Once a location on the local disk has been specified, the File.getDirectoryListing() method returns an Array of File objects for the currently referenced directory.

Before prompting the user to select a directory using the native dialog, the application needs to establish and register an event handler for Event.SELECT. The Event.target property on the raised event object will contain a reference to the File object that invoked the browse operation.

The File.browseForDirectory() method takes one argument, a String representing additional information that will be placed in the dialog box. This String is not the title of the dialog, as is the case with File.browseForOpen(). There is also no need to specify FileFilter objects, as the dialog box presented is specific to directories, and no files will be displayed.

After the user has selected a directory, the registered event handler will be called. The file reference, whether using a class or global reference, or Event.target, will now contain the path to the selected directory. At this point, File.getDirectoryListing() can be called, which returns an Array of File objects for the selected directory (as represented by the file reference). The File.getDirectoryListing() method takes no arguments:

var listing = directory.getDirectoryListing();

The File class can represent both files and directories on the local filesystem. You can use the File.isDirectory property to determine whether a specific File instance references a file or a directory.

NOTE

See the API documentation for a complete list of data exposed by the File API.

<html>
<head>

<title>Selecting a Directory</title>

<style type="text/css">
body {
    font-family: Verdana, Helvetica, Arial, sans-serif;
    font-size: 11px;
    color: #0B333C;
}
</style>

<script type="text/javascript" src="airaliases.js"></script>

<script type="text/javascript">
var directory = null;

function doBrowse()
{
    directory.browseForDirectory( 'Select a directory of 
    files:' );
}

function doLoad()
{
    directory = air.File.documentsDirectory;
    directory.addEventListener( air.Event.SELECT, doSelect );

    document.getElementById( 'browse' ).addEventListener
    ( 'click', doBrowse );
}

function doSelect( e )
{
    var files = directory.getDirectoryListing();
    var elem = null;
    var name = null;
    var mod = null;
    var size = null;

    for( var f = 0; f < files.length; f++ )
    {
         name = files[f].name;

         mod = files[f].modificationDate;
         mod = ( mod.month + 1 ) + '/' + mod.date + '/' + 
         mod.fullYear;

         size = Math.ceil( files[f].size / 1000 ) + ' KB';

         elem = document.createElement( 'div' );
         elem.innerText = name + ' is ' + size + ' 
         and was last modified on ' + mod;

         document.body.appendChild( elem );
    }
}
</script>

</head>
<body onLoad="doLoad();">

<input id="browse" type="button" value="Browse" />

</body>
</html>


					  

Previous Page Next Page