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

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
      		<title>File system writing, jQuery UI drag and drop</title>
  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
  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">
      		<img src="images/puppies_01.jpg" />
      		<span>only a mother...</span>
      <div id="rightSide">
      	<div id="target"></div>
      	<div id="console"><b>Saved data</b><br /></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
        	// 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
        		// update saved display to indicate which image/text have been saved
        		$saveDisplay.append('img: ' + newSave['img'].split('/')[1] + '<br />');
        		// write selected image to disk
        		// store the "saved data" array to disk
        		// remove the saved object from view
      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
        		// write the file to disk
        		// close the file
        	} 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.