jQuery and AIR: Writing content to the file system via drag and drop

Filed under: AIR, JavaScript, jQuery, video

comments (11) Views: 12,735

In this installment of my jQuery and AIR series we'll be exploring the ability to write content to the file system using AIR's built in file system access. We'll be copying images, and writing JavaScript variables as text files. We'll also be using jQuery UI's draggable and droppable libraries so that we don't have to write that functionality ourselves. First, a preview of what we'll be creating, and then we'll get started.
AIR app preview

  1. The first thing you'll need to do is to create a new Aptana project. Call it whatever you like, but make sure to include the AIR aliases, and jQuery JavaScript files.
    1. You'll also need to download this entire project to get the images, and CSS files used in this tutorial.
  2. Next we need to create, and download, a custom build of jQuery UI
    1. In the right sidebar, make sure you've selected whatever version is the current stable version
    2. At the top right, click "Deselect all components" to make sure we don't get anything we don't need
    3. In the Interactions section of the downloads page, click "Droppable", this should highlight "Draggable", and UI Core.
      jquery ui selection
    4. Click the download button, and save this file into your Aptana project. I'm using Aptana's default location: /lib/jquery/
      jquery ui save
  3. Open the main HTML page, most likely named the same as your project. We'll need to first clear out some of the default content added by Aptana's new project wizard.
    1. Delete everything in this file, replace it with the following code
      
      <html>
      	<head>
      		<title>File system writing, jQuery UI drag and drop</title>
      	</head>
      	<body>
      
      	</body>
      </html>
      
      
  4. In the HEAD tag:.
    1. Add a reference to a CSS file (which we'll discuss in a moment).
      
      <link href="css/styles.css" rel="stylesheet" type="text/css" />
      
      
    2. Add references to the AIRAliases, jQuery, and jQuery UI javascript files. Additionally, we'll add a script block with an empty jQuery document.ready call.
      
      <script type="text/javascript" src="lib/air/AIRAliases.js"></script>
      <script type="text/javascript" src="lib/jquery/jquery-1.3.2.min.js"></script>
      <script type="text/javascript" src="lib/jquery/jquery-ui-1.7.1.custom.min.js"></script>
      <script type="text/javascript">
      	$(document).ready(function() {
      		// insert code here
      	});
      </script>
      
      
  5. In the BODY tag:.
    1. We'll add three container DIV tags. One will hold our images, one will contain the target upon which we'll drop those images, and the last one will display which images we've dragged and dropped upon the target.
      
      <div id="picContainer">
      	<div>
      		<img src="images/puppies_01.jpg" />
      		<span>only a mother...</span>
      	</div>
      </div>
      
      <div id="rightSide">
      	<div id="target"></div>
      	<div id="console"><b>Saved data</b><br /></div>
      </div>
      
      
    2. Notice that while we have 5 images for this example, I've only given you the code for one of them. The images are numbered sequentially, so go ahead and add in the other 4 images with whatever captions you like. Simply duplicate the bolded code above.
  6. Now that we've got the structure of our project in place, we can briefly mention our CSS. The beautiful thing about developing HTML/JS AIR apps is that we can take advantage of the wonderful CSS support offered by the Webkit engine (what AIR uses to render HTML content). The only thing worth noting for this project is that we're adding borders to the DIV containers using CSS and not images, or some JavaScript plugin. It's as simple as this (note the bolded lines):
    
    #target {
    	width: 175px;
    	height: 175px;
    	margin-bottom: 15px;
    	border: 1px solid #000000;
    	-webkit-border-radius: 10px;
    	float: left;
    	background: red;
    }
    
    
  7. Next, we move on to the core of our app, the functional code. Our JavaScript will have 3 main parts: variable assignments, functionality assignments, and a function we'll use to do the actual file system access. Let's examine each of those in turn.
    1. Variable assignments
      1. Inside the empty document.ready block we place the following code.
        
        var saveData = [];
        var $saveDisplay = $('#console');
        $('#picContainer div').draggable();
        
        
      2. We create an empty array we'll use to store data about which images we drag to the target area. Next we're storing a reference to the output area, the container we'll use to display messages to the user. Finally, we initialize each image container as "draggable", which means it can be moved around by clicking and dragging it. There's also some built in methods and properties that get added to "draggable" containers, but we'll cover that later.
    2. Functionality assignments
      1. Next, we add in these lines of code.
        
        // add droppable functionality from jQuery UI
        $('#target').droppable({
        	// specifically the drop method
        	drop: function(event, ui) {
        		// create an empty, temp object to store image path and caption data
        		var newSave = {};
        		newSave['img'] = $(ui.draggable).children('img').attr('src');
        		newSave['txt'] = $(ui.draggable).children('span').html();
        		// store that in the save data array
        		saveData.push(newSave);
        		// update saved display to indicate which image/text have been saved
        		$saveDisplay.append('img: ' + newSave['img'].split('/')[1] + '<br />');
        		// write selected image to disk
        		saveToFileSystem(newSave['img'],'img');
        		// store the "saved data" array to disk
        		saveToFileSystem(saveData,'str');
        		// remove the saved object from view
        		$(ui.draggable).remove();
        	}
        });
        
        
      2. We start by assigning the droppable functionality to the target container. This allows it to receive draggable objects, and fire certain events triggered by said draggable. We're using the drop event, which fires when a draggable element is released on top of it. We pass in the standard jQuery event, as well as a special parameter, "ui". The ui parameters allows us to retrieve information about the object which was just dropped; in our case the image path, and caption. Lastly, using the special reference given to us by the ui parameters, we remove the dropped object from the DOM.
    3. Save to file system
      1. Finally, we add in these lines of code.
        
        function saveToFileSystem(obj,type) {
        	if (type == 'str') {
        		// open a new file stream
        		var fs = new air.FileStream();
        		// get a reference to the text file which will contain the save data
        		var savedFile = air.File.applicationStorageDirectory.resolvePath('saved.txt');
        		// open the file stream for writing
        		fs.open(savedFile,air.FileMode.WRITE);
        		// write the file to disk
        		fs.writeObject(obj);
        		// close the file
        		fs.close();
        	} else {
        		// store a reference to the application directory
        		var sourceFile = air.File.applicationDirectory;
        		// decide which file we want to save to the user's file system
        		sourceFile = sourceFile.resolvePath(obj);
        		// save reference to the application storage directory
        		var destination = air.File.applicationStorageDirectory;
        		// the incoming path to the image is "images/filename.jpg". We only want the filename
        		destination = destination.resolvePath(obj.split('/')[1]);
        		// copy the file from the app directory to the app storage directory
        		sourceFile.copyToAsync(destination, true);
        	}
        }
        
        
      2. Most of this code is going to be new to you, so I've liberally sprinkled comments throughout. We'll mention a few things here. While we're using JavaScript as our application code, we still have to abide by some traditional software rules. Namely we have to explicitly open, and close, files that we want to modify.

        The first branch of the if statement is used to write a text file (named saved.txt) to the user's application storage directory by way of the air.File.applicationStorageDirectory method. It's a shortcut to the folder on your system which stores user specific data for a specific app. We're using the writeObject method which allows us to write a serialized representation of a JavaScript variable to the file system.

        The second branch handles copying the selected image from the application directory (where the executable lives) to the user's app storage directory. We create references to both locations, then use the copyToAsync method to transfer the image from one directory to another asynchronously. We use async because our app doesn't depend care how long it takes the image to transfer.
  8. video camera icon That's pretty much the story. Put all that code together and you should have an app that functions much like the video to the right. Let's do a quick review of the new things we covered.
    1. Configured, downloaded, and used a custom build of jQuery UI
    2. Learned that Webkit allows for rounded corners in pure CSS
    3. We used various File methods to write serialized JS variables to the file system, as well as copy images from one directory to another.
    This app isn't meant to be production ready so there are a few things that could be improved.
    1. It's a best practice to externalize your JS code from your HTML code. While you don't need to worry about caching in an AIR app, it's still best to keep functional and structural code separate.
    2. The saveToFileSystem methods writes a new text file each time a user drags an image to the target container. Best practice would be to simply open the file and add in the changes. An even better way might be to store the saveData variable in memory until the app is closed, then write it to the file system.
    3. The saveToFileSystem method could also be improved and only called once, instead of twice, by simply passing in the image, and the variable at the same time.
  9. So that's it. Please feel free to comment with any questions or criticisms...especially the criticisms (they make me better). Also, if you've got a suggestion for a good jQuery   AIR topic let me know.

Amazon logo

If this article was interesting, or helpful, or even wrong, please consider leaving a comment, or buying something from my wishlist. It's appreciated!

Related Posts


Where is the saveToFileSystem function block placed? Why isn't it listed as saveToFileSystem: function next to drop: function for instance? Also, I get this error either way: TypeError: Value undefined does not allow function calls. at app:/FileSystemDragAndDrop.html : 12 at app:/lib/jquery/jquery-1.3.2.min.js : 19 at app:/lib/jquery/jquery-1.3.2.min.js : 12 at app:/lib/jquery/jquery-1.3.2.min.js : 19 at app:/lib/jquery/jquery-1.3.2.min.js : 19

Dave Babbitt - June 08, 2009 08:48 am

Dave... The saveToFileSystem function appears in a block right after the droppable assignment. It's just a plain function and should be outside the drop event. Download the sample code from the first section and take a look. If you're still having problems, email your html file to me and I'll take a look at it.

andy matthews - June 08, 2009 02:35 pm

You should include the compiled AIR as a download.

Raymond Camden - June 08, 2009 02:37 pm

That's an excellent idea Ray. Thanks for the suggestion. I'll take care of it tonight.

andy matthews - June 08, 2009 03:00 pm

How do you generate the FileSystemDragDrop.air file?

Greg - June 19, 2009 01:09 pm

Greg...assuming that you're a) using Aptana, and b) have all of the code correct here's how you build an AIR file from a project.

1) Look up in the top left corner of the Aptana interface. You should see a small icon which looks like a box, with the letters .air on it.
2) Click that button and you'll get this dialog box



3) Walk through the steps and you'll be golden.
If you get stuck along the way, refer to my jQuery and AIR for the front end designer presentation. Jump ahead to about 35 or 40 minutes in and I cover exporting the AIR file, creating your own certificate and more. Good luck and don't hesitate to ask more questions...that's what I'm here for.

andy matthews - June 19, 2009 01:18 pm

Oh...and make sure you don't click the one that says "Initiate Badge Export" at the top.

andy matthews - June 19, 2009 01:20 pm

Thanks Andy for info! :) That did the trick and I was able to export the air package. Aptana sure does a nice job of making it all pretty simple. This jQuery/Air combo looks pretty fun. Thanks for your blogs on the subject.

Greg - June 19, 2009 03:17 pm

Andy, I'd like to do a download and copy to disk instead of the drag-drop and copy to disk method of your tutorial. Do you know a way to go? I tried with: var fl = air.File.applicationStorageDirectory.resolvePath("idees.jpg"); var req = new air.URLRequest("http://isource.fr/Images/Fonds/idees.jpg"); var lo = new air.URLLoader(); lo.dataFormat = "URLLoaderDataFormat.BINARY"; lo.addEventListener(air.Event.COMPLETE, function(e){ var fs = new air.FileStream(); fs.open(fl, air.FileMode.WRITE); fs.writeBytes(lo.data, 0, lo.data.length); fs.close(); }); lo.load(req); but this does not work on the writing part. Any idea what's wrong in this code ? What's the way to go ? Thanks, Jerome

Jerome - May 13, 2010 11:09 am

Jerome... Look into File Promises in the pending AIR 2.0 release (labs.adobe.com). As of AIR 1.5 I don't believe it allows you to download files using the runtime. It's a security issue.

andy matthews - May 14, 2010 05:35 am

Andy...I am with AIR 2.0 beta and I successfully download and write to disk an XML file. To do so, I use a js xmlHTTPRequest and as statechangeHandler : var fileStream = new air.FileStream(); fileStream.open( diskFile, air.FileMode.WRITE ); fileStream.writeMultiByte( xhr.responseText, air.File.systemCharset ); fileStream.close(); where out of the handler scope, I declared beforehand: var diskFile = air.File.applicationStorageDirectory.resolvePath("myFile.xml"); xhr.open( "GET", "http://iSource.fr/XML/myFile.xml", true); xhr.send( null ); So, it is possible with the runtime. Though the above technique does not work with a byte file instead of xml/ text. I'd reckon this is basic stuff to manage an AIR app both off and on line. How to cache images when online for a future use when off line, as I do with the xml data ? Looks strange to me that AIR is not capable of it. How an AIR app such as the NY Times (with plenty of pictures) work in that respect ? Any idea? Thank you for your help, Jerome

Jerome DARDE - May 14, 2010 07:13 am