Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Web and WebWorks Development

How To: Save the contents of a <canvas> element to the BlackBerry 10 filesystem.

by Retired on ‎01-18-2013 10:31 AM (8,514 Views)

tl;dr: Github Sample

 

This is a question that comes up quite often amongst developers, and for some reason is a question without many complete solutions out there. This article aims to be a complete solution.

 

For starters, let's take a look at the following code.

 

canvas.toBlob(
	function (blob) {
		fileWriter.write(blob);
	},
	'image/png'
);

 

Ultimately, this code is the crux of the problem. In the code above:

  • canvas simply refers to our <canvas> DOM element which can be obtained through the document.querySelector or other DOM selector functions.
  • toBlob is some magical function that converts our canvas content to an appropriate encoding to be written to the filesystem; we're specifying its mime-type as image/png.
  • fileWriter is an instance of the HTML5 FileWriter API that allows us to write blob data to the filesystem.

However, toBlob isn't quite as magical as it might first seem. In fact, it's practically standard:

http://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html

 

The main problem here is that not many browsers have implemented the <canvas> toBlob functionality, so we have to go looking elsewhere.

 

Luckily, Eli Grey has provided the HTML5 standard as a cross-browser implementation on Github® under the MIT/X11 license:

https://github.com/eligrey/canvas-toBlob.js

 

So, how do we demystify toBlob? Easy:

 

<script type="text/javascript" src="https://raw.github.com/eligrey/canvas-toBlob.js/master/canvas-toBlob.min.js"></script>

 

All that remains now is coming up with a valid fileWriter instance to perform the actual save of our newly converted blob object. For this, we'll start at the beginning.

 

Before we can write to a filesystem, we need to gain access to the filesystem. In fact, there are some very handy HTML5 FileSystem APIs to help us do this. So the first question that arises is, "Where do we want to save our file?"

 

We could provide the user with a text box to enter the destination, or use some hard-coded values. In fact, the BlackBerry® WebWorks APIs provide some handy shortcuts for use on that front:

https://developer.blackberry.com/html5/apis/blackberry.io.html#.SDCard

 

But what about that BlackBerry® 10 experience? Well, through use of the Invocation Framework, we can actually present the user with a file browser to choose the location of their saved file. In our case, we'll default to the shared folder outside of our application sandbox.

 

blackberry.invoke.card.invokeFilePicker(
	{
		mode: blackberry.invoke.card.FILEPICKER_MODE_SAVER,
		type: [
			blackberry.invoke.card.FILEPICKER_TYPE_PICTURE
		],
		directory: [blackberry.io.sharedFolder]
	},
	function (path) {
		/* User choses a path, and we now have a variable referencing it! */
	},
	function (reason) {
		/* User cancelled. */
		console.log('User Cancelled: ' + reason);
	},
	function (error) {
		/* Invoked. */
		if (error) {
			console.log('Invoke Error: ' + error);
		}
	}
);

 

In total, we're passing four (4) arguments:

  • The first is a group of options that specify the File Picker mode (we'll be saving a file), the types of files to filter while picking, and the starting directory (the shared folder.) These can be adjusted to meet your own application's needs.
  • The second is a function that is triggered once the user successfully picks a path.
  • The third is a function that is triggered if the user cancels selection and does not pick a path.
  • The fourth is a function that gets triggered when the File Picker is invoked; if there is an error during the invocation, it is reported here.

So now we know where we'll be saving the image file, but we don't have a how. For this, we need to obtain an instance of the HTML5 FileSystem. The following code will be called within our function (path) implementation above.

 

/* User chose a path, and we now have a variable referencing it! */
blackberry.io.sandbox = false; window.webkitRequestFileSystem( window.PERSISTENT, 5.0 * 1024 * 1024, function (fileSystem) {
/* We were granted a FileSystem object! */ }, function (fileError) { /* Error. */ console.log('FileSystem Error: ' + fileError); } );

 

The call to blackberry.io.sandbox is BlackBerry 10 specific and is what enables us to access the filesystem outside of our application's self-contained sandbox.

 

Next, we call the WebKit implementation of the HTML5 requestFileSystem API and pass a few arguments. Most importantly, the third argument function (fileSystem) is what will be triggered if the operating system successfully grants us access. If you are not granted access, you may be missing some permissions; refer to the Github sample at the end of this document for a complete, integrated solution.

 

Now that we have the fileSystem, we can leverage it to obtain a fileEntry. The following code will be called within our function (fileSystem) implementation above.

 

/* We were granted a FileSystem object. */
fileSystem.root.getFile(
	path,
	{
		create: true
	},
	function (fileEntry) {
		/* We were granted a FileEntry object! */
	},
	function (fileError) {
		console.log('DirectoryEntry (fileSystem.root) Error: ' + fileError);
	}
);

 

First, note the reference to path. This is the path variable that was initially provided from the File Picker APIs. We also pass a flag to indicate that if the file does not exist, we want it to be created. Finally, we have two function arguments; the first if we succeed and the second if there is an error.

 

Now that we have a fileEntry that represents our desired path we can finally create a FileWriter instance; this is what will actually allow us to write our blob to the filesystem.  The following code will be called within our function (fileEntry) implementation above.

 

/* We were granted a FileEntry object. */
fileEntry.createWriter(
	function (fileWriter) {
		/* We were granted a FileWriter object! */
	},
	function (fileError) {
		console.log('FileEntry Error: ' + fileError);
	}
);

 

This one is the most straight-forward; we have a success callback and an error callback. On success, we will have the fileWriter object that we need. The last thing to do to save our canvas. The following code will be called within our function (fileWriter) implementation above.

 

/* We were granted a FileWriter object. */
fileWriter.onerror = function (fileError) {
	console.log('FileWriter Error: ' + fileError);
};
fileWriter.onwriteend = function () {
	blackberry.ui.toast.show('Canvas saved!');
};

canvas.toBlob(
	function (blob) {
		fileWriter.write(blob);
	},
	'image/png'
);

 

First, we've implemented the onerror and onwriteend functions of the FileWriter object to ensure that we can properly monitor what is going on. Following that, we call the same code snippet that we started with; again taking the <canvas> instance and converting it to a blob, then leveraging our fileWriter (which now exists and references our desired path) to actually save those contents to the filesystem. 

 

In the end, the solution is slightly longer than originally advertised, but should hopefully help guide developers in overcoming this common hurdle.

 

If you do have any feedback or questions regarding this sample, do not hesitate to post your comments or reach out directy via Twitter (@WaterlooErik).

 

Additional Resources

Comments
by Retired
on ‎01-18-2013 11:01 AM

At present, the Github sample is under review, but I will update here once it is live as well.

by Retired
on ‎01-18-2013 04:50 PM

Github sample is now live!

https://github.com/blackberry/BB10-WebWorks-Samples/tree/master/canvasToFilesystem

Users Online
Currently online: 28 members 769 guests
Please welcome our newest community members: