<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
  <title type="text">Mika Tuupola</title>
  <id>tag:www.appelsiini.net,2009:mephisto/</id>
  <generator uri="http://mephistoblog.com" version="0.8.0">Mephisto Drax</generator>
  
  <link href="http://www.appelsiini.net/" rel="alternate" type="text/html" />
  <updated>2009-11-02T13:49:03Z</updated>
  <subtitle type="html">Life and digital media in Baltics.</subtitle><link rel="self" href="http://feeds.feedburner.com/tuupola" type="application/atom+xml" /><feedburner:emailServiceId xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">tuupola</feedburner:emailServiceId><feedburner:feedburnerHostname xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://feedburner.google.com</feedburner:feedburnerHostname><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.bloglines.com/sub/http://feeds.feedburner.com/tuupola" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Ftuupola" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-10-12:6301</id>
    <published>2009-10-12T14:59:00Z</published>
    <updated>2009-11-02T13:49:03Z</updated>
    <category term="html5" />
    <category term="javascript" />
    <link href="http://www.appelsiini.net/2009/10/html5-drag-and-drop-multiple-file-upload" rel="alternate" type="text/html" />
    <title>HTML5 Drag and Drop Multiple File Upload</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/10/12/html5_logo.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Previously I experimented with &lt;a href="http://www.appelsiini.net/2009/10/drag-and-drop-file-upload-with-google-gears"&gt;drag and drop file upload with Google Gears&lt;/a&gt;. Recently FireFox 3.6 (codenamed Namoroka) &lt;del&gt;was the first to implement &lt;a href="http://dev.w3.org/2006/webapi/FileUpload/publish/FileAPI.html"&gt;File &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/del&gt;. It enables JavaScript to interact with local files.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Correction:&lt;/strong&gt; &lt;a href="http://blog.igstan.ro/"&gt;Ionut G. Stan&lt;/a&gt; pointed out that File &lt;span class="caps"&gt;API&lt;/span&gt; was actually available already in FireFox 3.0. What Namoroka introduced is the drag and drop interface for the files. Sorry for the confusion.&lt;/p&gt;


	&lt;p&gt;Here is how you can implement drag and drop multiple file upload with native JavaScript. No plugins needed. Just plain &lt;del&gt;old&lt;/del&gt; new &lt;span class="caps"&gt;HTML5&lt;/span&gt;. Again there is a &lt;a href="http://www.appelsiini.net/demo/html5_upload/demo.html"&gt;working demo&lt;/a&gt;. You will need FireFox 3.6 to test it. Full source code can be at &lt;a href="http://github.com/tuupola/demo_code/tree/master/html5_upload/"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/10/12/html5_logo.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Previously I experimented with &lt;a href="http://www.appelsiini.net/2009/10/drag-and-drop-file-upload-with-google-gears"&gt;drag and drop file upload with Google Gears&lt;/a&gt;. Recently FireFox 3.6 (codenamed Namoroka) &lt;del&gt;was the first to implement &lt;a href="http://dev.w3.org/2006/webapi/FileUpload/publish/FileAPI.html"&gt;File &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/del&gt;. It enables JavaScript to interact with local files.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Correction:&lt;/strong&gt; &lt;a href="http://blog.igstan.ro/"&gt;Ionut G. Stan&lt;/a&gt; pointed out that File &lt;span class="caps"&gt;API&lt;/span&gt; was actually available already in FireFox 3.0. What Namoroka introduced is the drag and drop interface for the files. Sorry for the confusion.&lt;/p&gt;


	&lt;p&gt;Here is how you can implement drag and drop multiple file upload with native JavaScript. No plugins needed. Just plain &lt;del&gt;old&lt;/del&gt; new &lt;span class="caps"&gt;HTML5&lt;/span&gt;. Again there is a &lt;a href="http://www.appelsiini.net/demo/html5_upload/demo.html"&gt;working demo&lt;/a&gt;. You will need FireFox 3.6 to test it. Full source code can be at &lt;a href="http://github.com/tuupola/demo_code/tree/master/html5_upload/"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; Upload Script&lt;/h3&gt;


	&lt;p&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; script for receiving the files is the same as last time. It accesses uploaded files using &lt;em&gt;$_FILES&lt;/em&gt; superglobal. For this demo we assume users upload only image files.&lt;/p&gt;


&lt;pre&gt;
&amp;lt;?php

$error_message[0] = "Unknown problem with upload.";
$error_message[1] = "Uploaded file too large (load_max_filesize).";
$error_message[2] = "Uploaded file too large (MAX_FILE_SIZE).";
$error_message[3] = "File was only partially uploaded.";
$error_message[4] = "Choose a file to upload.";

$upload_dir  = './tmp/';
$num_files = count($_FILES['user_file']['name']);

for ($i=0; $i &amp;lt; $num_files; $i++) {
    $upload_file = $upload_dir . basename($_FILES['user_file']['name'][$i]);

    if (!preg_match("/(gif|jpg|jpeg|png)$/",$_FILES['user_file']['name'][$i])) {
        print "I asked for an image...";
    } else {
        if (is_uploaded_file($_FILES['user_file']['tmp_name'][$i])) {
            if (move_uploaded_file($_FILES['user_file']['tmp_name'][$i], 
                $upload_file)) {
                /* Great success... */
            } else {
                print $error_message[$_FILES['user_file']['error'][$i]];
            }
        } else {
            print $error_message[$_FILES['user_file']['error'][$i]];
        }    
    }
}
?&amp;gt;
&lt;/pre&gt;

	&lt;h3&gt;Setup Drop Target&lt;/h3&gt;


	&lt;p&gt;Also JavaScript code is almost identical with the Google Gears version. Since only FireFox is supported we can leave Mozilla and IE initialization out. We only need to attach &lt;em&gt;drop&lt;/em&gt; event to div with id &lt;em&gt;#dropzone&lt;/em&gt;. Event will trigger function called &lt;em&gt;upload()&lt;/em&gt;.&lt;/p&gt;


&lt;pre&gt;
$(function() {

    /* Cannot use $.bind() since jQuery does not normalize native events. */
    $('#dropzone')
        .get(0)
        .addEventListener('drop', upload, false);

    function upload(event) { 
        /* Uploading will here. */
    }

}
&lt;/pre&gt;

	&lt;h3&gt;Upload Files With Native JavaScript&lt;/h3&gt;


	&lt;p&gt;JavaScript is used to build &lt;a href="http://www.ietf.org/rfc/rfc2388.txt"&gt;&lt;span class="caps"&gt;RFC2388&lt;/span&gt;&lt;/a&gt; string. This string is &lt;span class="caps"&gt;POST&lt;/span&gt;:ed using XMLHttpRequest to our &lt;span class="caps"&gt;PHP&lt;/span&gt; script. &lt;span class="caps"&gt;PHP&lt;/span&gt; will see the request as if it was &lt;span class="caps"&gt;POST&lt;/span&gt;:ed from normal &lt;em&gt;multipart/form-data&lt;/em&gt; form.&lt;/p&gt;


	&lt;p&gt;There are three differences to Gears version:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;&lt;span class="caps"&gt;RFC2388&lt;/span&gt; string is built using JavaScript string variable instead of Gears BlobBuilder &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/li&gt;
		&lt;li&gt;Data is &lt;span class="caps"&gt;POST&lt;/span&gt;:ed using native XMLHttpRequest instead of Gears HttpRequest &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/li&gt;
		&lt;li&gt;Information on dropped files are accessed using &lt;em&gt;event.dataTransfer&lt;/em&gt; variable instead of &lt;em&gt;desktop.getDragData()&lt;/em&gt; function call.&lt;/li&gt;
	&lt;/ul&gt;


&lt;pre&gt;
function upload(event) {

    var data = event.dataTransfer;

    var boundary = '------multipartformboundary' + (new Date).getTime();
    var dashdash = '--';
    var crlf     = '\r\n';

    /* Build RFC2388 string. */
    var builder = '';

    builder += dashdash;
    builder += boundary;
    builder += crlf;

    var xhr = new XMLHttpRequest();

    /* For each dropped file. */
    for (var i = 0; i &amp;lt; data.files.length; i++) {
        var file = data.files[i];

        /* Generate headers. */            
        builder += 'Content-Disposition: form-data; name="user_file[]"';
        if (file.fileName) {
          builder += '; filename="' + file.fileName + '"';
        }
        builder += crlf;

        builder += 'Content-Type: application/octet-stream';
        builder += crlf;
        builder += crlf; 

        /* Append binary data. */
        builder += file.getAsBinary();
        builder += crlf;

        /* Write boundary. */
        builder += dashdash;
        builder += boundary;
        builder += crlf;
    }

    /* Mark end of the request. */
    builder += dashdash;
    builder += boundary;
    builder += dashdash;
    builder += crlf;

    xhr.open("POST", "upload.php", true);
    xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' 
        + boundary);
    xhr.sendAsBinary(builder);        

    xhr.onload = function(event) { 
        /* If we got an error display it. */
        if (xhr.responseText) {
            alert(xhr.responseText);
        }
        $("#dropzone").load("list.php?random=" +  (new Date).getTime());
    };

    /* Prevent FireFox opening the dragged file. */
    event.stopPropagation();

}
&lt;/pre&gt;

	&lt;p&gt;And there you have it. Native drag and drop multiple file upload.&lt;/p&gt;


	&lt;h3&gt;More Reading&lt;/h3&gt;


	&lt;p&gt;Ryan from &lt;span class="caps"&gt;CSS&lt;/span&gt; Ninja has similar article but with &lt;a href="http://www.thecssninja.com/javascript/drag-and-drop-upload"&gt;progress meter and thumbnail image&lt;/a&gt;. &lt;span class="caps"&gt;PPK&lt;/span&gt; &lt;a href="http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html"&gt;got frustrated with &lt;span class="caps"&gt;HTML5&lt;/span&gt; drag and drop&lt;/a&gt;. Leslie Michael has nice article about &lt;a href="http://decafbad.com/blog/2009/07/15/html5-drag-and-drop"&gt;&lt;span class="caps"&gt;HTML5&lt;/span&gt; drag and drop&lt;/a&gt;. Last but not least Ionut G. Stan helped me understand &lt;a href="http://blog.igstan.ro/2009/01/pure-javascript-file-upload.html"&gt;how to build &lt;span class="caps"&gt;RFC2388&lt;/span&gt; strings&lt;/a&gt;.&lt;/p&gt;


	&lt;p&gt;Oh and almost forgot. If you need FireFox 3.6 check the &lt;a href="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/"&gt;nightlies folder&lt;/a&gt;.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=G-nb6QdNpUA:jY_w93Ctgks:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=G-nb6QdNpUA:jY_w93Ctgks:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/G-nb6QdNpUA" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-10-02:6050</id>
    <published>2009-10-02T09:27:00Z</published>
    <updated>2009-10-12T15:00:11Z</updated>
    <category term="gears" />
    <category term="javascript" />
    <link href="http://www.appelsiini.net/2009/10/drag-and-drop-file-upload-with-google-gears" rel="alternate" type="text/html" />
    <title>Drag and Drop File Upload With Google Gears</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/10/2/gearslogo.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://tools.google.com/gears/"&gt;Google Gears&lt;/a&gt; is an extension which adds new features to you browser. It lets your browser to interact with the desktop. You can store data locally to an SQLite based database. Browser will also have a worker pool for running JavaScript code on the background.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Update 20091007: I updated the tutorial and demo to support dragging and dropping multiple files for uploading simultaneously.&lt;/strong&gt;&lt;/p&gt;


	&lt;p&gt;Below is how you can do basic drag and drop file upload. Gears, &lt;span class="caps"&gt;PHP&lt;/span&gt; and jQuery are needed. If you want to try there is a &lt;a href="http://www.appelsiini.net/demo/gears_upload/demo.html"&gt;working demo&lt;/a&gt;. All source code for the demo can be found from &lt;a href="http://github.com/tuupola/demo_code/tree/master/gears_upload/"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/10/2/gearslogo.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://tools.google.com/gears/"&gt;Google Gears&lt;/a&gt; is an extension which adds new features to you browser. It lets your browser to interact with the desktop. You can store data locally to an SQLite based database. Browser will also have a worker pool for running JavaScript code on the background.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Update 20091007: I updated the tutorial and demo to support dragging and dropping multiple files for uploading simultaneously.&lt;/strong&gt;&lt;/p&gt;


	&lt;p&gt;Below is how you can do basic drag and drop file upload. Gears, &lt;span class="caps"&gt;PHP&lt;/span&gt; and jQuery are needed. If you want to try there is a &lt;a href="http://www.appelsiini.net/demo/gears_upload/demo.html"&gt;working demo&lt;/a&gt;. All source code for the demo can be found from &lt;a href="http://github.com/tuupola/demo_code/tree/master/gears_upload/"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; for Receiving the Files&lt;/h3&gt;


	&lt;p&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; has easy way of accessing uploaded files using &lt;em&gt;$_FILES&lt;/em&gt; array. For demo’s sake I am assuming we should upload only image files. Nothing special here. Just you plain old upload script.&lt;/p&gt;


&lt;pre&gt;
&amp;lt;?php

$error_message[0] = "Unknown problem with upload.";
$error_message[1] = "Uploaded file too large (load_max_filesize).";
$error_message[2] = "Uploaded file too large (MAX_FILE_SIZE).";
$error_message[3] = "File was only partially uploaded.";
$error_message[4] = "Choose a file to upload.";

$upload_dir  = './tmp/';
$num_files = count($_FILES['user_file']['name']);

for ($i=0; $i &amp;lt; $num_files; $i++) {
    $upload_file = $upload_dir . basename($_FILES['user_file']['name'][$i]);

    if (!preg_match("/(gif|jpg|jpeg|png)$/",$_FILES['user_file']['name'][$i])) {
        print "I asked for an image...";
    } else {
        if (is_uploaded_file($_FILES['user_file']['tmp_name'][$i])) {
            if (move_uploaded_file($_FILES['user_file']['tmp_name'][$i], 
                $upload_file)) {
                /* Success... */
            } else {
                print $error_message[$_FILES['user_file']['error']];
            }     
        } else {
            print $error_message[$_FILES['user_file']['error']];
        }    
    }    
}
?&amp;gt;
&lt;/pre&gt;

	&lt;h3&gt;Initialize Gears and Setup Drop Target&lt;/h3&gt;


	&lt;p&gt;Gears will be initialized after &lt;span class="caps"&gt;DOM&lt;/span&gt; is ready. We need desktop support for dragging and dropping. HTTPrequest support is used for uploading the files.&lt;/p&gt;


	&lt;p&gt;We will also setup event handlers for file dropping. Target is a div with id &lt;em&gt;#dropzone&lt;/em&gt;. Different browsers use different event names. Unfortunately jQuery does not normalize native dropping event name. Thus we need to initialize the event separately for each browser. We also need to use native &lt;span class="caps"&gt;DOM&lt;/span&gt; element fetched with .get(0). When drop happens &lt;em&gt;upload()&lt;/em&gt; function will be called.&lt;/p&gt;


	&lt;p&gt;When dropping a file default action for browser would be top open it. With Safari and IE this can be prevented by &lt;em&gt;false&lt;/em&gt; from dragover event. With FireFox you need to call &lt;em&gt;event.stopPropagation()&lt;/em&gt; later in the &lt;em&gt;upload()&lt;/em&gt; code.&lt;/p&gt;


&lt;pre&gt;
$(function() {

    /* Google Gears support. */
    var desktop = google.gears.factory.create('beta.desktop');
    var request = google.gears.factory.create('beta.httprequest');

    /* We cannot use $.bind() since jQuery does not normalize the */
    /* native events. */
    if ($.browser.mozilla) {
        $('#dropzone')
            .get(0)
            .addEventListener('dragdrop', upload, false);
    } else if ($.browser.msie) {
        $('#dropzone')
            .get(0)
            .attachEvent('ondrop', upload, false);
        $('#dropzone')
            .get(0)
            .attachEvent('ondragover', function(event) { 
                event.returnValue = false; 
            }, false);                
    } else if ($.browser.safari) {
        $('#dropzone')
            .get(0)
            .addEventListener('drop', upload, false);        
        $('#dropzone')
            .get(0)
            .addEventListener('dragover', function(event) { 
                event.returnValue = false; 
        }, false);
    }
};
&lt;/pre&gt;

	&lt;h3&gt;Upload Files With JavaScript&lt;/h3&gt;


	&lt;p&gt;There are two ways of uploading files with JavaScript. I will emulate normal &lt;span class="caps"&gt;HTML&lt;/span&gt; multipart/form-data type of form. &lt;span class="caps"&gt;PHP&lt;/span&gt; has native support for uploading files this way. JavaScript side will be slightly more complicated though.&lt;/p&gt;


	&lt;p&gt;Gear blobbuilder is used for building a string according to &lt;a href="http://www.ietf.org/rfc/rfc2388.txt"&gt;&lt;span class="caps"&gt;RFC2388&lt;/span&gt;&lt;/a&gt;. Most of the code in &lt;em&gt;upload()&lt;/em&gt; function is about building the multipart/form-data string. This string is then submitted to &lt;span class="caps"&gt;PHP&lt;/span&gt; script with Gears httprequest.&lt;/p&gt;


	&lt;p&gt;If &lt;em&gt;upload.php&lt;/em&gt; returns something we assume it is an error and we &lt;em&gt;alert()&lt;/em&gt; it. Otherwise we update the content of #dropzone with a thumbnailed list of images. This list is generated by &lt;em&gt;list.php&lt;/em&gt; script.&lt;/p&gt;


&lt;pre&gt;
function upload(event) {

    var data = desktop.getDragData(event, 'application/x-gears-files');

    var boundary = '------multipartformboundary' + (new Date).getTime();
    var dashdash = '--';
    var crlf     = '\r\n';

    /* Build RFC2388 string. */
    var builder = google.gears.factory.create('beta.blobbuilder');

    builder.append(dashdash);
    builder.append(boundary);
    builder.append(crlf);

    for (var i in data.files) {

        var file = data.files[i];

        /* Generate headers. */
        builder.append('Content-Disposition: form-data; name="user_file[]"');
        if (file.name) {
            builder.append('; filename="' + file.name + '"');
        }
        builder.append(crlf);

        builder.append('Content-Type: application/octet-stream');
        builder.append(crlf);
        builder.append(crlf); 

        /* Append binary data. */
        builder.append(file.blob);
        builder.append(crlf);

        /* Write boundary. */
        builder.append(dashdash);
        builder.append(boundary);
        builder.append(crlf); 
    }

    /* Mark end of the request. */
    builder.append(dashdash);
    builder.append(boundary);
    builder.append(dashdash);
    builder.append(crlf);        

    request.onreadystatechange = function() {
        switch(request.readyState) {
            case 4:
                /* If we got an error display it. */
                if (request.responseText) {
                    alert(request.responseText);
                }
                $("#dropzone").load("list.php?random=" +  (new Date).getTime());
                break;
        }
    };

    /* Use Gears to submit the data. */
    request.open("POST", "upload.php");
    request.setRequestHeader('content-type', 
        'multipart/form-data; boundary=' + boundary);
    request.send(builder.getAsBlob());

    /* Prevent FireFox opening the dragged file. */
    if ($.browser.mozilla) {
        event.stopPropagation();
    }

}
&lt;/pre&gt;

	&lt;h3&gt;Other Demo Code&lt;/h3&gt;


	&lt;p&gt;For demo’s sake there is a simple script which produces thumbnails of uploaded images. It uses ImageResize class taken from Peter Gassners &lt;a href="http://github.com/naehrstoff/image_resize"&gt;Image Resize&lt;/a&gt; Frog &lt;span class="caps"&gt;CMS&lt;/span&gt; plugin.&lt;/p&gt;


&lt;pre&gt;
&amp;lt;?php

require_once 'ImageResize.php';

foreach (glob("./tmp/*") as $source) {
    $file = pathinfo($source);
    if (!preg_match('#-100x100\.([a-z]+)$#i', $source)) {
        $destination = $file['dirname'] . '/' . $file['filename'] . 
                               '-100x100.' . $file['extension'];
        ImageResize::image_scale_cropped($source, $destination, 100, 100);
        printf('&amp;lt;a href="%s"&amp;gt;&amp;lt;img src="%s" widht="100" height="100" /&amp;gt;&amp;lt;/a&amp;gt;', 
                 $source, $destination);
    }
}
?&amp;gt;
&lt;/pre&gt;

	&lt;h3&gt;Known Issues&lt;/h3&gt;


	&lt;p&gt;Gears is currently &lt;a href="http://code.google.com/p/gears/source/detail?r=3393"&gt;broken with FireFox 3.5.x&lt;/a&gt;. Hopefully issue will be fixed with next release.  If you find any other issues or have feedback feel free to leave comment.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=hF5UM3hfDQ4:Gq8VJgbZ0oI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=hF5UM3hfDQ4:Gq8VJgbZ0oI:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/hF5UM3hfDQ4" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-09-21:6193</id>
    <published>2009-09-21T13:57:00Z</published>
    <updated>2009-09-21T14:15:27Z</updated>
    <category term="estonia" />
    <category term="work" />
    <link href="http://www.appelsiini.net/2009/9/estonian-internet-awards-2009" rel="alternate" type="text/html" />
    <title>Estonian Internet Awards 2009</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/9/21/bi09_winners.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;em&gt;All photos by Annika Haas, Fotobrigaad.&lt;/em&gt;&lt;/p&gt;


	&lt;p&gt;It &lt;a href="http://www.appelsiini.net/2009/4/state-of-kuldmuna-digital-and-what-to-do-about-it"&gt;was talked about&lt;/a&gt; in &lt;a href="http://www.velvet.ee/blog/2009/04/07/kuldmuna-2009/"&gt;several&lt;/a&gt; different &lt;a href="http://www.altex-marketing.com/estonian-emarketing-association-and-awards"&gt;places&lt;/a&gt;. Last thursday it became reality. First Estonian Internet Awards &lt;a href="http://est.best-marketing.com/?lang=est&amp;amp;#38;show=news&amp;amp;#38;id=824"&gt;winners&lt;/a&gt; were announced. These are my comments as a member of jury.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/9/21/bi09_winners.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;em&gt;All photos by Annika Haas, Fotobrigaad.&lt;/em&gt;&lt;/p&gt;


	&lt;p&gt;It &lt;a href="http://www.appelsiini.net/2009/4/state-of-kuldmuna-digital-and-what-to-do-about-it"&gt;was talked about&lt;/a&gt; in &lt;a href="http://www.velvet.ee/blog/2009/04/07/kuldmuna-2009/"&gt;several&lt;/a&gt; different &lt;a href="http://www.altex-marketing.com/estonian-emarketing-association-and-awards"&gt;places&lt;/a&gt;. Last thursday it became reality. First Estonian Internet Awards &lt;a href="http://est.best-marketing.com/?lang=est&amp;amp;#38;show=news&amp;amp;#38;id=824"&gt;winners&lt;/a&gt; were announced. These are my comments as a member of jury.&lt;/p&gt;
&lt;h3&gt;The Winners&lt;/h3&gt;


	&lt;p&gt;There was two world level entries. &lt;a href="http://www.edicy.com/"&gt;Edicy&lt;/a&gt; which was judged as an online service (not just a website). Fraktal is setting a benchmark for Estonian startups on how things should be done. &lt;a href="http://www.uniteddogs.com/stopkillingdogs/"&gt;Stop Killing Dogs&lt;/a&gt; which was great example of a campaign where social media usage has a point. At the same time it was clever self promotion of &lt;a href="http://uniteddogs.com/"&gt;Uniteddogs.com&lt;/a&gt;. Why a great self promotion? Because was not trying to promote too much.&lt;/p&gt;


	&lt;p&gt;Rest of shortlisted entries were good. Nothing earth shattering. Good basic work. I especially liked Velvet’s &lt;a href="http://www.rahvusmeened.ee/"&gt;Rahvusmeened.ee&lt;/a&gt; and &lt;a href="http://black.velvet.ee/ea_bannerid/ea_landingpage_dance/"&gt;Estonian Air Tantsupidu banner&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;What Was Missing?&lt;/h3&gt;


	&lt;p&gt;There are couple of good projects I think should have been submitted. &lt;a href="http://seeme.oskando.ee/"&gt;SeeMe&lt;/a&gt; by Oskando was missing. Maybe it was released before 2008? &lt;a href="http://en.unitedcats.com/welcome"&gt;Unitedcats.com&lt;/a&gt; and &lt;a href="http://en.uniteddogs.com/welcome"&gt;Uniteddogs.com&lt;/a&gt; as a service were missing too. &lt;a href="http://metrix.station.ee/"&gt;Metrix&lt;/a&gt; would have been a great non-profit candidate.&lt;/p&gt;


	&lt;h3&gt;More Catchy Name?&lt;/h3&gt;


	&lt;p&gt;Competition should be branded with a name. Now it is hard to descibe what you won. &lt;em&gt;“I won bronze in campaigns category of Estonian Internet Awards”&lt;/em&gt;. This is a bit awkward. It would be much easier to say &lt;em&gt;“I won bronze jaaniuss in campaigns category.”&lt;/em&gt;&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/9/21/bi09_people.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;h3&gt;About Categories&lt;/h3&gt;


	&lt;p&gt;I think next year the categories could be adjusted. Too many categories seemed to cause quite much confusion. Most of subcategories were merged together anyway. My suggestion for next years categories are:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;&lt;span class="caps"&gt;B2C&lt;/span&gt; services&lt;/li&gt;
		&lt;li&gt;&lt;span class="caps"&gt;B2B&lt;/span&gt; services&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Service could be a corporate website, online bank, online newspaper, online shop etc. I’m sure you get the point.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;&lt;span class="caps"&gt;B2C&lt;/span&gt; campaigns&lt;/li&gt;
		&lt;li&gt;&lt;span class="caps"&gt;B2B&lt;/span&gt; campaigns&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Campaign can be a mini- or microsite, banner, email newsletter or combination of all of them.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Best online ad&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Banner or a banner like advertisement. Can be also a banner submitted as part of project in campaigns category.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Non-profit&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Because not everyone has a large budget.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Best design&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;To keep those full Flash websites with sound and flying stuff from polluting other categories.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Best results&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Because results are important. Sometimes campaign can look awful but have spectacular results. This is the category for them.&lt;/p&gt;


	&lt;h3&gt;Thanks to All!&lt;/h3&gt;


	&lt;p&gt;Big thanks to J.Margus Klaar of &lt;a href="http://www.adcestonia.org.ee/eng/"&gt;&lt;span class="caps"&gt;ADC&lt;/span&gt; Estonia&lt;/a&gt; and Hando Sinisalu and Silja Oja of &lt;a href="http://www.best-marketing.com/"&gt;Best Marketing&lt;/a&gt; for organizing the event. Big up to Robin Gurney of &lt;a href="http://www.altex-marketing.com/"&gt;Altex Marketing&lt;/a&gt; being the chairman of the jury. Also hi-five to all other jury members. It was nice to finally meet you all in real life.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=sOcM5HeQeL8:MREXJ2XDT6Q:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=sOcM5HeQeL8:MREXJ2XDT6Q:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/sOcM5HeQeL8" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-09-04:5992</id>
    <published>2009-09-04T11:37:00Z</published>
    <updated>2009-09-04T11:37:40Z</updated>
    <category term="javascript" />
    <link href="http://www.appelsiini.net/2009/9/verify-estonian-isikukood-with-javascript" rel="alternate" type="text/html" />
    <title>Verify Estonian Isikukood With JavaScript</title>
<content type="html">
            &lt;p&gt;Yesterday I had to verify &lt;a href="http://en.wikipedia.org/wiki/National_identification_number#Estonia"&gt;Estonian national identification number&lt;/a&gt; with JavaScript. The &lt;a href="http://et.wikipedia.org/wiki/Isikukood"&gt;formula&lt;/a&gt; is pretty simple. You just have to read it through couple of times to understand. If you need other languages Dmitri Smirnov has  examples in &lt;a href="http://www.dmitri.me/blog/isikukood-valideerimine/"&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt;, Java and Ruby&lt;/a&gt;.&lt;/p&gt;



          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=0c0eUmxOEVE:LlWl9A-WXvc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=0c0eUmxOEVE:LlWl9A-WXvc:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/0c0eUmxOEVE" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-05-18:4179</id>
    <published>2009-05-18T09:17:00Z</published>
    <updated>2009-05-29T11:57:04Z</updated>
    <category term="frog" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2009/5/logging-api-for-frog-dashboard" rel="alternate" type="text/html" />
    <title>Logging API for Frog Dashboard</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/5/15/dashboard.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://www.appelsiini.net/projects/dashboard"&gt;Dashboard plugin&lt;/a&gt; for Frog &lt;span class="caps"&gt;CMS&lt;/span&gt; now provides simple &lt;span class="caps"&gt;API&lt;/span&gt; for other developers to log their events. Whenever you want to log something to dashboard just trigger  a log event. Include your message as parameter.&lt;/p&gt;


&lt;pre&gt;
Observer::notify('log_event', 'Something was done by :username.');
&lt;/pre&gt;

	&lt;p&gt;In your message you can include string :username to log the user name.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/5/15/dashboard.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://www.appelsiini.net/projects/dashboard"&gt;Dashboard plugin&lt;/a&gt; for Frog &lt;span class="caps"&gt;CMS&lt;/span&gt; now provides simple &lt;span class="caps"&gt;API&lt;/span&gt; for other developers to log their events. Whenever you want to log something to dashboard just trigger  a log event. Include your message as parameter.&lt;/p&gt;


&lt;pre&gt;
Observer::notify('log_event', 'Something was done by :username.');
&lt;/pre&gt;

	&lt;p&gt;In your message you can include string :username to log the user name.&lt;/p&gt;
&lt;h3&gt;Priority and Ident&lt;/h3&gt;


	&lt;p&gt;You can also provide priority and ident for the log message. Priority is used to inform about severity of the message. Ident is used to identify sender of the message. Plugin developers should use the ident of their plugin. For example &lt;a href="http://www.appelsiini.net/projects/funky_cache"&gt;Funky Cache&lt;/a&gt; plugin uses &lt;em&gt;funky_cache&lt;/em&gt; as ident.&lt;/p&gt;


&lt;pre&gt;
Observer::notify('log_event', 'Something else was done by :username.',
                               DASHBOARD_LOG_NOTICE, 'pluginname');
&lt;/pre&gt;

	&lt;p&gt;Priorities are defined in Dashboard code.&lt;/p&gt;


&lt;pre&gt;
define('DASHBOARD_LOG_EMERG',    0); /* system no longer available */
define('DASHBOARD_LOG_ALERT',    1); /* immediate action required */
define('DASHBOARD_LOG_CRIT',     2); /* critical condition */
define('DASHBOARD_LOG_ERR',      3); /* error condition */
define('DASHBOARD_LOG_WARNING',  4); /* warning messages */
define('DASHBOARD_LOG_NOTICE',   5); /* normal, but significant, condition */
define('DASHBOARD_LOG_INFO',     6); /* general informative messages */
define('DASHBOARD_LOG_DEBUG',    7); /* debugging information */
&lt;/pre&gt;

	&lt;p&gt;If your code uses constants and Frog is in debug mode and Dashboard plugin is not installed this will cause &lt;span class="caps"&gt;PHP&lt;/span&gt; to display &lt;em&gt;“Use of undefined constant”&lt;/em&gt; warnings.  If you want to be sure your user will never see these warnings you can use number  instead of constant:&lt;/p&gt;


&lt;pre&gt;
Observer::notify('log_event', 'Something else was done by :username.',
                               4, 'pluginname');
&lt;/pre&gt;

	&lt;p&gt;You could also define the constants yourself. But make sure they are not already defined (Dashboard installed). Defining constants twice will cause warnings.&lt;/p&gt;


	&lt;h3&gt;Download&lt;/h3&gt;


	&lt;p&gt;Download the &lt;a href="http://www.appelsiini.net/download/frog_assets.tar.gz"&gt;latest tarball&lt;/a&gt; or checkout from &lt;a href="http://github.com/tuupola/frog_dashboard"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=fmprAH-9tYI:xnGeklQioK0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=fmprAH-9tYI:xnGeklQioK0:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/fmprAH-9tYI" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-04-20:3138</id>
    <published>2009-04-20T13:21:00Z</published>
    <updated>2009-04-20T13:22:18Z</updated>
    <link href="http://www.appelsiini.net/2009/4/bloggers-meeting-at-altex" rel="alternate" type="text/html" />
    <title>Bloggers Meeting at Altex</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/4/17/bloggers.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Altex together with &lt;a href="http://konverentsid-ee.z132.zone.ee/?nodeid=3&amp;amp;#38;lang=et&amp;amp;#38;item=14502" title="Marketing Conference"&gt;Pärnu Turunduskonverets&lt;/a&gt; organized a small bloggers meeting. Aim was to discuss same subjects which will be present in the real conference.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;How to achieve big things in Internet with small amount of money?&lt;/li&gt;
		&lt;li&gt;How to convince your boss to invest in online marketing?&lt;/li&gt;
		&lt;li&gt;How to achieve competitive edge during recession?&lt;/li&gt;
		&lt;li&gt;Who will survive recession better – online or offline stores?&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;&lt;em&gt;&lt;span class="caps"&gt;DISCLOSURE&lt;/span&gt;: People who attended the bloggers were kindly asked (but not required) to mention and link to the conference page in a blog entry. Least we can do as a thanks to nice food and beverages.&lt;/em&gt;&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/4/17/bloggers.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Altex together with &lt;a href="http://konverentsid-ee.z132.zone.ee/?nodeid=3&amp;amp;#38;lang=et&amp;amp;#38;item=14502" title="Marketing Conference"&gt;Pärnu Turunduskonverets&lt;/a&gt; organized a small bloggers meeting. Aim was to discuss same subjects which will be present in the real conference.&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;How to achieve big things in Internet with small amount of money?&lt;/li&gt;
		&lt;li&gt;How to convince your boss to invest in online marketing?&lt;/li&gt;
		&lt;li&gt;How to achieve competitive edge during recession?&lt;/li&gt;
		&lt;li&gt;Who will survive recession better – online or offline stores?&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;&lt;em&gt;&lt;span class="caps"&gt;DISCLOSURE&lt;/span&gt;: People who attended the bloggers were kindly asked (but not required) to mention and link to the conference page in a blog entry. Least we can do as a thanks to nice food and beverages.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Money Money Money&lt;/h3&gt;


	&lt;p&gt;Us having short attention span discussion varied a bit. With money talk we figured out everything costs something. Blogging might seem like its free but when you put pricetag to your time it is not so cheap anymore. There was also some discussion of age old problem people thinking that because Internet equals computers it must equal cheap to do.&lt;/p&gt;


	&lt;p&gt;One issue which raised was how Estonian generally avoid using Paypal. It is for some reason found to be cumbersome to use (which I and others did not agree). There might be trust issues. Since Paypal is not any of the local banks (which are owned by Swedes anyway) it is not trusted.&lt;/p&gt;


	&lt;p&gt;In off topic discussion some had noted that there are problems sending properly Google tagged banners links to media. Long links usually get broken because advertising section cut and pastes only half of the &lt;span class="caps"&gt;URL&lt;/span&gt;.&lt;/p&gt;


	&lt;h3&gt;Conference&lt;/h3&gt;


	&lt;p&gt;But this blog post was supposed to be about the conference. On friday 15th of May the interesting parts are.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;SMALL BUDGET&lt;/span&gt;, BIG &lt;span class="caps"&gt;BENEFIT&lt;/span&gt; – &lt;span class="caps"&gt;EXPERIENCE FROM SOCIAL NETWORKS&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;
Hille Hinsberg, Estonian goverment &amp; Epp-Kristiina Keerov, Altex&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;WHAT DO PEOPLE DO IN THE INTERNET&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;
Anni Ronkainen, Google&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;HOW TO SELL E&lt;/span&gt;-MARKETING &lt;span class="caps"&gt;TO YOUR BOSS&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;
Craig Hanna, e-consultancy.com&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;WILL RECESSION KILL OFFLINE OR ONLINE BUSINESS&lt;/span&gt;?&lt;/strong&gt;&lt;br /&gt; 
Aivar Paalberg, Enter IT&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=zXVLeXGLUwg:-R-IrYvbRE8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=zXVLeXGLUwg:-R-IrYvbRE8:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/zXVLeXGLUwg" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-04-09:2717</id>
    <published>2009-04-09T15:08:00Z</published>
    <updated>2009-08-18T12:49:27Z</updated>
    <category term="estonia" />
    <category term="work" />
    <link href="http://www.appelsiini.net/2009/4/state-of-kuldmuna-digital-and-what-to-do-about-it" rel="alternate" type="text/html" />
    <title>State of Kuldmuna Digital and What To Do About It?</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/4/9/kuldmunad.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Last weekend Kuldmuna awards were given out. Someone is going to be offended by saying this. Level of most of entries shortlisted in &lt;a href="http://eral.vertical.ee/index.php?page=finalists&amp;amp;#38;id=29"&gt;Internet design&lt;/a&gt; and &lt;a href="http://eral.vertical.ee/index.php?page=finalists&amp;amp;#38;id=24"&gt;Digital advertising&lt;/a&gt; categories were shamefully low. I know there have been better projects in Estonia during last year. How did this happen?&lt;/p&gt;


	&lt;p&gt;&lt;em&gt;&lt;span class="caps"&gt;DISCLAIMER&lt;/span&gt;: I work in advertising agency myself. I have been working in advertising agency or online department of advertising agency for last 11 years. Good to remember when you work in advertising and feel offended by reading this.&lt;/em&gt;&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/4/9/kuldmunad.jpg" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Last weekend Kuldmuna awards were given out. Someone is going to be offended by saying this. Level of most of entries shortlisted in &lt;a href="http://eral.vertical.ee/index.php?page=finalists&amp;amp;#38;id=29"&gt;Internet design&lt;/a&gt; and &lt;a href="http://eral.vertical.ee/index.php?page=finalists&amp;amp;#38;id=24"&gt;Digital advertising&lt;/a&gt; categories were shamefully low. I know there have been better projects in Estonia during last year. How did this happen?&lt;/p&gt;


	&lt;p&gt;&lt;em&gt;&lt;span class="caps"&gt;DISCLAIMER&lt;/span&gt;: I work in advertising agency myself. I have been working in advertising agency or online department of advertising agency for last 11 years. Good to remember when you work in advertising and feel offended by reading this.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Kuldmuna Is an Advertising Competition&lt;/h3&gt;


	&lt;p&gt;This is the main reason. Kuldmuna is about advertising by advertising people. You can see it from the beginning. When submitting your project only names of project manager, copywriter, AD and creative director are asked. Same problem continues in the website and in the Kuldmuna book.&lt;/p&gt;


	&lt;p&gt;It might be surprising to some but in web projects there is much more. Frontend coder, backend coder and Flash designer are first to come into mind. With larger projects information architect, UI designer and UX designer might be involved. If nothing else please just add one field called &lt;em&gt;Technical designer&lt;/em&gt; for these people.&lt;/p&gt;


	&lt;h3&gt;Judging With Advertising Mindset&lt;/h3&gt;


	&lt;p&gt;Entries get judged with advertising mindset. In worst case just by showing a screenshot or presentation of a site. Judges don’t always have time or possibility to click through the site. We are not even talking about checking code quality here. It ends up being a beauty competition.&lt;/p&gt;


	&lt;p&gt;Easiest way to win is to make full Flash site with flying stuff and sounds. Even better if you invent some new “innovative” navigation. This is sometimes done in purpose. Agencies do separate projects which are good in Internet sense. Other projects which are just done for winning awards.&lt;/p&gt;


	&lt;p&gt;Smart agencies have learned what to submit to advertising competition. Good online projects do not get submitted to an advertising competition. They won’t win anything anyway.&lt;/p&gt;


	&lt;h3&gt;What To Do About It?&lt;/h3&gt;


	&lt;p&gt;We had exactly the same problem in Finland. Solution was &lt;a href="http://www.rekaksois.com/grandone/"&gt;Grand One&lt;/a&gt;. We could do exactly the same here in Estonia – Our own online awards organized and judged by online people.&lt;/p&gt;


	&lt;p&gt;I am willing to invest my own time and money for this. I just need some native Estonian speaker to join me (I do speak Estonian, just suck at writing it).&lt;/p&gt;


	&lt;h3&gt;Starting Steps&lt;/h3&gt;


	&lt;p&gt;I already figured out name for the awards. I have discussed the name with Estonian colleagues and everyone approved the idea. Name of the competition would be &lt;strong&gt;“Au tööle!”&lt;/strong&gt;.&lt;/p&gt;


	&lt;p&gt;To start things quickly &lt;a href="http://twitter.com/autoole"&gt;Au tööle! Twitter channel&lt;/a&gt; was set up. Purpose of this channel is to have place which lists what online projects agencies are doing. Yes, there is &lt;a href="http://pixel.ee/"&gt;pixel.ee&lt;/a&gt; but only some projects are submitted there. To add your project send a private tweet or email &lt;a href="mailto:lisa@autoole.ee"&gt;lisa@autoole.ee&lt;/a&gt;. Send the following info:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Name of the project&lt;/li&gt;
		&lt;li&gt;&lt;span class="caps"&gt;URL&lt;/span&gt; to the project&lt;/li&gt;
		&lt;li&gt;Name of the company(ies) who created it&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;This solves another problem we have. Lots of projects get done without other agencies knowing about them. It is nice to know what other are doing. Please send recent projects. Do not spam with everything you have done during last year.&lt;/p&gt;


	&lt;p&gt;If Twitter channel becomes we could start giving out smaller awards. Once a month might be too often. Every other month is a good start. Winner would be chosen by three judges. New judges are chosen each time. Judges will come from different agencies and there should be at least one designer and one coder.&lt;/p&gt;


	&lt;p&gt;Beginning of next year would be the first gala. Up to here everything has been free. Companies submitting projects to main event would have to pay a fee. There should be one category for non-profits or bloggers where entrance fee is free. Categories would be:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Best campaign&lt;/li&gt;
		&lt;li&gt;Best online service&lt;/li&gt;
		&lt;li&gt;Best design&lt;/li&gt;
		&lt;li&gt;Best online ad (banners etc)&lt;/li&gt;
		&lt;li&gt;Grand Prix&lt;/li&gt;
	&lt;/ul&gt;


	&lt;h3&gt;How Do You Feel About It?&lt;/h3&gt;


	&lt;p&gt;What do you think? You think I am crazy? It will never work? You would like to help? One thing is for sure – something needs to be done. If you have any ideas or suggestion leave a comment (in english or estonian).&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=e7_ZQeddRUo:9p7CsIDTn5w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=e7_ZQeddRUo:9p7CsIDTn5w:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/e7_ZQeddRUo" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-02-13:2481</id>
    <published>2009-02-13T10:21:00Z</published>
    <updated>2009-08-27T16:16:03Z</updated>
    <category term="jquery" />
    <link href="http://www.appelsiini.net/2009/2/lazy-load-inside-container" rel="alternate" type="text/html" />
    <title>Lazy Load Inside Container</title>
<content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/13/gazillion_photos.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://silentmatt.com/"&gt;Matthew Crumley&lt;/a&gt; contributed nice patch to &lt;a href="http://www.appelsiini.net/projects/lazyload"&gt;Lazy Load&lt;/a&gt; plugin which makes it work with containers. If you have a container which has a scrollbar.&lt;/p&gt;


&lt;pre&gt;
#container {
    height: 600px;
    overflow: scroll;
}
&lt;/pre&gt;

	&lt;p&gt;Images which are not visible are not loaded until you scroll to them. Check demo page for &lt;a href="http://www.appelsiini.net/projects/lazyload/enabled_container.html"&gt;horizontal&lt;/a&gt; and &lt;a href="http://www.appelsiini.net/projects/lazyload/enabled_wide_container.html"&gt;vertical&lt;/a&gt; scrolling.&lt;/p&gt;


	&lt;p&gt;To use new feature you can give the container as jQuery object.&lt;/p&gt;


&lt;pre&gt;
$("img").lazyload({         
     placeholder : "img/grey.gif",
     container: $("#container")
 });
&lt;/pre&gt;

	&lt;p&gt;Mathew also patched a bug where IE was not always loading images. To upgrade download the latest &lt;a href="http://www.appelsiini.net/download/jquery.lazyload.js"&gt;source&lt;/a&gt;, &lt;a href="http://www.appelsiini.net/download/jquery.lazyload.mini.js"&gt;minified&lt;/a&gt; or &lt;a href="http://www.appelsiini.net/download/jquery.lazyload.pack.js"&gt;packed&lt;/a&gt;.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=YC1GxeIgOMw:xp2xkfFJHho:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=YC1GxeIgOMw:xp2xkfFJHho:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/YC1GxeIgOMw" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2009-02-09:2435</id>
    <published>2009-02-09T21:56:00Z</published>
    <updated>2009-04-13T13:03:19Z</updated>
    <category term="jquery" />
    <link href="http://www.appelsiini.net/2009/2/search-jquery-api-docs-from-spotlight" rel="alternate" type="text/html" />
    <title>Search jQuery API Docs from Spotlight</title>
<summary type="html">&lt;p&gt;Yesterday &lt;a href="http://www.priithaamer.com/"&gt;Priit Haamer&lt;/a&gt; of Fraktal notified me about &lt;a href="http://priithaamer.com/blog/ruby-on-rails-dictionary-for-macosx"&gt;Spotlight searchable Ruby on Rails documentation&lt;/a&gt; he had made. Absolutely brilliant idea. When I saw it I knew I have to do same thing for jQuery.&lt;/p&gt;


	&lt;h3&gt;Why Is It cool?&lt;/h3&gt;


	&lt;p&gt;You can just hit &lt;b&gt;Apple + Space&lt;/b&gt; to enter spotlight and search for jQuery function.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery_spotlight.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Press and hold &lt;b&gt;Apple + Control + D&lt;/b&gt; over any function in TextMate (or Safari, iChat, Mail etc.) to get info popup.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;Yesterday &lt;a href="http://www.priithaamer.com/"&gt;Priit Haamer&lt;/a&gt; of Fraktal notified me about &lt;a href="http://priithaamer.com/blog/ruby-on-rails-dictionary-for-macosx"&gt;Spotlight searchable Ruby on Rails documentation&lt;/a&gt; he had made. Absolutely brilliant idea. When I saw it I knew I have to do same thing for jQuery.&lt;/p&gt;


	&lt;h3&gt;Why Is It cool?&lt;/h3&gt;


	&lt;p&gt;You can just hit &lt;b&gt;Apple + Space&lt;/b&gt; to enter spotlight and search for jQuery function.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery_spotlight.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Press and hold &lt;b&gt;Apple + Control + D&lt;/b&gt; over any function in TextMate (or Safari, iChat, Mail etc.) to get info popup.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery-ctrl-apple-d.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Docs are in your harddrive so they are always accessible. Even when you or docs.jquery.com are offline.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery_entry.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;It is native, based on &lt;span class="caps"&gt;OS X&lt;/span&gt; dictionary. No need to install &lt;span class="caps"&gt;AIR&lt;/span&gt; or any other applications.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery_search.png" alt="" /&gt;&lt;/p&gt;


	&lt;h3&gt;Install From Zip&lt;/h3&gt;


	&lt;p&gt;Download &lt;a href="http://www.appelsiini.net/download/jQuery.dictionary.zip" title="184KB"&gt;jQuery.dictionary.zip&lt;/a&gt; file. Unzip it and move to folder &lt;strong&gt;~/Library/Dictionaries&lt;/strong&gt; or  &lt;strong&gt;/Library/Dictionaries&lt;/strong&gt;. Enable it from Dictionary preferences.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2009/2/9/jquery_enable.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Sometimes it takes a while for Spotlight to see new dictionary. You can speed things up by restarting it.&lt;/p&gt;


&lt;pre&gt;
&amp;gt;killall Spotlight
&lt;/pre&gt;

	&lt;p&gt;According to Priit you should also drag new dictionary to be first on the list. This was not true in my case. However if you have problems it is worth trying.&lt;/p&gt;


	&lt;h3&gt;Install From Git&lt;/h3&gt;


	&lt;p&gt;If you like to live in bleeding edge install from Git and build your own.&lt;/p&gt;


&lt;pre&gt;
&amp;gt;git clone git://github.com/tuupola/jquery_dictionary.git
&amp;gt;cd jquery_dictionary
&amp;gt;make
&amp;gt;make install
&lt;/pre&gt;

	&lt;h3&gt;Acknowledgements&lt;/h3&gt;


	&lt;p&gt;Thanks to Priit Haamer for the &lt;a href="http://priithaamer.com/blog/ruby-on-rails-dictionary-for-macosx"&gt;original idea&lt;/a&gt;. David Serduke for the the &lt;a href="http://jqueryjs.googlecode.com/svn/trunk/tools/wikiapi2xml/createjQueryXMLDocs.py"&gt;Python script&lt;/a&gt; used  exporting  jQuery wiki to &lt;span class="caps"&gt;XML&lt;/span&gt; format. Jörn Zaefferer whose &lt;a href="http://jqueryjs.googlecode.com/svn/trunk/tools/api-browser/style.xsl"&gt;&lt;span class="caps"&gt;XSLT&lt;/span&gt; stylesheets&lt;/a&gt; were used as basis for &lt;span class="caps"&gt;XSLT&lt;/span&gt; stylesheet which converts exported jQuery docs to &lt;span class="caps"&gt;OS X&lt;/span&gt; dictionary &lt;span class="caps"&gt;XML&lt;/span&gt; source&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=1mPU7zKAnPk:qE6HruCorZc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=1mPU7zKAnPk:qE6HruCorZc:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/1mPU7zKAnPk" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-12-23:2128</id>
    <published>2008-12-23T15:50:00Z</published>
    <updated>2008-12-23T15:51:35Z</updated>
    <category term="frog" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/12/mephisto-style-asset-management-for-frog-cms" rel="alternate" type="text/html" />
    <title>Mephisto Style Asset Management for Frog CMS</title>
<content type="html">
            &lt;p&gt;One of the things I like about &lt;a href="http://mephistoblog.com/"&gt;Mephisto&lt;/a&gt; is the asset management. Especially the way how you insert image URLs by dragging thumbnail from sidebar to content area. &lt;a href="http://www.madebyfrog.com/"&gt;Frog &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt; lacked easy way for inserting images (or any other files). Obvious thing to do was implement Mephisto style asset management as a plugin.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/12/22/assets_page.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;It is not one to one copy on how Mephisto does it. Instead of tagging assets you can categorize them by uploading to different folders. There is also extra pulldown to select which assets to show in sidebar while editing.&lt;/p&gt;


	&lt;p&gt;Plugin depends on &lt;a href="http://github.com/naehrstoff/image_resize"&gt;Image Resize&lt;/a&gt; and &lt;a href="http://github.com/tuupola/frog_jquery"&gt;jQuery&lt;/a&gt; plugins to work.  Full installation instructions on &lt;a href="http://www.appelsiini.net/projects/frog_assets"&gt;project page&lt;/a&gt;.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=CuK5kiGIj2c:8gfVnB7qREw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=CuK5kiGIj2c:8gfVnB7qREw:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/CuK5kiGIj2c" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-11-28:2080</id>
    <published>2008-11-28T15:04:00Z</published>
    <updated>2009-01-20T21:29:28Z</updated>
    <category term="frog" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/11/send-emails-from-frog-cms" rel="alternate" type="text/html" />
    <title>Send Emails from Frog CMS</title>
<content type="html">
            &lt;p&gt;I needed a mailer backend which can handle complicated forms with any number of arbitary form fields. I also needed to be able to fully control the layout of sent emails. Something similar as oldie but goldie &lt;a href="http://web.mit.edu/wwwdev/cgiemail/"&gt;cgiemail&lt;/a&gt;.&lt;/p&gt;


	&lt;p&gt;Here comes &lt;a href="http://www.appelsiini.net/projects/email_template"&gt;Email Template&lt;/a&gt; plugin for &lt;a href="http://www.madebyfrog.com/"&gt;Frog &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;


	&lt;p&gt;It provides new page type called named &lt;em&gt;Email template&lt;/em&gt;. You can &lt;span class="caps"&gt;POST&lt;/span&gt; your forms to this page type. Page contains the layout of the mail including the headers. Template then parses &lt;span class="caps"&gt;POST&lt;/span&gt;:ed data and sends the email.&lt;/p&gt;


&lt;pre&gt;
To: Somebody &amp;lt;somebody@example.com&amp;gt;
From: &amp;lt;?php print $_POST['name'] ?&amp;gt; &amp;lt;&amp;lt;?php print $_POST['email'] ?&amp;gt;&amp;gt;
Subject: Frog Mail

1. Contact info

Name.............: &amp;lt;?php print $_POST['name'] ?&amp;gt; 
Company..........: &amp;lt;?php print $_POST['company'] ?&amp;gt; 
Email............: &amp;lt;?php print $_POST['email'] ?&amp;gt; 

2. Message

&amp;lt;?php print $_POST['message'] ?&amp;gt;

--
Sent by &amp;lt;?php print $_SERVER['REMOTE_ADDR'] ?&amp;gt;
&lt;/pre&gt;

	&lt;p&gt;Plugin assumes your &lt;span class="caps"&gt;PHP&lt;/span&gt; mail() function works properly. Currently it only supports plain text emails. &lt;a href="http://www.appelsiini.net/projects/email_template"&gt;Download and installation instructions&lt;/a&gt; at the project page. All feedback welcome.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=kgVpnjtkotA:AFXd8Mswkec:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=kgVpnjtkotA:AFXd8Mswkec:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/kgVpnjtkotA" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-11-21:2069</id>
    <published>2008-11-21T15:51:00Z</published>
    <updated>2009-09-21T15:00:32Z</updated>
    <category term="frog" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/11/ultrafast-frog-with-funky-cache" rel="alternate" type="text/html" />
    <title>Ultrafast Frog With Funky Cache</title>
<summary type="html">&lt;p&gt;&lt;img src="http://chart.apis.google.com/chart?chs=540x120&amp;amp;#38;chf=bg,s,ffffff|c,s,ffffff&amp;amp;#38;chxt=x,y&amp;amp;#38;chxl=1:|FunkyCache|FileCache|NoCache|0:|0.00|879.00|1,758.00&amp;amp;#38;cht=bhg&amp;amp;#38;chd=t:5.34,8.47,100.00&amp;amp;#38;chco=4d89f9&amp;amp;#38;chbh=25" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Funky caching is technique popularized by &lt;span class="caps"&gt;PHP&lt;/span&gt;.net site. It was first mentioned by Rasmus Lerdorf in &lt;a href="http://www.lerdorf.com/tips.pdf"&gt;2002 PHPCon slides&lt;/a&gt; (page 25). Content is cached as static file on the first access. All following requests are served using the cached static file. Editing a page will automatically expire cached files. Page is then re-cached on the next hit.&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://www.madebyfrog.com/"&gt;Frog &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt; is &lt;span class="caps"&gt;PHP&lt;/span&gt; port of Rails based &lt;a href="http://radiantcms.org/"&gt;Radiant &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt;. Radiant is great but there is always the hosting problem. Even with mod_rails existing it is still easier to get quality &lt;span class="caps"&gt;PHP&lt;/span&gt; hosting. Both Frog and Radiant are the only &lt;span class="caps"&gt;CMS&lt;/span&gt;’es I can say I like. Expression Engine I can live with. &lt;a href="http://www.edicy.com/"&gt;Edicy&lt;/a&gt; looks really promising. Everything else I rather not touch.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://chart.apis.google.com/chart?chs=540x120&amp;amp;#38;chf=bg,s,ffffff|c,s,ffffff&amp;amp;#38;chxt=x,y&amp;amp;#38;chxl=1:|FunkyCache|FileCache|NoCache|0:|0.00|879.00|1,758.00&amp;amp;#38;cht=bhg&amp;amp;#38;chd=t:5.34,8.47,100.00&amp;amp;#38;chco=4d89f9&amp;amp;#38;chbh=25" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Funky caching is technique popularized by &lt;span class="caps"&gt;PHP&lt;/span&gt;.net site. It was first mentioned by Rasmus Lerdorf in &lt;a href="http://www.lerdorf.com/tips.pdf"&gt;2002 PHPCon slides&lt;/a&gt; (page 25). Content is cached as static file on the first access. All following requests are served using the cached static file. Editing a page will automatically expire cached files. Page is then re-cached on the next hit.&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://www.madebyfrog.com/"&gt;Frog &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt; is &lt;span class="caps"&gt;PHP&lt;/span&gt; port of Rails based &lt;a href="http://radiantcms.org/"&gt;Radiant &lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/a&gt;. Radiant is great but there is always the hosting problem. Even with mod_rails existing it is still easier to get quality &lt;span class="caps"&gt;PHP&lt;/span&gt; hosting. Both Frog and Radiant are the only &lt;span class="caps"&gt;CMS&lt;/span&gt;’es I can say I like. Expression Engine I can live with. &lt;a href="http://www.edicy.com/"&gt;Edicy&lt;/a&gt; looks really promising. Everything else I rather not touch.&lt;/p&gt;
&lt;h3&gt;How About the Speed?&lt;/h3&gt;


	&lt;p&gt;Frog is reasonably fast. Simple ApacheBench running on localhost (to rule out network latency) shows it can deliver around 90 requests per second.&lt;/p&gt;


&lt;pre&gt;
&amp;gt; ab -n 1000 -c 20 http://dev.example.com/about_us.html

Time taken for tests:   10.600109 seconds
Requests per second:    94.34 [#/sec] (mean)
Time per request:       212.002 [ms] (mean)
Time per request:       10.600 [ms] (mean, across all concurrent requests)
Transfer rate:          255.66 [Kbytes/sec] received
&lt;/pre&gt;

	&lt;p&gt;I have seen slower. This would not stand Digg effect though.&lt;/p&gt;


	&lt;h3&gt;File Cache Plugin&lt;/h3&gt;


	&lt;p&gt;Using Gilles Doge’s &lt;a href="http://www.antistatique.net/blog/index.php/post/cache-plugin-frogcms"&gt;File Cache plugin&lt;/a&gt; gives improvement. Test server gives 150 requests per second. Problem is requests are still routed to backend. &lt;span class="caps"&gt;PHP&lt;/span&gt; logic decides whether to show cached contents or not.&lt;/p&gt;


&lt;pre&gt;
&amp;gt; ab -n 1000 -c 20 http://dev.example.com/about_us.html

Time taken for tests:   6.707378 seconds
Requests per second:    149.09 [#/sec] (mean)
Time per request:       134.148 [ms] (mean)
Time per request:       6.707 [ms] (mean, across all concurrent requests)
Transfer rate:          617.08 [Kbytes/sec] received
&lt;/pre&gt;

	&lt;p&gt;This still would not stand Digg effect.&lt;/p&gt;


	&lt;h3&gt;Enter Funky Cache Plugin&lt;/h3&gt;


	&lt;p&gt;Something needed to be done. Since I have had good experiences with &lt;a href="http://mephistoblog.com/"&gt;Mephisto&lt;/a&gt; which uses funky caching I decided to give it a try. This is where Frog first showed its power. Software was totally new to me. Still their plugin &lt;span class="caps"&gt;API&lt;/span&gt; was so clear to understand I was able to create first working version in one evening. Second evening was spent polishing the code.&lt;/p&gt;


	&lt;p&gt;So does it work? With Funky Caching enabled test server delivered 1750 requests per second.&lt;/p&gt;


&lt;pre&gt;
&amp;gt; ab -n 1000 -c 20 http://dev.example.com/about_us.html

Time taken for tests:   0.568727 seconds
Requests per second:    1758.31 [#/sec] (mean)
Time per request:       11.375 [ms] (mean)
Time per request:       0.569 [ms] (mean, across all concurrent requests)
Transfer rate:          7284.69 [Kbytes/sec] received
&lt;/pre&gt;

	&lt;p&gt;That should be enough to withstand both Digg effect and Slashdotting at the same time.&lt;/p&gt;


	&lt;h3&gt;Yes Gimme Some!&lt;/h3&gt;


	&lt;p&gt;&lt;a href="http://www.appelsiini.net/projects/funky_cache"&gt;Installation instructions&lt;/a&gt; at project page. If you want to peek under the hood check the source at &lt;a href="http://github.com/tuupola/frog_funky_cache"&gt;GitHub&lt;/a&gt;. Code is still beta quality. Please leave a comment if you find a bug or you have improvement ideas.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=SJHwsbQLrDA:kAct2G_uoD8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=SJHwsbQLrDA:kAct2G_uoD8:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/SJHwsbQLrDA" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-11-04:1954</id>
    <published>2008-11-04T20:13:00Z</published>
    <updated>2009-02-12T14:21:32Z</updated>
    <category term="google" />
    <category term="maps" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps" rel="alternate" type="text/html" />
    <title>Introduction to Marker Clustering With Google Maps</title>
<summary type="html">&lt;p&gt;Static Maps &lt;span class="caps"&gt;API&lt;/span&gt; has &lt;span class="caps"&gt;URL&lt;/span&gt; length limit of around 2048 characters. You can hit this limit quickly when adding lot of markers. You can keep &lt;span class="caps"&gt;URL&lt;/span&gt; short by clustering markers together.&lt;/p&gt;


	&lt;h3&gt;Square Based Clustering&lt;/h3&gt;


	&lt;p&gt;Clustering is usually done by dividing map to squares. Square size depends on map zoom level. Markers inside a square are then grouped into cluster. This technique has some limitations. Look at the following image.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/11/2/square_fail.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Two markers are close to each other. In fact they are so close they are overlapping. Both markers are also the only marker inside their square. Because markers are in separate square they wont be clustered.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;Static Maps &lt;span class="caps"&gt;API&lt;/span&gt; has &lt;span class="caps"&gt;URL&lt;/span&gt; length limit of around 2048 characters. You can hit this limit quickly when adding lot of markers. You can keep &lt;span class="caps"&gt;URL&lt;/span&gt; short by clustering markers together.&lt;/p&gt;


	&lt;h3&gt;Square Based Clustering&lt;/h3&gt;


	&lt;p&gt;Clustering is usually done by dividing map to squares. Square size depends on map zoom level. Markers inside a square are then grouped into cluster. This technique has some limitations. Look at the following image.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/11/2/square_fail.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Two markers are close to each other. In fact they are so close they are overlapping. Both markers are also the only marker inside their square. Because markers are in separate square they wont be clustered.&lt;/p&gt;
&lt;h3&gt;Distance Based Clustering&lt;/h3&gt;


	&lt;p&gt;We can also group markers together based on their distance from each other. We could cluster all markers inside 10 kilometer radius together. There is one problem with this approach. Kilometers (and miles) have different meaning in different zoom levels. In zoomed in map it might mean 100 pixels. In zoomed out maps one kilometer might be only one pixel.&lt;/p&gt;


	&lt;p&gt;There is only one distance unit which does not have this problem: pixels in current zoom level. One pixel on screen is always one pixel on screen. For example we want to cluster all markers which are 20 pixels from each other. I chose 20 pixels because it happens to be the distance after which markers start to overlap each other.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/11/2/distance_great_success.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Now the two markers would be clustered since they are inside 20 pixel radius.&lt;/p&gt;


	&lt;h3&gt;Distance Between Two Coordinates on Earth&lt;/h3&gt;


	&lt;p&gt;Distance between two points on earth can be calculated in several ways. &lt;a href="http://en.wikipedia.org/wiki/Haversine_formula"&gt;Haversine formula&lt;/a&gt; is reasonably accurate and widely used. It assumes earth is spherical (in reality earth is slightly ellipsoid). This causes accuracy to be +-2 km when calculating distances of around 20.000 km. 6371.0 km is used as average radius of earth.&lt;/p&gt;


	&lt;p&gt;Below is &lt;span class="caps"&gt;PHP&lt;/span&gt; implementation of Haversine formula:&lt;/p&gt;


&lt;pre&gt;
function haversineDistance($lat1, $lon1, $lat2, $lon2) {
    $latd = deg2rad($lat2 - $lat1);
    $lond = deg2rad($lon2 - $lon1);
    $a = sin($latd / 2) * sin($latd / 2) +
         cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
         sin($lond / 2) * sin($lond / 2);
         $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
    return 6371.0 * $c;
}
&lt;/pre&gt;

	&lt;p&gt;But didn’t wee need distance in pixels instead? For that we can use &lt;a href="http://en.wikipedia.org/wiki/Pythagorean_theorem#Distance_in_Cartesian_coordinates"&gt;Pythagoras’ theorem&lt;/a&gt;. Pythagoras’ theorem uses cartesian (pixel) coordinates. Some &lt;a href="http://en.wikipedia.org/wiki/Mercator_projection"&gt;Mercator&lt;/a&gt; magic can be used to convert latitude and longitude to pixel x and y values.&lt;/p&gt;


	&lt;p&gt;You might wonder where did number 268435456 come from? It is half of the earth circumference in pixels at zoom level 21. You can visualize it by thinking of full map. Full map size is 536870912 × 536870912 pixels. Center of the map in pixel coordinates is 268435456,268435456 which in latitude and longitude would be 0,0.&lt;/p&gt;


&lt;pre&gt;
define('OFFSET', 268435456);
define('RADIUS', 85445659.4471); /* $offset / pi() */

function lonToX($lon) {
    return round(OFFSET + RADIUS * $lon * pi() / 180);        
}

function latToY($lat) {
    return round(OFFSET - RADIUS * 
                log((1 + sin($lat * pi() / 180)) / 
                (1 - sin($lat * pi() / 180))) / 2);
}

function pixelDistance($lat1, $lon1, $lat2, $lon2, $zoom) {
    $x1 = lonToX($lon1);
    $y1 = latToY($lat1);

    $x2 = lonToX($lon2);
    $y2 = latToY($lat2);

    return sqrt(pow(($x1-$x2),2) + pow(($y1-$y2),2)) &amp;gt;&amp;gt; (21 - $zoom);
}
&lt;/pre&gt;

	&lt;p&gt;Now we have all needed mathematics in place. What to do with them?&lt;/p&gt;


	&lt;h3&gt;Cluster Markers Together&lt;/h3&gt;


	&lt;p&gt;Let’s write example clusterer function. It takes three parameters:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Array of &lt;em&gt;lat&lt;/em&gt; and &lt;em&gt;lon&lt;/em&gt; locations. &lt;/li&gt;
		&lt;li&gt;Distance in pixel inside which markers will be clustered.&lt;/li&gt;
		&lt;li&gt;Current map zoom level.&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Function will return another array where coordinates closer than &lt;em&gt;$distance&lt;/em&gt; are clustered together.&lt;/p&gt;


&lt;pre&gt;
function cluster($markers, $distance, $zoom) {
    $clustered = array();
    /* Loop until all markers have been compared. */
    while (count($markers)) {
        $marker  = array_pop($markers);
        $cluster = array();
        /* Compare against all markers which are left. */
        foreach ($markers as $key =&amp;gt; $target) {
            $pixels = pixelDistance($marker['lat'], $marker['lon'],
                                    $target['lat'], $target['lon'],
                                    $zoom);
            /* If two markers are closer than given distance remove */
            /* target marker from array and add it to cluster.      */
            if ($distance &amp;gt; $pixels) {
                printf("Distance between %s,%s and %s,%s is %d pixels.\n", 
                    $marker['lat'], $marker['lon'],
                    $target['lat'], $target['lon'],
                    $pixels);
                unset($markers[$key]);
                $cluster[] = $target;
            }
        }

        /* If a marker has been added to cluster, add also the one  */
        /* we were comparing to and remove the original from array. */
        if (count($cluster) &amp;gt; 0) {
            $cluster[] = $marker;
            $clustered[] = $cluster;
        } else {
            $clustered[] = $marker;
        }
    }
    return $clustered;
}
&lt;/pre&gt;

	&lt;p&gt;We can now test clusterer function with array of coordinates.&lt;/p&gt;


&lt;pre&gt;
$markers   = array();
$markers[] = array('id' =&amp;gt; 'marker_1', 
                   'lat' =&amp;gt; 59.441193, 'lon' =&amp;gt; 24.729494);
$markers[] = array('id' =&amp;gt; 'marker_2', 
                   'lat' =&amp;gt; 59.432365, 'lon' =&amp;gt; 24.742992);
$markers[] = array('id' =&amp;gt; 'marker_3', 
                   'lat' =&amp;gt; 59.431602, 'lon' =&amp;gt; 24.757563);
$markers[] = array('id' =&amp;gt; 'marker_4', 
                   'lat' =&amp;gt; 59.437843, 'lon' =&amp;gt; 24.765759);
$markers[] = array('id' =&amp;gt; 'marker_5', 
                   'lat' =&amp;gt; 59.439644, 'lon' =&amp;gt; 24.779041);
$markers[] = array('id' =&amp;gt; 'marker_6', 
                   'lat' =&amp;gt; 59.434776, 'lon' =&amp;gt; 24.756681);

$clustered = cluster($markers, 20, 11);

print_r($clustered);
&lt;/pre&gt;

	&lt;p&gt;If you &lt;a href="http://www.appelsiini.net/2008/11/clustering.php"&gt;run the code&lt;/a&gt; you can see how marker_3, marker_4 and marker_6 are clustered together. This can better be visualized as map screenshot before and after clustering. Blue marker is a cluster.&lt;/p&gt;


	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/11/3/no_cluster.gif" alt="" /&gt;
&lt;img src="http://www.appelsiini.net/assets/2008/11/3/with_cluster2.gif" alt="" /&gt;&lt;/p&gt;


	&lt;h3&gt;Real Life Usage?&lt;/h3&gt;


	&lt;p&gt;Obviously making an array of coordinates into a new array of coordinates is not really usefull. However the first  &lt;a href="http://github.com/tuupola/php_google_maps/tree/master/Google/Maps/Clusterer/Distance.php"&gt;clusterer for Static Maps&lt;/a&gt; I committed to GitHub uses previously described technique. Clustering a static map takes only two extra lines of code. First create a cluster. Then add it to the map object. Rest is taken care automatically.&lt;/p&gt;


&lt;pre&gt;
$clusterer = Google_Maps_Clusterer::create('distance');
$map-&amp;gt;setClusterer($clusterer);
&lt;/pre&gt;

	&lt;p&gt;You can see it in action in &lt;a href="http://www.appelsiini.net/projects/php_google_maps/cluster.html?center=17.41%2C15.15&amp;amp;#38;infowindow=&amp;amp;#38;zoom=2"&gt;capital cities of the world&lt;/a&gt; map. City locations are parsed from &lt;span class="caps"&gt;KML&lt;/span&gt; file. Note that in closer zooms locations are slightly off. Coordinates have only two decimals of latitude and longitude.&lt;/p&gt;


	&lt;p&gt;Currently I have demo code only for Static Maps. Serverside clustering for Google Maps &lt;span class="caps"&gt;API&lt;/span&gt; will follow soon. Thats a promise. Cross my heart.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=kP1lJEJZIk8:UXZjrnpJebo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=kP1lJEJZIk8:UXZjrnpJebo:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/kP1lJEJZIk8" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-10-24:1856</id>
    <published>2008-10-24T20:54:00Z</published>
    <updated>2008-11-16T21:19:27Z</updated>
    <category term="google" />
    <category term="maps" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/10/simple-zoom-and-pan-controls-with-static-maps" rel="alternate" type="text/html" />
    <title>Simple Zoom and Pan Controls With Static Maps</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/24/map_bubble.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Most of the code from previous Static Maps experiments is now put into one clean package. Previously I showed you how to work with &lt;a href="http://www.appelsiini.net/2008/10/simple-static-maps-with-php"&gt;markers and bounds&lt;/a&gt;. Now we go forward and add zoom and pan controls. It takes only few lines of code. If you just started reading the series  check the &lt;a href="http://www.appelsiini.net/2008/6/google-maps-without-javascript-part-2"&gt;theory how it works&lt;/a&gt;. As a bonus lets add infowindows / bubbles too.&lt;/p&gt;


	&lt;p&gt;Note! Image above is just a screenshot. You can test final result in the &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;demo&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;Create Map and Some Markers&lt;/h3&gt;


	&lt;p&gt;Start by creating new map object and set the size. We also need to give our &lt;span class="caps"&gt;API&lt;/span&gt; key. Markers are positioned on map using location object. Location can be latitude and longitude represented by &lt;em&gt;Google_Maps_Coordinate&lt;/em&gt; object. Location can also be map x and y represented by &lt;em&gt;Google_Maps_Point&lt;/em&gt; object.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/24/map_bubble.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Most of the code from previous Static Maps experiments is now put into one clean package. Previously I showed you how to work with &lt;a href="http://www.appelsiini.net/2008/10/simple-static-maps-with-php"&gt;markers and bounds&lt;/a&gt;. Now we go forward and add zoom and pan controls. It takes only few lines of code. If you just started reading the series  check the &lt;a href="http://www.appelsiini.net/2008/6/google-maps-without-javascript-part-2"&gt;theory how it works&lt;/a&gt;. As a bonus lets add infowindows / bubbles too.&lt;/p&gt;


	&lt;p&gt;Note! Image above is just a screenshot. You can test final result in the &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;demo&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;Create Map and Some Markers&lt;/h3&gt;


	&lt;p&gt;Start by creating new map object and set the size. We also need to give our &lt;span class="caps"&gt;API&lt;/span&gt; key. Markers are positioned on map using location object. Location can be latitude and longitude represented by &lt;em&gt;Google_Maps_Coordinate&lt;/em&gt; object. Location can also be map x and y represented by &lt;em&gt;Google_Maps_Point&lt;/em&gt; object.&lt;/p&gt;
&lt;p&gt;Because we put markers to map the center is calculated automatically. There is no need to call &lt;em&gt;$map-&amp;gt;setCenter()&lt;/em&gt;. We can also calculate the closest possible zoom with &lt;em&gt;$map-&amp;gt;zoomToFit()&lt;/em&gt;.&lt;/p&gt;


&lt;pre&gt;
require_once 'Google/Maps.php';

$map = Google_Maps::create('static');
$map-&amp;gt;setSize('540x300');
$map-&amp;gt;setKey(API_KEY);

$coord_1 = new Google_Maps_Coordinate('58.378700', '26.731110');
$coord_2 = new Google_Maps_Coordinate('58.379646', '26.764090');

$marker_1 = new Google_Maps_Marker($coord_1);
$marker_2 = new Google_Maps_Marker($coord_2);

$map-&amp;gt;addMarker($marker_1);
$map-&amp;gt;addMarker($marker_2);
$map-&amp;gt;zoomToFit();
&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/24/map_markers.png" alt="" /&gt;&lt;/p&gt;


	&lt;h3&gt;Add Zoom and Pan Controls&lt;/h3&gt;


	&lt;p&gt;Controls are created using &lt;em&gt;Google_Maps_Control::create()&lt;/em&gt; factory method. After creating a control you must attach it to a map. This alone is not enough. When panning and zooming new map center or zoom value is passed in query string. Last line passes the values from &lt;span class="caps"&gt;URL&lt;/span&gt; to the map object.&lt;/p&gt;


&lt;pre&gt;
$zoom = Google_Maps_Control::create('zoom');
$map-&amp;gt;addControl($zoom);
$pan = Google_Maps_Control::create('pan');
$map-&amp;gt;addControl($pan);

$map-&amp;gt;setProperties($_GET);
&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/24/map_controls.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Note! Image above is just a screenshot. You can test working controls in the &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;demo&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;Add Infowindows / Bubbles&lt;/h3&gt;


	&lt;p&gt;Infowindows (or bubbles as they are often referred) are represented by &lt;em&gt;Google_Maps_Infowindow&lt;/em&gt; object. You can set the content in constructor or using &lt;em&gt;$bubble-&amp;gt;setContent()&lt;/em&gt; method. Bubbles have a marker attached to them. Clicking the marker will open the infowindow. As with all other items you must attach them to map.&lt;/p&gt;


&lt;pre&gt;
$bubble_1 = new Google_Maps_Infowindow('Foo Bar');
$bubble_2 = new Google_Maps_Infowindow('Pler pop');

$bubble_1-&amp;gt;setMarker($marker_1);
$bubble_2-&amp;gt;setMarker($marker_2);

$map-&amp;gt;addInfowindow($bubble_1);
$map-&amp;gt;addInfowindow($bubble_2);
&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/24/map_bubble.png" alt="" /&gt;&lt;/p&gt;


	&lt;p&gt;Note! Image above is just a screenshot. You can test working infobubbles in the &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;demo&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;Where’s the Source?&lt;/h3&gt;


	&lt;p&gt;If you want to play around with code you can get from &lt;a href="http://github.com/tuupola/php_google_maps/tree"&gt;github&lt;/a&gt;. Patches, improvements and suggestion are welcome.&lt;/p&gt;


&lt;pre&gt;git clone git://github.com/tuupola/php_google_maps.git&lt;/pre&gt;
&lt;pre&gt;wget http://github.com/tuupola/php_google_maps/zipball/master&lt;/pre&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=4PEzMtQhHBU:8wyN5S_oywU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=4PEzMtQhHBU:8wyN5S_oywU:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/4PEzMtQhHBU" height="1" width="1"/&gt;</content>  </entry>
  <entry xml:base="http://www.appelsiini.net/">
    <author>
      <name>tuupola</name>
    </author>
    <id>tag:www.appelsiini.net,2008-10-10:1745</id>
    <published>2008-10-10T15:46:00Z</published>
    <updated>2009-03-13T10:17:57Z</updated>
    <category term="google" />
    <category term="maps" />
    <category term="php" />
    <link href="http://www.appelsiini.net/2008/10/simple-static-maps-with-php" rel="alternate" type="text/html" />
    <title>Simple Static Maps With PHP</title>
<summary type="html">&lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/10/staticmap.gif" alt="" /&gt; Lately I have been playing with Google Static Maps &lt;span class="caps"&gt;API&lt;/span&gt; a lot. Writing the same things again and again is tedious job. I decided to put the code together as one clean extendable package. Writing object oriented interface for generating &lt;span class="caps"&gt;URL&lt;/span&gt; is trivial. Real meat is having working &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;zoom and pan controls&lt;/a&gt; on static map &lt;del&gt;with just 9 lines of code&lt;/del&gt; (demo now includes also clickable markers and infowindows).&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://github.com/tuupola/php_google_maps/tree/master"&gt;Code&lt;/a&gt; is still alpha quality. &lt;span class="caps"&gt;API&lt;/span&gt; might change any time. But here is a quick walkthrough of current features. We will build the map you see above step by step.&lt;/p&gt;</summary><content type="html">
            &lt;p&gt;&lt;img src="http://www.appelsiini.net/assets/2008/10/10/staticmap.gif" alt="" /&gt; Lately I have been playing with Google Static Maps &lt;span class="caps"&gt;API&lt;/span&gt; a lot. Writing the same things again and again is tedious job. I decided to put the code together as one clean extendable package. Writing object oriented interface for generating &lt;span class="caps"&gt;URL&lt;/span&gt; is trivial. Real meat is having working &lt;a href="http://www.appelsiini.net/projects/php_google_maps/controls.html"&gt;zoom and pan controls&lt;/a&gt; on static map &lt;del&gt;with just 9 lines of code&lt;/del&gt; (demo now includes also clickable markers and infowindows).&lt;/p&gt;


	&lt;p&gt;&lt;a href="http://github.com/tuupola/php_google_maps/tree/master"&gt;Code&lt;/a&gt; is still alpha quality. &lt;span class="caps"&gt;API&lt;/span&gt; might change any time. But here is a quick walkthrough of current features. We will build the map you see above step by step.&lt;/p&gt;
&lt;h3&gt;Create a Map Object&lt;/h3&gt;


	&lt;p&gt;Map object is created using &lt;i&gt;Google_Maps::create(‘static’)&lt;/i&gt; factory method.  If no markers are set you also need to set the center of the map.&lt;/p&gt;


&lt;pre&gt;require_once 'Google/Maps.php';

$map = Google_Maps::create('static');

$map-&amp;gt;setSize('540x300');
$map-&amp;gt;setCenter(new Google_Maps_Coordinate('58.368488', '26.768908'));
$map-&amp;gt;setZoom(8);
$map-&amp;gt;setKey(API_KEY);&lt;/pre&gt;
    &lt;/p&gt;

    &lt;img src="http://maps.google.com/staticmap?center=58.368488%2C26.768908&amp;amp;#38;zoom=8&amp;amp;#38;markers=&amp;amp;#38;size=540x300&amp;amp;#38;key=ABQIAAAASWfI7GkTRVrz1brU7GwV2BRb4tuXOrVDWXaYNDB1tYm76RuEyxQuEAfETfgIzoUG0VXo0yBFqfuU2g" height="300" alt="" width="540" /&gt;
&lt;br /&gt;&lt;br /&gt;

	&lt;h3&gt;Add some markers&lt;/h3&gt;


	&lt;p&gt;Location on map can be in two ways. Latitude and longitude represented by &lt;i&gt;Google_Maps_Coordinate&lt;/i&gt; object. Or pixel x and pixel y location represented by &lt;i&gt;Google_Maps_Point&lt;/i&gt; object. You can use both when creating a marker.&lt;/p&gt;


&lt;pre&gt;$coord_1 = new Google_Maps_Coordinate('58.378700', '26.731110');
$coord_2 = new Google_Maps_Coordinate('58.368488', '26.768908');
$coord_3 = new Google_Maps_Coordinate('58.268488', '26.768908');

$marker_1 = new Google_Maps_Marker($coord_1);
$marker_2 = new Google_Maps_Marker($coord_2);
$marker_3 = new Google_Maps_Marker($coord_3);

$marker_1-&amp;gt;setColor('green');
$marker_2-&amp;gt;setColor('blue');
$marker_3-&amp;gt;setColor('orange');

$map-&amp;gt;setMarkers(array($marker_1, $marker_2, $marker_3));&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://maps.google.com/staticmap?center=58.368488%2C26.768908&amp;amp;#38;zoom=8&amp;amp;#38;markers=58.378700%2C26.731110%2Cgreen%7C58.368488%2C26.768908%2Cblue%7C58.268488%2C26.768908%2Corange%7C&amp;amp;#38;size=540x300&amp;amp;#38;key=ABQIAAAASWfI7GkTRVrz1brU7GwV2BRb4tuXOrVDWXaYNDB1tYm76RuEyxQuEAfETfgIzoUG0VXo0yBFqfuU2g" height="300" alt="" width="540" /&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;


	&lt;h3&gt;Automatically Calculate Zoom and Center&lt;/h3&gt;


	&lt;p&gt;Google Static Maps &lt;span class="caps"&gt;API&lt;/span&gt; can automatically calculate zoom and center for you. However there is no way to know what zoom level it chose. If you need to know automatically calculated zoom and center use &lt;i&gt;$map-&amp;gt;zoomToFit()&lt;/i&gt; method.&lt;/p&gt;


	&lt;p&gt;Here we also add new marker using pixel coordinates. Note that we also clear the center of the map we set in the beginning. This allows map to recenter the map according to markers on the map.&lt;/p&gt;


&lt;pre&gt;$point_1  = new Google_Maps_Point('308107197', '160958681');
$marker_4 = new Google_Maps_Marker($point_1);
$map-&amp;gt;setCenter(false);
$map-&amp;gt;addMarker($marker_4);
$map-&amp;gt;zoomToFit();&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://maps.google.com/staticmap?center=58.319541032213%2C26.717725334168&amp;amp;#38;zoom=10&amp;amp;#38;markers=58.378700%2C26.731110%2Cgreen%7C58.368488%2C26.768908%2Cblue%7C58.268488%2C26.768908%2Corange%7C58.262488128851%2C26.601975336671%2C%7C&amp;amp;#38;size=540x300&amp;amp;#38;key=ABQIAAAASWfI7GkTRVrz1brU7GwV2BRb4tuXOrVDWXaYNDB1tYm76RuEyxQuEAfETfgIzoUG0VXo0yBFqfuU2g" height="300" alt="" width="540" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;


	&lt;h3&gt;Show Marker Bounds&lt;/h3&gt;


	&lt;p&gt;Sometimes you need to be able to visually see bounding box where all the markers fit in.&lt;/p&gt;


&lt;pre&gt;$map-&amp;gt;showMarkerBounds();&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://maps.google.com/staticmap?center=58.319541032213%2C26.717725334168&amp;amp;#38;zoom=10&amp;amp;#38;markers=58.378700%2C26.731110%2Cgreen%7C58.368488%2C26.768908%2Cblue%7C58.268488%2C26.768908%2Corange%7C58.262488128851%2C26.601975336671%2C%7C&amp;amp;#38;path=58.378700%2C26.601975336671%7C58.378700%2C26.768908%7C58.262488128851%2C26.768908%7C58.262488128851%2C26.601975336671%7C58.378700%2C26.601975336671%7C&amp;amp;#38;size=540x300&amp;amp;#38;key=ABQIAAAASWfI7GkTRVrz1brU7GwV2BRb4tuXOrVDWXaYNDB1tYm76RuEyxQuEAfETfgIzoUG0VXo0yBFqfuU2g" height="300" alt="" width="540" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;


	&lt;h3&gt;Show Map Bounds at Chosen Zoom Level&lt;/h3&gt;


	&lt;p&gt;You can also calculate and show map bounds at any zoom level. Example below displays map bounds at zoom level 8. Map itself is at zoom level 7.&lt;/p&gt;


&lt;pre&gt;$map-&amp;gt;setZoom(7);
$map_bounds = $map-&amp;gt;getBounds(8);
$map-&amp;gt;setPath($map_bounds-&amp;gt;getPath());&lt;/pre&gt;

	&lt;p&gt;&lt;img src="http://maps.google.com/staticmap?center=58.319541032213%2C26.717725334168&amp;amp;#38;zoom=7&amp;amp;#38;markers=58.378700%2C26.731110%2Cgreen%7C58.368488%2C26.768908%2Cblue%7C58.268488%2C26.768908%2Corange%7C58.262488128851%2C26.601975336671%2C%7C&amp;amp;#38;path=58.749635911828%2C25.234571099281%7C58.749635911828%2C28.200879693031%7C57.884150180108%2C28.200879693031%7C57.884150180108%2C25.234571099281%7C58.749635911828%2C25.234571099281%7C&amp;amp;#38;size=540x300&amp;amp;#38;key=ABQIAAAASWfI7GkTRVrz1brU7GwV2BRb4tuXOrVDWXaYNDB1tYm76RuEyxQuEAfETfgIzoUG0VXo0yBFqfuU2g" height="300" alt="" width="540" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;


	&lt;h3&gt;Where’s the Source?&lt;/h3&gt;


	&lt;p&gt;If you want to play around with code you can get from &lt;a href="http://github.com/tuupola/php_google_maps/tree"&gt;github&lt;/a&gt;. Patches, improvements and suggestion are welcome.&lt;/p&gt;


&lt;pre&gt;git clone git://github.com/tuupola/php_google_maps.git&lt;/pre&gt;
&lt;pre&gt;wget http://github.com/tuupola/php_google_maps/zipball/master&lt;/pre&gt;

	&lt;p&gt;Related entries: &lt;a href="http://www.appelsiini.net/2008/9/infowindows-with-google-static-maps"&gt;Infowindows With Google Static Maps&lt;/a&gt;, &lt;a href="http://www.appelsiini.net/2008/6/clickable-markers-with-google-static-maps"&gt;Clickable Markers With Google Static Maps&lt;/a&gt;.&lt;/p&gt;
          &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=Kbp5b5OaX2U:4N7G6sWxdnc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/tuupola?a=Kbp5b5OaX2U:4N7G6sWxdnc:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/tuupola?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/tuupola/~4/Kbp5b5OaX2U" height="1" width="1"/&gt;</content>  </entry>
</feed>
