<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>CastlesBlog</title>
<link>http://www.castlesblog.com/</link>
<description>A blog about making a blog</description>
<copyright>All content is copyright to Marc Castles</copyright>
<language>en-au</language>
<generator>CastlesBlog</generator><item>
<title>Gravatar Support</title>
<link>http://www.castlesblog.com/2011/january/4/gravatars</link>
<description>&lt;p&gt;Just a quick post to say I've added &lt;a href=&quot;http://www.gravatar.com&quot;&gt;Gravatar&lt;/a&gt; support to the comments. Its something I've had on my list for ages. If you don't know what it is the &lt;a href=&quot;http://www.gravatar.com&quot;&gt;Gravatar site&lt;/a&gt; explains it well..&lt;/p&gt;&lt;blockquote&gt;Your Gravatar is an image that follows you from site to site appearing beside your name when you do things like comment or post on a blog. Avatars help identify your posts on blogs and web forums, so why not on any site?&lt;/blockquote&gt;&lt;p&gt;This site will load the avatars after the content thanks to this great &lt;a href=&quot;http://24ways.org/2010/speed-up-your-site-with-delayed-content&quot;&gt;article on 24ways.org&lt;/a&gt;&lt;/p&gt;</description>
<pubDate>Mon, 3 Jan 2011 13:13:17 +0000</pubDate>
</item><item>
<title>Launch Interactive</title>
<link>http://www.castlesblog.com/2011/january/2/launch-interactive</link>
<description>&lt;p&gt;Something I really should of posted a few months ago was the fact that I was leaving my previous work to start my own business.. &lt;a href=&quot;http://launchinteractive.com.au&quot;&gt;Launch Interactive&lt;/a&gt;. Currently, I'm focusing on Web Development, Hosting and whatever else in between. So far I've been flat out working on a large Flash project, a few content managed sites and various other jobs.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;http://launchinteractive.com.au&quot;&gt;Launch site&lt;/a&gt; is currently in development but check out the splash page in the interim. If your browser supports canvas you will get some nice snow to have a play with. The snow interacts with your mouse cursor and you can shift the direction of the wind. Its the first time I've played with canvas and I'm pretty happy with the results however, performance could be better as I found rendering 100 or so rotating images at 25FPS can bog down some browsers. If anyone has any suggestions on how I can improve this I'm all ears.&lt;/p&gt;&lt;p&gt;And, finally.. if you need any work done don't hesitate to check me out... &lt;/p&gt;&lt;a href=&quot;http://launchinteractive.com.au&quot;&gt;&lt;img src=&quot;http://static.castlesblog.com/data/launch-interactive.png&quot; alt=&quot;Launch Interactive&quot; width=&quot;200&quot; height=&quot;110&quot; /&gt;&lt;/a&gt;</description>
<pubDate>Sun, 2 Jan 2011 11:47:14 +0000</pubDate>
</item><item>
<title>How to setup a signature in Apple Mail</title>
<link>http://www.castlesblog.com/2010/december/31/apple-mail-signature</link>
<description>&lt;p&gt;One of my pet hates is people who don't send a signature with their email. A basic signature is not hard to do and makes it easier for recipients. I'm going to explain how I setup my signature in Apple Mail which is a bit of work but the result is worth it.&lt;/p&gt;&lt;p&gt;Firstly, open up Mail and goto &lt;strong&gt;Mail -&gt; Preferences -&gt; Signatures&lt;/strong&gt;. Hit the plus sign and create a signature. Apply the signature to your email account by the choose signature drop down.  If your happy with the result than skip the rest.&lt;/p&gt;
&lt;h2&gt;Why am I always sending attachments?&lt;/h2&gt;
&lt;p&gt;Here is the catch.. if you insert an image in the signature editor such as a logo &lt;strong&gt;Mail&lt;/strong&gt; will send the image as an attachment for every email you send, including reply's. Sure, its only a small file but if you reply to a reply's reply.. you get the point, there can be a lot of wasted bandwidth.&lt;/p&gt;&lt;p&gt;What I like to do is upload the signature image (remember to compress it for web) onto a web server and link to it within Mail. Easy enough.. well not really. Mail makes it hard to link to the file (Its just as hard in Outlook). Your going to have to get your hands dirty writing some HTML code. Open up textedit (in plain text mode) or something similar and code up your signature. Link the img tag to your remote location. Preview in Safari as you go. Here is an example of my &lt;a href=&quot;http://launchinteractive.com.au&quot;&gt;Launch&lt;/a&gt; signature:&lt;/p&gt;&lt;h2&gt;Example HTML Signature&lt;/h2&gt;&lt;pre&gt;&amp;lt;div style=&amp;quot;font-family:arial, helvetica;font-size:12px;color:#000&amp;quot;&amp;gt;
&amp;lt;br/&amp;gt;Kind Regards,&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;span style=&amp;quot;font-size:14px&amp;quot;&amp;gt;Marc Castles&amp;lt;/span&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;table border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; style=&amp;quot;margin-top:10px&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;
&amp;lt;img src=&amp;quot;http://launchinteractive.com.au/launch-interactive-signature.png&amp;quot; alt=&amp;quot;Launch Interactive&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;110&amp;quot; /&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;/td&amp;gt;&amp;lt;td style=&amp;quot;font-family:arial, helvetica;font-size:12px;color:#666&amp;quot;&amp;gt;&amp;lt;div style=&amp;quot;border-left:1px dashed #CCC;padding-left:20px&amp;quot;&amp;gt;0421636775&amp;lt;br/&amp;gt;
&amp;lt;a style=&amp;quot;color:#666&amp;quot; href=&amp;quot;http://launchinteractive.com.au&amp;quot;&amp;gt;launchinteractive.com.au&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;address style=&amp;quot;padding:0px;color:#666;font-style:normal;margin:0px&amp;quot;&amp;gt;PO Box 2272, Bakery Hill Mail Centre, Ballarat Victoria 3354&amp;lt;/address&amp;gt;
&amp;lt;a style=&amp;quot;color:#666&amp;quot; href=&amp;quot;http://twitter.com/launchinteract&amp;quot;&amp;gt;@launchinteract&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;a style=&amp;quot;color:#666&amp;quot; href=&amp;quot;http://www.facebook.com/launchinteractive&amp;quot;&amp;gt;www.facebook.com/launchinteractive&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;&lt;/pre&gt;&lt;p&gt;Notice all the inline css and table junk. This is because email clients are stuck in the dark ages and support is terrible... if any. A good reference for email support can be &lt;a href=&quot;http://www.campaignmonitor.com/css/&quot;&gt;found here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Once you are happy with your HTML preview the signature in Safari and goto &lt;strong&gt;Save As&lt;/strong&gt; and save it as a Web Archive. Navigate to ~/Library/Mail/Signatures (/Users/[Your Account Name]/Library/Mail/Signatures) and replace the Web Archive in that folder (this will be the signature you created earlier). If you need to preview the signature open the Web Archive in Safari.&lt;/p&gt;&lt;p&gt;Close and Open Mail and you should have your new signature at your disposal. Here is my finished result:&lt;/p&gt;&lt;img src=&quot;/data/launch-interactive-email-signature.png&quot; alt=&quot;Launch Interactive Email Signature&quot; width=&quot;567&quot; height=&quot;174&quot; /&gt;</description>
<pubDate>Thu, 30 Dec 2010 22:56:42 +0000</pubDate>
</item><item>
<title>Get mySQL Enumeration Values as an Array </title>
<link>http://www.castlesblog.com/2010/august/30/enums-to-array</link>
<description>&lt;p&gt;I often use &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/enum.html&quot;&gt;enumeration values&lt;/a&gt; when defining a list of options in mySQL. In the past I would make sure my web form matched my database and update each form when I change the database.... but not any more.. I've written a quick function that will return the possible values from mySQL as an array. I can then use the result in my forms and the only place that needs to change is the database.&lt;/p&gt;

&lt;pre&gt;function getEnums($table,$field) {
	$sql = &quot;SELECT
			COLUMN_TYPE
		FROM 
			INFORMATION_SCHEMA.COLUMNS
		WHERE
			TABLE_NAME = '$table'
			AND COLUMN_NAME = '$field'&quot;;

	$this-&gt;query($sql);

	preg_match_all('/\'(.[^\']+)\'/',$this-&gt;get('COLUMN_TYPE'),$matches);

	return $matches[1];
}&lt;/pre&gt;&lt;p&gt;I'm using this in my database class. Modify it to suit your needs.&lt;/p&gt;</description>
<pubDate>Mon, 30 Aug 2010 12:02:51 +0000</pubDate>
</item><item>
<title>MythTV Minimal OSD</title>
<link>http://www.castlesblog.com/2010/august/30/mythtv-minimal-osd</link>
<description>&lt;h2&gt;A new theme&lt;/h2&gt;
&lt;p&gt;Presenting a new OSD theme for Mythtv .23. Designed to be a minimalistic theme that covers up the smallest amount of picture.&lt;/p&gt;&lt;p&gt;&lt;del&gt;&lt;strong&gt;Minimal&lt;/strong&gt; is built for 16:10 screens and I don't really have any plans to make a 16:9 version. If your after a different aspect ratio post a comment and I might think about it.&lt;/del&gt; &lt;a href=http://gazela.be/?q=node/11&gt;Tom Sneyers&lt;/a&gt; liked Minimal so much that he has made a 16:9 version. Visit &lt;a href=http://gazela.be/?q=node/11&gt;his site&lt;/a&gt; to download.&lt;/p&gt;&lt;h2&gt;Download&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;del&gt;&lt;a href=&quot;http://static.castlesblog.com/public/Minimal-OSD-V1.zip&quot;&gt;Version 1&lt;/a&gt;&lt;/del&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://static.castlesblog.com/public/Minimal-OSD-V1.1.zip&quot;&gt;Version 1.1 - Minor change to allow for longer status text&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Please Note&lt;/h2&gt;
&lt;p&gt;This has only been tested in MythTV .23 - The OSD theming for OSD's has changed significantly in .24 and hence this will probably break when its released. I intend to update this theme as well as my &lt;a href=&quot;/2009/november/30/mythtv-glass-osd&quot;&gt;Glass theme&lt;/a&gt; when .24 is available.&lt;/p&gt;&lt;h2&gt;Screenshots&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;http://static.castlesblog.com/data/mythtv-minimal-osd-screenshot-1.jpg&quot; alt=&quot;Mythtv Minimal OSD Theme Screenshot 1&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;http://static.castlesblog.com/data/mythtv-minimal-osd-screenshot-2.jpg&quot; alt=&quot;Mythtv Minimal OSD Theme Screenshot 2&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;http://static.castlesblog.com/data/mythtv-minimal-osd-screenshot-3.jpg&quot; alt=&quot;Mythtv Minimal OSD Theme Screenshot 3&quot; /&gt;&lt;/p&gt;</description>
<pubDate>Sun, 29 Aug 2010 13:04:25 +0000</pubDate>
</item><item>
<title>Using PHP to minify Javascript and CSS</title>
<link>http://www.castlesblog.com/2010/august/14/php-javascript-css-minification</link>
<description>&lt;h2&gt;An Update to the Dynamic Javascript include&lt;/h2&gt;
&lt;p&gt;&lt;a href=/2010/february/27/cookieless-domain#dynamic-static&gt;Back in February&lt;/a&gt; I wrote the code to combine all the javascript into one file with the intention to limit the requests to the server as recommended by &lt;a href=http://code.google.com/speed/page-speed/docs/rtt.html#CombineExternalJS&gt;PageSpeed&lt;/a&gt; and &lt;a href=http://developer.yahoo.com/performance/rules.html#num_http&gt;YSlow&lt;/a&gt;. I have revisited this code to make it do a few extra things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Checks which file was last edited and returns a Not Modified header if applicable&lt;/li&gt;&lt;li&gt;Minification (white space removed)&lt;/li&gt;&lt;li&gt;Links to Google&amp;#39;s CDN for jQuery and fall back to a local version&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So lets look at the new script...&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

$files = array(
    
    &amp;#39;../javascript/jquery.easing.js&amp;#39;
    
    ,&amp;#39;../templates/castlesblog/template.js&amp;#39;

);
    
$modified = 0;

foreach($files as $file) {        
    $age = filemtime($file);
    if($age &amp;gt; $modified) {
        $modified = $age;
    }
}

$offset = 60 * 60 * 24 * 7; // Cache for 1 weeks
header (&amp;#39;Expires: &amp;#39; . gmdate (&amp;quot;D, d M Y H:i:s&amp;quot;, time() + $offset) . &amp;#39; GMT&amp;#39;);

if (isset($_SERVER[&amp;#39;HTTP_IF_MODIFIED_SINCE&amp;#39;]) &amp;amp;&amp;amp; strtotime($_SERVER[&amp;#39;HTTP_IF_MODIFIED_SINCE&amp;#39;]) &amp;gt;= $modified) {
    header(&amp;quot;HTTP/1.0 304 Not Modified&amp;quot;);
    header (&amp;#39;Cache-Control:&amp;#39;);
} else {
    header (&amp;#39;Cache-Control: max-age=&amp;#39; . $offset);
    header (&amp;#39;Content-type: text/javascript; charset=UTF-8&amp;#39;);
    header (&amp;#39;Pragma:&amp;#39;);
    header (&amp;quot;Last-Modified: &amp;quot;.gmdate(&amp;quot;D, d M Y H:i:s&amp;quot;, $modified ).&amp;quot; GMT&amp;quot;);
    
    function compress($buffer) {
        /* remove comments */
        $buffer = preg_replace(&amp;quot;/((?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:\/\/.*))/&amp;quot;, &amp;quot;&amp;quot;, $buffer);
        /* remove tabs, spaces, newlines, etc. */
        $buffer = str_replace(array(&amp;quot;\r\n&amp;quot;,&amp;quot;\r&amp;quot;,&amp;quot;\t&amp;quot;,&amp;quot;\n&amp;quot;,&amp;#39;  &amp;#39;,&amp;#39;    &amp;#39;,&amp;#39;     &amp;#39;), &amp;#39;&amp;#39;, $buffer);
        /* remove other spaces before/after ) */
        $buffer = preg_replace(array(&amp;#39;(( )+\))&amp;#39;,&amp;#39;(\)( )+)&amp;#39;), &amp;#39;)&amp;#39;, $buffer);
        return $buffer;
    }
    
    ob_start(&amp;#39;ob_gzhandler&amp;#39;);

        foreach($files as $file) {
            if(strpos(basename($file),&amp;#39;.min.&amp;#39;)===false) { //compress files that aren&amp;#39;t minified
                ob_start(&amp;quot;compress&amp;quot;);
                    include($file);
                ob_end_flush();
            } else {
                include($file);
            }
        }
    
    ob_end_flush();
}&lt;/pre&gt;
&lt;h2&gt;Explanation&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;Firstly, you will see an array of javascript files to include. It loops through each one to work out which is the newest.&lt;/li&gt;&lt;li&gt;The next two lines set a header that should cache the file for one week.&lt;/li&gt;&lt;li&gt;After that we work out if the modified date is newer than what the browser has in cache and if so return the Not Modified header.&lt;/li&gt;&lt;li&gt;Alternatively if the file is modified or new to the browser it sets/resets a few more headers and then loops through our array again and includes the code.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I&amp;#39;ve wrapped the include in two ob_start methods. The first one gzips the output and the second runs the defined compress function.&lt;/p&gt;&lt;p&gt;It will only run the compress on files that don&amp;#39;t have .min. in the filename. This was added because the compress function messes up scripts that are already minified such as jQuery. The compress function uses a few regular expressions for the minification. Suggestions on how to improve this is welcome.&lt;/p&gt;&lt;h2&gt;A few things to note&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Using &lt;a href=http://developer.yahoo.com/yui/compressor/&gt;Yahoo&amp;#39;s YUI Compressor&lt;/a&gt; or &lt;a href=http://code.google.com/closure/compiler/&gt;Google&amp;#39;s Closure Compiler&lt;/a&gt; will do a better job. The advantage of this is there is less work involved in compressing the files. All you need to change when adding a new script is the array.&lt;/li&gt;&lt;li&gt;The regular expressions can break the javascript if there is a double forward slash in the code (URLs etc). The fix is to escape the slashes.. htt:\/\/www.url...&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;CSS Minification&lt;/h2&gt;&lt;p&gt;The CSS is using a similar minification process. &lt;/p&gt;&lt;pre&gt;&amp;lt;?php

$files = array(
    
    &amp;#39;../templates/castlesblog/template.css&amp;#39;

);
    
$modified = 0;

foreach($files as $file) {        
    $age = filemtime($file);
    if($age &amp;gt; $modified) {
        $modified = $age;
    }
}

$offset = 60 * 60 * 24 * 7; // Cache for 1 weeks
header (&amp;#39;Expires: &amp;#39; . gmdate (&amp;quot;D, d M Y H:i:s&amp;quot;, time() + $offset) . &amp;#39; GMT&amp;#39;);

if (isset($_SERVER[&amp;#39;HTTP_IF_MODIFIED_SINCE&amp;#39;]) &amp;amp;&amp;amp; strtotime($_SERVER[&amp;#39;HTTP_IF_MODIFIED_SINCE&amp;#39;]) &amp;gt;= $modified) {
    header(&amp;quot;HTTP/1.0 304 Not Modified&amp;quot;);
    header (&amp;#39;Cache-Control:&amp;#39;);
} else {
    header (&amp;#39;Cache-Control: max-age=&amp;#39; . $offset);
    header (&amp;#39;Content-type: text/css; charset=UTF-8&amp;#39;);
    header (&amp;#39;Pragma:&amp;#39;);
    header (&amp;quot;Last-Modified: &amp;quot;.gmdate(&amp;quot;D, d M Y H:i:s&amp;quot;, $modified ).&amp;quot; GMT&amp;quot;);
    
    function compress($buffer) {
        /* remove comments */
        $buffer = preg_replace(&amp;#39;!/\*[^*]*\*+([^/][^*]*\*+)*/!&amp;#39;, &amp;#39;&amp;#39;, $buffer);
        /* remove tabs, spaces, newlines, etc. */
        $buffer = str_replace(array(&amp;quot;\r\n&amp;quot;,&amp;quot;\r&amp;quot;,&amp;quot;\n&amp;quot;,&amp;quot;\t&amp;quot;,&amp;#39;  &amp;#39;,&amp;#39;    &amp;#39;,&amp;#39;     &amp;#39;), &amp;#39;&amp;#39;, $buffer);
        /* remove other spaces before/after ; */
        $buffer = preg_replace(array(&amp;#39;(( )+{)&amp;#39;,&amp;#39;({( )+)&amp;#39;), &amp;#39;{&amp;#39;, $buffer);
        $buffer = preg_replace(array(&amp;#39;(( )+})&amp;#39;,&amp;#39;(}( )+)&amp;#39;,&amp;#39;(;( )*})&amp;#39;), &amp;#39;}&amp;#39;, $buffer);
        $buffer = preg_replace(array(&amp;#39;(;( )+)&amp;#39;,&amp;#39;(( )+;)&amp;#39;), &amp;#39;;&amp;#39;, $buffer);
        return $buffer;
    }
    
    ob_start(&amp;#39;ob_gzhandler&amp;#39;);

        foreach($files as $file) {
            if(strpos(basename($file),&amp;#39;.min.&amp;#39;)===false) { //compress files that aren&amp;#39;t minified
                ob_start(&amp;quot;compress&amp;quot;);
                    include($file);
                ob_end_flush();
            } else {
                include($file);
            }
        }
    
    ob_end_flush();
}&lt;/pre&gt;&lt;h2&gt;Changes to .htaccess&lt;/h2&gt;&lt;p&gt;To make nice urls to these files the following is added to the .htaccess&lt;/p&gt;&lt;pre&gt;RewriteRule ^scripts.js$ includes/scripts.php [L]

RewriteRule ^styles.css$ includes/styles.php [L]&lt;/pre&gt;&lt;h2&gt;Use Google&amp;#39;s CDN for jQuery&lt;/h2&gt;&lt;p&gt;One final change was to remove jQuery from the included scripts and use Google CDN version.&lt;/p&gt;&lt;pre&gt;&amp;lt;script src=&amp;quot;http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;!window.jQuery &amp;amp;&amp;amp; document.write(&amp;#39;&amp;lt;script src=&amp;quot;js/jquery-1.4.2.min.js&amp;quot;&amp;gt;&amp;lt;\/script&amp;gt;&amp;#39;)&amp;lt;/script&amp;gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=http://happyworm.com/blog/2010/01/28/a-simple-and-robust-cdn-failover-for-jquery-14-in-one-line/&gt;Here&lt;/a&gt; is a few reasons why this is a good idea.&lt;/p&gt;</description>
<pubDate>Sat, 14 Aug 2010 09:52:25 +0000</pubDate>
</item><item>
<title>Cross Domain Fonts in Firefox</title>
<link>http://www.castlesblog.com/2010/august/12/cross-domain-fonts-in-firefox</link>
<description>&lt;p&gt;Today I realised that @font-face wasn't working properly in Firefox because I'm linking to the resource on a different domain (&lt;a href=/2010/february/27/cookieless-domain&gt;see cookieless domain post&lt;/a&gt;). It appears that Firefox has cross-site permissions that prevent resources being read. To allow remote access all I needed to add to the .htaccess on the server thats hosting the fonts was:&lt;/p&gt;&lt;pre&gt;&amp;lt;FilesMatch &amp;quot;\.(ttf|otf|eot|woff)$&amp;quot;&amp;gt;
    &amp;lt;IfModule mod_headers.c&amp;gt;
        Header set Access-Control-Allow-Origin &amp;quot;http://castlesblog.com&amp;quot;
    &amp;lt;/IfModule&amp;gt;
&amp;lt;/FilesMatch&amp;gt;&lt;/pre&gt;&lt;p&gt;For more information checkout the &lt;a href=https://developer.mozilla.org/en/HTTP_access_control&gt;HTTP access Control&lt;/a&gt; article at the Mozilla Dev Center.&lt;/p&gt;</description>
<pubDate>Thu, 12 Aug 2010 11:54:43 +0000</pubDate>
</item><item>
<title>HTML5 Drag and Drop Upload</title>
<link>http://www.castlesblog.com/2010/july/13/html5-drag-drop-upload</link>
<description>&lt;p&gt;I've been playing around with drag and drop in HTML5 and I'm pretty excited about it. A lot of the examples I've found online are firefox specific which isn't ideal so I've come up with a solution that works in as many browsers as possible.&lt;/p&gt;&lt;h2&gt;Browser Support&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;http://www.getfirefox.com&quot;&gt;&lt;img src=&quot;http://static.castlesblog.com/data/firefox-icon.jpg&quot; alt=&quot;Firefox&quot; width=&quot;60&quot; height=&quot;60&quot;/&gt;&lt;/a&gt;&lt;a href=&quot;http://www.apple.com/safari&quot;&gt;&lt;img src=&quot;http://static.castlesblog.com/data/safari-icon.jpg&quot; alt=&quot;Safari&quot; width=&quot;60&quot; height=&quot;60&quot;/&gt;&lt;/a&gt;&lt;a href=&quot;http://www.google.com/chrome&quot;&gt;&lt;img src=&quot;http://static.castlesblog.com/data/chrome-icon.jpg&quot; alt=&quot;Chrome&quot; width=&quot;60&quot; height=&quot;60&quot;/&gt;&lt;/a&gt;&lt;a href=&quot;http://www.webkit.org/&quot;&gt;&lt;img src=&quot;http://static.castlesblog.com/data/webkit-icon.jpg&quot; alt=&quot;Webkit&quot; width=&quot;60&quot; height=&quot;60&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Support for drag and drop &amp;amp; the file API is limited to cutting edge browsers and as the &lt;a href=&quot;http://dev.w3.org/html5/spec/Overview.html&quot;&gt;spec&lt;/a&gt; isn't finalised features are inconsistent... to say the least.&lt;/p&gt;&lt;p&gt;I've come up with a solution thats tested and works in Firefox 3.6, Safari 5, Chrome 6 and Webkit. I'll be adding support for Opera and IE if/when its available.&lt;/p&gt;&lt;h2&gt;What I've discovered:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;SAFARI 5 doesn't support FileReader&lt;/li&gt;&lt;li&gt;Only Firefox 3.6 supports addEventListener on FileReaders&lt;/li&gt;&lt;li&gt;Only Firefox 3.6 supports sendAsBinary in XML Http Requests&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Because of these issues I've coded three ways to upload files.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Browsers that support sendAsBinary send the data using proper headers and PHP can easily read the $_FILES array&lt;/li&gt;&lt;li&gt;Browsers that support FileReader send the file as base64 encoded string. The file information is passed in via headers&lt;/li&gt;&lt;li&gt;Browsers that don't support FileReader send the file itself and is read the same way as no. 2&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;The Code&lt;/h2&gt;&lt;p&gt;So, without any further ado, &lt;a href=&quot;/html5_upload.php&quot;&gt;here is a demo&lt;/a&gt;. And here is the code:&lt;/p&gt;
&lt;pre&gt;&amp;lt;?php
$upload_folder = &amp;#39;data&amp;#39;;

if(count($_FILES)&amp;gt;0) { //browser supported sendAsBinary()
	if( move_uploaded_file( $_FILES[&amp;#39;upload&amp;#39;][&amp;#39;tmp_name&amp;#39;] , $upload_folder.&amp;#39;/&amp;#39;.$_FILES[&amp;#39;upload&amp;#39;][&amp;#39;name&amp;#39;] ) ) {
		echo &amp;#39;done&amp;#39;;
	}
	exit();
} else if(isset($_GET[&amp;#39;up&amp;#39;])) {

	if(isset($_GET[&amp;#39;base64&amp;#39;])) {
		$content = base64_decode(file_get_contents(&amp;#39;php://input&amp;#39;));
	} else {
		$content = file_get_contents(&amp;#39;php://input&amp;#39;);
	}

	$headers = getallheaders();
	
	$headers = array_change_key_case($headers, CASE_UPPER); //different case was being used for different browsers
	
	if(file_put_contents($upload_folder.&amp;#39;/&amp;#39;.$headers[&amp;#39;UP-FILENAME&amp;#39;], $content)) {
		echo &amp;#39;done&amp;#39;;
	}
	exit();
}
?&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=utf-8&amp;gt;
    &amp;lt;title&amp;gt;HTML5 Upload Demo&amp;lt;/title&amp;gt;
    
    &amp;lt;style&amp;gt;
    body{font-family:Helvetica,Arial;font-size:12px}
	#drop-area{border:2px solid black;padding:10px;background-size:contain;min-height:200px;overflow:auto}
	#drop-area.hover{border:2px dashed red}
	#drop-area div{width:150px;height:150px;border:1px solid #CCC;font-size:.5em;padding:5px;background-position:center;background-repeat:no-repeat;background-size:contain;float:left;margin:10px;word-wrap:break-word}
	#status {background-color:black;color:white;padding:5px 20px;margin-bottom:10px}
	&amp;lt;/style&amp;gt;
    
	&amp;lt;!--[if lt IE 9]&amp;gt;
	&amp;lt;script src=http://html5shiv.googlecode.com/svn/trunk/html5.js&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;![endif]--&amp;gt;
	
	&amp;lt;script src=http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

&amp;lt;div id=status&amp;gt;Drag and Drop files to begin...&amp;lt;/div&amp;gt;
&amp;lt;div id=drop-area&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;

var up = {
	
	$drop :			null,
	queue :			[],
	processing :	null,
	uploading :		false,
	binaryReader :	null,
	dataReader :	null,
	xhr:			null,

	init : function() {
		up.$drop = $(&amp;#39;#drop-area&amp;#39;);
		
		up.$drop.bind(&amp;#39;dragenter&amp;#39;,up.enter);
		up.$drop.bind(&amp;#39;dragleave&amp;#39;,up.leave);
		up.$drop.bind(&amp;#39;dragover&amp;#39;,up.over);
		up.$drop.bind(&amp;#39;drop&amp;#39;,up.drop);
		
		$(&amp;#39;#status&amp;#39;).click(up.cancel);
		
		up.xhr = new XMLHttpRequest();
		up.xhr.upload.addEventListener(&amp;#39;progress&amp;#39;, up.uploadProgress , false);
		up.xhr.upload.addEventListener(&amp;#39;load&amp;#39;, up.uploadLoaded , false);
		
	},
	
	enter : function(e){
		$(e.target).addClass(&amp;#39;hover&amp;#39;);
		return false;
	},
	
	leave : function(e){
		$(e.target).removeClass(&amp;#39;hover&amp;#39;);
		return false;
	},
	
	over : function(e){
		return false;
	},
	
	drop : function(e){
		$(e.target).removeClass(&amp;#39;hover&amp;#39;);
		
		var files = e.originalEvent.dataTransfer.files;
		for (var i = 0; i&amp;lt;files.length; i++) {
			var file = files[i];
			up.queue.push(file);
		}

		if(up.uploading == false) {
			up.uploading = true;
			up.process();
		}
		
		return false;
	},
	
	process : function() {
		up.processing = up.queue.shift();
		
		up.$drop.append(&amp;#39;&amp;lt;div&amp;gt;&amp;#39;+up.processing.name+&amp;#39;&amp;lt;/div&amp;gt;&amp;#39;);
		
		if(window.FileReader) { //firefox 3.6, Chrome 6, Webkit

			if(up.processing.type.match(/image/gi) != null) { //is an image - read it
				up.dataReader = new FileReader();
				if(up.dataReader.addEventListener) { //firefox
					up.dataReader.addEventListener(&amp;#39;loadend&amp;#39;, up.binaryLoad, false);
					up.dataReader.addEventListener(&amp;#39;error&amp;#39;, up.loadError, false);
					up.dataReader.addEventListener(&amp;#39;progress&amp;#39;, up.loadProgress, false);
				} else { //chrome / webkit
					up.dataReader.onloadend = up.binaryLoad;
					up.dataReader.onerror = up.loadError;
					up.dataReader.onprogress = up.loadProgress;
				}
				up.dataReader.readAsDataURL(up.processing);
			}
			
			up.binaryReader = new FileReader();
			if(up.binaryReader.addEventListener) { //firefox
				up.binaryReader.addEventListener(&amp;#39;loadend&amp;#39;, up.binaryLoad, false);
				up.binaryReader.addEventListener(&amp;#39;error&amp;#39;, up.loadError, false);
				up.binaryReader.addEventListener(&amp;#39;progress&amp;#39;, up.loadProgress, false);
			} else { //chrome / webkit
				up.binaryReader.onloadend = up.binaryLoad;
				up.binaryReader.onerror = up.loadError;
				up.binaryReader.onprogress = up.loadProgress;
			}
			up.binaryReader.readAsBinaryString(up.processing);
			
			
		} else { // safari 5 + others?
		
			up.xhr.abort(); //make sure xhr is a new request
			up.xhr.open(&amp;#39;POST&amp;#39;, &amp;#39;/html5_upload.php?up=true&amp;#39;, true);
		
			up.xhr.setRequestHeader(&amp;#39;UP-FILENAME&amp;#39;, up.processing.name);
			up.xhr.setRequestHeader(&amp;#39;UP-SIZE&amp;#39;, up.processing.size);
			up.xhr.setRequestHeader(&amp;#39;UP-TYPE&amp;#39;, up.processing.type);
			
			up.xhr.send(up.processing); 
			up.xhr.onload = up.onload;
		}
		
	},
	
	loadError : function(e) {
		switch(e.target.error.code) {
			case e.target.error.NOT_FOUND_ERR:
				alert(&amp;#39;File Not Found!&amp;#39;);
			break;
			case e.target.error.NOT_READABLE_ERR:
				alert(&amp;#39;File is not readable&amp;#39;);
			break;
			case e.target.error.ABORT_ERR:
			break; 
			default:
				alert(&amp;#39;An error occurred reading this file.&amp;#39;);
		};

	},
	
	loadProgress : function(e) {
		if (e.lengthComputable) {
			var percentage = Math.round((e.loaded * 100) / e.total);
			$(&amp;#39;#status&amp;#39;).html(&amp;#39;loaded: &amp;#39;+percentage+&amp;#39;%&amp;#39;);
		}
	},
		
	binaryLoad : function(e) {
		
		var isimage = (up.processing.type.match(/image/gi)!=null);
		if( isimage &amp;amp;&amp;amp; up.dataReader.readyState == 2 &amp;amp;&amp;amp; up.$drop.find(&amp;#39;div:last&amp;#39;).css(&amp;#39;background-image&amp;#39;)==&amp;#39;none&amp;#39;) {
			up.$drop.find(&amp;#39;div:last&amp;#39;).css(&amp;#39;background-image&amp;#39;,&amp;#39;url(&amp;#39; + up.dataReader.result + &amp;#39;)&amp;#39;);	
		}
		
		if(isimage &amp;amp;&amp;amp; up.dataReader.readyState == 2 &amp;amp;&amp;amp; up.binaryReader.readyState == 2 || !isimage &amp;amp;&amp;amp; up.binaryReader.readyState == 2 ) {
			
			up.xhr.abort(); //make sure xhr is a new request
			
			var binary = e.target.result;
			
			if(up.xhr.sendAsBinary != null) { //firefox
			
				up.xhr.open(&amp;#39;POST&amp;#39;, &amp;#39;/html5_upload.php?up=true&amp;#39;, true);
	
				var boundary = &amp;#39;xxxxxxxxx&amp;#39;;
			    
				var body = &amp;#39;--&amp;#39; + boundary + &amp;quot;\r\n&amp;quot;;  
				body += &amp;quot;Content-Disposition: form-data; name=&amp;#39;upload&amp;#39;; filename=&amp;#39;&amp;quot; + up.processing.name + &amp;quot;&amp;#39;\r\n&amp;quot;;  
				body += &amp;quot;Content-Type: application/octet-stream\r\n\r\n&amp;quot;;  
				body += binary + &amp;quot;\r\n&amp;quot;;  
				body += &amp;#39;--&amp;#39; + boundary + &amp;#39;--&amp;#39;;  
			    
			    up.xhr.setRequestHeader(&amp;#39;content-type&amp;#39;, &amp;#39;multipart/form-data; boundary=&amp;#39; + boundary);
			    up.xhr.sendAsBinary(body);        	
			    
		    } else { //for browsers that don&amp;#39;t support sendAsBinary yet
			 
			 	up.xhr.open(&amp;#39;POST&amp;#39;, &amp;#39;/html5_upload.php?up=true&amp;amp;base64=true&amp;#39;, true);
			 
			 	up.xhr.setRequestHeader(&amp;#39;UP-FILENAME&amp;#39;, up.processing.name);
			 	up.xhr.setRequestHeader(&amp;#39;UP-SIZE&amp;#39;, up.processing.size);
			 	up.xhr.setRequestHeader(&amp;#39;UP-TYPE&amp;#39;, up.processing.type);
			 	
				up.xhr.send(window.btoa(binary)); 
		    }
			 
			up.xhr.onload = up.onload;
			
		}
	},
	
	uploadProgress : function(e) {
		if (e.lengthComputable) {
			var percentage = Math.round((e.loaded * 100) / e.total);
			$(&amp;#39;#status&amp;#39;).html(&amp;#39;uploaded: &amp;#39;+percentage+&amp;#39;%&amp;#39;);
		}
	},
	
	uploadLoaded : function(e) {
		$(&amp;#39;#status&amp;#39;).html(&amp;#39;Uploaded: 100%&amp;#39;);
	},
	
	onload : function (e) {
		if(up.queue.length &amp;gt; 0) {
			up.process();
		} else {
			up.uploading = false;
			$(&amp;#39;#status&amp;#39;).html(&amp;#39;Queue Uploaded&amp;#39;);
		}
	},
	
	cancel : function(e) {
		if(up.dataReader) {
			up.dataReader.abort();
		}
		if(up.dataReader) {
			up.binaryReader.abort();
		}
		if(up.xhr) {
			up.xhr.abort();
		}
		up.uploading = false;
		up.queue = [];
		up.processing = null;
		$(&amp;#39;#status&amp;#39;).html(&amp;#39;Drag and Drop files to begin...&amp;#39;);
		return false;
	}

}

$(up.init);

&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;p&gt;To keep it simple there is no size or filetype limit but could be added fairly easily. If the browser has FileReader support and the dropped file is an image it will read contents and display a preview.&lt;/p&gt;&lt;p&gt;If you have any questions please post them in the comments.&lt;/p&gt;&lt;h2&gt;References&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;http://www.robertnyman.com/html5/fileapi/fileapi.html&quot;&gt;www.robertnyman.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en/Using_files_from_web_applications&quot;&gt;developer.mozilla.org&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://www.appelsiini.net/2009/10/html5-drag-and-drop-multiple-file-upload&quot;&gt;Mika Tuupola&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://html5stars.com/?p=99&quot;&gt;HTML5STARS&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://www.html5rocks.com/tutorials/file/dndfiles/&quot;&gt;www.html5rocks.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://www.thecssninja.com/javascript/fileapi&quot;&gt;www.thecssninja.com&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
<pubDate>Tue, 13 Jul 2010 01:23:06 +0000</pubDate>
</item><item>
<title>Same name cookie with a different path</title>
<link>http://www.castlesblog.com/2010/march/21/javascript-same-name-cookie-different-path</link>
<description>&lt;p&gt;I've been having mixed results setting and retrieving cookies with the same name and different paths. A cookie set with a path of &quot;/&quot; is accessible anywhere within a site. I wanted my cookies to be different depending on the URL. My solution is to name cookies in the root of a website different to the rest. Pretty simple really, just took me far too long to work this out.&lt;/p&gt;&lt;pre&gt;var cookie_name = 'my_great_cookie';
if(document.location.pathname == '/') {
    cookie_name = 'root_' + cookie_name;
}
//set/retrive cookie here
&lt;/pre&gt;</description>
<pubDate>Sun, 21 Mar 2010 07:39:17 +0000</pubDate>
</item><item>
<title>Feed Icon</title>
<link>http://www.castlesblog.com/2010/march/9/feed-icon</link>
<description>&lt;p&gt;I've made a few tweaks to the castlesblog template. Images now have padding and margins and headings look a bit neater.&lt;/p&gt;&lt;p&gt;You might also notice I added a feed icon to the top right. If your browser supports it there is a CSS rotation on it to it to make it a bit interesting. If its not rotated this is what you are missing out on...&lt;/p&gt;&lt;img src=&quot;http://static.castlesblog.com/data/feed-screenshot.png&quot; alt=&quot;Feed Screenshot&quot; /&gt;&lt;p&gt;As far as I know the rotation works in Firefox, Safari, Chrome and Opera. The CSS to do it is fairly simple:&lt;/p&gt;&lt;pre&gt;-o-transform:rotate(-15deg); /*OPERA*/
-moz-transform:rotate(-15deg); /*MOZILLA*/
-webkit-transform:rotate(-15deg); /*WEBKIT*/&lt;/pre&gt;
&lt;h2&gt;Update 14th March 2010&lt;/h2&gt;&lt;p&gt;I've just discovered you can rotate elements in IE using pure CSS... firstly a much simpler way of rotating is using the &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms532918(VS.85).aspx&quot;&gt;rotation property&lt;/a&gt; filter but it only allows 90 degree increments. Thanks to this &lt;a href=&quot;http://www.boogdesign.com/examples/transforms/matrix-calculator.html&quot;&gt;matrix calculator&lt;/a&gt; by robertc it now works using the &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms533014%28VS.85%29.aspx&quot;&gt;Matrix filter&lt;/a&gt;. The catch is the origin of rotation is different in IE so I have setup a new conditional stylesheet for IE that sets a different margin and included all IE specific CSS there.&lt;/p&gt;</description>
<pubDate>Mon, 8 Mar 2010 13:39:14 +0000</pubDate>
</item></channel></rss>
