Hello and Welcome to Sugar Mountain!

....a place of random thoughts

Working with Safari Extensions

Filed Under (Coding, Javascript, Safari) by Olle on 20-08-2010

So I have had my first go with a Safari Extension and it has not been a smooth ride I tell you! The documentation over at Apple really leaves one wishing for more.

Some of the questions that I have manage to fix without documentation are the following

“How the heck do I add icons to the Safari Settings page?

Unless you want to have the default Safari Extension icons in Safari Settings(after installation), in the Safari Extension Builder and in the Installation Dialog, make sure you add three icons to your extension folder and call them:
Icon-32.png
Icon-48.png
Icon-68.png

And I guess you can figure out the dimensions of the icons, yes it 32×32, 48×48 and 68×68.
The result will be this:

Why the heck does not the updates work?

I made a lot of  misstakes when it comes to the update part of the Extension.
I finally got it to work by leaving the default value when it comes to “Bundle Identifier “.

When you create your new Extension you choose a name, for example myex.ver1, and the builder will create the folder(myex.ver1.safariextension with the Info.plist) for you and also create the default “Bundle Indentifier” in the Extension Builder. In my example the Identifier will be com.yourcompany.myexver1.

Now make sure that you only change the “com.yourcompany” part of the Identifier. I made the mistake of modifying the last part as well. In my example my Identifier looked like this “net.subelement.myex.ver1“.

The problem with this is that the updates will not ever work, when you have a .plist that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>Extension Updates</key>
   <array>
     <dict>
       <key>CFBundleIdentifier</key>
       <string>net.subelement17.myex.ver1</string>
       <key>Developer Identifier</key>
       <string>YOUR IDENTIFIER</string>
       <key>CFBundleVersion</key>
       <string>1.0</string>
       <key>CFBundleShortVersionString</key>
       <string>1.0</string>
       <key>URL</key>
       <string>http://subelement17.net/projectplace/projectplace.safariextz</string>
     </dict>
   </array>
</dict>
</plist>

So the thing is to always user the suggested Identifier, or you might end up in the same problems as I did.

HTML5 and the File API (Firefox 3.6)

Filed Under (HTML5, Javascript) by Olle on 10-12-2009

So, I’m having my first encounter with HTML 5 and the great power of it.
I have mostly been looking into the Firefox 3.6 support for the File API, but also features like Copy/Paste, Undo etc looks really promising?

Firefox HTML 5 File API support through Drag n’ Drop

There are a number of blogs out there that has great examples on how to use the Drag n’ Drop  and the File API in HTML5, and more specify, HTML5 in Firefox 3.6. But I have not found any example on the combination of Drag n’ Drop in conjunction with the new File API, most often the File API is “called” from a standard <input type=”file”..> object.

Uploading files from the Desktop, through Drag n’ Drop is really simple with the use of the HTML5 events ondragenter, ondragover and ondrop. By using the file API we are able to read the files, get the name and also the size of any standard file formats, like txt, pdf, png or jpg for instance. In the long run, when the browsers out there will start to support HTML5 better, my hope is that we will not see any more browser specific plugins and such.

First of all we have the FileReader object. In short, it has three interfaces to read files asynchronous; readAsBinaryString, readAsText, and readAsDataURL. The three do the same thing, it reads files into memory(readAsText also has the extra attribute of specifying encoding).

Since the reading of the filedata into the FileReader object is connected to the “standard” event chain, the possibility to keep track of the progress of the loading of the file into memory is as easy as loading of any DOM object. The loaded data can now be saved/uploaded to a server through a Ajax call.

The only really sad thing right now, is have not found any browser supplier, except Firefox, that have implemented the FileReader interfaces, and we are left wishing and hoping that more browsers then Firefox 3.6 will have this.

My somewhat working example(for Firefox 3.6)

Instead of using the FileReader object like the example below, for reasons I will state later on, I have choosen to use the getAsBinary interface of the File API. getAsBinary is deprecated, and surely will be replaced by the FileReader interface, as below.

function readFileIntoMem(file){
var reader = new FileReader();
reader.readAsText(files, 'UTF-8');
}

But as said, my working example(for Firefox 3.6) uses some functions that are deprecated, but never mind.. Over to the demo app.

Demo
[Live Demo]

I have a html page that looks like this:

<html>
<head>
<style media="screen">
	#file-drop-target{
        font-family: Georgia;
		border:1px solid #000;
		height:30px;
        text-align:center;
        padding:40px;
	}
    h4{
        font-family: Georgia;
        text-align:center;
    }
</style>
<script language="javascript" type="text/javascript" src="handleFiles.js"></script>
</head>
<body>
<h4>Drag 'n' Drop file(Firefox 3.6)</h4>
<div id="file-drop-target">drop files here</div>
<div id="status-msg"></div>
</body>
</html>

Nothing really fancy happens here, the main thing really that I load a javascript file called handleFiles.js and that we have a div with the id of file-drop-target. In handleFiles.js I have added a function call to addOnLoadEvent, that will run onload, that setts the onload handler, the setEventHandlers function, for the window object.

function addOnLoadEvent(sFunc)
{
    if(window.addEventListener)
        window.addEventListener('load', sFunc, false);
    else if(window.attachEvent)
        window.attachEvent('onload', sFunc);
}

addOnLoadEvent(setEventHandlers);

When the window object loads we will add a call to setEventHandlers that will handle the three events ondragover, ondragenter and ondrop for the html object file-drop-target. The first two events we just cancel. Our main focus is the ondrop event, and by attaching an eventhandler to it we will get access to the objects being dropped. The function doing this is handleDrop function. The first thing I do it terminate the event and hinder any bubbling of the event through out the DOM.

After the preventDef function call we get the new dataTransfer object and get the files that are being draged into the drop target.
And after we have got the list of files being dropped, we need to access and store the files somehow.
You can find a lot of Ajax scripts on the web that will save a file on a server, but they all have one problem. They all have the problem that the filename will not be submitted to the server, a problem as I see it, if one would like to use this technic in a web service. So what I’m doing is faking a post of a form, and in a form post we have the possibility on the server to get the name of the file being uploaded.
After this has been done, a standard XMLHttpRequest call can be done, just make sure that content-type is multipart/form-data.
The fake-form-posting is very much inspired by a script found on http://www.captain.at/


function handleDrop(event)
{
	preventDef(event)

    var dt = event.dataTransfer;
  	var files = dt.files;

	for(var i = 0; i < files.length;i++)
  	{

		http_request = new XMLHttpRequest();
		var boundaryString = 'the_boundery--';
		var boundary = '--' + boundaryString;
		var requestbody = boundary + '\n'

		+ 'Content-Disposition: form-data; name="thefilename"' + '\n'
		+ '\n'
		+ files[i].fileName + '\n'
		+ '\n'
		+ boundary + '\n'
		+ 'Content-Disposition: form-data; name="thefile"; filename="'
			+ files[i].fileName + '"' + '\n'
		+ 'Content-Type: application/octet-stream' + '\n'
		+ '\n'
		+ files[i].getAsBinary()
		+ '\n'
		+ boundary;

		http_request.onreadystatechange = _handleReadyState;
		http_request.open('POST', 's.php', true);
		http_request.setRequestHeader("Content-type", "multipart/form-data; \
			boundary=\"" + boundaryString + "\"");
		http_request.setRequestHeader("Connection", "close");
		http_request.setRequestHeader("Content-length", requestbody.length);
		http_request.sendAsBinary(requestbody);
    }
}

function _handleReadyState()
{
    if (this.readyState == 4)
    {
        if(this.status == 200)
        {
            var pathStored = this.responseText
            document.getElementById('status-msg').innerHTML += ' <br />';

            var linkToUpload = document.createElement('a');
            linkToUpload.href = pathStored;
            linkToUpload.appendChild(document.createTextNode(pathStored));
            document.getElementById('status-msg').appendChild(linkToUpload);
        }
    }
}

/**
* Event handlers
*/

function preventDef(event)
{
	event.preventDefault();
	event.stopPropagation();
}

function setEventHandlers()
{

	var dropTarget = document.getElementById('file-drop-target');
	addEvent(dropTarget,'dragover',preventDef, true);
	addEvent(dropTarget,'dragenter',preventDef, true);
	addEvent(dropTarget,'drop',handleDrop, true);
}

function addEvent(obj, evType, fn, useCapture)
{
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
      var r = obj.attachEvent("on"+evType, fn);
    return r;
  }
}

function addOnLoadEvent(sFunc)
{
    if(window.addEventListener)
        window.addEventListener('load', sFunc, false);
    else if(window.attachEvent)
        window.attachEvent('onload', sFunc);
}

addOnLoadEvent(setEventHandlers);

I hope you like parts of this and share any great changes you might do to the script.. What I’d really like to see is a more browser none specific script then mine!