<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tommy Lacroix &#187; Programming</title>
	<atom:link href="http://www.tommylacroix.com/category/web-development/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.tommylacroix.com</link>
	<description>Professional Blog</description>
	<lastBuildDate>Wed, 16 Feb 2011 16:35:50 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.5</generator>
		<item>
		<title>Yet another IE6 rant, and catching up</title>
		<link>http://www.tommylacroix.com/2011/02/16/ie6-rant-and-catching-up/</link>
		<comments>http://www.tommylacroix.com/2011/02/16/ie6-rant-and-catching-up/#comments</comments>
		<pubDate>Wed, 16 Feb 2011 06:04:38 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[ie6]]></category>
		<category><![CDATA[ie7]]></category>
		<category><![CDATA[internet explorer]]></category>
		<category><![CDATA[internet explorer 6]]></category>
		<category><![CDATA[microsoft]]></category>
		<category><![CDATA[wga]]></category>
		<category><![CDATA[windows genuine advantage]]></category>
<category>html</category><category>ie6</category><category>ie7</category><category>internet explorer</category><category>internet explorer 6</category><category>microsoft</category><category>wga</category><category>windows genuine advantage</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=154</guid>
		<description><![CDATA[It's been a while since I've posted on this blog. Mostly because I've been busy on zillion projects. I'll soon write an update to my cascades queuing post. For a recent project of mine, <a href="http://www.flexivo.tv">flexivo.tv</a>, I've been researching an efficient way to get rid of Amazon's SQS service for once big reason, I needed simultaneous queueing capabilities. One message being in multiple queues at once, that is. My experiments with file system and MySQL queues, even when cascaded with memcached wheren't performant enough. I found my answer in NoSQL. Enough for the intro, now the rant ;-)]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s been a while since I&#8217;ve posted on this blog. Mostly because I&#8217;ve been busy on zillion projects. I&#8217;ll soon write an update to my <a href="/2009/12/08/cascaded-queueing/">cascades queuing post</a>. For a recent project of mine, <a href="http://www.flexivo.tv">flexivo.tv</a>, I&#8217;ve been researching an efficient way to get rid of <a href="http://aws.amazon.com/sqs/">Amazon&#8217;s Simple Queue Service (SQS)</a> service for one main reason, I needed simultaneous queueing capabilities. One message being in multiple queues at once, that is. My experiments with file system and MySQL queues, even when cascaded with memcached wheren&#8217;t performant enough. I found my answer in NoSQL. But now, enough for the intro, now the rant <img src='http://www.tommylacroix.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
<p>I&#8217;m writing this while watching <a target="_blank" href="http://www.youtube.com/watch?v=Lv1wEUq2vyA">Stephen Graham making a statement as Al Capone in Boardwalk Empire</a>. So I shall make one too. Now about that fellah&#8217;, the INFAMOUS Internet Explorer 6. A friend of mine recently showed me a site of his making where he directs the IE6 users to a deadend page where he encourages them to upgrade. Sweet. I totally agree. Then he tells me that IE6 is a plague. It sure is, still agreeing. Then that it&#8217;s been alive that long because of all the conciliatory developers like him and me that worked, harder an harder as new standards like transparent PNGs came along, to ensure IE6 compatibility. And that&#8217;s where I disagree.</p>
<p><span id="more-154"></span></p>
<p>He sure is right if you don&#8217;t look at the big picture. IE6 couldn&#8217;t have stayed alive for that long without us, hard working developers. But if you go a bit deeper, you&#8217;ll agree that us, hard working developers, are taking &#8220;orders&#8221; from project managers, who take them from account managers, who take them from clients. And for a client, as well as for an account manager, when still 30% of the population uses IE6, you&#8217;re faced with the <a href="http://en.wikipedia.org/wiki/Prisoner's_dilemma">prisoner&#8217;s dilemma</a>: will you refuse to support it knowing that you competitor might? Probably not.</p>
<p>But the rabbit hole goes deeper than that. Why was 30% of the population stuck with IE6 not so long ago? To me, it&#8217;s a combination of two major factors: the Windows Genuine Advantage, and Windows Vista failure. </p>
<p>According to <a href="http://en.wikipedia.org/wiki/Windows_Genuine_Advantage" target="_blank">Wikipedia</a>, &#8220;the Windows Genuine Advantage  is an anti-piracy system created by Microsoft that enforces online validation of the licensing of several recent Microsoft Windows operating systems&#8221;, starting with Windows 2000 Pro, and including Windows XP and Windows Vista. While I can&#8217;t be against their motives &#8212; they&#8217;re a proprietary software business, they&#8217;re in for the money, and that&#8217;s alright, we need them as much as we need open source if you ask me, but that&#8217;s another debate &#8212; I&#8217;m against the fact that they denied browser upgrades to pirate Windows installation. Sure, these users are pirates, outlaws. They don&#8217;t deserve upgrade. But we do! That decision contributed to two things: keeping the net unsafe and slowing standards adoption by preventing a majority of dummy users (read: not smart enough to get they should install FireFox or Chrome, and that&#8217;s still fine, it&#8217;s not their fault, the internet success resides in its mass adoption, not it&#8217;s adoption by the early adopters, but again, that&#8217;s another debate) to seamlessly upgrade to a safer and more modern browser.</p>
<p>The <a href="http://en.wikipedia.org/wiki/Windows_vista#Criticism" target="_blank">Windows Vista failure</a> was just an aggravating factor. Until <a href="http://en.wikipedia.org/wiki/Windows_7" target="_blank">Windows 7</a> started shipping late 2009 (and even after that, until people realized it was a lot better than Vista), people where still ordering computers with Windows XP. Unfortunately for us, web developers, that included &#8220;cheap&#8221; people who wanted to save a few (hundred) bucks on a (not so cheap) operating system. Internet Explorer 6.0 was released on August 27th, 2001. Almost 10 years ago. But people were still having fresh Windows XP installs no more than 2 years back because Vista known to be crap.</p>
<p>So yes, in the end, we web developers are responsible for not refusing to build web sites compatible with an obsolete and insecure browser. But to me, the whole IE6 mess we&#8217;ve all got to cope with is Microsoft fault. Not because they tried to enforced their right to be paid for copy of their operating system that&#8217;s being installed, but because they did so regardless of internet safety and standards adoption. The net would be so much farther without that decision. But we&#8217;re where we&#8217;re at, and IE6 doesn&#8217;t have enough market shares to rule the world anymore. <a href="http://googleenterprise.blogspot.com/2010/01/modern-browsers-for-modern-applications.html" target="_blank">Google opened the march</a> not long ago. Please, let IE6 rest in peace, so we can get rid of IE7 while hoping that IE9 will be a bliss. Hate it? Drop it, for the sake of us all.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2011/02/16/ie6-rant-and-catching-up/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP based MP4/F4V meta data reader</title>
		<link>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/</link>
		<comments>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/#comments</comments>
		<pubDate>Thu, 11 Jun 2009 14:45:40 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
<category>duration</category><category>f4v</category><category>flash</category><category>flv</category><category>height</category><category>meta</category><category>meta data</category><category>mp4</category><category>mp4info</category><category>php</category><category>tool</category><category>width</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=124</guid>
		<description><![CDATA[You might have read my post about my FLV meta data and cue point reader/writer. You might also know that the F4V format (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up. The MP4Info class is a simple extensible PHP class reading the MP4 container&#8217;s frames [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:left;margin:0 10px 10px 0;" title="F4V File Format" src="http://www.tommylacroix.com/wp-content/uploads/2009/06/f4v.gif" alt="F4V File Format" width="128" height="128" /><br />
You might have read my post about my <a title="PHP based FLV meta and cue points reader/writer" href="http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/" target="_self">FLV meta data and cue point reader/writer</a>. You might also know that the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video#File_formats" target="_blank">F4V format</a> (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up.</p>
<p>The <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">MP4Info class</a> is a simple extensible PHP class reading the MP4 container&#8217;s frames (called boxes) to get various information, namely the video duration, the video/audio codecs, the width and the height, as well as the embedded <a title="Adobe's Extensible Meta-data Platform (XMP), at Wikipedia" href="http://en.wikipedia.org/wiki/Extensible_Metadata_Platform" target="_blank">XMP meta data</a>.</p>
<p>The F4V format is better than the FLV format in many aspects:</p>
<ul>
<li>Better compression ratio</li>
<li>Better quality at similar bit rates</li>
<li>Open source compressors available, through ffmpeg and libh264 (Flash 8 On2 VP6 codec is proprietary)</li>
<li>Decoding H.264 is a lot less CPU intensive on the host computer than decoding On2 VP6</li>
</ul>
<div>With <a title="Adobe Flash Player Version Penetration" href="http://www.adobe.com/products/player_census/flashplayer/version_penetration.html" target="_blank">Flash 9 being at a rate of penetration of 98.9% in mature markets</a><sup>1</sup> and will all the advantages, MP4 Flash video is becoming more and more common.</div>
<div><span id="more-124"></span></div>
<div>It really only has been tested with Sorenson Squeeze encoded F4V files for now, and therefore should be considered beta.</p>
<p>You&#8217;ll find the project <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">at Google Code, under php-mp4info</a>.</p>
<p><small><sup>1</sup> As per Adobe&#8217;s Adobe Flash Player Version Penetration statistics, on June 11, 2009<br />
<sup>2</sup> F4V logo at the top found at <a title="Flash Develop And Design" href="http://www.flashdevelop.net/postshow_975.html" target="_blank">Flash Develop And Design</a></small></div>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Watermarker: A tool for pixel-perfect web integration</title>
		<link>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/</link>
		<comments>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/#comments</comments>
		<pubDate>Tue, 13 Jan 2009 21:45:00 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[integration]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[tools]]></category>
<category>css</category><category>html</category><category>integration</category><category>programming</category><category>tools</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=117</guid>
		<description><![CDATA[Having worked with many fastidious web designers in the past requesting that every single integrated page looks precisely to the pixel like the PSD file &#8212; you know who you are &#8212; I developped this little JavaScript technique that allows the integrator to enable/disable a 80% alpha JPEG image overlay, in order to visually find the discrepancies. [...]]]></description>
			<content:encoded><![CDATA[<p>Having worked with many fastidious web designers in the past requesting that every single integrated page looks precisely to the pixel like the PSD file &#8212; you know who you are &#8212; I developped this little JavaScript technique that allows the integrator to enable/disable a 80% alpha JPEG image overlay, in order to visually find the discrepancies. The code is very ugly but cross-browser compatible, and I never felt compelled to make it better as it does perfectly what it has to do.</p>
<p>It&#8217;s quite easy to use: simply load the JavaScript file at the end of your HTML file, and have it point on your JPEG image to an default X/Y position.</p>
<pre class="code">&lt;script language="javascript" type="text/javascript" src="watermarker.js"&gt;&lt;/script&gt;
&lt;script language="javascript" type="text/javascript"&gt;
setWatermark("images/mywatermark.jpg",-4,-5);
&lt;/script&gt;</pre>
<p>And voila, you can toggle your JPEG overlay, move it around, change it&#8217;s alpha transparency and reset it to its default position.</p>
<p><a title="Watermarker Demo" href="http://www.tommylacroix.com/demos/watermarker/" target="_blank">A small demo is available here</a>.</p>
<p><a title="Watermarker JavaScript source" href="http://www.tommylacroix.com/demos/watermarker/watermarker.js.txt"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="Watermarker JavaScript source" href="http://www.tommylacroix.com/demos/watermarker/watermarker.js.txt">watermarker.js</a> <em>(7.8k)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP Design Pattern: Building a Tree</title>
		<link>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/</link>
		<comments>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/#comments</comments>
		<pubDate>Wed, 10 Sep 2008 17:12:27 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[pattern]]></category>
		<category><![CDATA[tree]]></category>
		<category><![CDATA[tree building]]></category>
<category>pattern</category><category>php</category><category>tree</category><category>tree building</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=89</guid>
		<description><![CDATA[Categories and sub categories. Directories and sub directories. Building a tree of nodes that have parents is a common problem these days. There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience. Imagine, for the sake of clarity, a simple [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:right; border:1px solid black; margin-left: 5px; margin-bottom: 5px; padding:5px; background-color:white;" src="/wp-content/uploads/2008/09/imagebinarytree.jpg" alt="Tree, in computing" width="289" height="253" />Categories and sub categories. Directories and sub directories. Building a <a title="Tree, at Wikipedia" href="http://en.wikipedia.org/wiki/Tree_(data_structure)" target="_blank">tree </a>of nodes that have parents is a common problem these days.</p>
<p>There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience.</p>
<p><span id="more-89"></span></p>
<p>Imagine, for the sake of clarity, a simple dataset that goes as follow:</p>
<pre class="code">Array
(
    [0] =&gt; Array
        (
            [name] =&gt; Node 0
            [parent] =&gt; null
        )

    [1] =&gt; Array
        (
            [name] =&gt; Node 1
            [parent] =&gt; 4
        )

    [2] =&gt; Array
        (
            [name] =&gt; Node 2
            [parent] =&gt; 8
        )
    ...</pre>
<p>The common approach is to use a recursive function that finds all the root nodes, then all their sub nodes, then all the sub nodes of their sub nodes, and so on.</p>
<pre class="code">function mapTree($dataset, $parent=null) {
	$tree = array();
	foreach ($dataset as $id=&gt;$node) {
		if ($node['parent'] !== $parent) continue;
		$node['children'] = mapTree($dataset, $id);
		$tree[$id] = $node;
	}

	return $tree;
}</pre>
<p>The problem with this method is that, in terms of calculation, the problem complexity is exponential: you need to search all the dataset for children nodes for each node. Thus, building a tree of 1,000 nodes is 100 times more complicated than building a tree of 100 nodes, because you will have to search 10 times more nodes, 10 times more often.</p>
<p>A more efficient way to build the tree is to use dereferencing. This allows to map the whole tree in a single pass, without recursion. The problem complexity then becomes linear rather than exponential, and 10 times more nodes equals to 10 times more time (although the time required by PHP to find a specific index of an array isn&#8217;t taken into account here).</p>
<pre class="code">function mapTree($dataset) {
	$tree = array();
	foreach ($dataset as $id=&gt;&amp;$node) {
		if ($node['parent'] === null) { // root node
			$tree[$id] = &amp;$node;
		} else { // sub node
			if (!isset($dataset[$node['parent']]['children'])) $dataset[$node['parent']]['childs'] = array();
			$dataset[$node['parent']]['children'][$id] = &amp;$node;
		}
	}

	return $tree;
}</pre>
<p>The full PHP code of examples can be found below:</p>
<p><a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip">tree.zip</a> <em>(1.2k)</em></p>
<p><small>Tree image from <a title="Binary Tree Image" href="http://en.wikipedia.org/wiki/Image:Binary_tree.svg">Wikipedia.org</a></small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>HTTP caching basics</title>
		<link>http://www.tommylacroix.com/2008/07/17/http-caching-basics/</link>
		<comments>http://www.tommylacroix.com/2008/07/17/http-caching-basics/#comments</comments>
		<pubDate>Thu, 17 Jul 2008 18:56:56 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[http caching]]></category>
		<category><![CDATA[http headers]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[proxy]]></category>
<category>browser</category><category>caching</category><category>http</category><category>http headers</category><category>performance</category><category>php</category><category>proxy</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=70</guid>
		<description><![CDATA[The HTTP protocol is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly. But so much [...]]]></description>
			<content:encoded><![CDATA[<p>The <a title="Hyper-Text Transfer Protocol, at Wikipedia" href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP protocol</a> is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly.</p>
<p>But so much things are cacheable: pages, images, CSS, JavaScript, even many REST web services! Yes, even in this social web era where content changes faster than you can write, there&#8217;s still plenty of slow changing information, such as home pages, or lists of countries, regions and cities.</p>
<p>Efficiently using caching translates into:</p>
<ul>
<li>Better response and loading time</li>
<li>Decreased load on the server</li>
<li>Better user experience</li>
</ul>
<p>This article aims to present a simple explanation of the HTTP protocol and proper use of HTTP caching.</p>
<p><span id="more-70"></span></p>
<h3>The HTTP Protocol basics</h3>
<p>The HTTP protocol is a communication scheme between two or three actors: the server, the browser, and the often forgotten proxy.</p>
<p>First, to see the implicated headers, let&#8217;s have a look to the typical request &#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"</strong>
<strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</strong></pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
<strong>Expires: Wed, 11 Jan 1984 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: private, must-revalidate, max-age=0
Vary: User-Agent
</strong>Content-Type: text/html; charset=UTF-8

&lt;html&gt;...&lt;/html&gt;</pre>
<h3>Basic Caching</h3>
<p>The response headers returned by the server give the browser and the proxy information to make caching decisions.</p>
<p>First, the <a title="The Cache-Control header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9" target="_blank"><code>Cache-Control</code> header</a> tells if the content is cacheable or not. If they&#8217;re not sent, it&#8217;s cacheable by default. Consider the following headers:</p>
<pre class="code">Cache-Control: public, max-age=86400</pre>
<p>It basically says that the page is cacheable in a <code>public</code> scope, and that the content shouldn&#8217;t be kept in the cache without revalidation for more than 1 day (86400 seconds). The scope can be:</p>
<ul>
<li><code>public</code>: Cacheable by browsers and proxies, even if authenticated</li>
<li><code>Private</code>: Cacheable by browsers only (and proxies, but only for requests from the same clients)</li>
<li><code>no-cache</code>: Cacheable, but revalidation is required every time.</li>
<li><code>no-store</code>: Not cacheable at all.</li>
</ul>
<p>In addition, there are a few other keywords that you can add to your <code>Cache-Control</code> header. Keep in mind that you must separate multiple keywords by commas.</p>
<ul>
<li><code>must-revalidate</code>: Some proxies can be configured to ignore the <code>Expires</code> and <code>maxage</code>. This keyword forces them to always act like the resource was expired.</li>
<li><code>proxy-revalidate</code>: Same as <code>must-revalidate</code>, but only for proxies.</li>
<li><code>s-maxage</code>: Same as <code>maxage</code>, but only for proxies.</li>
</ul>
<p>Finally, for HTTP/1.0 compatibility, you should send a <code>Pragma</code> header when using the <code>Cache-Control no-cache</code> directive.</p>
<pre class="code">Pragma: no-cache</pre>
<p><strong>Browsers and proxies cache resources based on their URL</strong>. You can take advantage of this in many ways at the design stage of your web site or web application. For example, you should put non private REST web service parameters (such as language, or country) in the URL:</p>
<pre class="code">http://www.somesite.com/webservice/regionslist/country/usa/language/en</pre>
<p>Browsers and proxies also store three important information about the page: the <code>Expires</code>, <code>Last-Modified</code> and <code>Etag</code> headers.</p>
<ul>
<li>The <a title="The Expires header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21" target="_blank"><code>Expires</code> header</a> tells the browser and proxy, along with the <code>max-age</code> component of the <code>Cache-Control</code> header, until when this version of the content should be valid.</li>
<li>The <a title="The Last-Modified header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29" target="_blank"><code>Last-Modified</code> header</a> tells the browser and proxy the date and time of the last modification to this page. It isn&#8217;t always provided.</li>
<li>The <a title="The Etag header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19" target="_blank"><code>Etag</code> header</a>, which stands for Entity Tag, gives the browser and proxy a unique identifier that describes the content it returned. If a page&#8217;s content changes, its <code>Etag</code> changes as well.</li>
</ul>
<p>When you requested this page, your browser (and the proxy in the middle if there was one) did check if the page is cacheable, and since it was, stored a copy of the page associated with the URL, and the three metrics above. For subsequent requests, the server might use this cached copy.</p>
<p>Might? Yes, because the cached page won&#8217;t be good until the end of times. At some point, the browser (or the proxy) will check with the server if the page it has in cache is still valid. This is called revalidation, and the <code>Expires</code> HTTP response header along with the <code>max-age</code> component of the <code>Cache-Control</code> header control it.<a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif"><img class="aligncenter size-full wp-image-71" title="HTTP caching, with the browser\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif" alt="" width="476" height="267" /></a></p>
<p>When the browser or proxy revalidates a page, it sends information about its version: the <code>Etag</code>, and the <code>Last-Modified</code> the server sent when he cached the page. These are sent as <a title="The If-Modified-Since header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25" target="_blank"><code>If-Modified-Since</code></a>, and <a title="The If-None-Match header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26" target="_blank"><code>If-None-Match</code></a>, respectively:</p>
<pre class="code">GET /some-cachable-page HTTP/1.1
Host: www.tommylacroix.com
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"
</strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>The server then compares this information with the <code>Etag</code> and the <code>Last-Modified</code> of the up-to-date page. If the browser&#8217;s cached copy appears to be valid, the server replies with the <code>Expires</code> and <code>Etag</code> (if available), headers, and no content:</p>
<pre class="code">HTTP/1.1 304 Not modified
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"</pre>
<p>If the browser&#8217;s cached copy appears to be out-dated, the server replies with the whole page, as usual.</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif"><img class="aligncenter size-full wp-image-72" title="HTTP caching, with the server\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif" alt="" width="417" height="308" /></a></p>
<h3>Specific Cases</h3>
<p>Now, here comes the tricky cases. For the sake of clarity, I&#8217;ll use the following plot for the scenarios below:</p>
<blockquote><p>« Bob, Alice and Gregg work in the same office. Their office is equiped with a caching web proxy. Bob and Gregg share the same computer with the same user (ok, not credible, so lets say it&#8217;s a under-financed non-profit organization), and Alice has her own (she&#8217;s the boss).  »</p></blockquote>
<h4>Scenario 1: Secure sessions</h4>
<blockquote><p>« Bob goes on his SuperSocial profile page, at http://www.supersocial.com/profile/. His browser and the office proxy will check if the page is cacheable, and it is. They both store a copy of the page associated with the URL.</p>
<p>But what happens when Gregg or Alice log in his/her SuperSocial&#8217;s profile page right after? The browser will give him/her Bob&#8217;s page! »</p></blockquote>
<p><strong>The <code>Cache-Control</code>, <code>Expires</code>, and the <code>Etag</code> headers. </strong>Setting the <code>Expires</code> header in the past and the <code>max-age</code> to zero will cause the browser to revalidate the content each time. If the cached content is valid, only a header with no content will be sent. This is slightly slower than full blown caching, but no as much as no caching at all.</p>
<p>We could also set the <code>Cache-Control</code> scope to private, as each copy of the page will obviously only be valid one user.</p>
<p>We should also use <code>Etag</code>, as the content returned for the same URL isn&#8217;t the same for Bob and for Gregg, so the <code>Etag</code> will change, and the browser will reload the page for Gregg.</p>
<h4>Scenario 2: Content optimization</h4>
<blockquote><p>« The three unproductive workers browse a news site that has a wicked design. So wicked that the guys behind it had to make browser specific optimizations. Therefore, when you&#8217;re with Internet Explorer, the page is IE optimized, when you&#8217;re with Safari, some in-line CSS styles are different, and when you&#8217;re with Firefox, you get the regular page because this beauty is standard-compliant. Standard-what? Lets not digress&#8230;</p>
<p>When Bob and Gregg browse the site with IE from the same computer, it&#8217;s fine. But when Alice accesses it a short while after with Safari, she gets an ugly page as it&#8217;s IE optimized. The reason is, the server sent the IE version to Bob, and the proxy cached it. When Alice requests it, the proxy sends the IE page, thinking that it&#8217;s all fine. »</p></blockquote>
<p><strong>The <code>Vary</code> header.</strong> When the content of a page changes based on miscellaneous headers contained in the request, the server must tell the browser and the proxy using the <a title="The Vary header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44" target="_blank"><code>Vary</code> header</a>. The <code>Vary</code> header basically says: If you cache this content, beware that if this header changes, the content might change as well, so use the URL with these fields to make sure you serve a valid version later.</p>
<p>Here&#8217;s an example. The request&#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: public, max-age=86400
Vary: User-Agent
Content-Type: text/html; charset=UTF-8</pre>
<h3>Code Snippet</h3>
<p>Here&#8217;s a code snippet I wrote quite some time ago. It&#8217;s not pretty, but it works so I think it&#8217;s a good basic practical example.</p>
<pre class="code">/**
* setCacheHTTPHeaders
*
* @author	Tommy Lacroix
* @param	string	$privacy		Scope: public, private or no-cache
* @param	int	$lastModified		Unix timestamp of last page modification (optional)
* @param	int	$maxage			Maximum caching time before revalidation (optional)
* @param	string	$etag			Entity tag, page-content specific (optional)
* @return	bool				TRUE if content need to be sent, FALSE if no content need to be sent
*/
function setCachePolicy($privacy = 'public', $lastModified = false, $maxage = false, $etag = false) {
	// Sanitize privacy
	switch ($privacy) {
		case 'privacy':
		case 'public':
		case 'no-cache':
			break;
		default:
			$privacy = "public";
			break;
	}

	// Calculate expiry and max-age
	if (is_string($maxage)) { // Expiry is a string, interpret
		unset($m);
		if (preg_match('/^([0-9]+)([smhdwy])$/', $maxage, $m)) {
			$maxage = $m[1];
			switch ($m[2]) {
				case 's':	break;
				case 'm':	$maxage *= 60; 		break;
				case 'h':	$maxage *= 3600; 	break;
				case 'd':	$maxage *= 86400; 	break;
				case 'w':	$maxage *= 604800; 	break;
				case 'y':	$maxage *= 31536000; 	break;
			}
		}
	}
	if ($privacy != 'no-cache') {
		header('Expires: '.gmdate("r", time()+$maxage));
	} else {
		header('Expires: '.gmdate("r", time()-31536000));
		$maxage = 0;
	}

	// Send ETag headers
	if ($etag !== false) {
		header('ETag: "'.$etag.'"');
	}

	// Determine wheter we need to send content or not
	$outputContent = true;

	// Check ETag
	if ((isset($_SERVER['HTTP_IF_NONE_MATCH'])) &amp;&amp; ($etag !== false)) {
		if ($_SERVER['HTTP_IF_NONE_MATCH'] == '"'.$etag.'"') {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	if ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) &amp;&amp; ($lastModified !== false)) {
		$ifModifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
		if ($ifModifiedSince &gt;= $lastModified) {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	// Remove content if output has been disabled
	if (!$outputContent) {
		// Return false: You don't need to send content
		return false;
	} else {
		// Send other headers
		header('Cache-Control: '.$privacy.', must-revalidate, post-check=0, pre-check=0, max-age='.$maxage);
		if ($privacy == 'no-cache') header('Pragma: no-cache');
		if ($lastModified !== false) header('Last-Modified: '.gmdate('r',$lastModified));

		// Return true: you need to send content
		return true;
	}
}</pre>
<h3>Conclusion</h3>
<p>The caching HTTP headers are simple to implement, and provide a huge performance bonus. If done properly, this convert into a better user experience, as there&#8217;s less waiting, and more browsing.</p>
<p>Shall you like me to add other scenarios, feel free to drop me a line and I&#8217;ll see what I can do.</p>
<h3>Further Reading</h3>
<ul>
<li><a title="Caching in HTTP, at W3" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html" target="_blank">Caching in HTTP, at W3</a></li>
<li><a title="HTTP Caching in Mozilla, at Mozilla" href="http://www.mozilla.org/projects/netlib/http/http-caching-faq.html" target="_blank">HTTP Caching in Mozilla, at Mozilla</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/17/http-caching-basics/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Super-charged Amazon SQS Sample in PHP</title>
		<link>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/</link>
		<comments>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/#comments</comments>
		<pubDate>Fri, 11 Jul 2008 20:00:58 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[example]]></category>
		<category><![CDATA[sample]]></category>
		<category><![CDATA[simple queue service]]></category>
		<category><![CDATA[sqs]]></category>
<category>amazon</category><category>example</category><category>php</category><category>sample</category><category>simple queue service</category><category>sqs</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=69</guid>
		<description><![CDATA[If you played with the pretty cool Amazon Web Services, you probably started off fetching a sample off the Developer&#8217;s Connection pages. At least, I did. One cool sample for Simple Queue Service is the one by Justin@AWS. But (you saw it coming, I suppose), being anti-PEAR (for many reason, such as dependencies, weight, version [...]]]></description>
			<content:encoded><![CDATA[<p>If you played with the pretty cool <a title="Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Amazon Web Services</a>, you probably started off fetching a sample off the <a title="Amazon Web Services Developer's Connection" href="http://developer.amazonwebservices.com/connect/index.jspa">Developer&#8217;s Connection pages</a>. At least, I did.</p>
<p>One cool sample for <a title="Simple Queue Service, at Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Simple Queue Service</a> is the one by <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">Justin@AWS</a>. But (you saw it coming, I suppose), being anti-<a title="PEAR - PHP Extension and Application Repository, at PHP.net" href="http://pear.php.net/" target="_blank">PEAR</a> (for many reason, such as dependencies, weight, version conflicts, non-catchiness of the name, fructose intolerance, etc.), I just couldn&#8217;t leave the sample like that. Yes, this is me again reinventing the wheel.</p>
<p>The original library depended on two PEAR components:</p>
<ul>
<li><a title="Crypt_HMAC PEAR package, at PHP.net" href="http://pear.php.net/package/Crypt_HMAC" target="_self"><code>Crypt_HMAC</code></a>, for authentication</li>
<li><a title="HTTP_Request PEAR package, at PHP.net" href="http://pear.php.net/package/HTTP_Request" target="_blank"><code>HTTP_Request</code></a>, for HTTP calls</li>
</ul>
<h3><span id="more-69"></span>The Crypt_HMAC replacement</h3>
<p>I replaced the <code>Crypt_HMAC</code> dependency by three potential routines: <a title="Hash PECL, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>hash PECL</code></a>, <a title="mhash, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>mhash</code></a>, and <a title="Lance's HMAC function, at PHP.net" href="http://ca.php.net/manual/en/function.mhash.php">Lance&#8217;s function</a> (which you will never hear in a Numb3r&#8217;s episode, as it has nothing to do with a mathematician, but rather with a smart dude who posted a fully PHP HMAC code snippet in the PHP comments of the <code>mhash()</code> function).</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * HMAC function using hash PECL (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_hash($stringToSign) {
	return base64_encode(pack('H*',hash_hmac('sha1', $stringToSign, $this-&gt;secretKey)));
}	

/**
 * HMAC function using mhash (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_mhash($stringToSign) {
	return base64_encode(mhash(MHASH_SHA1, $stringToSign, $this-&gt;secretKey));
}

/**
 * HMAC function using Lance's function (http://ca.php.net/manual/en/function.mhash.php, see comments)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_lance($stringToSign) {
    $b = 64;
	if (strlen($this-&gt;secretKey) &gt; $b) {
		$key = pack("H*",sha1($this-&gt;secretKey));
	} else {
		$key = $this-&gt;secretKey;
	}
	$key  = str_pad($key, $b, chr(0x00));
	$ipad = str_pad('', $b, chr(0x36));
	$opad = str_pad('', $b, chr(0x5c));
	$k_ipad = $key ^ $ipad ;
	$k_opad = $key ^ $opad;

	return base64_encode(pack('H*', sha1($k_opad  . pack("H*",sha1($k_ipad . $stringToSign)))));
}</pre>
<h3>HTTP_Request replacement</h3>
<p>I replaced the <code>HTTP_Request</code> dependency by two potential routines: cURL and URL wrappers.</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * Fetch with curl
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_curl($url, $qs, $post) {
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($curl, CURLOPT_HEADER, false);
	curl_setopt($curl, CURLOPT_POST, $post);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
	curl_setopt($curl, CURLOPT_URL, $url . (!$post ? '?'.$qs : ''));
	if ($post) {
		curl_setopt($curl, CURLOPT_POSTFIELDS, $qs);
	}

	// Execute
	$output = array();
	$output[0] = curl_exec($curl);
	$output[1] = curl_getinfo($curl,CURLINFO_HTTP_CODE);

	// Close handle
	curl_close($curl);

	return $output;
}

/**
 * Fetch with urlwrappers
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_urlwrappers($url, $qs, $post) {
	$output = array();

	if ($post) {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"POST",
		    'header'=&gt;"Content-type: application/x-www-form-urlencoded\r\n" .
		              "Content-length: " . strlen($qs),
		    'content'=&gt;$qs
		  )
		);
		$context = stream_context_create($opts);
	} else {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"GET"
		  )
		);
		$context = stream_context_create($opts);
		$url .= '?'.$qs;
	}

	$output[0] = '';
	$f = @fopen($url,'r',null,$context);
	if (!$f) {
		$output[0] = '';
		$output[1] = 404;
		return $output;
	}
	while (!feof($f)) {
		$output[0] .= fread($f,1024);
	}
	$meta_data = stream_get_meta_data($f);
	if (preg_match('/^HTTP\/1\.[01] ([0-9]{3})/',$meta_data['wrapper_data'][0],$m)) {
		$output[1] = $m[1];
	} else {
		$output[1] = false;
	}
	fclose($f);

	$output[2] = 'wrappers';

	return $output;
}</pre>
<h3>Detection and conclusion</h3>
<p>Finally, the constructor includes a small detection mechanism that selects an HMAC and a fetching function:</p>
<pre class="code">/**
 * Constructor
 *
 * @author Justin@AWS
 * @author Tommy Lacroix
 * @param string $accessKey
 * @param string $secretKey
 * @param string $endpoint	http://queue.amazonaws.com
 * @param string $queueName	optional
 * @return SQSClient
 */
public function SQSClient($accessKey, $secretKey, $endpoint = 'http://queue.amazonaws.com', $queueName = '')
{
	.
	.
	.

	// Select fetch function
	// Call it with $this-&gt;{$this-&gt;fetchFunction}(...)
	if (function_exists('curl_init')) $this-&gt;fetchFunction = 'fetch_curl';
		else $this-&gt;fetchFunction = 'fetch_urlwrappers';

	// Select hash function
	// Call it with $this-&gt;{$this-&gt;hmacFunction}(...)
	if (function_exists('hash_hmac')) {
		$this-&gt;hmacFunction = 'hmac_hash';
	} else if (function_exists('mhash')) {
		$this-&gt;hmacFunction = 'hmac_mhash';
	} else if (class_exists('Crypt_HMAC')) {
		$this-&gt;hmacFunction = 'hmac_pear';
	} else {
		$this-&gt;hmacFunction = 'hmac_lance';
	}

	.
	.
	.
}</pre>
<p>The complete modified sqsClient package can be downloaded below. The original Justin@AWS package is <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">available here</a>.</p>
<p><a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip" >sqsClientPHP-2.1.zip</a> <em>(11k)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP based FLV meta and cue points reader/writer</title>
		<link>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/</link>
		<comments>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/#comments</comments>
		<pubDate>Fri, 04 Jul 2008 21:30:26 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[cue point]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[meta data]]></category>
<category>cue point</category><category>flash</category><category>flv</category><category>meta data</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=63</guid>
		<description><![CDATA[Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated. Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool. Update (Jun 11, 2009): The [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone size-medium wp-image-68" style="float:left; margin: 0 8px 0 8px;" title="Flash Video" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/64px-flashvideo.png" alt="" width="64" height="64" /><strong>Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated.</strong></p>
<p><strong>Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool.</strong></p>
<p><strong>Update (Jun 11, 2009): The project has been moved to </strong><a title="php-flvinfo at Google Code" href="http://code.google.com/p/php-flvinfo/" target="_blank"><strong>Google Code</strong></a><strong>. MP4Info, an F4V file (or more genericaly MP4 file) class is also available: check out <a title="PHP based MP4/F4V meta data reader" href="http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/" target="_self">blog post</a></strong><strong> and project at <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">Google Code</a></strong><strong>.</strong></p>
<p>If you&#8217;ve done Flash banners or micro-sites that embed video with cue points for synchronization before, you know that it&#8217;s a pain in the arse. There doesn&#8217;t seem to be any tool around to modify the damn points once the file is encoded, so you&#8217;ve got to re-encode the file over and over to have your things synced correctly.</p>
<p>If you&#8217;ve tried to reverse engineer the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flv" target="_blank">FLV format</a> before, you know that it&#8217;s a pain in the arse as well. The AMF0 format is anything but intuitive, and the documentation has been lacking for a long long time &#8212; although there&#8217;s apparently an SDK now. Luckily, there was <a title="OSFlash" href="http://osflash.org/" target="_blank">OSFlash</a>, and <a title="SabreAMF PHP Library, at Google Code" href="http://sabreamf.googlecode.com/" target="_blank">SabreAMF</a> and <a title="AMFPHP library" href="http://osflash.org/projects/amfphp" target="_blank">AMFPHP</a> that could be used as a documentation source.</p>
<p>If you recognize yourself, this might be your lucky day. After many hours of trial and error, I&#8217;ve finally been able to reverse engineer it, and to build a library that extract essential FLV information, reads the Meta, and allows you to write them back &#8212; including the cue points.</p>
<p><span id="more-63"></span></p>
<p>First, you&#8217;ll need to FLVInfo2 and the AMF0Parser libraries, below. The FLVInfo2 library has three main functions:</p>
<ul>
<li><code>getInfo</code>, which returns information gotten from analyzing the FLV file and it&#8217;s meta. Frame rate, bit rate, audio/video codec, width, height, etc.
<pre class="code">(
    [signature] =&gt; 1
    [hasVideo] =&gt; 1
    [hasAudio] =&gt; 1
    [minimalFlashVersion] =&gt; 8
     =&gt; stdClass Object
        (
            [codec] =&gt; 4
            [width] =&gt; 320
            [height] =&gt; 240
            [keyframeRatio] =&gt; 0.0180537208278
            [keyframeEvery] =&gt; 55.3902439024
            [fps] =&gt; 15
            [bitrate] =&gt; 448
            [codecStr] =&gt; On2 VP6
        )

    [audio] =&gt; stdClass Object
        (
            [codec] =&gt; 2
            [frequency] =&gt; 22
            [depth] =&gt; 16
            [channels] =&gt; 2
            [bitrate] =&gt; 48
            [codecStr] =&gt; MP3
        )
)</pre>
</li>
<li><code>getMeta</code>, which returns the meta data, and the cue points
<pre class="code">Array
(
    [metas] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onMetaData
                    [1] =&gt; Array
                        (
                            [duration] =&gt; 151.46
                            [width] =&gt; 320
                            [height] =&gt; 240
                            [videodatarate] =&gt; 500
                            [canSeekToEnd] =&gt; 1
                            [videocodecid] =&gt; 4
                            [audiodatarate] =&gt; 48
                            [audiocodecid] =&gt; 2
                            [framerate] =&gt; 15
                            [creationdate] =&gt; Tue Jun 17 13:06:15 2008
                            [Encoded_By] =&gt; orangetango Video Encoder
                            [Encoded_With] =&gt; orangetango Video Encoder
                            [metadatacreator] =&gt; orangetango FLV meta data writer
                        )
                )
        )
    [cuepoints] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1
                            [time] =&gt; 4.41
                            [type] =&gt; event
                        )
                )
            [1] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1_end
                            [time] =&gt; 6.7
                            [type] =&gt; event
                        )
                )
        )
)</pre>
</li>
<li><code>rewriteMeta</code>, which inputs an FLV file, meta data and cue points, and outputs a new FLV file.</li>
</ul>
<p>As a practical example, you find the <code>rewriteTagsForFile</code> function in the <code>example.php</code> file, that rewrites the meta data and gets the cue points from a FLVCoreCuePoints XML file.</p>
<p><a title="FLVInfo2 class, reads and writes FLV meta data from a Flash Video File." href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;">flvinfo2.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(20k , updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;">AMF0Parser.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(16k, updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;">example.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(12k)</span></em><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><br />
</a></p>
<p><small>Flash Video logo found on <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video">Wikipedia</a>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>CodeIgniter 1.6.x OpenID module</title>
		<link>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/</link>
		<comments>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/#comments</comments>
		<pubDate>Wed, 25 Jun 2008 12:42:22 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Social networks]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[ciopenid]]></category>
		<category><![CDATA[codeigniter]]></category>
		<category><![CDATA[openid]]></category>
<category>authentication</category><category>ciopenid</category><category>codeigniter</category><category>openid</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=61</guid>
		<description><![CDATA[I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet). I&#8217;ve stumbled upon Rémi Prévost &#8212; who&#8217;s blog a follow [...]]]></description>
			<content:encoded><![CDATA[<p>I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet).</p>
<p>I&#8217;ve stumbled upon <a title="Rémi Prévost CIOpenID module" href="http://remiprevost.com/2007/10/codeigniter-openid-1a" target="_blank">Rémi Prévost &#8212; who&#8217;s blog a follow more or less consistently due to the lack of time &#8212; CIOpenID module</a>. It&#8217;s basically a CI embed of <a title="Janrain's PHP_OpenID" href="http://openidenabled.com/php-openid/" target="_blank">Janrain&#8217;s PHP_OpenID</a>.</p>
<p>His code is great and works well. But one thing I don&#8217;t like about it is that it requires a different bootstrap file (a modified version of <code>index.php</code>) and a somewhat hacked version of your typical <code>.htaccess</code> file. The reason is, CodeIgniter annihilates the <code>$_GET</code> variable during initialization, because GET queries aren&#8217;t secure (ok, this is overly simplified, but you get the idea).</p>
<p>Being who I am, and somewhat liking to reinvent the wheel during my free time, I used a different approach. Rather than using a different bootstrap file, I rather chose to use the <code>pre_system</code> hook.</p>
<p><span id="more-61"></span></p>
<p>The installation process is quite simple:</p>
<ol>
<li>Put my <code>ciopenid.php</code> library in your <code>/system/application/libraries</code> directory.</li>
<li>Create <code>/system/application/libraries/openid</code>, and unzip PHP_OpenID 2.x.x&#8217;s <code>Auth</code> directory in it.</li>
<li>Open your <code>/system/application/config/config.php</code> file, and make sure that hooks are enabled:
<pre class="code">$config['enable_hooks'] = TRUE;</pre>
</li>
<li>Open your <code>/system/application/config/hooks.php</code> file, and add the following lines:
<pre class="code">$hook['pre_system'][] = array(
   'class'    =&gt; 'ciopenid',
   'function' =&gt; 'pre_system_hook',
   'filename' =&gt; 'ciopenid.php',
   'filepath' =&gt; 'libraries',
   'params'   =&gt; null
   );</pre>
</li>
</ol>
<p>To make an authentication query to an OpenID URL in <code>$openid</code>:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
try {
   $redirect_url = $this-&gt;ciopenid-&gt;authenticate($openid, site_url($this-&gt;uri-&gt;uri_string()));
   header("Location: ".$redirect_url);
} catch (Exception $e) {
   die('Error: '.$e-&gt;getMessage());
}</pre>
<p>And to get the OpenID query response:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
if (isset($this-&gt;ciopenid-&gt;response)) {
   switch ($this-&gt;ciopenid-&gt;response-&gt;status) {
      case Auth_OpenID_SUCCESS:
         $openid = $this-&gt;ciopenid-&gt;response-&gt;endpoint-&gt;claimed_id;
         print 'Success: '.$openid;
         break;
      case Auth_OpenID_CANCEL:
         print 'User cancelled';
         break;
      default:
         print 'Internal error';
         break;
   }
}</pre>
<p>That&#8217;s it! Ooops, here&#8217;s the file&#8230;</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip"><img style="border: 0pt none;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source (zipped)" width="16" height="16" /></a> <a title="CIOpenID library, 1.0" href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip">ciopenid.zip (3k)<br />
</a></p>
<p>Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>JavaScript database with CRUD interface: TaffyDB</title>
		<link>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/</link>
		<comments>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/#comments</comments>
		<pubDate>Wed, 12 Mar 2008 14:07:20 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
<category>AJAX</category><category>database</category><category>JavaScript</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/</guid>
		<description><![CDATA[As the post title says, TaffyDB is a JavaScript database with a CRUD &#8212; Create, Read, Update, Delete &#8212; interface. Object oriented, under 10K and compatible with most AJAX frameworks around, TaffyDB is a cool tool to bring your AJAX applications to the next level. The object usage is quite intuitive&#8230; see for yourself: products.find({price:{lessthan:10}, [...]]]></description>
			<content:encoded><![CDATA[<p>As the post title says, <a href="http://taffydb.com/" title="TaffyDB: A JavaScript database with a CRUD interface">TaffyDB</a> is a JavaScript database with a CRUD &#8212; Create, Read, Update, Delete &#8212; interface. Object oriented, under 10K and compatible with most AJAX frameworks around, <a href="http://taffydb.com/" title="TaffyDB: A JavaScript database with a CRUD interface">TaffyDB</a> is a cool tool to bring your AJAX applications to the next level.</p>
<p>The object usage is quite intuitive&#8230; see for yourself:</p>
<pre class="code">products.find({price:{lessthan:10},
              type:{not:"Book"}});

products.update({status:"NA"},
                {manufacturer:"XZYDesign"});

products.orderBy(
   ["type",{"price":"asce"},{"quantity":"asce"}]);</pre>
<p>I&#8217;m definitely going to find a use for this in a project soon. Meanwhile, I&#8217;m still messing with it to seize its full power. Enjoy&#8230;!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tips and tricks for accessible stylish HTML controls</title>
		<link>http://www.tommylacroix.com/2008/02/29/accessible-stylish-controls/</link>
		<comments>http://www.tommylacroix.com/2008/02/29/accessible-stylish-controls/#comments</comments>
		<pubDate>Fri, 29 Feb 2008 16:25:14 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[accessibility]]></category>
		<category><![CDATA[codeigniter]]></category>
		<category><![CDATA[helper]]></category>
		<category><![CDATA[javascript]]></category>
<category>accessibility</category><category>codeigniter</category><category>css</category><category>html</category><category>javascript</category><category>usability</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/02/29/accessible-stylish-controls/</guid>
		<description><![CDATA[A friend of mine was twittering about accessibility, which gave me the idea to write on how to do no-javascript friendly stylish controls such as check boxes, buttons, and radio buttons. Because graphic designers care more about the look and site designers care more about accessibility and usability, it&#8217;s often the developers&#8217; role to satisfy [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.tommylacroix.com/wp-content/uploads/2008/02/accessibility.gif" alt="Accessibility" style="float: right; margin-left: 5px; margin-bottom: 5px" />A friend of mine was <a href="http://www.twitter.com" title="Twitter" target="_blank">twittering</a> about accessibility, which gave me the idea to write on how to do <a href="http://www.tommylacroix.com/demos/stylishcontrols/" title="No-javascript friendly Stylish Controls Demo Page">no-javascript friendly stylish controls</a> such as check boxes, buttons, and radio buttons.</p>
<p>Because graphic designers care more about the look and site designers care more about accessibility and usability, it&#8217;s often the developers&#8217; role to satisfy them both. So here are the tricks I use to make stylish controls look cool when JavaScript is activated, and usable when it is not.</p>
<p>Being mainly a PHP backend programmer, I hate to see the design cripple the code. Therefore, I always make sure that stylish and standard controls end up sending the same data to the server.</p>
<p>In this post, I cover <a href="#buttons" title="Stylish buttons">buttons</a>, <a href="#checkboxes" title="Stylish check boxes">check boxes</a> and <a href="#radios" title="Stylish radio buttons">radio buttons</a>, and provide a <a href="#helper" title="A simple PHP helper to generate stylish check boxes and radio buttons code">simple PHP helper</a> that automatically generates the code. You might also want to check out the <a href="http://www.tommylacroix.com/demos/stylishcontrols/" title="No-javascript friendly Stylish Controls Demo Page" target="_blank">demo page</a> to have an idea of what it looks like.</p>
<p><span id="more-45"></span></p>
<h2><a title="buttons" name="buttons"></a>Buttons</h2>
<p>Without a doubt, this is the easiest one. We usually use an <em>A</em> tag with an <em>on-click</em> or <em>javascript: URL</em> event to show buttons when JavaScript is enabled. The reason is: the input-image tag results in drawing tiny dashed borders upon click, and that kind of kills the nice shadow effects.</p>
<p>Note that I use the CSS image cropping technique, well described by the guys at <a href="http://uxd.forumone.com/archives/149-Primary-Navigation-Image-Replacement.html" title="CSS image cropping technique" target="_blank">UXD </a>(or in french at <a href="http://www.quantiksolutions.com/fr/articles/astuce-html-des-balises-h1-et-image-indexables.html" title="CSS image cropping technique" target="_blank">Quantik Solutions</a>).<br />
So the typical JavaScript code for a button would be (I compacted the style sheet because it&#8217;s pretty long) :</p>
<pre class="code"><code>&lt;style type="text/css"&gt;
.button {
 border: 0; margin: 0; padding: 3px 0 0 0;
 background: transparent url(btn_bg.gif) no-repeat 0 0;
 display:block; width: 146px; height: 28px;
 overflow: hidden; font-family: Arial, Helvetica, sans-serif;
 text-decoration: none; font-weight: bold; color: black;
 outline: none; text-align: center;
}

.button:hover {
 background: transparent url(btn_bg.gif) no-repeat 0 -54px;
}

.button:active {
 background: transparent url(btn_bg.gif) no-repeat 0 -103px;
}
&lt;/style&gt;

&lt;h1&gt;Button&lt;/h1&gt;
&lt;a class="button" href="javascript:submitForm('formName');"
    title="Send"&gt;Send&lt;/a&gt;</code></pre>
<p>The no-JavaScript code could be simpler:</p>
<pre class="code">&lt;h1&gt;Button&lt;/h1&gt;
&lt;input type="submit" name="submit" value="Send" /&gt;</pre>
<p>Finally, the JavaScript/no-JavaScript condition display is done using <em>document.write</em> and the <em>noscript</em> tag.</p>
<pre class="code">&lt;h1&gt;Button&lt;/h1&gt;
&lt;script type="text/javascript"&gt;
document.write('&lt;a class="button" href="javascript:submitForm(\'formName\');"
    title="Send"&gt;Send&lt;/a&gt;');
&lt;/script&gt;
&lt;noscript&gt;&lt;input type="submit" name="submit" value="Send" /&gt;&lt;/noscript&gt;</pre>
<h2><a title="checkboxes" name="checkboxes"></a>Check boxes</h2>
<p>This one is a little more complex, but not as much as radio buttons (which I kept for dessert). It is basically the same principle as the button, except that JavaScript required to control the checking and unchecking action when in stylish mode (while this was being done with CSS for the buttons).</p>
<p>First, we define a simple CSS for checked and unchecked states:</p>
<pre class="code">&lt;style type="text/css"&gt;
.checked {
 background-color: black;
 width: 20px;
 display: inline-block;
 text-decoration: none;
 padding-left: 20px;
}
.unchecked {
 background-color: silver;
 width: 20px;
 display: inline-block;
 text-decoration: none;
 padding-left: 20px;
}
&lt;/style&gt;</pre>
<p>Then we create the check box, the stylish one first in JavaScript, and the standard one right after in a <em>noscript</em> tag.</p>
<pre class="code">&lt;script type="text/javascript" language="javascript"&gt;
// Write the clickable A tag
document.write('&lt;a id="checkbox1" href="#"
    onclick="toggleStylishCheckbox(\'checkbox1\', \'val\', \'checked\', \'unchecked\');"
    class="unchecked"&gt;&lt;/a&gt;');
// Write the hidden input tag
document.write('&lt;input type="hidden" name="checkbox1" id="checkbox1_val" value="" /&gt;');
&lt;/script&gt;
&lt;noscript&gt;&lt;input type="checkbox" name="test1" value="val" /&gt;&lt;/noscript&gt;
Check box label&lt;br /&gt;</pre>
<p>Finally, we need to include the control code that enables or disables the stylish check box, called by the <em>on-click</em> event.</p>
<pre class="code"><code>&lt;script type="text/javascript" language="javascript"&gt;
function toggleStylishCheckbox(id, value, classOn, classOff) {
  if (document.getElementById(id+"_val").value != "") {
    document.getElementById(id+"_val").value = "";
    document.getElementById(id).className = classOff;
  } else {
    document.getElementById(id+"_val").value = value;
    document.getElementById(id).className = classOn;
  }
}&lt;/script&gt;</code></pre>
<h2><a title="radios" name="radios"></a>Radio buttons</h2>
<p>This one is the trickiest: you need JavaScript to also to uncheck the previously checked control when checking a new one. Thus, we need to keep track of which group each radio buttons are, so we can quickly access its siblings.</p>
<p>The script goes pretty much as the check boxes one, but with a twist.You first need to define a checked and unchecked class for your radio buttons. You can use the same one as for check boxes.</p>
<p>Then we create the radio button, the stylish one first in JavaScript, and the standard one right after in a <em>noscript</em> tag.</p>
<pre class="code"><code>&lt;script type="text/javascript" language="javascript"&gt;
// Register the button in the group
var radioGroup_1;
if (radioGroup_1 == undefined) {
	// This is the first element, create the group
	radioGroup_1 = ["stylishRadio_1"];
} else {
	// This is not the first element, add to the group
	radioGroup_1.push("stylishRadio_1");
}

// Write the actual button code
document.write('&lt;a id="stylishRadio_1" href="#"
    onclick="toggleStylishRadio(\'stylishRadio_1\', \'radioGroup_1\', \'1\', \'checked\', \'unchecked\');"
    class="checked"&gt;&lt;/a&gt;');

// Create an input hidden for the group if it doesn't exist
if (!document.getElementById("radioGroup_1")) {
	document.write('&lt;input type="hidden" name="set1" id="radioGroup_1" value="1" /&gt;');
}
&lt;/script&gt;

&lt;noscript&gt;
&lt;input type="radio" name="set1" value="1"checked="checked" /&gt;
&lt;/noscript&gt;
Set 1, Option 1&lt;br /&gt;
</code></pre>
<p>Finally, we need to include the control code that enables or disables the stylish radio buttons and their siblings, called by the <em>on-click</em> events.</p>
<pre class="code"><code>&lt;script type="text/javascript" language="javascript"&gt;
function toggleStylishRadio(id, groupName, value, classOn, classOff) {
  // Get the group
  var group = eval(groupName);

  // Process each radio button within it
  for (var i=0;i&lt;group.length;i++) {
    // Get the class for this button
    var myClass;
    if (group[i] == id) {
      myClass=classOn;
    } else {
      myClass=classOff;
    }

    // Set the class
    document.getElementById(group[i]).className = myClass;
  }

  // Update the hidden input
  document.getElementById(groupName).value=value;
}
&lt;/script&gt;
</code></pre>
<h2><a title="helper" name="helper"></a>PHP helper</h2>
<p>As you probably noticed, there is a lot of repetitive markup one need to include for every single check box or radio button. Duplication and maintenance is a pain, so why not let a PHP script do it?</p>
<p>Here&#8217;s the code for a PHP helper that handles the check boxes and the radios buttons automagically. This helper has been made for <a href="http://www.codeigniter.com/" title="CodeIgniter, Rapid Develpment PHP Framework" target="_blank">CodeIgniter</a>, but can be used without it.</p>
<p>I wrote it as a demonstration, and there are many improvements to do. Including the JavaScript control scripts in a separate JavaScript file, for instance, and wrapping check box and radio buttons creation into a function to reduce the amount of repeated JavaScript code. Nonetheless, it works and can be useful as is for simple projects.</p>
<p>Also note that I did no helper for the buttons, as it is pretty simple, and the HTML markup varies a lot from project to project.</p>
<p><img src="/img/icons/script_code.gif" alt="PHP Script" border="0" height="16" width="16" /> <a href="http://www.tommylacroix.com/demos/stylishcontrols/stylishControls_helper.phps" title="No-javascript friendly stylish controls PHP helper " target="_blank">stylishControls_helper.phps</a> (6 KB)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/02/29/accessible-stylish-controls/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

