Статьи

Как читать локальный файл с помощью JavaScript и Flash

Здесь ситуация.   У вашего пользователя есть данные о клиенте.   Вы хотите поместить эти данные в свое веб-приложение.   В результате вы сначала загружаете данные на сервер, затем обрабатываете их, а затем отправляете обратно.   В Adobe Flash Player 9 появился локальный доступ к файлам, и вы можете использовать его из JavaScript с минимальным воздействием на существующее приложение.   В этой статье вы изучите основы, а затем увидите чуть более надежный пример в действии.

Чтение текстового файла

Первое, что нам нужно сделать, это предоставить пользователю место для нажатия.   Все операции доступа к файлу должны быть инициированы одним щелчком мыши во Flash Player.   Это не означает, что весь ваш пользовательский интерфейс (UI) должен быть доставлен через Flash Player.   В этом случае мы будем использовать одну кнопку — кнопку, на которую пользователь в противном случае нажал бы, чтобы начать операцию доступа к файлу.

btnup = new Loader();
btnup.load(
new URLRequest(
this.loaderInfo.parameters["buttonup"]
)
);

addChild( btnup );

...

btnup.addEventListener( MouseEvent.CLICK, doClick );

...

protected function doClick( event:MouseEvent ):void
{
...
}

 

В этом случае вы заметите, что вместо того, чтобы использовать векторы Flash Player для рисования обложки для кнопки, я загружаю имена изображений из параметров HTML, чтобы использовать их для различных состояний кнопок.   Это позволяет дополнительно контролировать внешний вид кнопки без необходимости изменять или перекомпилировать код.   Другими словами, настройка без необходимости знать что-либо о разработке для Flash Player.

Класс, который делает всю тяжелую работу за нас здесь, это класс FileReference.   Когда пользователь нажимает кнопку, мы будем использовать метод FileReference.browse (), чтобы он мог выбрать файл.   Чтобы узнать, когда они выбрали файл, мы также хотим прослушать событие Event.SELECT.   Как только это событие получено, мы можем вызвать FileReference.load () для чтения физического содержимого файла.

file = new FileReference();
file.addEventListener( Event.SELECT, doFileSelect );
file.addEventListener( Event.COMPLETE, doFileLoaded );
file.browse();

...

protected function doFileSelect( event:Event ):void
{
file.load();
}

...

protected function doFileLoaded( event:Event ):void
{
var data:String = null;

data = file.data.readMultiByte(
file.data.bytesAvailable, UTF_8
);

ExternalInterface.call( "setData", data );

}

 

Имейте в виду, что файл данных, который вы загружаете из системы пользователей, должен быть загружен в память, так что это может быть не лучшим вариантом для файла фильма с несколькими гигабайтами.   Сам Flash Player, однако, был протестирован на файлы размером до 100 Мб, что дает вам много передышки.   Вы можете получать события прогресса по мере загрузки файла, но в этом случае я буду беспокоиться только о завершении операции чтения всего файла.

That is it!  At this point, the FileReference.data property consists of a ByteArray object.  You can treat the bytes of the file as literal bytes and start parsing details of files such as EXIF information, or as textual content such as CSV or XML.  We will do the later for now, and also get that data over to JavaScript so it can be displayed in the user interface.

 

Exchanging Data with JavaScript

Content running in the Flash Player can call JavaScript methods by name, and pass arguments to them.  The class used to call JavaScript methods from the Flash Player is the ExternalInterface class, and there is not much to it.  For the purposes of calling JavaScript we merely need to call the static method ExternalInterface.call() and send the text data from the file. 

var setdata:String = loaderInfo.parameters["setdata"];
ExternalInterface.call( setdata, data );

...

// Using jQuery
function setData( data )
{
$( '#edit' ).attr( 'value', data );
}

 

The name of the JavaScript function to be used is specified as a parameter to the Flash Player content.  Again, this lets you reuse this example without having to change the Flash content.  When JavaScript gets called it then takes that text, and in this case, puts it into a large text area.  Now the user is free to edit the content, and they have never made a round trip to the server.

 

Saving a Text File

Remember earlier that I noted that load and save operations needed to be initiated by a user mouse click in the Flash Player.  If we want to save that changed content back into the file it came from, then we will need to let the user initiate that with a button presented in the Flash Player.  Again, the images for the button are loaded from HTML parameters to the Flash Player object.

When clicked the Flash Player calls the designated JavaScript function, it has merely to grab the text from the text area, and then return that value. 

When the FileReference.save() method is called, it takes a ByteArray object.  While JavaScript has no specific binary handling, there are times when you may want to handle binary data.  In these cases you will want to consider encoding the binary data with Base-64.  For now, we will simply create a new ByteArray object, and then write the text content into it using ByteArray.writeMultiBytes(). 

var data:String = ExternalInterface.call(
loaderInfo.parameters["getdata"] );
var bytes:ByteArray = new ByteArray();

bytes.writeMultiByte( data, "utf-8" );
file.save( bytes, "myfile.txt" );

 

With the ByteArray populated, we can pass it to the FileReference.save() method call.  The result will be a dialog box presented to the user.  At this point all the programming work is done.  The user on the other hand has a couple options.  While you can specify a default name, the user can change it if they prefer something else.  They also get to choose where to save the file.

 

Saving Image Files from Canvas

Among the exciting new features of the HTML5 specification, is support for CANVAS.  This blank slate opens the door to all fashion of different visualizations.  In the following example I let the user draw a picture using their mouse.  The question then becomes, how do you let the user save the picture to their local system?  Once again we find ourselves caught in the upload, process, send workflow.

As it turns out, canvas actually gives you access to the raw pixel data that has been drawn.  This might be user generated content, mathematically generated content, image content and more.  Since you now have the ability to save content without the round-trip to the server, you might be inclined to send an array of pixels over to the Flash Player, let it encode them to an image format, and then save the file out to disk.  And for the most part, you would be successful.

http://www.kevinhoyt.org/paint

 

Wait?  Encoding images?  Sure!  There are ActionScript libraries that do JPEG, PNG, BMP and more.  You pass in pixels, and get back the bytes of an image.  Sweet!

Well, not so fast!  Encoding an image takes time.  That is a lot of number crunching to be sure.  For example, a canvas that is a small 200 pixel by 200 pixels space is 40,000 pixels.  Each pixel consists of three values (red, green, and blue), which means 120,000 items to be reviewed at a bare minimum.  And 200×200 is not much creative space.  The larger the canvas, the more time it will take to encode that image.

While ExternalInterface is pretty snappy, passing an array of 120,000 integers will also slow us down.

It turns out that canvas has a toDataURL() method provided for by the specification.  Calling this method asks the browser, with its native code base, to make a PNG image file for us.  Since JavaScript does not have specific binary processing APIs, the result is a base-64 encoded string.  I have already showed you how to have Flash Player ask JavaScript for text, so you are almost there.

// Get the Base64 encoded PNG
// Inspired by http://bit.ly/930WOF
// Sending raw pixels is slow
// Let the native browser do the work

function getImageData()
{
return document.getElementById( 'stage' ).toDataURL();
}

...

var png:ByteArray = null;
var start:Number = 0;
var data:String = null;

data = ExternalInterface.call( "getImageData" );
start = data.indexOf( "," ) + 1;

png = Base64.decodeToByteArray(
data.substr( start, data.length - start )
);

file.save( png, "fromjs.png" );

 


Just as there are ActionScript libraries for encoding images, there are also ActionScript libraries for working with base-64.  In this case we can use one such library, the ActionScript Core Library (open source), to get the bytes back out of the encoded string by calling Base64.decodeToByteArray().  Once we have our ByteArray, we can call the FileReference.save() method.  At that point the user gets prompted to save the file.

It may sound a little more complex for this image approach, but it all happens in the blink of an eye, even with a larger canvas.

 

The Big Picture

Web standards are great, and they are growing in capability and sophistication.  Yet nowhere in the proposed specifications will you find local file access.  Sadly, you also will not find an API in JavaScript to support more direct binary manipulation.  Will these types of features eventually find their way into standards and browsers?  Probably!  But how long will that take?

Part of the reason we are seeing the web standards expand to include more functionality, is because users are demanding more compelling interfaces, and ultimately experiences.  These demands result in application requirements that were not originally envisioned with the previous specifications.  The interesting paradox that emerges is that it is much harder to differentiate yourself on user experience if everybody is using the same standards.

To that end, browsers have plug-in APIs for content such as Flash Player.  These plug-ins are not constrained by the standards process, and are free to innovate as fast as the market allows.  In the case of the Flash Player, that has been roughly one iteration every year, that is broadly distributed and nearly ubiquitous in about one year after that.  Does that mean you have to go all in on Flash?  No!

In these two examples we have used web standards to build the user interface and drive the core application logic.  We have extended those web standards with content running in the Flash Player to provide innovative new features such as local file access.  And we have even leveraged the native browser implementation to perform image encoding where we would have had problems scaling in both JavaScript and ActionScript.  The moral is simply, use the right tool for the job.

Download the source code for these examples to add local file access to your toolbox.

NOTE: The above ActionScript programs can be compiled using the freely available and open source, Flex SDK.  Should you desire a developer IDE, Flash Develop is a good open source option, Flash Builder is the commercial Adobe offering, and you can even use Visual Studio via the Amethyst plug-in from Sapphire Steel.  Or you know, if SWF provided here does what you need, then that works too.