Skip to content

March 13, 2012

4

Mobile File Explorer with PhoneGap/Cordova and jQuery Mobile Part 1

by nady
shot_000030

This tutorial will show you how to create a cross-platform mobile file explorer using web technologies like PhoneGap/Cordova and jQuery Mobile UI. This is a two part tutorial, the first part contains the browsing part of the folders and files from your file system device, and the second part contains implementation of the actions that can be made with the folders and files, like open, copy, move, delete.

Final result:





Download Sources

Start by preparing your project like in this tutorial of basic cross-platform app. We are now mainly testing this example application on an Android device, but it will work the same on other devices. If you are working on an Android device, follow this tutorial to setup your project for Android.

Let’s recap the structure of the www folder (assets/www):

  • index.html – the main file for our application
  • mylibs – where we have the files for jQuery Mobile v1.0.1, PhoneGap v1.5 and jQuery v1.7.1 (put all your additional libraries in this folder)
  • js – all your javascript files (script.js)
  • css – all your css files (style.css)
  • img – all the images for your application

Open the main file index.html and set in place all the libraries and files that you will use:

<!DOCTYPE HTML>
<html>
	
<head>
	<title>DigitalNoiz</title>
	<!-- jQuery Mobile setup -->
    	<meta name="viewport" content="width=device-width, initial-scale=1">
    	<link rel="stylesheet" href="mylibs/jQueryMobile/jquery.mobile-1.0.1.min.css" />
    	<script src="mylibs/jquery-1.7.1.min.js"></script>
   	<script src="mylibs/jQueryMobile/jquery.mobile-1.0.1.min.js"></script>
	<!-- Phonegap setup -->
	<script type="text/javascript" charset="utf-8" src="mylibs/cordova-1.5.0.js"></script>
	
	<link rel="stylesheet" href="css/style.css" />
    	<script type="text/javascript" charset="utf-8" src="js/script.js"></script>
</head>

<body>

<div id="home" data-role="page">
      <div data-role="header" data-position="fixed">
            <a id="backBtn" href="#" data-icon="arrow-l">Back</a>
            <h1>File Explorer</h1>
            <a id="homeBtn" href="index.html" data-icon="home" data-transition="pop">Home</a>
      </div>
      <div data-role="content">
            <h2>Folder</h2>
      </div>
</div>

</body>

</html>

In the above code, we have an header bar with a back button that we’ll use to go up on level in the file structure and a home button that will always go to the root of the file system.
Now let’s create a grid view to list the content from the file system of the device, put this code under the h2 in the content:

<div class="ui-grid-c" id="dirContent">
	<div class="ui-block-a">
		<div class="folder"><p>folder name</p></div>
	</div>
	<div class="ui-block-b">
		<div class="folder"><p>folder name</p></div>
	</div>
	<div class="ui-block-c">
		<div class="folder"><p>folder name</p></div>
	</div>
	<div class="ui-block-d">
		<div class="folder"><p>folder name</p></div>
	</div>
	<!-- next row -->
	<div class="ui-block-a">
		<div class="file"><p>file name</p></div>
	</div>
	<div class="ui-block-b">
		<div class="file"><p>file name</p></div>
	</div>
	<div class="ui-block-c">
		<div class="file"><p>file name</p></div>
	</div>
	<div class="ui-block-d">
		<div class="file"><p>file name</p></div>
	</div>
</div>

The class ui-grid-c will create a 4 column grid and every row starts with an div that has class ui-block-a.
To add some visual meaning to the items from the grid use class folder and class file and show the icons from img folder, add the following lines to css/style.css:

h2{
      text-align:center;
      color:#060;
}

.folder, .file{
    font-family:sans;
    font-size:10px;
    cursor:pointer;
}

.folder{
	background: url('../img/folder.png') no-repeat 50% 0%;
	margin:2px;
	padding-top:25px;
	text-align:center;
}


.file{
	background: url('../img/file.png') no-repeat 50% 0%;
	margin:2px;
	padding-top:25px;
	text-align:center;
}

We have now a basic view for our files and folders.
It’s time to make the magic to happen. Open js/script.js and add:

// handling document ready and phonegap deviceready
window.addEventListener('load', function () {
    document.addEventListener('deviceready', onDeviceReady, false);
}, false);

var root = null; // File System root variable
var currentDir = null; // Current DirectoryEntry listed
var parentDir = null; // Parent of the current directory
 
// Phonegap is loaded and can be used
function onDeviceReady(){
	$('#backBtn').hide();
}

Because we always need to know what is our root, the current directory that we’re in and his parent to be able to easily go one level up, we’ve added the global variables root, currentDir and parentDir. The first time we open the app there’s no other level up to go, that’s why we hide the back button, this will be made visible while we navigate through the file system.

Invoke at onDeviceReady() the function getFileSystem() and create it:

/* get the root file system */
function getFileSystem(){
	window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
		function(fileSystem){ // success get file system
			root = fileSystem.root;
			listDir(root);
		}, function(evt){ // error get file system
			console.log("File System Error: "+evt.target.error.code);
		}
	);
}

With the requestFileSystem() function we get an DirectoryEntry object pointnig to the root of the file system. After we have the root, we want to show the content of a directory, make this by creating the listDir() function.

/* show the content of a directory */
function listDir(directoryEntry){
	if( !directoryEntry.isDirectory ) console.log('listDir incorrect type');
	$.mobile.showPageLoadingMsg(); // show loading message
	
	currentDir = directoryEntry; // set current directory
	directoryEntry.getParent(function(par){ // success get parent
		parentDir = par; // set parent directory
		if( (parentDir.name == 'sdcard' && currentDir.name != 'sdcard') || parentDir.name != 'sdcard' ) $('#backBtn').show();
	}, function(error){ // error get parent
		console.log('Get parent error: '+error.code);
	});
	
	var directoryReader = directoryEntry.createReader();
	directoryReader.readEntries(function(entries){
		var dirContent = $('#dirContent');
		dirContent.empty();
		
		var dirArr = new Array();
		var fileArr = new Array();
		for(var i=0; i<entries.length; ++i){ // sort entries
			var entry = entries[i];
			if( entry.isDirectory && entry.name[0] != '.' ) dirArr.push(entry);
			else if( entry.isFile && entry.name[0] != '.' ) fileArr.push(entry);
		}
		
		var sortedArr = dirArr.concat(fileArr); // sorted entries
		var uiBlock = ['a','b','c','d'];
		
		for(var i=0; i<sortedArr.length; ++i){ // show directories
			var entry = sortedArr[i];
			var blockLetter = uiBlock[i%4];
			//console.log(entry.name);
			if( entry.isDirectory )
				dirContent.append('<div class="ui-block-'+blockLetter+'"><div class="folder"><p>'+entry.name+'</p></div></div>');
			else if( entry.isFile )
				dirContent.append('<div class="ui-block-'+blockLetter+'"><div class="file"><p>'+entry.name+'</p></div></div>');
		}
		$.mobile.hidePageLoadingMsg(); // hide loading message
	}, function(error){
		console.log('listDir readEntries error: '+error.code);
	});
}

To find out more about the file system objects used by PhoneGap, check this photo gallery tutorial. Now I only present the use of this objects without explaining their structure.

I’ll break this listDir() function into smaller pieces to better understand what it has been done.
First of all verify if the parameter is an DirectoryEntry, after that, to indicate that the application is preparing information in the background show the loading message $.mobile.showPageLoadingMsg() .
The currentDir becomes the directory that we’re going to show. Set the parentDir global var by using the getParent() method from the DirectoryEntry object. If this method doesn’t work for your device, than an workaround would be to set the parentDir as the currentDir and after that to set the currentDir the DirectoryEntry that we’re going to list.

parentDir = currentDir;
currentDir = directoryEntry;

In order to get the content of the directory we need a DirectoryReader object. By using the readEntries() method we iterate through the directory items and sort them after the type, the folders are listed first, then the files, all the names that start with ‘.’ are excluded.
Now that we have the sorted items, list them using the html that we made for the grid, and remove the sample grid blocks from index.html. After the items are listed, remove the loading message $.mobile.hidePageLoadingMsg()

You can now browse your file system of the mobile device. For more functionality of the file explorer app read part 2 of this tutorial.

Read more from Android, Mobile
4 Comments Post a comment
  1. Oze
    Apr 3 2012

    Buenas, me ha sido de gran utilidad tu ejemplo. Al ponerlo en práctica creo que lo habrás probado en un terminal rooteado, porque al probar en uno que no lo está da errores al intentar obtener el padre de la ruta “sdcard”. Al menos a mí me los ha dado, por lo que he tenido que modificar un poco el código para evitarlos.

    Muchas gracias por el post, me ha ayudado mucho. Un saludo.

    Reply
  2. Oze
    Apr 3 2012

    I’m sorry, I wrote my previous comment in Spanish. Let me translate it into English ;)

    The example has been really useful to me, but I think you tested it on a rooted device because when putting it to work on a non-rooted one it throws errors at trying to access the parent path of the ‘sdcard’ folder. Al least it happened to me and I had to fix some lines of your code and add others to make it work properly.

    Anyway, you helped me a lot. It’s an awesome post. Thank you so much.

    Reply
    • noise
      Apr 3 2012

      Can you please tell me the version of OS you tested on and your device model? And can you also post your modified code?

      Reply

Trackbacks & Pingbacks

  1. Mobile File Explorer with PhoneGap/Cordova and jQuery Mobile Part 2 - DigitalNoiz

Leave a Reply

required
required

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments