Content with Style2011-03-19T21:27:58+00:00http://www.contentwithstyle.co.uk/Genstatic, my first sip of coffee2011-03-06T06:16:54+00:00http://www.contentwithstyle.co.uk/content/genstatic-my-sip-try-of-coffee/genstatic-my-sip-try-of-coffee<p>
Having to spend more time at home means I can try out things. I know I'm late to the party, but I really wanted to have a go at using <a href="http://jashkenas.github.com/coffee-script/">coffeescript</a>. Sceptical at first, I really got to like it and quickly threw together a little static site generator: <a href="https://github.com/pascalopitz/genstatic">genstatic</a>.
</p>
<p>
I had seen .coffee files in many node projects lately, but it was a little <a href="http://twitter.com/#!/pascalopitz/status/41121018554753024">twitter converstaion</a> with our own Mike Stenhouse of course that finally brought me to taking a closer look.
</p>
<p>
I had to do a pretty tedious static HTML job that didn't fit the bog standard blog structure, so I decided to roll my little static site generator, and that's how genstatic came about.</p>
<h2>Coffescript creates Javascript</h2>
<p>The first way I used coffee scrip was to compile it down to javascript, then run javascript in node. For this I'd have a a folder called <code>./lib</code> and a folder called <code>./src</code>. Then you run:</p>
<pre><code>
coffee -w -c -o ./lib ./src
</code></pre>
<p>
This will watch the files in <code>./src</code> and automatically compile them down to js into the <code>./lib</code> folder.
</p>
<p>
I figure that with today's requirement of minification and concatination most people have to have some sort of compilation step in their deployment process anyway. Using coffee script could also be used in that. The <code>-j</code> flag will join scripts together for you as well, so in that respect it might make testing even easier.
</p>
<h2>Coffescript in node</h2>
<p>While the above is great, in node we even can use coffeescript at runtime:</p>
<pre><code>
var coffee = require('coffee-script');
require('my.coffee');
str = coffee.compile(mystr);
</code></pre>
<p>This is very useful of course, and I have made good use of it in genstatic where you define meta stuff and helpers in coffeescript as well. These will then be read into strings and executed via node's brilliant <code>vm.runInNewContext()</code>.</p>
<h2>Wrapping it up</h2>
<p>Having this little go my initial scepticism vanished pretty quickly and I think I'll use it from now on where ever I can.</p>
<p>If you want to see coffeescript in action, go to the <a href="http://jashkenas.github.com/coffee-script/">coffeescript project pages</a> or clone the git of <a href="https://github.com/pascalopitz/genstatic">genstatic</a>.</p>
<p>Of course if you need to generate a static site, maybe give genstatic a go and let me know how it worked for you. A simple <code>npm install genstatic</code> and you should be on your way.</p>
<p>So there you have it: genstatic, my first sip of coffee</p>Express.js Route Middleware2011-01-18T09:12:07+00:00http://www.contentwithstyle.co.uk/content/expressjs-route-middleware/expressjs-route-middleware<p>I have just read the about the <a href="http://expressjs.com/guide.html#Route-Middleware">Route Middleware</a> of <a href="http://expressjs.com">express.js</a>, and it already helped me to get rid of about 100 lines of code.</p>
<p>This approach is really useful for managing permission and global objects, but at the same time so much more neat than the init() method in the Zend Framework Controllers.</p>
<p>Lovin it!</p>buggy behaviour of parent:: in PHP 5.3.32010-12-18T18:26:09+00:00http://www.contentwithstyle.co.uk/content/behaviour-of-parent-in-php-533/behaviour-of-parent-in-php-533<p>So, this app I hadn't been looking at in a few months did not work at all. I traced the bug down to a method that itself called a parent method. The parent only contains __call and __callStatic methods, and for some reason __callStatic was called, although the class it was called from was an object instance.</p>
<p>To isolate the case I used the code example <a href="http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods">provided in the PHP manual</a>, extended it and ooo'd and ahh'd at the result.</p>
<pre>
<?php
class MethodTest {
public function __call($name, $arguments) {
// Note: value of $name is case sensitive.
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments) {
// Note: value of $name is case sensitive.
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
class MethodTestExtended extends MethodTest {
public function runExtendedTest($var){
parent::runTest($var);
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
//Calling object method 'runTest' in object context
MethodTest::runTest('in static context');
//Calling static method 'runTest' in static context
$obj = new MethodTestExtended;
$obj->runTest('in object context');
//Calling object method 'runTest' in object context
MethodTestExtended::runTest('in static context');
//Calling static method 'runTest' in static context
$obj = new MethodTestExtended;
$obj->runExtendedTest('in object context');
// PHP 5.3.3: Calling object method 'runTest' in static context
// __callStatic is called, and $this is not available.
// PHP 5.3.4: Calling object method 'runTest' in object context
// __call is called, and $this is available.
MethodTestExtended::runExtendedTest('in static context');
//Calling static method 'runTest' in static context, with a
//Strict Standards warning, though (as the method isn't declared static)
</pre>
<p>The call is always routed via __callStatic, when called from within the extending class, regardless if it's in a static or object context, but not so when called directly.</p>
<p>Not believing my eyes, I started searching more creatively, and found that I was not the only one complaining about it; and luckily <a href="http://www.serverphorums.com/read.php?7,220473">the right people thought so, too</a>.</p>
<p>It so turns out that this behaviour was introduced in PHP 5.3.3, as part of another <a href="http://bugs.php.net/51176">bug dealing with the opposite desired behaviour</a> (calling class methods statically when in an object context), and removed again in PHP 5.3.4, smelling <a href="http://bugs.php.net/52713">a little bit like a bug</a>. A simple sudo port upgrade php5 did the job, bumping my install up to 5.3.4, and everything worked as intended. Yay!</p>merry xmas everyone v0.22010-12-08T14:04:20+00:00http://www.contentwithstyle.co.uk/content/merry-xmas-everyone-v02/merry-xmas-everyone-v02<p>
Before the year will come to an end and we're all off to stuff ourselves with chocolate, I just wanted to wish everyone a <a href="javascript:(function(){var k=document.getElementsByTagName('body')[0];var i=document.createElement('canvas');var p=i.getContext('2d');var j,g=150,o=1,l,f,d;i.style.cursor='pointer';i.style.zIndex='999999';i.style.top=0;i.style.left=0;i.style.position='absolute';k.appendChild(i);i.onclick=function(){i.parentNode.removeChild(i)};window.onresize=function(){window.clearTimeout(d);n()};function n(){l=window.innerWidth-20;f=window.innerHeight-20;i.setAttribute('width',l);i.setAttribute('height',f);j=[];for(var b=0;b!=g;b++){j.push({x:Math.random()*l,y:((Math.random()*f)-o),})}a()}function e(b,c){p.fillStyle='#ffffff';p.beginPath();p.arc(b,c,5,0,Math.PI*2,true);p.closePath();p.fill()}function m(b,c){p.fillStyle='white';p.strokeStyle='black';p.globalAlpha='1.0';p.lineWidth='1';p.lineCap='butt';p.lineJoin='round';p.mitterLimit='1';p.font='normal normal 12 Courier';p.fillStyle='#cf7122';p.beginPath();p.moveTo(190.5+b,110+c);p.bezierCurveTo(212.76+b,137.04+c,228.7+b,183.28+c,201+b,214.5+c);p.bezierCurveTo(177.76+b,240.7+c,137.26+b,243.54+c,115+b,216.5+c);p.bezierCurveTo(92.74+b,189.46+c,95.24+b,144.54+c,117.5+b,117.5+c);p.bezierCurveTo(139.76+b,90.46+c,168.24+b,82.96+c,190.5+b,110+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#dd381b';p.beginPath();p.moveTo(164+b,125+c);p.bezierCurveTo(189.77+b,161.32+c,206.77+b,222.68+c,181+b,259+c);p.bezierCurveTo(155.23+b,295.32+c,77.77+b,296.32+c,52+b,260+c);p.bezierCurveTo(26.23+b,223.68+c,36.21+b,158.1+c,66+b,125+c);p.bezierCurveTo(93+b,95+c,138.23+b,88.68+c,164+b,125+c);p.closePath();p.fill();p.fillStyle='#f8d5a1';p.beginPath();p.moveTo(48+b,169.5+c);p.bezierCurveTo(52.06+b,174.75+c,44.75+b,178+c,45.82+b,190.33+c);p.bezierCurveTo(46.7+b,200.46+c,38.15+b,193.63+c,33.92+b,188.06+c);p.bezierCurveTo(29.69+b,182.48+c,29.69+b,173.45+c,33.92+b,167.88+c);p.bezierCurveTo(38.15+b,162.3+c,43.94+b,164.25+c,48+b,169.5+c);p.closePath();p.fill();p.beginPath();p.moveTo(184.25+b,209+c);p.bezierCurveTo(179.33+b,204.25+c,187.98+b,205.19+c,186.25+b,190.5+c);p.bezierCurveTo(184.87+b,178.73+c,189.51+b,181.16+c,193.03+b,186.04+c);p.bezierCurveTo(196.56+b,190.93+c,197.1+b,199.29+c,194.24+b,204.71+c);p.bezierCurveTo(191.38+b,210.14+c,189.17+b,213.75+c,184.25+b,209+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#ffd5a2';p.beginPath();p.moveTo(142.82+b,38.47+c);p.bezierCurveTo(160.39+b,56.44+c,160.39+b,85.56+c,142.82+b,103.53+c);p.bezierCurveTo(125.25+b,121.49+c,96.75+b,121.49+c,79.18+b,103.53+c);p.bezierCurveTo(61.61+b,85.56+c,61.61+b,56.44+c,79.18+b,38.47+c);p.bezierCurveTo(96.75+b,20.51+c,125.25+b,20.51+c,142.82+b,38.47+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.strokeStyle='#000000';p.lineWidth='1.000000';p.lineJoin='miter';p.beginPath();p.moveTo(-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.closePath();p.stroke();p.fillStyle='#4f4f4f';p.lineWidth='1';p.lineJoin='round';p.beginPath();p.moveTo(100.03+b,54.81+c);p.bezierCurveTo(101.59+b,56.42+c,101.59+b,59.03+c,100.03+b,60.64+c);p.bezierCurveTo(98.47+b,62.25+c,95.93+b,62.25+c,94.37+b,60.64+c);p.bezierCurveTo(92.81+b,59.03+c,92.81+b,56.42+c,94.37+b,54.81+c);p.bezierCurveTo(95.93+b,53.2+c,98.47+b,53.2+c,100.03+b,54.81+c);p.closePath();p.fill();p.beginPath();p.moveTo(128.83+b,54.81+c);p.bezierCurveTo(130.39+b,56.42+c,130.39+b,59.03+c,128.83+b,60.64+c);p.bezierCurveTo(127.27+b,62.25+c,124.73+b,62.25+c,123.17+b,60.64+c);p.bezierCurveTo(121.61+b,59.03+c,121.61+b,56.42+c,123.17+b,54.81+c);p.bezierCurveTo(124.73+b,53.2+c,127.27+b,53.2+c,128.83+b,54.81+c);p.closePath();p.fill();p.fillStyle='#ffffff';p.beginPath();p.moveTo(135.06+b,89.53+c);p.bezierCurveTo(142.85+b,107.86+c,130.06+b,138.27+c,123.51+b,157.06+c);p.bezierCurveTo(110.97+b,192.98+c,111.61+b,192.98+c,95.27+b,157.06+c);p.bezierCurveTo(87.02+b,138.91+c,77.83+b,108.48+c,85.62+b,90.14+c);p.bezierCurveTo(93.42+b,71.81+c,127.26+b,71.19+c,135.06+b,89.53+c);p.closePath();p.fill();p.fillStyle='#4f4f4f';p.beginPath();p.moveTo(117.93+b,97.68+c);p.bezierCurveTo(122.03+b,95.92+c,122.03+b,93.08+c,117.93+b,91.32+c);p.bezierCurveTo(113.82+b,89.56+c,107.18+b,89.56+c,103.07+b,91.32+c);p.bezierCurveTo(98.97+b,93.08+c,98.97+b,95.92+c,103.07+b,97.68+c);p.bezierCurveTo(107.18+b,99.44+c,113.82+b,99.44+c,117.93+b,97.68+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#dc381b';p.beginPath();p.moveTo(127.25+b,12.09+c);p.bezierCurveTo(146.48+b,27.35+c,158.99+b,59.74+c,147+b,52.5+c);p.bezierCurveTo(123+b,38+c,92+b,40+c,72.5+b,54.5+c);p.bezierCurveTo(65.64+b,59.6+c,77+b,29.5+c,89.5+b,16+c);p.bezierCurveTo(103.44+b,0.94+c,109.5+b,-2+c,127.25+b,12.09+c);p.closePath();p.fill()}function a(){p.clearRect(0,0,l,f);m((l/2)-120,(f/2)-150);for(var b in j){var c=j[b];c.y+=o;if(c.y>f){c.y=-10;c.x=Math.random()*l}e(c.x,c.y)}d=window.setTimeout(a,20)}n()})();">merry merry christmas</a>.
</p>
<p>Use the above link as bookmarklet, if you want. If you wonder how this works, here's a <a href="https://gist.github.com/733311">gist of the non-compressed version</a></p>jQuery clickable percentage bars2010-12-03T11:50:10+00:00http://www.contentwithstyle.co.uk/content/jquery-clickable-percentage-bars/jquery-clickable-percentage-bars<p>Just a little script I have been coding up to deal with input fields that represent a percentage value. See <a href="http://www.contentwithstyle.co.uk/resources/percentage/index.html">the demo</a> or get the <a href="https://github.com/pascalopitz/jquery-percentage">code on github</a>. Enjoy.</p>
<p><strong>Update</strong>: I modified the code and moved it from a gist into a public repo on <a href="https://github.com/pascalopitz/jquery-percentage">github</a>.</p>
<pre><code>
(function(){
var _self = this;
function render(conf, val) {
var elems = this;
if(conf == 'destroy') {
$(elems).each(function() {
$(this).show();
if(this.percentage) {
$(this.percentage).remove();
}
})
return elems;
}
var defaults = {
width: 200,
height: 15,
border: '1px solid #000000',
color: '#cc0000',
background: '#ffffff',
clickable: true,
classname: 'percentage',
display: 'inline-block'
};
if(conf == undefined) {
conf = defaults;
} else {
for(i in defaults) {
if(conf[i] == undefined) {
conf[i] = defaults[i];
}
}
}
function getLeft(newVal) {
return Math.floor((conf.width * -1) + (conf.width / 100 * newVal))
}
function updateElem(elem, val) {
if($(elem).is('textarea,input,select')) {
$(elem)
.attr('value', val)
.val(val)
.attr('title', val + '%')
;
} else {
$(elem)
.text(val + '%')
;
}
}
if(conf == 'set') {
$(elems).each(function() {
var elem = this;
conf = elem.percentage_conf;
updateElem(elem, val);
$(elem.percentage).find('div').css('left', getLeft(val));
});
return elems;
}
$(elems).each(function() {
var elem = this;
var bar = $('<div></div>');
var inner = $('<div></div>');
var percent;
var hasValue;
if($(elem).is('textarea,input,select')) {
percent = $(elem).val();
hasValue = true;
} else {
percent = parseInt($(elem).text().replace('%', ''));
hasValue = false;
}
function handleClick(e) {
var p = $(this).offset();
var percent = Math.ceil((e.clientX - p.left) / (conf.width / 100));
updateElem(elem, percent);
$(elem).trigger('clickupdate');
$(inner)
.css('left', getLeft(percent))
;
return false;
}
$(bar)
.attr('title', percent + '%')
.css('display', conf.display)
.css('border', conf.border)
.css('background', conf.background)
.css('position', 'relative')
.css('overflow', 'hidden')
.css('width', conf.width)
.css('height', conf.height)
;
if(conf.clickable) {
$(bar).click(handleClick);
}
if(conf.classname) {
$(bar).addClass(conf.classname);
}
$(inner)
.css('background', conf.color)
.css('position', 'absolute')
.css('top', 0)
.css('left', getLeft(percent))
.css('width', conf.width)
.css('height', conf.height)
.appendTo(bar)
;
$(elem)
.hide()
.after(bar)
;
this.percentage = bar;
this.percentage_conf = conf;
});
return elems;
}
jQuery.fn.percentage = function() {
render.apply(this, arguments);
};
})();
</code></pre>Debug your node.js with node-inspector!2010-11-29T12:36:40+00:00http://www.contentwithstyle.co.uk/content/debug-your-node-js-with-node-inspector/debug-your-node-js-with-node-inspector<p>If you haven't tried it yet, drop everything and do it now. You're only one simple npm install away from step by step debugging and much more in any webkit browser.</p>
<p>Visit node-inspector on <a href="https://github.com/dannycoates/node-inspector">github</a>.</p>
<p>There's some neat youtube video that should give you a little overview:</p>
<p>
<object type="application/x-shockwave-flash" style="width:480px; height:385px;" data="http://www.youtube.com/v/AOnK3NVnxL8?fs=1&hl=en_US"><param name="movie" value="http://www.youtube.com/v/AOnK3NVnxL8?fs=1&hl=en_US" /></object>
</p>Callback bonanza - linearising asynchronous calls2010-11-15T07:40:01+00:00http://www.contentwithstyle.co.uk/content/callback-bonanza---linearising-asynchronous-calls/callback-bonanza---linearising-asynchronous-calls<p>
I am coding in node.js at the moment, and one thing drives me mad:
The constant need to nest or pass callbacks.
So I did a little research on some libraries that help.
</p>
<p>
Two libraries in particular seemed to do what I was looking for: <a href="https://github.com/caolan">caolan's</a> <a href="https://github.com/caolan/async">async</a> and <a href="https://github.com/creationix">creatonix</a> <a href="https://github.com/creationix/step">step</a>.
Both provide similar interfaces to end the callback bonanza.</p>
<p>Check out the <a href="https://github.com/pascalopitz/async_experiments">little experiments on github</a>, that show how those two libraries can be used.</p>
Reconnecting2010-11-11T08:33:46+00:00http://www.contentwithstyle.co.uk/content/reconnecting-with-twitter-delicious-and-github/reconnecting-with-twitter-delicious-and-github<p>Earlier this week I read this <a href="http://techcrunch.com/2010/11/09/why-is-quora-mass-creating-twitter-accounts-on-mechanical-turk/">gossip about Quora mass-installing twitter accounts</a>, and they responded by saying that "Quora is looking at using Twitter as an alternative for topic RSS feeds". Hmmmm, interesting. Can we try that, too?</p>
<h2>Twitter</h2>
<p>Let's ignore the mass-account spamming for now, as people said in the techcrunch comments, one could use lists or hashtags and such. We're <a title="Content with Style on Twitter" href="http://twitter.com/contentwstyle">already on twitter</a>, and <a href="http://www.contentwithstyle.co.uk/content/cws-blog-posts-now-twitter">have the blog already hooked up</a> (<a href="http://www.contentwithstyle.co.uk/content/using-zendclienttwitter-with-oauth-and-a-single-access-token">twice!</a>), but now I'd like to utilize twitter further:</p>
<ul>
<li>Every reply to a CwS generated tweet should be treated similar to a comment</li>
<li>Every retweet of a CwS generated tweet should be treated similar to a pingback or trackback</li>
</ul>
<p>Now, we don't show pingbacks/trackbacks anyway, and I doubt we will show RTs publicly in detail, but it'd be nice to see them in some shape or form, maybe as the well-known "Retweeted 63412363 times".</p>
<p>But pulling the replies into the conversation, that might be something. It short, and might take away a bit from the linearity of a single comments stream, but that doesn't mean it's lesser content. Actually, if you want your voice to be heard as a commenter, twitter is a much better platform than most sites could ever be.</p>
<h2>Del.icio.us</h2>
<p>Again, this is building on top of something we have already integrated in part. On your right to this article on our site you can see the "elsewhere" feed, which is our joint bookmark feed from del.icio.us. Wouldn't it be nice if we added ourselves to it (yes yes, and filter it back out on the site, because you don't need to see it twice), so it comes up on del.icio.us once we publish a post, rather than wait until you or our other reader that uses del.icio.us decide to bookmark it? We could also suggest the tags ourselves, given that we've wanted to tag our posts on our list for the latter part of this decade!</p>
<h2>Github</h2>
<p>In the end this is a tech blog, though, so you should expect to see code examples, demo projects etc. In fact, Pascal has <a href="http://www.contentwithstyle.co.uk/content/whiteboard-with-nodejs-and-socketio-">littered</a> <a href="http://www.contentwithstyle.co.uk/content/long-polling-example-with-nodejs">several</a> <a href="http://www.contentwithstyle.co.uk/content/nodejs-load-balancer-proof-of-concept">posts</a> this year with links to his code on github.</p>
<p>Why not create gists out of all <strong>useful</strong> example code that we write about? And why not show the activity of those gists and repositories back here? There's already several WordPress plug-ins for this stuff out there, so bringing it home shouldn't be too hard.</p>
<h2>Time machines</h2>
<p>Because it's real actual work, and we have other obligations in our lives, that's why. So, instead of half doing something and never releasing it (see tagging, del.icio.us integration), or announcing something that doesn't have a follow-up, I thought that, for a change, I'll share my thoughts first, not promise anything, and then we'll see. If you are fired up by it, let me know how it went for you in the comments. Or, hey, just reply to the tweet.</p>I took it, and they say that so should you2010-10-20T16:07:02+01:00http://www.contentwithstyle.co.uk/content/i-took-it-and-they-say-that-so-should-you/i-took-it-and-they-say-that-so-should-you<p>
While the excited and slightly stern style of invitation is not so much to my liking, I've taken <a href="http://alistapart.com/articles/survey2010">this survey</a> every year since inception, and reading the <a href="http://aneventapart.com/alasurvey2009/">previous year's results</a> is always interesting. For me it's a good benchmark and prompts me to take stock over what I'm doing right now, and what I want to look for in the near future... you know, that question they always ask you in job interviews!
</p>
<p>A List Apart started this survey in 2007 because they felt that us web workers weren't statistically captured enough, something I agree very strongly with. Here's what they have to say about it:</p>
<blockquote><p>That’s why each year since 2007, we’ve asked you, the members of the web design community, a few dozen questions about your professional life, and compared your answers to those of your colleagues. Each time we’ve asked, over 30,000 of you have kindly obliged with details about your salary, location, background, and more. The data that you provide and we analyze is the only significant information about our profession as a profession to be published anywhere, by anyone. [...] And so, as you have in years past, we ask that you please take a few minutes to complete this year’s survey.</p></blockquote>
<p>
So, I've got nothing else to say today but to tell you:
<br/>
<a href="http://alistapart.com/articles/survey2010"><img src="http://aneventapart.com/webdesignsurvey/templates/ala/images/i-took-the-2010-survey.gif" alt="I Took the Survey For People Who Make Websites 2010" /></a>
</p>
Whiteboard with node.js and Socket.IO 2010-10-13T14:49:44+01:00http://www.contentwithstyle.co.uk/content/whiteboard-with-nodejs-and-socketio-/whiteboard-with-nodejs-and-socketio-<p>
Yes, I know I am late to the party, but I still wanted to try my hand at something with node.js and the amazing Socket.IO library. So fresh <a href="http://github.com/pascalopitz/whiteboard">on github there's my little whiteboard application</a> for you. Enjoy.
</p>
Promote JS2010-10-08T10:51:49+01:00http://www.contentwithstyle.co.uk/content/promote-js/promote-js<p><a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number' title='JavaScript Number .toFixed'><img src='http://static.jsconf.us/promotejsv.gif' height='280' width='160' alt='JavaScript Number .toFixed'/></a></p>
abbr. Fri2010-10-01T12:33:41+01:00http://www.contentwithstyle.co.uk/content/abbr-fri/abbr-fri<p>This is not at all from us, but from my colleague Andrew, who showed them to me last Friday in Perl.</p>
<p>I am not sure if I would use either of those in production code, it's... slightly cheeky. Nevertheless, enjoy:</p>
<pre>
<?php
//turn anything into a boolean
$boolean_flag = <strong>!!</strong> $any_variable;
//does the same as
$boolean_flag = (bool) $any_variable;
</pre>
<p>I like the importance the double exclamation marks trigger when you read this in code. IT'S LIKE WHEN PEOPLE WRITE MUNDANE STUFF IN CAPS!!</p>
<pre>
<?php
//I'm clearly iterating towards 0
$counter = 100;
while($counter<strong>--></strong>0) {
//do stuff
}
//does the same as
$counter = 100;
while($counter > 0) {
$counter--;
//do stuff
}
</pre>
<p>This one's great, too. The arrow seems to make the loop a very dynamic element. Great, have I really just explained my joke post? Shocking, I must get out more. If you've been inside for too long, let me know what unusual syntax you've come across.</p>Setting up SSL for Nginx2010-09-30T19:56:53+01:00http://www.contentwithstyle.co.uk/content/setting-up-ssl-for-nginx/setting-up-ssl-for-nginx<p>Quite a kerfuffle trying to set up SSL encryption for <a href="http://www.tagbento.com">www.tagbento.com</a>, which runs on Nginx. Here is how it was done.</p>
<p>The files I had gotten came via the client from <a href="http://www.startssl.com/">startSSL</a>. The following were present:</p>
<pre><code>
ssl.crt
ssl.key
ca.pem
sub.class2.server.ca.pem
</code></pre>
<p>I was used to having a cert and a key file from <a href="http://articles.slicehost.com/2007/12/19/ubuntu-gutsy-self-signed-ssl-certificates-and-nginx">generating self signed certificates</a>, but the rest? Unfortunately nginx wasn't mentioned in the <a href="http://www.startssl.com/?app=20">howto pages</a> at all.</p>
<h2>Remove the password from the key file:</h2>
<p>If you don't do <a href="http://www.madboa.com/geek/openssl/#key-removepass">this</a> with every configtest or reload/restart of nginx you'll have to type in the PEM password.</p>
<pre><code>
mv ssl.key bak.key
openssl rsa -in bak.key -out ssl.key
</code></pre>
<h2>Creating a combined key certificate</h2>
<p>Probably the most confusing point. The <a href="http://wiki.nginx.org/NginxHttpSslModule">Nginx manual</a> explains why you'll have to do this:</p>
<blockquote><p>
if you have a chain of certificates — by having intermediate certificates between the server certificate and the CA root certificate — they're not specified separately like you would do for Apache. Instead you'll need to concatenate all the certificates, starting with the server certificate, and going deeper in the chain running through all the intermediate certificates.
</p></blockquote>
<pre><code>
mv ssl.crt bak.crt
cat ssl.crt ca.pem sub.class2.server.ca.pem > ssl.crt
</code></pre>
<h3>Tribulations</h3>
<p>At this point I was quite confused, because instructions for level 1certificates were slightly different. The main problem, as it turned out though, was that the ssl.crt I had been given had Windows line breaks in it. So yeah, if stuff doesn't work, have a look at the concatenated file in vim and see if you can see something unusual.</p>
<h2>Enable SSL in the Nginx Vhost</h2>
<pre><code>
server {
listen 443;
ssl on;
ssl_certificate /path/to/ssl.crt;
ssl_certificate_key /path/to/ssl.key;
server_name www.tagbento.com;
...
}
</code></pre>
<h2>Test and Reload Nginx configuration</h2>
<pre><code>
/etc/init.d/nginx configtest
/etc/init.d/nginx reload
</code></pre>
<p>That's it, enjoy!</p>Zend_Client_Twitter with OAuth and a single access token2010-09-26T16:26:11+01:00http://www.contentwithstyle.co.uk/content/using-zendclienttwitter-with-oauth-and-a-single-access-token/using-zendclienttwitter-with-oauth-and-a-single-access-token<p>Just noticed that twitter switched their API to OAuth only, which broke our blog updates on twitter. Here's a hotfix to use the <a href="http://dev.twitter.com/pages/oauth_single_token">single access token provided by Twitter</a>.</p>
<h2>Config</h2>
<p>Of course you'll have to get the right data from <a href="http://dev.twitter.com/apps">dev.twitter.com</a>.</p>
<pre><code>
twitter.user = "myusername"
twitter.consumer_key = "xxxxxx"
twitter.consumer_secret = "yyyyyy"
twitter.access_token = "XXXXXX"
twitter.access_secret = "YYYYYY"
</code></pre>
<h2>Code</h2>
<pre><code>
$message = "my update";
$token = new Zend_Oauth_Token_Access();
$token->setToken($this->config->twitter->access_token);
$token->setTokenSecret($this->config->twitter->access_secret);
$twitter_client = $token->getHttpClient(array(
'callbackUrl' => 'http://example.com/callback.php',
'siteUrl' => 'http://twitter.com/oauth',
'consumerKey' => $this->config->twitter->consumer_key,
'consumerSecret' => $this->config->twitter->consumer_secret,
));
$twitter = new Zend_Service_Twitter(array(
'username' => $this->config->twitter->user,
'accessToken' => $token,
));
$twitter->setLocalHttpClient($twitter_client);
$response = $twitter->account->verifyCredentials();
$twitter->status->update($message);
</code></pre>Buffalo NAS and iTunes - A weekend of nerding away2010-09-26T15:26:49+01:00http://www.contentwithstyle.co.uk/content/buffalo-nas-and-itunes---a-weekend-of-nerding-away/buffalo-nas-and-itunes---a-weekend-of-nerding-away<p>Thank you Apple, for <a href="http://discussions.apple.com/thread.jspa?threadID=2564925&tstart=0">messing up the compatibility between iTunes 10 and any NAS drive</a> using <a href="http://www.fireflymediaserver.org/">firefly</a>. You officially suck.</p>
<p>That said, there's no such thing as giving up, and here I am, coming up with an elaborate way to be able to listen to my music again.</p>
<h2>Firefly broken</h2>
<p>So firely doesn't get recognised by iTunes 10 anymore. What a bummer! Until now I had all the music on a NAS drive, and streamed it to itunes on my Mac MINI which is acting as TV (using an Elgato EyeTV USB thingy) and stereo.</p>
<p>There seems to be zero alternatives when it comes to DAAP players, apart from Songbird with a flaky plugin. Not my weapon of choice, really, especially because a search or scrolling or anything else makes Songbird hang for what feels like a minute.</p>
<p>Bottom line was that I went and bought a hard drive to wire up to the Mac Mini, and used rsync to copy all music on to that.</p>
<pre><code>
rsync -azv -e ssh user@nasdrive:/mnt/disk1/itunes /Volumes/NewHD/
</code></pre>
<h2>Adding to the iTunes library</h2>
<p>The next problem is that just because files are on the hard drive, iTunes doesn't necessarily know about them. Normally we drag and drop them in. With an automatically synced library is not an option. Apple script to the rescue. Make sure that you disable the options "Keep iTunes media folder organized" and "Copy files to iTunes media folder when adding to library" in the iTunes preferences, or this will really cause havoc.</p>
<p><img src="/resources/itunes_prefs.jpg" alt="" /></p>
<p>Once you've done that though, adding a file twice should just ignore it, therefore there is no risk in running this script multiple times.</p>
<pre><code>
-- filename: /Users/tvmini/Desktop/itunes.scpt
property type_list : {"MPG3", "MIDI", "AIFF", "MPG4"}
property extension_list : {"mp3", "mid", "aif", "m4p", "m4a"}
on run
scan_folder("NewHD:itunes:")
end run
on scan_folder(file_item)
set folder_items to list folder file_item without invisibles
repeat with i from 1 to the count of folder_items
set this_item to alias ((file_item as text) & (item i of folder_items as text))
set item_info to info for this_item
if folder of the item_info is true then
scan_folder(this_item)
else if (alias of the item_info is false) and ¬
((the file type of the item_info is in the type_list) or ¬
the name extension of the item_info is in the extension_list) then
check_file(this_item)
end if
end repeat
end scan_folder
on check_file(file_item)
try
tell application "iTunes"
launch
set this_track to add file_item to playlist "Library" of source "Library"
end tell
end try
end check_file
</code></pre>
<p>This script was inspired by <a href="http://dougscripts.com/itunes/scripts/ss.php?sp=addtolib">this one at dougscripts.com</a>.</p>
<h2>The Flac problem</h2>
<p>Firefly was kind of neat, as it also managed to automatically transcode FLAC files into an MP3 stream. With this function gone, I am using a shell script to transcode the files:</p>
<pre><code>
#!/bin/sh
# filename: /Users/tvmini/Desktop/flac2mp3.sh
if [ "$1" ]
then
find "$1" -name '*.flac' | while read fn;
do
basename=$(basename "$fn" .flac).mp3
dirname=$(dirname "$fn")
outfile="$dirname/$basename"
if [ -e "$fn" ] && [ ! -e "$outfile" ];
then
id3title=$(metaflac --show-tag=TITLE "$fn" | perl -pe "s/^TITLE=(.*)/\$1/i")
id3artist=$(metaflac --show-tag=ARTIST "$fn" | perl -pe "s/^ARTIST=(.*)/\$1/i")
id3album=$(metaflac --show-tag=ALBUM "$fn" | perl -pe "s/^ALBUM=(.*)/\$1/i")
id3date=$(metaflac --show-tag=DATE "$fn" | perl -pe "s/^DATE=(.*)/\$1/i")
id3tracknumber=$(metaflac --show-tag=TRACKNUMBER "$fn" | perl -pe "s/^TRACKNUMBER=(.*)/\$1/i")
id3genre=$(metaflac --show-tag=GENRE "$fn" | perl -pe "s/^GENRE=(.*)/\$1/i")
flac -c -d "$fn" | \
lame -h -m s -b 360 \
--tt "$id3title" \
--ta "$id3artist" \
--tl "$id3album" \
--ty "$id3date" \
--tn "$id3tracknumber" \
--tg "$id3genre" \
- "$outfile"
else
echo >&2 ""$fn" -- skipping."
fi
done
else
echo >&2 "Usage: "$(basename "$0")" DIRNAME"
exit 1
fi
</code></pre>
<p>Note that I leave the flac file in place so that it won't get uploaded again every time I run the rsync command. The script then checks for a transcoded mp3 file that exists in the same directory and will only transcode again if this is not the case.</p>
<p>Inspiration for this came from mainly from <a href="http://discussions.apple.com/message.jspa?messageID=788809#788809">this message on the Apple Support Forums</a>.</p>
<h2>Keeping things up to date</h2>
<p>
Calling the above scripts via cron job should keep everything up to date:
</p>
<pre><code>
0 2 * * * rsync -azv -e ssh user@nasdrive:/mnt/disk1/itunes /Volumes/NewHD/
0 3 * * * osascript /Users/tvmini/Desktop/itunes.scpt
0 4 * * * sh /Users/tvmini/Desktop/flac2mp3.sh /Volumes/NewHD/itunes
</code></pre>
Preparing the button element for sliding doors2010-09-15T14:20:13+01:00http://www.contentwithstyle.co.uk/content/preparing-the-button-element-for-sliding-doors/preparing-the-button-element-for-sliding-doors<p>A helpful little trick to get the FF button elements into a state where we can apply sliding doors. Via johannes krtek of <a href="http://flachware.com">flachware.com</a></p>
<pre><code>
button::-moz-focus-inner { /* now you can handle firefox buttons like safari buttons */
padding: 0; /* remove inner padding */
border: 0; /* remove dotted outline in buttons in firefox */
}
</code></pre>
<p>This removes the strange clicking effect Firefox applies to the button elements. Now we can do normal sliding doors stuff with them.</p>Zend Framework with nginx and php-fastcgi2010-08-04T21:16:46+01:00http://www.contentwithstyle.co.uk/content/zend-framework-with-nginx/zend-framework-with-nginx<p>
Since I while I heard good things about nginx and wanted to use it for my Zend Framework MVC applications.
I just got a Ubuntu server working after one of those days that seem to be a never ending Google search and debugging session, so I thought I'd share the outcome with you.
</p>
<h2>Install packages</h2>
<p>
First off you'll need to install a couple of packages, as outlined in the excellent <a href="http://davidwinter.me.uk/articles/2009/06/13/php-and-nginx-the-easy-way/">howto by david winter</a>:
</p>
<pre><code>sudo aptitude install php5-cgi nginx
</code></pre>
<h2>Configure PHP fastcgi</h2>
<p>Then create an init.d file to manage the php-fastcgi process</p>
<pre><code>sudo nano /etc/init.d/php-fastcgi
</code></pre>
<p>Inside put</p>
<pre><code>#!/bin/bash
BIND=127.0.0.1:9000
USER=www-data
PHP_FCGI_CHILDREN=15
PHP_FCGI_MAX_REQUESTS=1000
PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0
start() {
echo -n "Starting PHP FastCGI: "
start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
RETVAL=$?
echo "$PHP_CGI_NAME."
}
stop() {
echo -n "Stopping PHP FastCGI: "
killall -q -w -u $USER $PHP_CGI
RETVAL=$?
echo "$PHP_CGI_NAME."
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL
</code></pre>
<p>Then we change permissions on this, start the fastcgi and register it as startup item</p>
<pre><code>sudo chmod +x /etc/init.d/php-fastcgi
sudo /etc/init.d/php-fastcgi start
sudo update-rc.d php-fastcgi defaults
</code></pre>
<h2>Configure nginx</h2>
<p>
Now we need to set up a vhost for ZF application in nginx.
This is a pretty straight forward job, as the nginx package in Ubuntu features the well knows sites-available/sites-enabled folder structure.
I will assume that the application lives in /var/www/zfapp, has all the needed dependencies in the library folder and the host name for it will be zfapp.
</p>
<p>First off we create a config file in the sites-available folder:</p>
<pre><code>sudo vim /etc/nginx/sites-available/zfapp</code></pre>
<p>Inside we put the vhost config:</p>
<pre><code>server {
listen 80;
server_name zfapp;
root /var/www/zfapp/public;
index /index.php;
# remove trailing slash, that throws ZF router
rewrite ^/(.*)/$ /$1 break;
location / {
# if file is existing, skip fastcgi parsing
if (-e $request_filename) {
break;
}
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/zfapp/public/index.php;
# get the relevant environment
fastcgi_param APPLICATION_ENV staging;
}
# expires headers on known filetypes
location ~* ^.+.(css|js|jpeg|jpg|gif|png|ico) {
expires 30d;
}
}
</code></pre>
<p>Now we just need to symlink this file into sites-enabled and restart nginx</p>
<pre><code>sudo ln -s /etc/nginx/sites-available/zfapp /etc/nginx/sites-enabled/zfapp
sudo /etc/init.d/nginx restart
</code></pre>
<p>That's it. Should be working.</p>Nodestalker - A beanstalkd client for node.js2010-05-14T12:31:15+01:00http://www.contentwithstyle.co.uk/content/nodestalker-a-beanstalkd-client-for-nodejs/nodestalker-a-beanstalkd-client-for-nodejs<p>Following my <a href="http://www.contentwithstyle.co.uk/content/php-worker-processes-with-beanstalk-and-daemontools">previous article</a> about PHP and <a href="http://kr.github.com/beanstalkd/">beanstalkd</a> I was keen to use beanstalk in combination with node.js, but wasn't entirely satisfied with the available <a href="http://github.com/benlund/node-beanstalk-client">implementation</a>. So as a little brain exercise I wrote <a href="http://github.com/pascalopitz/nodestalker">nodestalker</a> from scratch.</p>
<h2>Usage</h2>
<p>Usage should be pretty straight forward. Below is the code for putting a job into the queue:</p>
<pre><code>var sys = require('sys');
var bs = require('../lib/beanstalk_client');
var client = bs.Client();
client.use('default').onSuccess(function(data) {
sys.puts(sys.inspect(data));
client.put('my job').onSuccess(function(data) {
sys.puts(sys.inspect(data));
client.disconnect();
});
});
</code></pre>
<h2>Issues during development</h2>
<p>Writing it wasn't as easy as I expected, but that was because I didn't read the documentation on sockets properly, and missed out on <code>stream.setKeepAlive()</code> and <code>stream.setNoDelay()</code> first, which made my sockets close on me in what I thought was a random manner.</p>
<p>Another issue I had was <a href="http://github.com/visionmedia/js-yaml">the yaml library for node</a>, which just didn't like the yaml formatted beanstalk output, which doesn't contain the expected indentations. Not sure why, maybe a yaml version conflict or something, but I hacked the yaml parsing with some regular expressions.</p>
<pre><code>var corrected = str.replace(/\n-\ ([\w\d_-]+)/mig, '\n - \'$1\'') //indent list
.replace(/(\w)\-(\w)/mgi, '$1_$2') // replace minuses in hash names
.replace(/\n([\w\d_-]+)\:\ ([\.\,\w\d_-]+)/mig, '\n $1: \'$2\''); // format hashes
try {
return kiwi.require('yaml').eval(corrected);
} catch(e) {
Debug.log(e);
Debug.log(corrected);
return str;
}
</code></pre>
<h2>Demo application: Beanspector</h2>
<p>I also included a demo application, which I baptised "beanspector". It is a simple command line client to inspect, list and empty tubes and put some content into them. This is how I use it in action:</p>
<pre><code>$ node beanspector.js -lt
[ 'default', 'confirm' ]
$ node beanspector.js -pt default "here"
61
$ node beanspector.js -lc default
listing tube default
{ id: '61', data: 'here' }
</code></pre>
<h2>Download</h2>
<p><a href="http://github.com/pascalopitz/nodestalker">Get nodestalker from GitHub</a>, and feel free to fork and do some improvements if you want.</p>Google Web Optimiser being unbalanced2010-05-11T11:05:38+01:00http://www.contentwithstyle.co.uk/content/google-web-optimiser-being-unbalanced/google-web-optimiser-being-unbalanced<p>Whatever prompted <a href="http://adwords.google.com/support/aw/bin/answer.py?hl=en-uk&answer=64418">Google to use an "unbalanced" noscript tag in their Web Optimiser</a>, it ain't pretty!</p>
<p>The problem is not just Web Optimiser, but also other tools like Analytics. The whole "generate me a code snippet to paste" approach leaves many many otherwise neatly coded littered with inline script blocks. While this is a quick and easy way for people to get stuff working in their WP blog, at least there should be a neat and valid alternative for experienced developers.</p>
<p>Why Google don't try to create cleaner ways to include their tools, is completely beyond me. They really have loads of skilled people at hand, so resource can't be a problem, can it?</p>
<p>Below is their generated Analytics code, vs my take on it the encapsulated way.</p>
<pre><code>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try{
var pageTracker = _gat._getTracker("UA-xxxxxx-x");
pageTracker._trackPageview();
} catch(err) {}</script>
</code></pre>
<pre><code>
CWS.GoogleAnalytics = new function() {
this.NS = 'CWS.GoogleAnalytics';
var _t = 200;
var _checkTracker = function() {
if(window._gat !== undefined) {
try {
var pageTracker = window._gat._getTracker("UA-XXXXXXX-X");
pageTracker._trackPageview();
} catch(err) {}
} else {
window.setTimeout(_checkTracker, _t);
}
}
var gaJsHost = (("https:" == document.location.protocol) ? 'https://ssl.' : 'http://www.');
var str = unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E");
$('body').append(str);
window.setTimeout(_checkTracker, _t);
};
</code></pre>
<p>However, that doesn't solve the problem of Google Web Optimiser insisting on an "unbalanced" noscript tag. For all those people that use XML parsers or send their content as application/xhtml+xml, this is a big nono. Poor show, Google!</p>
Node.js load balancer proof of concept2010-05-10T10:32:49+01:00http://www.contentwithstyle.co.uk/content/nodejs-load-balancer-proof-of-concept/nodejs-load-balancer-proof-of-concept<p>Down with a cold, the only bit I managed was a bit more experimenting with node.js this weekend. Here's a little example of node.js acting as a very basic load balancer with fault tolerance.</p>
<p>I did create a <a href="http://github.com/pascalopitz/nodeload">repo on github</a>, feel free to contribute or fork if you find this useful.</p>
<h2>Project Files</h2>
<pre><code>
$ ls -h
README cluster.conf.json
balancer.js testserver.js
</code></pre>
<h2>Load Balancer</h2>
<pre><code>
var sys = require('sys'),
http = require('http'),
fs = require('fs');
var LoadBalancer = new function() {
var _self = this;
var _server;
var _cluster = [];
var _active = [];
var _port = 8888;
var _checkInterval = 10000;
var _checkTimeout = [];
var _updateActives = function() {
_active = [];
for(var i=0; i<_cluster.length; i++) {
if(_cluster[i].active) {
_active[_active.length] = _cluster[i];
}
}
};
var _loadCluster = function(callback) {
fs.readFile('./cluster.conf.json', function (err, data) {
if (err) throw err;
_cluster = JSON.parse(data.toString().replace('\n', ''));
callback();
});
};
var _checkCluster = function() {
for(var i=0; i<_cluster.length; i++) {
_clusterNodeCheck(_cluster[i]);
}
};
var _clusterNodeCheck = function(node) {
var client = http.createClient(parseInt(node.port, 10), node.host);
var request = client.request('GET', '/is_up', {"host" : node.host});
request.addListener('response', function (response) {
if(response.statusCode == 200) {
response.addListener('data', function(data) {
if(data == 'ok') {
node.active = true;
} else {
node.active = false;
}
});
} else {
node.active = false;
}
});
request.addListener('error', function (err) {
node.active = false;
});
client.addListener('error', function (err) {
node.active = false;
});
request.end();
setTimeout(_updateActives, 200);
_checkTimeout[node.host + ':' + node.port] = setTimeout(function() {
_clusterNodeCheck(node);
}, _checkInterval);
};
var _requestHandler = function(request, response) {
if(_active.length == 0) {
response.writeHead(500, {'Content-Type': 'text/html'});
response.write('no server active');
response.end();
} else {
var index = Math.floor(Math.random()*_active.length);
var node = _active[index];
var proxy_headers = request.headers;
var proxy_client = http.createClient(parseInt(node.port, 10), node.host);
var proxy_request = proxy_client.request(request.method, request.url, proxy_headers);
proxy_request.addListener("response", function (proxy_response) {
response.writeHeader(proxy_response.statusCode, proxy_response.headers);
proxy_response.addListener("data", function (chunk) {
response.write(chunk);
});
proxy_response.addListener("end", function () {
response.end();
});
});
proxy_client.addListener("error", function (error) {
for(var i=0; i<_cluster.length; i++) {
if(node.host == _cluster[i].host && node.port == _cluster[i].port) {
sys.puts('error, deactivating: '+node.host+':'+node.port);
_cluster[i].active = false;
_updateActives();
}
clearTimeout(_checkTimeout[_cluster[i].host + ':' + _cluster[i].port]);
_clusterNodeCheck(_cluster[i]);
}
setTimeout(function() {
_requestHandler(request, response);
}, 200);
});
proxy_request.end();
}
};
var _run = function() {
_loadCluster(_checkCluster);
_server = http.createServer().
addListener('request', _requestHandler)
.listen(_port);
sys.puts('Listening to port ' + _port);
};
_run();
};
</code></pre>
<h2>Config file</h2>
<pre><code>
[{
"host" : "127.0.0.1",
"port" : "8200"
},
{
"host" : "127.0.0.1",
"port" : "8300"
}]
</code></pre>
<h2>Test Server</h2>
<pre><code>
var sys = require('sys'),
http = require('http');
var _port;
function checkUsage() {
if(process.argv.length != 3) {
sys.puts('usage: node testserver.js <port>');
process.exit(1);
}
var port = parseInt(process.argv[2], 10);
if('NaN' == port.toString()) {
sys.puts('usage: node testserver.js <port>');
process.exit(1);
}
_port = port;
};
var TestServer = function() {
var _self = this;
var _server;
var _routes = {
'/' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('hello world\n');
response.end();
},
'/is_up' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('ok');
response.end();
},
}
var _requestHandler = function(request, response) {
sys.puts('Request '+request.url+' from '+request.connection.remoteAddress+' to '+request.headers.host);
if(_routes[request.url] === undefined) {
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('not found\n');
response.end();
} else {
_routes[request.url].call(this, request, response);
}
};
var _run = function() {
_server = http.createServer().
addListener('request', _requestHandler)
.listen(_port);
sys.puts('Listening to port ' + _port);
};
_run();
};
checkUsage();
server = new TestServer();
</code></pre>
<h2>Readme</h2>
<pre><code>
A simple load balancer in node.js
Start testservers in seperate shells:
node testserver.js 8200
node testserver.js 8300
Start load balancer:
node balancer.js
Request to balancer:
curl http://127.0.0.1:8888
</code></pre>
Long polling example with node.js2010-05-07T17:15:12+01:00http://www.contentwithstyle.co.uk/content/long-polling-example-with-nodejs/long-polling-example-with-nodejs<p>Here's a little long polling example that I have thrown together while playing around with <a href="http://nodejs.org">node.js</a>. I must say it's a pretty slick tool. Anyone familiar with JavaScript suddenly can create powerful server side applications.</p>
<h2>The Server</h2>
<p>First we need to create a file called test.js!</p>
<pre><code>
var events = require('events');
var sys = require('sys');
var http = require('http');
var fs = require('fs');
var TestApp = function() {
var _self = this;
var counter = 0;
var routes = {
'/' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/html'});
var string = _self.indexTemplate;
response.write(string);
response.end();
},
'/jsupdate' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/javascript'});
updateLoop.call(this, request, response, counter);
},
'/jsstatus' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/javascript'});
response.write('counter: ' + counter + '\n');
response.end();
},
'/increment' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
counter++;
response.write('counter: ' + counter + '\n');
response.end();
},
'/reset' : function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
counter = 0;
response.write('reset\n');
response.end();
}
}
var updateLoop = function(request, response, current_counter) {
if(current_counter != counter) {
response.write('counter: ' + counter + '\n');
response.end();
return false;
}
setTimeout(function() {
updateLoop.call(this, request, response, current_counter);
}, 1000);
};
var _requestHandler = function(request, response) {
sys.puts('request: \'' + request.url + '\'');
if(routes[request.url] === undefined) {
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('not found\n');
response.end();
} else {
routes[request.url].call(this, request, response);
}
};
var _updateHandler = function(request, socket, head) {
sys.puts('update');
};
var _closeHandler = function() {
sys.puts('close');
};
sys.puts('New TestApp');
fs.readFile('./index.html', function (err, data) {
if (err) throw err;
_self.indexTemplate = data;
sys.puts('Template loaded');
});
var _server = http.createServer().
addListener('request', _requestHandler)
.addListener('close', _closeHandler)
.addListener('update', _updateHandler)
.listen(8000);
sys.puts('Listening to port 8000');
};
new TestApp();
</code></pre>
<h2>The Client</h2>
<p>The client side is just a small index.html file that gets loaded by the server application to be served up. Note that you'll have to restart the server in order to make changes to it.</p>
<pre><code>
<html>
<head>
<title>Long polling test</title>
</head>
<body>
<p>Long polling test.</p>
<p><textarea id="output"></textarea></p>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
var client = new function() {
var _poll = function() {
$.get('/jsupdate', function(response) {
$('textarea').text(response);
_poll();
});
}
$.get('/jsstatus', function(response) {
$('textarea').text(response);
_poll();
});
}
</script>
</html>
</code></pre>
<h2>Testing the application</h2>
<p>Now all you need to do is run <code>node test.js</code> and you should be able to open one or more browser windows with the test page.</p>
<p>To check the other actions I used curl:</p>
<pre><code>
$ curl http://localhost:8000/increment
counter: 1
$ curl http://localhost:8000/increment
counter: 2
$ curl http://localhost:8000/increment
counter: 3
$ curl http://localhost:8000/reset
reset
</code></pre>
<p>On you test page updates should come through in the textfield.</p>
<h2>Where does this leave us?</h2>
<p>Node.js seems to gain momentum big time at the moment. There's tons of client libraries already, and frameworks sitting on top of it seem to pop up like mushrooms.</p>
<p>Especially PHP coders will find themselves frustrated when it comes to long polling, as PHP usually uses Apache or other blocking servers, or one is forced to create one process per request, which becomes hard to manage.</p>
<p>After seeing a <a href="http://www.pgrs.net/2010/2/1/web-proxy-in-node-js-for-high-availability">proxy example in node.js</a>, I believe node.js could be a simple solution: Run Apache on port 8080 or something and use node.js to either serve long-polling stuff or proxy through to your Apache run application.</p>
<p>But even long polling aside, the event based programming that's coming with node.js would be attractive for the creation of daemons and other little system tools. How stable node.js performs under high load is a different story, and I haven't done enough research yet as to make a comment on that. I can imagine that Supervisord would be a nice helper to manage node.js itself, in case it crashes or your applications develop memory leaks.</p>
<p>If you, Dear Reader, have some experiences in the production environment, please go ahead and share in the comments.</p>
<h2>Update 13/05/2010</h2>
<p>I have put the <a href="http://github.com/pascalopitz/long_polling_example">example files on github</a>.</p>Linkedin and Zend_Oauth2010-05-05T17:43:49+01:00http://www.contentwithstyle.co.uk/content/linkedin-and-zendoauth/linkedin-and-zendoauth<p>Matthias has made look into Linkedin and OAuth for a bit. <a href="http://www.formatix.eu/en/update-linkedin-status-using-zend-oauth.html">This example</a> was a very interesting read, but I think it's overcomplicating things slightly. That's why I want to show a very simple example inspired by what we've done for the brand new CwS Author pages.</p>
<p>Before we get started, some good news: No more bugs in Zend_Oauth. Hence all that hacking of the ZF code is now obsolete as well.</p>
<p>Also the example contained a couple of silly things that just confused me: The use of $_SESSION for example, or the fact that with ZF 1.10 the parameter was called 'userAuthori<strong>z</strong>ationUrl' rather than 'userAuthori<strong>s</strong>ationUrl'.</p>
<h2>My simple example</h2>
<p>I am assuming that we're using a controller for the user, and here's an action called linkedin for it:</p>
<pre><code>public function linkedinAction() {
$user_id = $this->loginHelper->userId();
$ns = new Zend_Session_Namespace('linkedin_oauth');
$options = array(
'localUrl' => 'http://site/user/linkedin/',
'callbackUrl' => 'http://site/user/linkedin/',
'requestTokenUrl' => 'https://api.linkedin.com/uas/oauth/requestToken',
'userAuthorizationUrl' => 'https://api.linkedin.com/uas/oauth/authorize',
'accessTokenUrl' => 'https://api.linkedin.com/uas/oauth/accessToken',
'consumerKey' => $this->config->linkedin->key,
'consumerSecret' => $this->config->linkedin->secret,
);
$consumer = new Zend_Oauth_Consumer($options);
if(empty($ns->request_token)) {
// get request token and redirect to linkedin
$token = $consumer->getRequestToken();
$ns->request_token = serialize($token);
$consumer->redirect();
die();
} else {
try {
// get access token and store in DB
$token = $consumer->getAccessToken($_GET, unserialize($ns->request_token));
$this->userModel->setLinkedinToken($author_id, serialize($token));
} catch(Exception $e) {
// reset token in DB and empty session if there was a fault
$this->userModel->setLinkedinToken($author_id, '');
$ns->request_token = '';
}
}
// redirect user to own details page
return $this->_redirect('/user/details/');
}
</code></pre>
<p>As you can see you'll need the infrastructure to obtain the user id and store the token in the DB, but otherwise this is pretty straight forward. We're saving the request_token in a session to then obtain the access_token when linkedin redirects us to the same page.</p>
<p>Assuming that you then have managed to read the token from the database, this is what you need to get the users current status:</p>
<pre><code>$options = array(
'consumerKey' => $this->config->linkedin->key,
'consumerSecret' => $this->config->linkedin->secret,
);
$token = unserialize($token);
$client = $token->getHttpClient($options);
$client->setUri('https://api.linkedin.com/v1/people/~:(current-status)');
$client->setMethod(Zend_Http_Client::GET);
$response = $client->request();
$content = $response->getBody();
</code></pre>
<p>And that's it really. For more details read the great original articles <a href="http://www.formatix.eu/en/php-linkedin-api-zend-oauth.html">here</a> and <a href="http://jeroenbourgois.be/blog/2010/03/21/linkedin-using-zend-oauth/">here</a>.</p>N900: AIM and ICQ account problems2010-04-28T11:05:41+01:00http://www.contentwithstyle.co.uk/content/n900-and-problems-with-aim-and-icq/n900-and-problems-with-aim-and-icq<p>I'm a happy n900 user since a while, and one of the best features of the whole device is it's contact list and IM conversations. In fact it's about the coolest way to have all your contacts in one place, and as a mobile device for IM it's the best I've ever seen.</p>
<p>Annoyingly since an upgrade the AIM and ICQ accounts refused to connect and threw a network error.</p>
<p>The solution came via <a href="http://talk.maemo.org/showpost.php?p=461439&postcount=7">the maemo forums.</a></p>
<p>You'll have to downgrade libpurple:</p>
<pre><code>
apt-get install purple-extra-protocols=2.6.3-6nix0
apt-get install libpurple0=2.6.3-6nix0
</code></pre>
PHP worker processes with Beanstalk and Daemontools2010-03-31T22:26:47+01:00http://www.contentwithstyle.co.uk/content/php-worker-processes-with-beanstalk-and-daemontools/php-worker-processes-with-beanstalk-and-daemontools<p>
Before I get started on this one I want to apologize for being slack the last couple of months. Maybe being on holiday for two months slightly corrupted my morale, or maybe that was down to the nice wine tasting sessions in the Barossa Valley and the McLaren Vale.
In any case, it's been a long time. I have been back for one month but I am still finding it hard to live up to my own expectations in terms of writing and tech research.
</p>
<p>But after moaning, on with the programme:</p>
<p>
Sometimes things just get too heavy for a straight forward approach. Memory usage might be too high or interaction might be delayed. In this case it might make sense to queue the task up for later execution.
</p>
<h2>A message queue</h2>
<p>
<a href="http://kr.github.com/beanstalkd/">Beanstalkd</a> is a very easy to use message queue. There are client libraries for it in many languages, and it seems to be very popular amongst the Ruby crowd.
</p>
<p>Installing it on OSX using macports is easy peasy:</p>
<pre><code>sudo port install beanstalkd</code></pre>
<p>Kicking it off is equally as easy:</p>
<pre><code>beanstalkd -d -l 127.0.0.1 -p 11300</code></pre>
<h2>Pushing things into the queue</h2>
<p>
We're using pheanstalk as the client library to connect to beanstalkd, and this script is just generating 1000 dummy jobs to be picked up later by our daemonized worker process.
</p>
<pre><code><?php
require_once('pheanstalk/pheanstalk_init.php');
$pheanstalk = new Pheanstalk('127.0.0.1:11300');
for($i=0; $i<1000; $i++) {
$job = new stdClass();
$job->envelope_id = rand();
$job->date = date('Y-m-d H:i:s');
$job_data = json_encode($job);
$pheanstalk->useTube('test')->put($job_data);
echo "pushed: " . $job_data . "\n";
}
</code></pre>
<h2>Picking up things from the queue</h2>
<p>Our worker script now needs to connect to the queue and pick up the jobs. Things get dumped into a logfile which we can have an eye on to see if it's running alright. In order to prevent memory leaks it terminates itself when it hits a certain memory threshold. In this case the threshold is just picked for demo purposes, and the counter and the done_jobs array are just there to increase the memory footprint.</p>
<pre><code><?php
class Worker {
private $path;
public function __construct($path) {
$this->setBasePath($path);
$this->log('starting');
require_once('pheanstalk/pheanstalk_init.php');
$this->pheanstalk = new Pheanstalk('127.0.0.1:11300');
}
public function __destruct() {
$this->log('ending');
}
private function setBasePath($path) {
$this->path = $path;
}
public function run() {
$this->log('starting to run');
$cnt = 0;
$done_jobs = array();
while(1) {
$job = $this->pheanstalk->watch('test')->ignore('default')->reserve();
$job_encoded = json_decode($job->getData(), false);
$done_jobs[] = $job_encoded;
$this->log('job:'.print_r($job_encoded, 1));
$this->pheanstalk->delete($job);
$cnt++;
$memory = memory_get_usage();
$this->log('memory:' . $memory);
if($memory > 1000000) {
$this->log('exiting run due to memory limit');
exit;
}
usleep(10);
}
}
private function log($txt) {
file_put_contents($this->path . '/log/worker.txt', $txt . "\n", FILE_APPEND);
}
}
$worker = new Worker(dirname($argv[0]));
$worker->run();
</code></pre>
<h2>Daemonize the worker process.</h2>
<p>Now the only problem we have is keeping the worker process running. <a href="http://cr.yp.to/daemontools.html">Deamontools</a> are a collection of binaries that can supervise processes and restart them when they stop.</p>
<h3>Installing daemontools</h3>
<p>Again, installing on OSX is pretty straight forward:</p>
<pre><code>sudo port install daemontools</code></pre>
<p>We also need a shell script called <code>./run</code> to be supervised by daemontools. Usually these go into a subfolder of <code>/service</code>, but the macports installation uses <code>/opt/local/var/svscan/service</code> instead. I chose to create a subfolder in my application and then symlink it into there. The shell script itself is pretty simple:</p>
<pre><code>#!/bin/sh
php ../worker.php</code></pre>
<p>Now launch daemontools and you're up and away. Kick off the push script and the worker activity should show up nicely in the logfile.</p>
<p>
In this case I am using daemontools to do this, but there are other tools to do it as well.
The best option seemed to be <a href="http://supervisord.org/">supervisord</a>, but the download page was down when I did the proof of concept and I had to settle for something else. Supervisord also seems to be able to watch the memory footprint of a task so this bit in the worker script might be obsolete.</p>
<p>There are also options to use init.d scripts, but my knowledge of that is lacking. If you cannot be bothered to daemonize, you could also just kick off a cron job.
</p>
<p>I hope this is a helpful little writeup. Feel free to <a href="http://www.contentwithstyle.co.uk/resources/queue_test.zip">download the nicely zipped up demo code</a>, and do drop a comment if you have anything to add.</p>Happy New Year 20102010-01-04T06:52:37+00:00http://www.contentwithstyle.co.uk/content/happy-new-year-2010/happy-new-year-2010<p>It's four days into the new year, and I've had more sunshine in that short time than in the last 3 months together. I'm reasonably sure this applies to Pascal as well, given that at the moment he's making sure the southern hemisphere gets a little CwS love, too. Seems like a good opportunity to say thanks to you, for reading and contributing to our site over the last 12 months.</p>
<h2>What was</h2>
<p>The most exciting thing for CwS in 2008 must've been that Pascal and I got to spend about half the year working together, as real life desk neighbours at Photobox, when I managed to hire him for 2 projects in spring and autumn.</p>
<p>We got around to churn out some lovely best practice code (almost all Zend Framework based) and wrote about it. Gaining some traction with CwS, we started looking into other distribution channels, <a href="http://twitter.com/contentwstyle">Twitter</a> being the first, and our articles were gladly picked up by <a href="http://www.phpdeveloper.org">PHPDeveloper</a> as well as the <a href="http://devzone.zend.com">Zend DevZone</a>. I always meant to write about the increase of traffic and the benefit of hustling your site a little bit (we received some very helpful comments in the process, so it's not all just numbers and pretty graphs), but didn't get around to it. I guess you'll have to trust me on this one.</p>
<p>What definitely wasn't was a series about tips I wanted to publish for people newer to the world of web development, mostly because <a href="http://www.456bereastreet.com/archive/200812/quick_tips_for_web_developers_and_web_designers/">Roger Johansson had the same idea</a> round-about the same time, and he does it so much better.</p>
<p>For your collective and individual enjoyment, I've grouped our 15 most popular posts in (not necessarily from) 2009:</p>
<ol>
<li><a href="http://www.contentwithstyle.co.uk/content/a-css-framework">A CSS Framework</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps">Fixing the Back Button and Enabling Bookmarking for AJAX Apps</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/modular-css">Modular CSS</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/unit-testing-controllers-with-zend-framework">Unit testing controllers with Zend Framework</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/css-background-image-on-html-image-element">CSS Background image on html image element?</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/deploying-php-applications-with-vlad">Deploying PHP applications with Vlad and SVN</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/dynamic-tables-with-xslt">Dynamic tables with XSLT</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/clean-urls-for-a-better-search-engine-ranking">Clean URLs for a better search engine ranking</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/find-your-node-advanced-xpath-commands">Find your node: Advanced XPATH commands</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/a-caching-pattern-for-models">A caching pattern for models</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/xml-validation-in-php">XML validation in PHP</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working">4 ssh config tips for faster remote working</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/css-is-worthless">CSS is Worthless</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/my-take-on-jquery-charts">My take on jQuery charts</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/unit-testing-web-service-based-models-in-zend-framework">Unit testing web service based models in Zend Framework</a></li>
</ol>
<h2>What will be (maybe)</h2>
<p>Of course we're looking forward to another year of CwS that will discuss all aspects of web technology that we deal with, client and server side. Right now there are many exciting news on the horizon (if not already knocking on our door daily), I'm thinking Zend Framework 2.0, PHP 5.3, push concepts such as Comet (especially in the PHP world), and of course the exciting new world of HTML 5 with all its possibilities and pitfalls. The last word in deployment and build processes is surely not spoken without having a look at integrating dpkg or rpm mechanisms, and in more concrete news, I'm doing a PHP certification this year, so hopefully you'll hear some of my experience learned there, too.</p>
<p>Of course we're still open for contributors to write a post here and there, so feel free to contact us if you've got an idea or maybe simply a nice code example you'd like to share.</p>
<p>This leaves me with wishing you all a happy new year, thanks again for being such a smart audience. Here's to you and a great 2010,</p>
<p>from all of us at Content with Style</p>Viewing BLOB content in phpMyAdmin2009-11-20T08:25:39+00:00http://www.contentwithstyle.co.uk/content/viewing-blob-content-in-phpmyadmin/viewing-blob-content-in-phpmyadmin<p>In my phpMyAdmin installation, version 3.1.1, none of the old style config settings such as</p>
<pre>$cfgShowBlob = true</pre>
<p> or</p>
<pre>$cfg['ShowBlob']</pre>
<p>had any effect, and the solution was terribly elusive. "view blob", "show blob", "display blob", all that Google suggested were people asking the same question, at various ages of the internet.</p>
<p>Even more annoying, the most common answer was "Why would you want to do that?", and too many times I was reading that they shouldn't be read anyway, because it could be large amounts of, well, binary data, bla bla bla.</p>
<p>In my case this makes very much sense, though, because I was looking at serialized data objects. Don't ask me why it's not a TEXT field (another smartypants suggestion in a forum), that's just the setup I was dealing with.</p>
<p>After much digging and the wish for a search engine that will suggest better search terms to me, I finally decided to bother the source instead, and found "display_blob". Hurray! It comes in this form:</p>
<pre>$_SESSION['userconf']['display_blob'];</pre>
<p>All you need to do is set this session variable (possibly by sticking it into the config? I didn't try it out), or alternatively: Open the content frame and put</p> <pre>&display_blob=true</pre>
<p>into the querystring, and it will keep it in the session for you, when you use the frameset as normal afterwards.</p>
<p>While writing this, I also <a href="http://www.yegq.org/archives/177">saw this page</a>, can anyone translate?</p>Fulltext searches with Xapian and PHP2009-11-03T16:35:15+00:00http://www.contentwithstyle.co.uk/content/searching-with-xapian-and-php/searching-with-xapian-and-php<p>Sometimes MySQL just isn't quick enough. Especially when it comes to fulltext searches. Everything needs to be indexed correctly, and if we're using different fields with different weights for a relevance percentage, things get very complicated quickly. <a href="http://xapian.org/">Xapian</a> to the rescue.</p>
<h2>What is Xapian?</h2>
<p>Xapian is a Search Engine Library, similar to Lucene and Sphinx. It's compiled from C++ code and therefore pretty low level. There are PHP, Perl and Python bindings available for it, which are straight forward to use. Packages are available for Ubuntu and Red Hat, it compiles on OSX and you can run it on Windows via CygWin.</p>
<h2>Demo Scripts</h2>
<p>
Rather than explaining why and how, I decided to create some demo scripts instead.
The XapianWrapper PHP class I've created is quite large, so feel free to <a href="http://www.contentwithstyle.co.uk/resources/xapian-example.zip">download the example zip file</a> as well.
</p>
<h3>db.sql</h3>
<pre><code>
CREATE DATABASE `demo`;
CREATE TABLE `demo`.`demo` (
`id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`unique_key` VARCHAR( 255 ) NOT NULL ,
`name` VARCHAR( 255 ) NULL DEFAULT NULL ,
`summary` TEXT NULL DEFAULT NULL ,
`date` DATETIME NULL DEFAULT NULL ,
UNIQUE (`unique_key`));
INSERT INTO `demo`.`demo`
(`id`, `unique_key`, `name`, `summary`, `date`)
VALUES (NULL, 'foo', 'foo', 'foo bar test', '2008-11-05 00:00:00'),
(NULL , 'bar', 'bar', 'test foo bar', '2009-11-05 00:00:00');
</code></pre>
<h3>XapianWrapper.php</h3>
<pre><code>
<?php
// includes
require_once 'xapian.php';
// main class
class XapianWrapper {
const XAPIAN_FIELD_URL = 0;
const XAPIAN_FIELD_NAME = 1;
const XAPIAN_FIELD_DATE = 2;
const XAPIAN_FIELD_UID = 3;
const XAPIAN_FIELD_SUMMARY = 4;
const XAPIAN_PREFIX_UID = "UID:";
const SETTINGS_XAPIAN_DB = './xapian_db';
const SETTINGS_MYSQL_HOST = 'localhost';
const SETTINGS_MYSQL_USER = 'root';
const SETTINGS_MYSQL_PASS = 'root';
const SETTINGS_MYSQL_DB = 'demo';
const SETTINGS_MYSQL_TABLE = 'demo';
const DEFAULT_COUNT = 10;
private $mysql_link;
private $category_cache;
private $xapian_read_db;
private $xapian_write_db;
private $xapian_stemmer;
private $xapian_indexer;
private $xapian_enquire;
private function xapian_init_readonly() {
try{
$this->xapian_read_db = new XapianDatabase(self::SETTINGS_XAPIAN_DB);
$this->xapian_stemmer = new XapianStem("english");
$this->xapian_enquire = new XapianEnquire($this->xapian_read_db);
} catch(Exception $e) {
throw new Exception('Could initialize Xapian: ' . $e->getMessage());
}
}
private function xapian_init_writable() {
try{
$this->xapian_write_db = new XapianWritableDatabase(self::SETTINGS_XAPIAN_DB, Xapian::DB_CREATE_OR_OPEN);
$this->xapian_indexer = new XapianTermGenerator();
$this->xapian_stemmer = new XapianStem("english");
$this->xapian_indexer->set_stemmer($this->xapian_stemmer);
} catch(Exception $e) {
throw new Exception('Could initialize Xapian: ' . $e->getMessage());
}
}
private function mysql_init() {
$this->mysql_link = mysql_connect(self::SETTINGS_MYSQL_HOST, self::SETTINGS_MYSQL_USER, self::SETTINGS_MYSQL_PASS);
if (!$this->mysql_link) {
throw new Exception('Could not connect: ' . mysql_error());
}
$db_selected = mysql_select_db(self::SETTINGS_MYSQL_DB, $this->mysql_link);
if (!$db_selected) {
throw new Exception('Can\'t use db : ' . mysql_error());
}
}
/**
* Index method
*
*/
public function index($params) {
$this->xapian_init_writable();
$this->mysql_init();
$start = microtime(true);
$response = new stdClass();
$response->indexed = array();
$offset = (isset($params['offset'])) ? intval($params['offset']) : 0;
$count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT;
$sql = 'SELECT * FROM '.self::SETTINGS_MYSQL_TABLE.' LIMIT ' . $offset . ', ' . $count . ';';
$result = mysql_query($sql);
if (!$result) {
throw new Exception('Invalid query: ' . mysql_error());
}
$this->xapian_write_db->begin_transaction();
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
$response->indexed[] = $this->index_row($row);
}
$this->xapian_write_db->commit_transaction();
mysql_free_result($result);
mysql_close($this->mysql_link);
return $response;
}
private function index_row($row) {
$doc = new XapianDocument();
$this->xapian_indexer->set_document($doc);
$this->xapian_indexer->index_text($row['name'],50);
$this->xapian_indexer->index_text($row['summary'], 1);
$GUID = self::XAPIAN_PREFIX_UID . $row['unique_key'];
$doc->add_term($GUID);
$doc->add_value(self::XAPIAN_FIELD_URL, $row['url']);
$doc->add_value(self::XAPIAN_FIELD_DATE, date('Ymd', strtotime($row['date'])));
$doc->add_value(self::XAPIAN_FIELD_UID, $row['unique_key']);
$doc->add_value(self::XAPIAN_FIELD_NAME, $row['name']);
$doc->add_value(self::XAPIAN_FIELD_SUMMARY, $row['summary']);
$this->xapian_write_db->replace_document(strval($GUID), $doc);
$row_response = array();
$row_response['name'] = $row['name'];
$row_response['guid'] = $row['unique_key'];
$row_response['url'] = $row['url'];
return $row_response;
}
/**
* Delete method
*
*/
public function delete($params) {
$this->xapian_init_writable();
$this->xapian_write_db->begin_transaction();
$response = array();
foreach($params['items'] as $param_guid) {
$GUID = self::XAPIAN_PREFIX_UID . $param_guid;
$this->xapian_write_db->delete_document(strval($GUID));
$response[] = $param_guid;
}
$this->xapian_write_db->commit_transaction();
return $response;
}
/**
* Search method
*
*/
public function search($params) {
$this->xapian_init_readonly();
$start = microtime(true);
// queries array to later construct full query
$arr_queries = array();
// from date
if(!empty($params['date_from'])) {
$arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_GE, 6, date('Ymd', strtotime($params['date_from'])));
}
// to date
if(!empty($params['date_to'])) {
$arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_LE, 6, date('Ymd', strtotime($params['date_to'])));
}
// unique key
if(!empty($params['unique_key'])) {
$arr_queries[] = new XapianQuery(self::XAPIAN_PREFIX_UID . $params['unique_key']);
}
// normal search query parsed
if(!empty($params['search'])) {
$qp = new XapianQueryParser();
$qp->set_stemmer($this->xapian_stemmer);
$qp->set_database($this->xapian_read_db);
$qp->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$arr_queries[] = $qp->parse_query($params['search']);
}
// Find the results for the query.
// construct final query
$query = array_pop($arr_queries);
foreach($arr_queries as $sq) {
$query = new XapianQuery(XapianQuery::OP_AND, $query, $sq);
}
$this->xapian_enquire->set_query($query);
// set the count to the specified params
$offset = (isset($params['offset'])) ? intval($params['offset']) : 0;
$count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT;
$matches = $this->xapian_enquire->get_mset($offset, $count);
$response = new stdClass();
$response->result_count = $matches->get_matches_estimated();
$results = array();
$i = $matches->begin();
while (!$i->equals($matches->end())) {
$m = array();
$n = $i->get_rank() + 1;
$doc = $i->get_document();
$m['position'] = $n;
$m['url'] = $doc->get_value(self::XAPIAN_FIELD_URL);
$m['name'] = $doc->get_value(self::XAPIAN_FIELD_NAME);
$m['summary'] = $doc->get_value(self::XAPIAN_FIELD_SUMMARY);
$m['date'] = $doc->get_value(self::XAPIAN_FIELD_DATE);
$m['unique_key'] = $doc->get_value(self::XAPIAN_FIELD_UID);
$m['percent'] = $i->get_percent();
$results[count($results)] = $m;
$i->next();
}
$response->results = $results;
$end = microtime(true);
// runtime info
$response->execute = new stdClass();
$response->execute->call = 'search';
$response->execute->offset = $offset;
$response->execute->count = $count;
$response->execute->start = $start;
$response->execute->end = $end;
$response->execute->time = $end - $start;
// debug stuff
$response->execute->debug = $query->get_description();
return $response;
}
}
</code></pre>
<h3>index.php</h3>
<pre><code>
<?php
require_once 'XapianWrapper.php';
$x = new XapianWrapper();
$res = $x->index(array());
print_r($res);
</code></pre>
<h3>search.php</h3>
<pre><code>
<?php
require_once 'XapianWrapper.php';
$x = new XapianWrapper();
$params = array('search' => 'foo');
$res = $x->search($params);
print_r($res);
</code></pre>
<h3>delete.php</h3>
<pre><code>
<?php
require_once 'XapianWrapper.php';
$x = new XapianWrapper();
$params = array(
'items' => array('foo'),
);
$res = $x->delete($params);
print_r($res);
</code></pre>
<h3>Using the example</h3>
<p>Once you've unzipped the <a href="http://www.contentwithstyle.co.uk/resources/xapian-example.zip">expamples</a>, you should now be able to create the DB from the db.sql file, and run the php examples via command line.</p>
<pre><code>
bash$ php index.php
stdClass Object
(
[indexed] => Array
(
[0] => Array
(
[name] => foo
[guid] => foo
[url] =>
)
[1] => Array
(
[name] => bar
[guid] => bar
[url] =>
)
)
)
bash$ php search.php
stdClass Object
(
[result_count] => 2
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => foo
[summary] => foo bar test
[date] => 20081105
[unique_key] => foo
[percent] => 100
)
[1] => Array
(
[position] => 2
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 50
)
)
[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674866.79
[end] => 1256674866.79
[time] => 0.000944852828979
[debug] => Xapian::Query(Zfoo:(pos=1))
)
)
bash$ php delete.php
Array
(
[0] => foo
)
bash$ php search.php
stdClass Object
(
[result_count] => 1
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 100
)
)
[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674876.02
[end] => 1256674876.02
[time] => 0.000872850418091
[debug] => Xapian::Query(Zfoo:(pos=1))
)
)
</code></pre>
<p>Right, I leave it up to you to amend the examples to suit your individual needs. As always any feedback or improvements are welcome. Happy indexing and searching everyone.</p>Quick helper script for ZF view translations 2009-10-29T10:34:01+00:00http://www.contentwithstyle.co.uk/content/quick-helper-script-for-zf-view-translations--/quick-helper-script-for-zf-view-translations--<p>Manual copy and paste jobs are a pain. Faced with the prospect of internationalizing 30+ big view files, I thought there had to be a better way. When I started to use the reg exp search facility in Textmate, the penny dropped. A helper script is what was needed.</p>
<h2>PHP cli script 'internationalize.php'</h2>
<pre><code>
<?php
// check for CLI
if (php_sapi_name() != "cli") {
print "This script is written to run under the command line ('cli') version of\n";
print "the PHP interpreter, but you're using the '".php_sapi_name()."' version\n";
exit(1);
}
// check for valid syntax
if(empty($argv[1])) {
die('syntax: php internationalize.php <file> <optional token prefix>');
}
function get_key($str, $prefix) {
$key = str_replace(' ', '_', strtolower($str));
$key = preg_replace('/[\\:\\.\\,\\!\\?]{0,}/', '', $key);
$prefix = $GLOBALS['prefix'];
return $prefix.$key;
}
function key_replace($matches) {
if(trim($matches[2]) != "") {
$key = get_key($matches[2]);
return '<'.$matches[1].'><?php echo $this->translate->_("'.$key.'"); ?></'.$matches[3].'>';
} else {
return $matches[0];
}
}
// get a prefix for the tokens
$prefix = (!empty($argv[2])) ? $argv[2] . '_' : '';
// identify and load file
$file = realpath($argv[1]);
$contents = file_get_contents($file);
// create backup file
copy($file, $file.'.bak');
// reg exp to find all text in between tags
$pattern = '/<([\\w \\=\\%\\"\\(\\)\\\'\\:]+)>([\\w\\s\\:\\.\\,\\!\\?_-]+)<\\/([\\w]+)>/';
// replace with translation calls
$translated = preg_replace_callback($pattern, 'key_replace', $contents);
file_put_contents($file, $translated);
// start generate ini file
$rows = array();
$matches = array();
preg_match_all($pattern, $contents, $matches);
for($i=0; $i<count($matches[1]); $i++) {
if(trim($matches[2][$i]) != "") {
$key = get_key($matches[2][$i]);
$rows[] = $key.'="'.addslashes($matches[2][$i]).'"';
}
}
$ini_string = implode("\n", $rows);
file_put_contents($file.'.translation.ini', $ini_string);
</code></pre>
<h2>Example view file 'test.phtml'</h2>
<pre><code>
<p>Fooo</p>
<blockquote style="test">Ich teste gerade</blockquote>
<ul>
<li>Test list</li>
<li>Test list 2</li>
</ul>
</code></pre>
<h2>Translate it!</h2>
<p>Now we're set. We just need to call:</p>
<pre><code>
php internationalize.php test.phtml
</code></pre>
<p>We end up with a this in test.phtml:</p>
<pre><code>
<p><?php echo $this->translate->_("fooo"); ?></p>
<blockquote style="test"><?php echo $this->translate->_("ich_teste_gerade"); ?></blockquote>
<ul>
<li><?php echo $this->translate->_("test_list"); ?></li>
<li><?php echo $this->translate->_("test_list__2"); ?></li>
</ul>
</code></pre>
<p>and this in test.phtml.translation.ini:</p>
<pre><code>
fooo="Fooo"
ich_teste_gerade="Ich teste gerade"
test_list="Test list"
test_list__2="Test list 2"
</code></pre>
<h2>In progress</h2>
<p>This is not meant as THE solution. It's merely a quick script I put together in a couple of minutes. There are a couple of things that it can't do, like translating attributes and all that.
</p>
<p>I'm no reg exp guru, and if you can improve please feel free to comment. Otherwise, have fun.</p>Quick lint check on changed php files with svn st and xargs2009-10-20T10:12:05+01:00http://www.contentwithstyle.co.uk/content/quick-lint-check-on-changed-php-files-with-svn-st-and-xargs/quick-lint-check-on-changed-php-files-with-svn-st-and-xargs<p>If you haven't done it in a <a href="http://blueparabola.com/blog/subversion-commit-hooks-php">hook</a> already, here's the quick way to check all your changed files for syntax errors:</p>
<pre><code>
svn st | grep .php | grep M | cut -c 8- | xargs -n1 php -l
</code></pre>
<p>
I use xargs quite a lot lately, but some good clues came from David Singletons post "<a href="http://dsingleton.co.uk/blog/using-xargs-bash-example">Using xargs like you mean it</a>".
</p>Zend Framework DB and Mysql pre 5.172009-10-08T10:38:34+01:00http://www.contentwithstyle.co.uk/content/zend-framework-db-and-mysql-pre-5.17/zend-framework-db-and-mysql-pre-5.17<p>I was getting weird errors when running multiple queries with Zend Framework, that I just couldn't replicate on my local environment.</p>
<pre><code>
Exception information:
Message: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
Stack trace:
#0 /project/ZendFramework-1.7.1/Zend/Db/Statement.php(109): Zend_Db_Statement_Pdo->_prepare('UPDATE `foo...')
#1 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Pdo/Abstract.php(170): Zend_Db_Statement->__construct(Object(Zend_Db_Adapter_Pdo_Mysql), 'UPDATE `foo...')
#2 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Abstract.php(429): Zend_Db_Adapter_Pdo_Abstract->prepare('UPDATE `foo...')
#3 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Pdo/Abstract.php(220): Zend_Db_Adapter_Abstract->query('UPDATE `foo...', Array)
#4 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Abstract.php(551): Zend_Db_Adapter_Pdo_Abstract->query('UPDATE `foo...', Array)
...
</code></pre>
<p>Turns out the server runs MySQL pre 5.17:</p>
<pre><code>
$ yum list installed | grep mysql
mysql.i386 5.0.45-7.el5 installed
mysql-server.i386 5.0.45-7.el5 installed
php-mysql.i386 5.1.6-23.2.el5_3 installed
</code></pre>
<p>In order to fix this we need to turn on query buffering:</p>
<pre><code>
$pdoParams = array(
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
);
$params = array(
'host' => '127.0.0.1',
'username' => 'webuser',
'password' => 'xxxxxxxx',
'dbname' => 'test',
'driver_options' => $pdoParams
);
$db = Zend_Db::factory('Pdo_Mysql', $params);
</code></pre>
<p>
Thanks to <a href="http://mysqltalk.wordpress.com/">Joe Devon from MySQL Talk</a> for <a href="http://mysqltalk.wordpress.com/2008/11/09/enabling-mysql-query-cache-with-zend-framework-through-pdomysql_attr_use_buffered_query/">pointing out the relevant part of the Zend Framework documentation</a> that points out why it happens and how to fix it.</p>Looking after your health is important2009-08-30T19:15:33+01:00http://www.contentwithstyle.co.uk/content/looking-after-your-health-is-important/looking-after-your-health-is-important<p>About three months ago I suffered a contractors nightmare: An injury that meant I couldn't work for quite some time.</p>
<p>In my instance it was a very nasty episode of lower back pain, which made me go through a month of not being able to tie my shoes. Of course it could have been worse. I did have a break planned anyway (ironically it was to get fit and do the <a href="http://en.wikipedia.org/wiki/Dunwich_Dynamo">Dunwich Dynamo</a>) so I didn't have to bail out of a current contracting situation, and I was lucky that my osteopath didn't confirm my fears and sent me to hospital to have any surgery done. However, I didn't feel OK with taking on any contracting job for two and a half months, and I did spend quite some money on sessions to get my spine clicked into position. Also, following the routine I was recommended means I have to do back and core strength exercises every day.</p>
<p>This is why I want to give you contractors out there a couple of words on your way:</p>
<h2>Stay healthy</h2>
<p>I know it sounds lame, but you might want to consider doing the exercises before the pain comes. This might keep you fit and healthy to cope with 8 to 10 hours sitting each day, plus it might also help to balance out the stress that sometimes comes with contracting work, deadlines and delivery dates.</p>
<h2>Look at your working environment</h2>
<p>If you can help it, make sure you get a good chair, the computer set up and all the rest. I am sure you know the drill.</p>
<h2>Insurance</h2>
<p>In my case it didn't really matter that much, but what if I had kids, a mortgage and an ongoing contract? Clearly I should be insured against loss of income? There's a <a href="http://www.contractoruk.com/money/phi.html">pretty neat guide on Contractors UK about what to look for when you purchase insurance</a>. I recommend reading it and contemplating the necessary steps.</p>
<br />
<br />
<p>Finally, if you're suffering from back pain like I do, have a look at <a href="http://www.youtube.com/watch?v=LodlEH6TluM&feature=PlayList&p=FEC7C9FF242C8C6C&playnext=1&playnext_from=PL&index=22">youtube</a>. The videos of yoga exercises have given me some good clues of what to incorporate into my exercise routine.</p>
<p>Also, If you have any tips to share, be it about injury prevention, workplace set up or experiences with insurance claims, please do!</p>
Remove nodes in SimpleXMLElement2009-07-15T18:56:07+01:00http://www.contentwithstyle.co.uk/content/remove-node-in-simplexml/remove-node-in-simplexml<blockquote><p>SimpleXML provides no way to remove [...] XML nodes.</p></blockquote>
<p>You might think otherwise and <a href="http://www.kavoir.com/2008/12/how-to-delete-remove-nodes-in-simplexml.html">hack it with <code>unset()</code></a>, as it was done in one of the web applications I inherited at work, but today I found out that this works under some conditions, but not with every setup. I'd love to tell you what exactly the differences are that make it break, but I didn't spend the time tracking it down.</p>
<p>I can tell you what <code>unset()</code> did the the specific setup (PHP 5.1.2 on Windows Vista): Nothing. It happily executed it without error or warning, but also without changing anything.</p>
<p>Luckily S. Gehrig had a good and in my eyes not-hacky idea in <a href="http://stackoverflow.com/questions/262351/remove-a-child-with-a-specific-attribute-in-simplexml-for-php#262556">a thread on stackoverflow.com</a>: He suggests to use the DOM, specifically <code>dom_import_simplexml()</code>, to then simply use <code>removeChild()</code>.</p>
<p>In my case I had a predictable XML structure to parse, and only needed to kick out the first of a set of nodes, so my solution (using the same xml setup as example) is as follows:</p>
<pre><code>
$data='<data>
<seg id="A1"/>
<seg id="A5"/>
<seg id="A12"/>
<seg id="A29"/>
<seg id="A30"/>
</data>';
$doc=new SimpleXMLElement($data);
$dom=dom_import_simplexml($doc->seg[0]);
$dom->parentNode->removeChild($dom);
echo $doc->asXml();
/*outputs:
* <?xml version="1.0"?>
* <data><seg id="A5"/><seg id="A12"/><seg id="A29"/><seg id="A30"/></data>
*/
</code></pre>
<p>For more complex XML structures (and probably closer to most real-life scenarios), I agree with S. in suggesting xpath as a way to detect your unwanted node.</p>Custom Zend Form Image Upload Element2009-06-29T23:59:44+01:00http://www.contentwithstyle.co.uk/content/custom-zend-form-image-upload-element/custom-zend-form-image-upload-element<p>It really is time to post something, isn't it? Here is a quick way to have an image preview inside of a form based on Zend Form.</p>
<h2>Assumptions</h2>
<p>I am using the auto include mechanism for this one, utilizing the PEAR naming convention, and all my files will sit underneath a Shared folder within the library folder.</p>
<h2>A custom form Element</h2>
<p>This is sitting under Shared/Form/Element/Image.php</p>
<pre><code>
<?php
class Shared_Form_Element_Image extends Zend_Form_Element
{
public $helper = "imageUpload";
public $options;
public function __construct($image_name, $attributes, $data_item) {
$this->options = $data_item;
parent::__construct($image_name, $attributes);
}
}
</code></pre>
<p>As you can see, the form Element itself doesn't contain much code, but it does overwrite the property 'helper'. Which bings us to the next file:</p>
<h2>A custom view helper</h2>
<p>In our example, this sits under Shared/Views/Helpers/ImageUpload.php</p>
<pre><code>
<?php
class Shared_View_Helper_ImageUpload extends Zend_View_Helper_FormFile {
public function imageUpload($name, $value, $attribs, $options) {
$str = parent::formFile($name, $attribs = null);
if(!empty($options[$name])) {
$str .= $this->getImagePreview($name, $options[$name]);
} else {
$str .= $this->getEmptyPreview();
}
return $str;
}
private function getImagePreview($name, $path) {
$img = ($this->view->doctype()->isXhtml())
? '&lt;img src="/'.$path.'" alt="'.$name.'" />'
: '&lt;img src="/'.$path.'" alt="'.$name.'">';
return '&lt;p class="preview">'.$img.'&lt;/p>';
}
private function getEmptyPreview() {
return '&lt;p class="preview">No image uploaded.&lt;/p>';
}
}
</code></pre>
<p>Pretty straight forward, I am just subclassing the FormFile helper that Zend brings us. However, you might want to create a different helper all together, if you need to use much more customized markup than I do.</p>
<h2>Invocation</h2>
<p>The form itself will no contain the image path as element, therefore we need to inject it into the form data inside the Controller, then invoke the Form with that data:</p>
<pre><code>
$data['image'] = $image_path;
$form = new My_From($data);
</code></pre>
<p>Inside our Form, all we need to do now is to add a new element using our custom ImageUpload element:</p>
<pre><code>
$preview = new Shared_Form_Element_Image('image', null, $options);
$preview->addValidator('Count', false, 1);
$preview->addValidator('Extension', false, 'jpg,png,gif');
$preview->setLabel('My Image');
$fields[] = $preview;
</code></pre>
Getting phpunit to work with MAMP2009-05-13T12:41:46+01:00http://www.contentwithstyle.co.uk/content/getting-phpunit-to-work-with-mamp/getting-phpunit-to-work-with-mamp<p>In order to run my unit tests, I needed to get phpunit running with MAMP. Thanks to the <a href="http://mark-kirby.co.uk/2009/installing-phpunit-with-mamp/">helpful post of Mark Kirby</a> on this, I figured it out.</p>
<ol>
<li>cd into your PEAR installation directory</li>
<li>Make backup of phpunit file in bin folder</li>
<li>Edit the original file, replace #!/usr/bin/php with #!/Applications/MAMP/bin/php5/bin/php</li>
<li>3. Also remove this:
<pre><code>
if (strpos('/usr/bin/php', '@php_bin') === 0) {
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
}
</code></pre></li>
<li>And finally, something that Mark didn't mention, add PEAR include path into /Applications/MAMP/conf/php5/php.ini</li>
</ol>Deploying PHP applications with Vlad and SVN2009-05-08T15:12:32+01:00http://www.contentwithstyle.co.uk/content/deploying-php-applications-with-vlad/deploying-php-applications-with-vlad<p>In my current day job I have to deploy a number of web apps every few weeks.
The person I took over from did the whole thing in a manual and terribly cumbersome way:</p>
<ul>
<li>exporting the project locally,</li>
<li>zipping it up,</li>
<li>moving it to the server,</li>
<li>unpacking and symlinking it, to finally</li>
<li>run a small script that would set the correct production config, chmod log and cache folders etc.</li>
</ul>
<p>In the following article, I'm going to describe how I went from there to deploying my Zend Framework-driven PHP apps with Vlad. To save yourself some time, feel free to <a href="#ruby-setup">skip the back story</a>, the <a href="#pre-configure" title="skip installing ruby, gem and rake">pre-requisite of installing ruby, gem and rake</a>, or <a href="#deployment-recipes">go straight to the deployment recipes</a>.</p>
<p id="backstory">I did the above method for a while, and realized quickly how much time is wasted by this process. Remembering my efforts of <a href="http://www.contentwithstyle.co.uk/content/php-deployment-with-capistrano">deploying PHP apps with Capistrano</a>, I felt
it was time to set up an automated deployment routine. While re-reading the article, thoughts returned of how unwieldy and badly documented Capistrano came across,
and I started looking for something more lean, that wouldn't scare of my non-ruby colleagues who might have to do my job when I'm away.</p>
<p>Very quickly I was lead to <a href="http://rubyhitsquad.com/Vlad_the_Deployer.html">Vlad the Deployer</a>, replacing one deployment app where the person in charge of the name had one tipsy field day with, well, another.</p>
<p>Taking it for its first spin, I felt that while there's a lot of confidence behind Vlad, the word "simple" crops up lots of times, but again,
the documentation is a bit of a thinking man's manual; As soon as you go beyond the initial example it skips a few steps here and there,
which can leave someone who is unfamiliar with the concept struggling. Maybe it's a case of "it's advanced stuff, just live with it". </p>
<p>Luckily I have my Capistrano experience, so I got started fairly quickly using Vlad for my own purposes. What I liked immediately about Vlad was that it declares as one of the project goals to "use the right tool for the job (ssh, rsync, etc)."</p>
<p id="vlad-goals">My initial goal was to use Vlad</p>
<ul>
<li>to have a deployment one-liner,</li>
<li>to roll back if needed, and</li>
<li>to be able to share logs and other assets between releases</li>
</ul>
<p id="example-app">For the example below I created a demo Zend Framework application with <a href="http://devzone.zend.com/article/4559-Zend_Tool-and-ZF-1.8">Zend_Tool</a>,
but what I'm describing should work for any PHP app out of the box, actually for any app, if you add any potential server tasks yourself.
This example is using Subversion, but there is other examples out there that'll tell you how to use it with Git and other SCM software.
</p>
<h2 id="ruby-setup">Setup</h2>
<p>To be able to deploy with Vlad you need to have the following 4 things set up:</p>
<h3>1. unix/linux development platform with SVN</h3>
<p>not much to add, Vlad works on Mac OS X and various linux flavours, but not under Windows. For this setup I'm assuming you're working with SVN.</p>
<h3>2. server with SVN client</h3>
<p>Your webserver should be able to work with SVN, and it should be able to reach your SVN repository.</p>
<h3>3. install vlad dependencies</h3>
<p>on OS X 10.5 you already have a rails installation coming with the developer tools; just do</p>
<pre><code>% gem update rails</code></pre>
<p>Alternatively and on earlier OS X, you can use MacPorts, to install Ruby with</p>
<pre><code>% port install ruby</code></pre>
<p>You might also be able to use fink, and on debian/ubuntu you can install via aptitude</p>
<pre><code>
% sudo apt-get install ruby
% sudo apt-get install rubygems</code></pre>
<p>For other platforms and compiling from source see here:<br/>
<a href="http://www.ruby-lang.org/en/downloads/">http://www.ruby-lang.org/en/downloads</a></p>
<p>Next you need to install Rake. For all systems, to update gem and install Rake, do the following. You don't need to <code>gem update</code> if you started from scratch:</p>
<pre><code>
% gem update --system
% gem update</code></pre>
<p>
<a href="http://www.virtualmin.com/index.php?option=com_flyspray&Itemid=82&do=details&task_id=4901&Itemid=82&project=1&pagenum=4">Gem may fail initially</a> - if so this is due to a bug in the gem command and requires you to edit the gem script (found with the command <code>which gem</code> on the command line, it's probably in <code>/usr/bin/gem</code>) by adding to the require statements (under <code>require 'rubygems'</code>):</p>
<pre><code>
require 'rubygems/gem_runner'
</code></pre>
<p>And finally, to install Rake itself:</p>
<pre><code>% gem install rake</code></pre>
<p>There are 3 more dependencies,</p>
<ul>
<li>Hoe</li>
<li>Rubyforge</li>
<li>open4</li></ul>
<p>but they're installed automatically with the next step.</p>
<h3>4. install vlad as a gem</h3>
<p>To install vlad as a gem, all you need to do is</p>
<pre><code>% sudo gem install vlad</code></pre>
<p>while allowing for dependencies to be installed, which is set by default. Congratulations, you're ready to configure Vlad.</p>
<h2 id="pre-configure">Pre-Configure</h2>
<p>As Vlad is working closely with your system tools, you should set up your SSH for it first. You need to make sure the remote user is set up correctly, and you'll want some way of not having to enter your password a million times. Luckily <a href="http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working" title="4 ssh config tips for faster remote working">I wrote about this recently</a>. For the server I'd like to deploy to I added this. You might not need the ControlMaster/ControlPath, the important line is to set the remote user:</p>
<pre><code>
Host myserver.pretendco.com
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p
User remoteuser
</code></pre>
<p>Now let's have a look at the server itself. There is a possibility that the web directory isn't writable by your remote user and you need to have root permissions. As you should use the root user for all this, and sudo croaks on remote commands, it's best to set your future releases directory to be owned by your remote user.</p>
<p>on the remote machine</p>
<pre><code>
% sudo chown remoteuser:remotegroup /path/to/releases/directory
% chmod 775 /path/to/releases/directory
</code></pre>
<h2>Configure and Deploy</h2>
<p>Next you need to create 2 files for Vlad: A Rakefile in your root directory, and a deploy.rb config file in application/configs. Your project should look a little something like this (yes, this is slightly abbreviated):</p>
<pre> |-application
|---configs
|-----config.ini
|-----deploy.rb <-- just created
|---controllers
|---models
|---views
|-library
|-public
|-Rakefile <-- just created
</pre>
<p id="deployment-recipes">At its core, the Rakefile is very straight forward; I've lifted the initial loading step more or less from the Vlad documentation, with the exception of setting the path for deploy.rb manually. Should your application have a root folder <code>/config</code>, you're following default convention and won't need the instructions in the curly braces. Further down I'm resetting the <code>:update_symlinks</code> task to link to shared logs and media folders, as well as linking the Zend library into my library. By default Vlad would set 3 shared folders: /log, /system and /pids, which I am all omitting here.</p>
<pre><code>
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
begin
require 'vlad'
Vlad.load {:config => 'application/configs/deploy.rb'} # set path for deploy.rb
rescue LoadError
# do nothing
end
namespace :vlad do
# Clear existing symlink task so that we can redefine.
Rake.clear_tasks('vlad:update_symlinks')
# we've only got log to share
remote_task :update_symlinks, :roles => :app do
run "rm -Rf #{latest_release}/log && ln -s #{shared_path}/log #{latest_release}/log && chmod -f 666 #{latest_release}/log/*"
run "rm -Rf #{latest_release}/public/media && ln -s #{shared_path}/cache #{latest_release}/public/media"
run "rm -Rf #{latest_release}/library/Zend && ln -s #{shared_path}/Zend #{latest_release}/library/Zend"
end
end
</code></pre>
<p>Next up is deploy.rb, the config file for the to-be-deployed app. The only 3 mandatory lines in all cases are <code>:domain</code>, <code>:deploy_to</code>, and <code>:repository</code>.</p>
<pre><code>
set :application, "Example application"
set :domain, "pretendco.com"
set :deploy_to, "/project/example_releases"
set :repository, 'http://svn.pretendco.com/example/trunk'
set :svn_cmd, 'svn --username svnuser --password svnpwd'
</code></pre>
<p>The default SVN command is <code>svn</code>, but in my case the SVN user is different to local and remote user, and for some reason the SVN password prompt from within the deployment step kept failing on me, so I added it right here. Not the safest thing in the world, but I'm using a limited read-only SVN user for these purposes, so that's good enough for me. Any suggestions of improvement are welcome.</p>
<p>For your convenience I have zipped up <a href="http://www.contentwithstyle.co.uk/resources/vlad-config.zip">both files</a>, or if you like, the <a href="http://www.contentwithstyle.co.uk/resources/vlad-example.zip">whole demo project</a>.</p>
<p>The first time you run vlad, you do</p>
<pre><code>
% rake vlad:setup vlad:update
</code></pre>
<p>You can skip the additional <code>vlad:migrate vlad:start</code> mentioned in the <a href="http://hitsquad.rubyforge.org/vlad/doco/getting_started_txt.html">vlad documentation</a>, as we're neither migrating DBs (this time), nor does Apache need to be restarted.</p>
<p><code>vlad:setup</code> has now created a folder structure on your server that looks like this (abbreviated again):</p>
<pre> |-project
|---example_releases
|-----current <-- link to your web application
|-----releases
|-------yyyymmddhhiiss <-- your web application
|---------application
|---------library
|-----------Zend <-- link to Zend Framework
|---------log <-- link to shared logs
|---------public
|-----------media <-- link to shared media
|-----revisions.log
|-----scm <-- folder used to checkout release
|-----shared
|-------log
|-------media
|-------Zend
</pre>
<p><code>vlad:update</code> has exported your application from the HEAD of your SVN repository, pushed it into a release folder, and pointed <code>/current</code> to this release. This means once you've verified this initial deployment step you can set your Apache webroot to point to <code>/project/example_releases/current</code>. You can also see that the folders <code>/log</code>, <code>/public/media</code> and <code>/library/Zend</code> have been symlinked, so you'll want to move all your dynamically created files into the shared folder now.</p>
<p>As a side note, I've had two major criticisms about this, out of which I have fixed one in this example: The first one was that initially I didn't include Zend Framework in my release, but rather added it to the global include_path on the server. "Your applications should be self-contained!", Pascal was shouting from his desk, including some expletives. The other thing he flagged up was that the version of my application config checked into SVN was set up for the live environment, so that I didn't have to change a thing when deploying. I have fixed this in the meantime as well, with a set of other convenient changes, but you'll have to be patient for me to write my next post about it and stick with this solution for now.</p>
<p id="finish-line">The good news are that you're ready to go now!
To release a new version:</p>
<ul>
<li>check in your changes into SVN</li>
<li>make sure that the repository URL in deploy.rb points to the same as yours</li>
<li>run <code>% rake vlad:update</code> to deploy your app</li>
<li>put your feet up, and enjoy the rest of your day.</li>
</ul>
<p>If your release is broken, roll back with</p>
<pre><code>% rake vlad:rollback</code></pre>
<p>This will remove the last release and symlink to the previous version.</p>
<p>As a final word of warning: Using any means of deployment that rely on your code versioning software mean that you have to keep it clean. Only check in code you've tested locally, don't use the branch you deploy from as a backup mechanism. If you and any other people working with your code follow this advice, Vlad will save you lots of time, and turn the pain of updating a site into the joy of releasing something new.</p>DOMDocument loadXML throws errors: A bug?2009-04-22T17:37:46+01:00http://www.contentwithstyle.co.uk/content/domdocument-loadxml-throws-errors-a-bug-not-a-feature/domdocument-loadxml-throws-errors-a-bug-not-a-feature<p>So I was wondering why loadXML gives me parsing errors, despite a big try and catch around it ... <a href="http://php.filearena.net/manual/kr/function.dom-domdocument-loadxml.php#69295">it's not a bug they say</a>.</p>
<p>
Gavin Sinai put up a code snippet in the PHP.net reference, which shows how to set the error handler to get rid of it:
</p>
<pre><code>
<?php
function HandleXmlError($errno, $errstr, $errfile, $errline)
{
if ($errno==E_WARNING && (substr_count($errstr,"DOMDocument::loadXML()")>0))
{
throw new DOMException($errstr);
}
else
return false;
}
function XmlLoader($strXml)
{
set_error_handler('HandleXmlError');
$dom = new DOMDocument();
$dom->loadXml($strXml);
restore_error_handler();
return $dom;
}
?>
</code></pre>Unit testing web service based models in Zend Framework2009-04-20T09:37:30+01:00http://www.contentwithstyle.co.uk/content/unit-testing-web-service-based-models-in-zend-framework/unit-testing-web-service-based-models-in-zend-framework<p>Web applications nowadays use an increasingly distributed set of resources. How do we test our MVC applications that use web services in their models?</p>
<h2>What is the problem?</h2>
<p>Unit tests for database driven models are relatively straight forward. Usually we connect to a separate database, containing a known set of data. Since we know the data we can run assertions against it.</p>
<p>However, unit testing models that talk to web services presents us with a different set of problem. Usually our application runs requests against something that is not in our control. Unless the service provider gives us some kind of test mode, we're operating with dynamic live data which shouldn't be used for testing, since it's harder to test in the first place, but also could be harmful to be manipulated.</p>
<p>Let's have a look at two strategies to test out models.</p>
<h2>Mock objects for Services</h2>
<p>If we are using an object to represent the API calls to the service from within our model, i.e. <a href="http://framework.zend.com/manual/en/zend.service.delicious.html">Zend_Service_Delicious</a>, we can swap this object for a mock object. A simple accessor allows us to overwrite private properties of the object:</p>
<pre><code>
class DeliciousModel {
private $delicious;
public function __construct() {
$this->delicious = new Zend_Service_Delicious('username', 'password');
}
public function getPosts() {
return $this->delicious->getAllPosts();
}
public function setProperty($name, $value) {
if(UNITTEST_CONTEXT) {
$this->$name = $value;
}
}
}
</code></pre>
<p>Now it's easy swap out private properties from within a unit test:</p>
<pre><code>
public function testDeliciousModel {
$mockPosts = array('foo', 'bar');
$deliciousMock = $this->getMock('Zend_Service_Delicious', array('getAllPosts'));
$deliciousMock->expects($this->once())
->method('getAllPosts')
->will($this->returnValue($mockPosts));
$model = new DeliciousModel();
$model->setProperty('delicious', $deliciousMock);
$posts = $model->getPosts();
$this->assertEquals($posts, $mockPosts);
}
</code></pre>
<p>
Of course this would mean that we're not launching any requests to our services at all, but instead check that models are doing the right method calls to the service API object, and get the right data back.
</p>
<h2>Validate service requests</h2>
<p>
Generally speaking, models that are using web services will transform method calls into HTTP requests against web service end points. Those might come in a variety of flavours, but all together we can say that, if we can intercept the request, we can make sure the model serializes method calls correctly into known request data.
</p>
<h3>Test adapter for Zend_HTTP calls</h3>
<p>If our model uses Zend_Http to launch the API calls, we might provide a hook in the model so that we can swap the HTTP adapter for the <a href="http://framework.zend.com/manual/en/zend.http.client.adapters.html#zend.http.client.adapters.test">test adapter</a> that ships with Zend Framework. We then have access to the entire request that the model launched, which we can use for assertions:</p>
<pre><code>
public function testJsonCall() {
$adapter = new Zend_Http_Client_Adapter_Test();
$response = "";
$response .= "HTTP/1.1 200 OK" . "\r\n";
$response .= "Content-type: application/json" . "\r\n";
$response .= "\r\n";
$response .= '{}';
$adapter->setResponse($response);
$model = new JsonModel();
$model->setAdapter($this->adapter);
$request_raw = $model->http_client->getLastRequest();
// do your assertions here
}
</code></pre>
<h3>Swap the endpoint</h3>
<p>
Instead of changing the adapter to intercept the call, we could swap the endpoint of the web service against a mock service. This endpoint then can respond with known data which we can use for our assertions.
</p>
<p>Depending on what kind of web services we have to mock, we might use the servers provided by Zend, e.g. <a href="http://framework.zend.com/manual/en/zend.rest.server.html">Zend_Rest_Server</a> or <a href="http://framework.zend.com/manual/en/zend.soap.html#zend.soap.server">Zend_Soap_Server</a>.
</p>
<h2>A better understanding</h2>
<p>Looking at the two strategies above, it still might seem to be a hassle to do, what is essentially a replication of the web service functionality with test data, whether that happens as mock object, on the request/response level, or as test endpoint.</p>
<p>But instead, this exercise is a very <em>valuable documentation of ones understanding of the service functionality</em> at a particular point in time. Given that service APIs are often in a constant state of change, things might break in the future, but the tests we've written can help us understand whether it's down to our own code or the web service instead. If things fail at the web service level, we can now refer to our tests to compare what we expect the service to do with what it actually does.</p>
<h2>Related Links</h2>
<ul>
<li><a href="http://www.pointbeing.net/weblog/2009/04/unit-testing-code-which-consumes-soap-services.html">Unit Testing Code which Consumes SOAP Services</a></li>
<li><a href="http://www.soapui.org/gettingstarted/mocking.html">Mocking with SoapUI</a></li>
<li><a href="http://www.codeguru.com/columns/vb/article.php/c15209">Unit Testing with Stubs or Mocks</a></li>
</ul>4 ssh config tips for faster remote working2009-04-17T12:00:02+01:00http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working/4-ssh-config-tips-for-faster-remote-working<p>With the vast majority of web developers deploying their own code, and ssh playing a central role in the toolbox, I felt that tweaking this part of my work saves me some time, and if not, definitely some frustration over mistyping passwords and host names.</p>
<p>So, for myself as much as everyone out there, here is my collected useful knowledge that has helped me ease the pain of remote work.</p>
<h3>1. Execute commands remotely as one-liners</h3>
<p>This is what some deployment tools such as <a href="http://rubyhitsquad.com/Vlad_the_Deployer.html">vlad</a> use, but for a quick look at something, it cuts out valuable seconds you could look at web comics instead.</p>
<p>Let's try a simple example:</p>
<pre><code>
matthias:~ matthias$ ssh remoteuser@myserver.pretendco.com ls -l
remoteuser@myserver.pretendco.com's password:
total 804
lrwxrwxrwx 1 remoteuser remoteuser 31 Nov 10 2007 access-logs -> /links/to/my/access-logs
drwxr-xr-x 2 remoteuser remoteuser 2048 Apr 6 2008 cgi-bin
-rw-r--r-- 1 remoteuser remoteuser 808417 Jul 17 2007 error_log_dump
drwxr-x--- 3 remoteuser mail 2048 Oct 1 2007 etc
drwxrwx--- 7 remoteuser remoteuser 2048 Apr 4 02:33 mail
drwxr-x--- 3 remoteuser remoteuser 2048 May 23 2008 public_ftp
drwxr-x--- 30 remoteuser nobody 2048 Dec 4 06:33 public_html
drwxr-xr-x 7 remoteuser remoteuser 2048 Sep 24 2008 tmp
lrwxrwxrwx 1 remoteuser remoteuser 11 Apr 1 2006 www -> public_html
matthias:~ matthias$
</code></pre>
<p>You can even <a href="http://lookherefirst.wordpress.com/2007/11/29/executing-command-remotely-via-ssh/">pipe through to a remote command</a>. Here's an example taken from Look Here First, where the content of a local file is concatenated to a remote file.</p>
<pre><code>
matthias:~ matthias$ cat localfile.txt | ssh remoteuser@myserver.pretendco.com "cat - >> remotefile.txt"
</code></pre>
<h3>2. Add your public key to your remote server</h3>
<p>Next let's see how we can get around typing our password all the time. One way of doing this is to <a href="http://sial.org/howto/openssh/publickey-auth/" title="OpenSSH Public Key Authentication">add your public key to your remote server</a>.</p>
<pre><code>
# first, upload public key from client to server
client$ scp ~/.ssh/id_rsa.pub remoteuser@myserver.pretendco.com:~
# next, setup the public key on server
server$ mkdir ~/.ssh
server$ chmod 700 ~/.ssh
server$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
server$ chmod 600 ~/.ssh/authorized_keys
server$ rm ~/id_rsa.pub
</code></pre>
<p>Of course, we can combine with the previous example and run it like this:</p>
<pre><code>
matthias:~ matthias$ cat ~/.ssh/id_dsa.pub | ssh remoteuser@myserver.pretendco.com "cat - >> ~/.ssh/authorized_keys2"
</code></pre>
<p>Windows users fear not, <a href="http://www.aota.net/Telnet/puttykeyauth.php4" title="Key Authentication with PuTTY">you can do this too</a>, with PuTTY and PuTTYgen.</p>
<h3>3. Let the ControlMaster handle your sessions</h3>
<p>Sadly this might not always possible, maybe your key management on your server is different, or you don't have permissions to the file.</p>
<p>Luckily you can get around this by <a href="http://blog.zenlinux.com/?p=270">letting your first ssh connection act as ControlMaster</a>. By adding the following to ~/.ssh/config, you only need to enter your password once, and all parallel sessions won't ask for a password</p>
<pre><code>
Host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p
</code></pre>
<h3>4. Set your user and hostname for your host</h3>
<p>Finally, more often than not your username won't be the same on your local machine and on whatever server you'd like to connect to, or your server might not use the default ssh port. Or maybe you're just tired of typing that superlong domain name, and look for a less challenging typing task. ~/.ssh/config to the rescue.</p>
<p>As final example:</p>
<pre><code>
Host myserver
User remoteuser
Port 22022
HostName myserver.pretendco.com
</code></pre>
<p>Most likely you won't need the Port instruction in there, because your server runs ssh on the default port 22, so just drop the line.</p>
<p>All this allows us something as compact as:</p>
<pre><code>
matthias:~ matthias$ ssh myserver mycommand
</code></pre>
<p>Have fun, and let's hope this got you into your weekend early!</p>A caching pattern for models2009-04-06T11:50:07+01:00http://www.contentwithstyle.co.uk/content/a-caching-pattern-for-models/a-caching-pattern-for-models<p>This is a caching pattern for models using Zend_Cache and the __call magic method.</p>
<h2>The Basic Idea</h2>
<p>
The initial thought behind this is that a model should be able to return calls either uncached or cached, without initializing some cache object every time. It should be easy to switch between the two calls, and the cache should be coming with the model object already.
</p>
<h3>The old way</h3>
<p>
Before introducing the cache pattern, I would create an object that then would use Zend_Cache_Frontend_Class, then initialize a new instance of my model in there, then redefine a method that acts as somewhat like a proxy, but uses the cache.
And if that wasn't enough, I'd have to initialize them both, like in this example:
</p>
<pre><code>
$model = MyModel();
$model_cached = MyModelCached();
$values_direct = $model->doStuff();
$values_cached = $model_cached->doStuff();
</code></pre>
<h3>The new way</h3>
<p>
So Matthias asked me: Would it not be nicer if the cache was already available in the model, and you could do something like this:
</p>
<pre><code>
$model = MyModel();
$values_direct = $model->doStuff();
$values_cached = $model->cache->doStuff();
</code></pre>
<p>
A great idea. As you can see this proposed way saves a bit of code, it's intuitive and easy to change, if you ever need to switch between cached and non-cached calls.
</p>
<h2>The Code</h2>
<p>First we need to create an object that initializes the cache. We'll initialize a cache with Zend_Cache and then utilize the __call function to check whether a call exists in the cached object or not. This enables us to only do valid calls.</p>
<pre><code>
class BaseModelCache {
private $object;
private $cache;
public function __construct($object) {
$backendName = 'File';
$frontendName = 'Class';
$frontendOptions = array(
'lifetime' => 1800,
);
$backendOptions = array(
'cache_dir' => '/my/cache/dir/',
);
$this->object = $object;
$frontendOptions['cached_entity'] = $object;
try {
Zend_Loader::loadClass('Zend_Cache');
$this->cache = Zend_Cache::factory($frontendName, $backendName, $frontendOptions, $backendOptions);
} catch(Exception $e) {
throw($e);
}
}
public function __call($method, $args) {
$class = get_class($this->object);
$class_methods = get_class_methods($class);
if(in_array($method , $class_methods)) {
$caller = Array($this->cache, $method);
return call_user_func_array($caller, $args);
}
throw new Exception( " Method " . $method . " does not exist in this class " . get_class($class ) . "." );
}
}
</code></pre>
<p>The second step is to create a base model class, that we'll use for all our models. It will initialize the cache object using the BaseModelCache and make it available as public property:</p>
<pre><code>
abstract class BaseModel {
public $cache;
public function __construct() {
$this->cache = new BaseModelCache($this);
}
}
</code></pre>
<p>Et Voilà! Now we can create models by extending the BaseModel. The cache will be available as described above.</p>Unit testing controllers with Zend Framework2009-04-02T14:47:48+01:00http://www.contentwithstyle.co.uk/content/unit-testing-controllers-with-zend-framework/unit-testing-controllers-with-zend-framework<p>Unit testing your Zend Framework driven MVC applications is not hard at all. This post tries to give a brief overview on how to test your Controllers with Zend_Test.</p>
<h2>Install PHPUnit</h2>
<p>Zend Framework integrates with PHPUnit. PHPUnit is a <a href="http://pear.php.net/">PEAR</a> module, and the usual way is to install PEAR that first and then <a href="http://www.phpunit.de/manual/current/en/installation.html">use the pear installer to install PHPUnit for you</a>.</p>
<h2>Create an application bootstrap include</h2>
<p>If you're using the <a href="http://framework.zend.com/manual/en/zend.controller.front.html">front controller pattern</a> and have all your variables set up in the index.php of your web folder, now is the time to <a href="http://framework.zend.com/docs/quickstart/create-a-bootstrap-file">move things into an include file</a>, before you create the controller and dispatcher. We'll use this very file when doing unit tests with Zend_Test</p>
<h2>Start Testing Controllers</h2>
<p>
With the help of Zend_Test we're now able to create tests that dispatch our controllers. We're then able to do assertions against the response, using the provided methods of the <a href="http://framework.zend.com/apidoc/core/Zend_Test/PHPUnit/Zend_Test_PHPUnit_ControllerTestCase.html">ControllerTestCase</a>.
</p>
<pre><code>
class IndexTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function setUp() {
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
public tearDown() {
$this->resetRequest();
$this->resetResponse();
parent::tearDown();
}
public function appBootstrap() {
require_once('/my/bootstrap.php');
}
public function testHomepage() {
$this->dispatch('/');
$this->assertController('index');
$this->assertAction('index');
$this->assertXpath("//form[@action = '/foo']");
}
}
</code></pre>
<h3>Manipulate the request, evaluate the response</h3>
<p>
Now that we've dispatched our controller, we can manipulate the request and access the response, to see what the dispatched action does. For example an action that requires post data, and then redirects us to another page:
</p>
<pre><code>
public testPostAction() {
$request = $this->getRequest();
$request->setMethod('POST');
$request->setPost(array(
'foo' => 'bar',
'baz' => 'x',
));
$this->dispatch('/my/post/action');
$this->assertRedirectTo('/my/expected/redirect');
}
</code></pre>
<h3>Checking data manipulation</h3>
<p>
If we're working with data manipulations in our tests, we could use models to create the data we manipulate, and then to evaluate it after the controller has been dispatched. Finally we'll clean up after the test. Of course you'd want to have stable models at that point.
</p>
<pre><code>
public function testSave() {
$my_id = $model->create();
$request = $this->getRequest();
$request->setMethod('POST');
$request->setPost(array(
'name' => 'unittest modified',
'id' => $my_id,
));
$this->dispatch("/save/object/");
$this->assertRedirectRegex('#/edit/object/id/[\d]+$#');
$data = $model->get($my_id);
$this->assertEquals($data['name'], 'unittest modified');
$my_id = $model->delete($my_id);
}
</code></pre>
<h2>Related links</h2>
<ul>
<li><a href="http://framework.zend.com/manual/en/zend.test.html">Zend_Test Reference Guide</a></li>
<li><a href="http://devzone.zend.com/article/2772-An-Introduction-to-the-Art-of-Unit-Testing-in-PHP">An Introduction to the Art of Unit Testing in PHP</a></li>
<li><a href="http://blog.fedecarg.com/2008/12/27/phpunit-testing-zend-framework-controllers/">PHPUnit: Testing Zend Framework Controllers</a></li>
<li><a href="http://www.zfforums.com/zend-framework-general-discussions-1/installation-configuration-3/zend_test-how-set-up-1569.html">Zend_Test: how to set up?</a></li>
</ul>
Zend_Translate with dynamic parameters2009-03-13T11:46:57+00:00http://www.contentwithstyle.co.uk/content/zendtranslate-with-dynamic-parameters/zendtranslate-with-dynamic-parameters<p>Just a quick snippet to have dynamic parameters in the underscore function, without having to write sprintf every time.</p>
<pre><code>
<?php
class Translate extends Zend_Translate {
public function _() {
$args = func_get_args();
$num = func_num_args();
$adapter = $this->getAdapter();
$args[0] = $adapter->_($args[0]);
if($num <= 1) {
return $args[0];
}
return call_user_func_array('sprintf', $args);
}
}
</code></pre>
<p>Usage would be something like the following:</p>
<pre><code>
$t = new Translate('array', $array_translation, $lang);
echo $t->_('My name is %s', 'Pascal');
echo $t->_('I have a %s and a %s', 'Cat', 'Horse');
</code></pre>My take on jQuery charts2009-02-28T20:24:47+00:00http://www.contentwithstyle.co.uk/content/my-take-on-jquery-charts/my-take-on-jquery-charts<p>A while back I did <a href="http://www.contentwithstyle.co.uk/search/canvas">a couple of posts</a> touching the subject HTML 5 and the canvas tag. Of course I have been tinkering around with chart drawing APIs and so on, and couldn't help to write a little script myself.</p>
<p>I started out with pie charts, and essentially did a mashup between <a href="http://joncom.be/code/excanvas-piechart">Jon Combes approach</a> and <a href="http://www.mattknott.com/content/blog/2008/04/Advanced_Canvas_Based_Pie_Chart.html">Advanced Canvas Based Pie Chart by Matthew Knott</a>, then wrapped a JQuery plugin around it.</p>
<p>Later I added some sidebar charts, which were much simpler to do and don't require to understand the fact that javascript does trigonometric functions in radians rather than degrees, a fact that made my brain boil when I tried to add in the labels, because I was expecting different numbers to come back from my calculations ...</p>
<p>I was keen on passing the chart values in as array, so I could gather data from an HTML table with a couple of lines of JS, for example. I left this out in <a href="http://www.contentwithstyle.co.uk/resources/jquery_canvascharts_demo/index.html">the little demo that I mocked up</a>, which I uploaded <a href="http://www.contentwithstyle.co.uk/resources/jquery_canvascharts_demo.zip">as a zip file</a> as well, but I am sure you guys figure what I mean.</p>
<p>The whole thing is nothing new, and <a href="http://www.liquidx.net/plotkit/">Plotkit</a> or <a href="http://bluff.jcoglan.com">Bluff</a> are way more advanced, but I think it's a nice little demo to read and understand, without being overwhelmed by large files of code. So I hope you enjoy it.</p>Perl validation from PHP2009-02-26T11:07:31+00:00http://www.contentwithstyle.co.uk/content/perl-validation-from-php/perl-validation-from-php<p>A quick method to validate perl scripts from PHP. Took me a long time that perl-c does not use stdout but stderr as output stream ...</p>
<pre><code>
public function validationErrorPerl($content) {
$hash = md5(microtime() . $content);
$infile = CACHE_DIR . '/validation_' . $hash . '.pl';
$outfile = CACHE_DIR . $this->config->cache->dir . '/validation_' . $hash . '.txt';
file_put_contents($infile, $content);
$cmd = "/usr/bin/perl -c $infile 2> /dev/null &> $outfile";
exec($cmd);
$result = file_get_contents($outfile);
unlink($infile);
unlink($outfile);
$result = str_replace($infile, 'perl script', $result);
if(strpos($result,'syntax OK') !== false) {
return false;
}
return $result;
}
</code></pre>PyObjC newbie frustration2009-02-21T18:27:25+00:00http://www.contentwithstyle.co.uk/content/pyobjc-newbie-frustration/pyobjc-newbie-frustration<p>So I was trying out to build a graphical interface for my VHOST shell script, this time in Python, but couldn't get one past hurdle: I need superuser rights to edit /etc/hosts and the httpd.conf.</p>
<p>While I was able to build a quick interface for a python script to do essentially the same, using the PyObjC bridge and the Interface Builder, I couldn't figure out how the hell I am supposed to get the authentication going to execute the script as superuser ... and a look into the <a href="http://developer.apple.com/documentation/Security/Reference/authorization_ref/Reference/reference.html#//apple_ref/c/func/AuthorizationExecuteWithPrivileges">documentation</a> does give no clue on how to use Authorization with PyObjC, but only shows a C function ...?</p>
<p>Apart from that I found the Interface Builder pretty intriguing, and I'm starting to like XCode as well. Of course I haven't got much of a clue of how Python is done properly, and especially the underscore convention for actions threw me off at first. Also, what do people mean when they say Pythonic? What makes an app/script/library exactly that?</p>
<p>Of course I am way out of my comfort zone here. Would appreciate some hints and guidance.</p>
You and the stu nicholls guy2009-02-09T10:25:26+00:00http://www.contentwithstyle.co.uk/content/you-and-the-stu-nicholls-guy/you-and-the-stu-nicholls-guy<p>It amazes me time and time again how much emotion goes into our work life. Not that I can exclude myself, I don't even want to: I'm proud if I've achieved a great end result, be it in code or in planning an application, I'm happy when my machines and applications run as expected. Equally I feel the discomfort of a bug that's down to my sloppiness, or worse, a conceptual error that has me repeating or redoing my work, because I couldn't be bothered to fully understand a concept or the implications of a decision made early.</p>
<p>To avoid the latter, I've never stopped learning, reading up on how it's supposed to be done, and trying to see the benefit in generalizations others have suggested.</p>
<p>Web Standards and the people that actively try shaping them is such a subject. Over the years I've followed a number of blogs and web publications, and looked at how <em>they</em> do it, taking on suggestions when they seemed to make sense, trying to work out the benefits when the don't. Lucky me, living in London, even had a chance to see, and at times even chat to some of <em>them</em>, the people who seem to have so many good ideas.</p>
<p>One of them is <a href="http://www.flickr.com/photos/ptg/216749262/">Dean Edwards</a>, who I've met several times at the Pub Standards, when his and my occasional attendance overlapped.</p>
<p>Now, he's forgotten to renew his domain, and the reminder didn't reach its intended target. That's kinda <a href="http://www.reddit.com/r/programming/comments/7vl0t/what_can_i_do_to_get_a_stolen_domain_back/c07j4yd">stupid</a>, but I can confirm that although it has so far not happened to me, I'm uncomfortable about this subject every 2 years, because my registrar has such a bad user interface that I'm never sure if it updated credit card details, if a changed status means that everything is (going to be) ok, etc. (feel free to suggest a registrar that tells me in simple words where I'm at)</p>
<p>Maybe I'm just too forgiving, but imagine: Somebody who has been publicly, on the web and elsewhere, trying to standardize and improve his work universe, forgets something important. That's crazy, and clearly deserves a <a href="http://www.reddit.com/r/programming/comments/7vl0t/what_can_i_do_to_get_a_stolen_domain_back/c07j5t0">telling-off, throwing everything in</a> except the kitchen sink.</p>
<p>Oh, it's so funny, I have to quote it here (who knows, maybe it'll be taken down). So, on the erroneously titled subject of a lost-and-snatched domain with unlicensed content replication, someone called "gmn17" had this to say:</p>
<blockquote><p>man oh man what happens to you guys?, you and the quirksmode guy and the stu nicholls guy, you guys get burned out and then whore yourselves out and can't handle what you created and started, putting down people for using tables and innerhtml like the guy who explains how different the internet and the world wide web are so different, trying to create an atmosphere of holier than thou web design and make people feel like crap for using tables and document.write, it's hard to feel sorry for you, you yourself said many times on that site that you only use it sparingly or can not update it as frequently, and that stu nicholls guy, man what a sellout, someone needs to tell him his javascript is horrible, you just have to be patient, I always liked your site too, lots of nice stuff, can't believe you can write code like that and get into a situation like this, what a world,</p></blockquote>
<p>Amazing! I mean, it's hilarious, it looks so menacing! I wanted to only quote one sentence, but, there is no full stop. I'm interpreting a lot of pent-up anger in there, and the chance of a lifetime. Ironically, his comment wouldn't have been too out of place at Ajaxian, where a <a href="http://ajaxian.com/archives/css-for-layout-another-rant">discussion about CSS (or tables)for layout</a> was reheated and served.</p>
<p>I'm sure you'll find a lot of flak in the comments, too, maybe even some condescending advice. But as far as I read, they only get as personal as "you're probably a backend dev" or the general denial of professionalism on the part of the original author. A great week for making the web a better place, that's for sure.</p>Shell script for adding virtual hosts in Leopard2009-02-08T08:22:59+00:00http://www.contentwithstyle.co.uk/content/shell-script-for-adding-virtual-hosts-in-leopard/shell-script-for-adding-virtual-hosts-in-leopard<p>I was getting annoyed with constantly editing config files myself. That's why I hacked together a shell script to add the vhosts for me.</p>
<h2>Prerequisites</h2>
<p>
Open the Terminal and execute the following commands:
</p>
<pre><code>
sudo sed -i -e s@^#Include\ /private/etc/apache2/extra/httpd-vhosts.conf@Include\ /private/etc/apache2/extra/httpd-vhosts.conf@ /private/etc/apache2/httpd.conf
</code></pre>
<p>
This will enable the vhost include file to be read from the httpd conf.
</p>
<pre><code>
sudo sed -i -e s/^\</#\</ /private/etc/apache2/extra/httpd-vhosts.conf
sudo sed -i -e s/^\ /#\ / /private/etc/apache2/extra/httpd-vhosts.conf
sudo sed -i -e s/^#NameVirtualHost/NameVirtualHost/ /private/etc/apache2/extra/httpd-vhosts.conf
</code></pre>
<p>
This will enable the Virtualhosts in apache.
<br />
Now let's enable PHP as well:
</p>
<pre><code>
sudo sed -i -e s@^#LoadModule\ php5_module@LoadModule\ php5_module@ /private/etc/apache2/httpd.conf
</code></pre>
<p>
All we need to do is restart apache:
</p>
<pre><code>
sudo apachectl restart
</code></pre>
<h2>Adding a vhost</h2>
<p>
Now we're all set, and can create the shell script in some location of your choice. In the example below it's assumed that it is called add.sh.
</p>
<p>The shell script itself:</p>
<pre><code>
#!/bin/sh
#
# Script to validate files in directory
#
add_host_entry() {
echo "127.0.0.1 $1" >> /etc/hosts
}
add_vhost_conf() {
cat localvhost.tpl | sed "s/{VHOSTNAME}/$1/" | sed "s@{VHOSTDIRECTORY}@$2@" >> /private/etc/apache2/extra/httpd-vhosts.conf
}
if [ $# -lt 2 ]; then
echo "syntax: sh add.sh vhostname vhostdir"
exit
fi
add_host_entry $1
add_vhost_conf $1 $2
</code></pre>
<p>The template file 'localvhost.tpl' needs to sit in the same folder like 'add.sh':</p>
<pre><code>
#VHOSTSTART {VHOSTNAME}
<VirtualHost *:80 >
DocumentRoot "{VHOSTDIRECTORY}"
ServerName {VHOSTNAME}
ErrorLog /private/var/log/apache2/{VHOSTNAME}-error_log
CustomLog /private/var/log/apache2/{VHOSTNAME}-access_log common
</VirtualHost>
<Directory {VHOSTDIRECTORY}>
Options Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>
#VHOSTEND {VHOSTNAME}
</code></pre>
<p>
You should now be able to add vhosts by typing:
</p>
<pre><code>
cd /path/to/shellscript/folder
sudo add.sh vhostname /path/to/vhost/webroot
</code></pre>
<p>
Once you're done do an apache restart again:
</p>
<pre><code>
sudo apachectl restart
</code></pre>Installing APC on Leopard2009-02-03T12:02:17+00:00http://www.contentwithstyle.co.uk/content/installing-apc-on-leopard/installing-apc-on-leopard<p>There are a few steps needed to install <a href="http://www.php.net/apc">APC</a> on OS X Leopard. Don't worry, it may look a bit daunting if you've never had to compile anything before but it's very straight forward.</p>
<ul>
<li>First, download the appropriate version of the <a href="http://pecl.php.net/package/APC">APC package</a></li>
<li>Unpack it (doesn't matter where)</li>
<li>Now start Terminal and cd to the unpacked directory from the above step</li>
<li>Run 'phpize'</li>
<li>Now it needs to be configured. Copy the following configuration (note: it must be entered into the Terminal prompt as a <strong>single</strong> line)</li>
</ul>
<pre><code>MACOSX_DEPLOYMENT_TARGET=10.5 CFLAGS="-arch ppc -arch ppc64 -arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp" CCFLAGS="-arch ppc -arch ppc64 -arch i386 -arch x86_64 -g -Os -pipe" CXXFLAGS="-arch ppc -arch ppc64 -arch i386 -arch x86_64 -g -Os -pipe" LDFLAGS="-arch ppc -arch ppc64 -arch i386 -arch x86_64 -bind_at_load" ./configure</code></pre>
<ul>
<li>Nearly there: now type 'make'</li>
<li>sudo make install (you will need to enter your root password)</li>
<li>Finally, add 'extension=apc.so' into your php.ini</li>
</ul>
<p>And that's it!</p>
Spotify now in the news2009-01-29T14:20:10+00:00http://www.contentwithstyle.co.uk/content/spotify-now-in-the-news/spotify-now-in-the-news<p>One of my favourite apps lately, <a href="http://www.spotify.com/">Spotify</a> has now reached the <a href="http://www.guardian.co.uk/technology/2009/jan/29/spotify">technology news section of the Guardian</a></p>
<p>To me this doesn't come as such a big surprise, since I have been raving about it ever since I got myself a cheeky login without invitation code, something that a google search will reveal If you can't get the latter.</p>
<p>It's no often that I do big up an application, but spotify seems to do everything right that other music players didn't quite achieve. For example, I really liked last.fm, but I always wanted to be able to search for a track and then play the album.</p>
<p>Even more amazing that it does get things right that I didn't even ask for. Already my listening behaviour has changed, because I started to use Spotify's direct URLs and collaborative play lists. Let's just wait an see what else is coming in terms of nifty integration.</p>
<p>Also let's hope that they don't screw it up big time. Already they had to <a href="http://www.spotify.com/blog/archives/2009/01/28/some-important-changes-to-the-spotify-music-catalogue/">remove some tracks</a>, and the potential to bomb the application with adverts until everyone tunes out is huge. If they get it right it might be as much of an important app like the iplayer or hulu.</p>
CwS blog posts now on twitter2009-01-29T08:00:19+00:00http://www.contentwithstyle.co.uk/content/cws-blog-posts-now-twitter/cws-blog-posts-now-twitter<p>Posts from our blog will now automatically pop up on <a href="https://twitter.com/contentwstyle">Twitter</a>.</p>
<p>This is only a couple of lines of code, thanks to the <a href="http://framework.zend.com/manual/en/zend.service.twitter.html">API wrapper</a> that Zend Framework now offers, but also thanks to the <a href="http://www.scripting.com/stories/2007/06/27/tinyurlHasAnApi.html">TinyURL API</a>:</p>
<pre><code>
// TinyURL for the blog post
$client = new Zend_Http_Client($config->tinyurl->endpoint);
$client->setParameterGet('url', $url);
$response = $client->request('GET');
$tiny_url_response = $response->getBody();
// TinyURL into the message
$message = $message . ' ' . $tiny_url_response;
// Twitter
$twitter = new Zend_Service_Twitter($config->twitter->user, $config->twitter->password);
$twitter->status->update($message);
</code></pre>
<p>So feel free to follow us, even though twittering to <strong>ME</strong> only makes sense if it happens automatically, as I personally am no big fan of messages like "I am going outside now" or anything like that.</p>Remove .AppleDouble folders on *NIX boxes2009-01-22T22:54:42+00:00http://www.contentwithstyle.co.uk/content/remove-appledouble-folders-on-nix-boxes/remove-appledouble-folders-on-nix-boxes<p>Quick note to myself on how to get rid of those silly .AppleDouble folders</p>
<p>This fails, if there's funny characters in the filenames:</p>
<pre><code>
find . -name '.AppleDouble' | xargs rm -Rf
xargs: unmatched single quote
</code></pre>
<p>
Hence we need to wrap the string into quotation marks:
</p>
<pre><code>
find . -name '.AppleDouble' -printf '"%p"\n' | xargs rm -Rf
</code></pre>Make sure firebug console debug doesn't break everything.2009-01-16T14:49:49+00:00http://www.contentwithstyle.co.uk/content/make-sure-that-firebug-console-debug-doesnt-break-everything/make-sure-that-firebug-console-debug-doesnt-break-everything<p>Maybe some of you have already had problems with leaving debug statements in JS code. A <code>console.log()</code> left in the deployed code can break the whole application, and it might only come up when someone without firebug is testing it at a later stage.</p>
<p>Very embarrassing indeed, and I recall a story where the whole development team had neglected to test without firebug, but a scheduled session of Acceptance Tests held at a different site was about to fail. Someone then had to rush there and managed to save the day by installing firebug on the test machine, so that they could get on with it. This taught me a valuable lesson about what can happen in an environment where there are many developers: <strong>Mistakes WILL happen!</strong></p>
<p>
People that do flash will know the <code>trace()</code> function, which gives you debug output, but can be turned off in the export settings, so that the compiler removes those calls from the generated SWF.
<br />
But javascript isn't a compiled language so there is no auto removal of debug specific code.
<br />
This is why we have to ensure that, even though code does still contain debug code, it shouldn't break. For javascript development this means that at least console.log and console.dir statements need to be dealt with. And yes, I agree: It shouldn't be in there in the first place. But we have to either make sure that calls to the console get removed or that the rest of the javascript application isn't affected when they're made.
</p>
<h2>Suggestions</h2>
<h3>Version control hooks</h3>
<p>
CVS, SVN or GIT offer hooks. I haven't tried this out properly, but I imagine a simple shell script could help. Like doing a <em>grep</em> for console.log and stopping the commit if there's anything in there that should only be debug code.
<br />
Of course with this method the downside would be that there would be no means to share the debugging code with other developers through the version control.
</p>
<h3>Build tools</h3>
<p>
A similar check/removal process could be done in the deployment stage.
Using <em>grep</em>, <em>sed</em> or something similar on the project files to remove any debug related code would ensure that the javascript always gets deployed without debug code.
</p>
<h3>A mock object</h3>
<p>We could provide a mock object, so that calls to the console wouldn't result in an error. This would ensure that the javascript doesn't fail, even though debug code is still in the code.</p>
<pre><code>
if(!window.console) {
window.console = new function() {
this.log = function(str) {};
this.dir = function(str) {};
};
}
</code></pre>
<h3>Wrapping the debug code</h3>
<p>Another valid approach would be to wrap the calls to console.log, and provide error checking or try-catch inside the wrapper.
<br />
Of course this only works if all developers will use the wrapper instead of the firebug console object.
</p>
<pre><code>
var Logger = new function() {
this.log = function(str) {
try {
console.log(str);
} catch(e) {
// do nothing
}
};
};
</code></pre>
<h2>Finally ...</h2>
<p>Please keep in mind that what I've written is about keeping code from breaking the testing or staging environment. The truth is that, if there is anything that breaks the App in it's live deployment, then something is wrong with the testing process.</p>
<h2>Related links</h2>
<ul>
<li><a href="http://ajax-prototype.blogspot.com/2006/12/workaround-to-make-firebug-consolelog.html">Workaround to make firebug console.log function bug free on IE</a></li>
<li><a href="http://www.jennyandlih.com/resolved-logging-firebug-console-breaks-ie">jennyandlih on firebug console and IE</a></li>
<li><a href="http://wordaligned.org/articles/a-subversion-pre-commit-hook">A subversion pre commit hook</a></li>
</ul>Looking forward, looking back2009-01-08T17:44:03+00:00http://www.contentwithstyle.co.uk/content/looking-forward-looking-back/looking-forward-looking-back<h2>Looking back to 2008</h2>
<p>The biggest change of 2008 must've been the relaunch of Content with Style. We talked through the theory back in Autumn/Winter 2007, and Pascal and I made some room in our schedules in January. The first day we met, we looked at the then current Wordpress release, weighed the pros and cons against turning our backs to PHP and use Python. Wordpress seemed the quicker option, and as Pascal was going on a big trip a week later, I felt more comfortable pushing something I know. After doing some amazing doodles on A2 paper I lifted from the office, we were all psyched up and ready to go, and it was only early afternoon!</p>
<p>The rest of the day we spent realizing that our "customized" pre-1.0 release of Textpattern was in no way happy to work with the importer tool for Wordpress. It later turned out that both our data format as well as the importer were broken, and an import that would keep all IDs intact would be a pest. Sadly that realization took place late in the second day. What then followed was some last minute woes in planning Pascal's trip, and me getting a stinking cold, that knocked me out for the whole week. The next thing we knew it was May, and nothing was done.</p>
<p>With work and other commitments I will mention further down both Mike and I were blocked. As Pascal was worried that we'd never get it done, he took on development on his own (with a few really minor additions by myself) and commissioned <a href="http://koopd.com/">Alastair Holt</a> from our good friends at <a href="http://seventytwo.co.uk/">Seventy-two</a>. You're looking at <a href="http://www.contentwithstyle.co.uk/content/a-new-style-for-our-content">the result</a> right now!</p>
<p>On top of all the work invested into CwS, Pascal also extended his server-farm to two, and successfully carved himself a very nice career out of <a href="http://www.ilikecode.co.uk/">contracting around London</a>.</p>
<p>In a parallel universe, Mike has not only managed to make a successful move from freelance to permanent back in 2007 and keep it that way, but he also blossomed in his position as <a href="http://www.trampolinesystems.com/about/people">Head of User Experience at Trampoline</a>, taking a good step away from development, while exploring his communicative side as speaker all around the world. London, Brighton, USA, <a href="http://iceweb.svef.is/speakers/">Iceland</a> (post-bank-collapse!), you name it, he'll grab some slides and a glass of water.</p>
<p>All this engagement around the world lead him to take a back seat in the world of Content with Style; out of which he <a href="http://www.contentwithstyle.co.uk/content/reading-up-on-cloud-computing#comment-4541">sometimes rises to the occasion</a>.</p>
<p>Needless to say, his legacy of <a href="http://www.contentwithstyle.co.uk/archive/author/3/Mike+Stenhouse">amazing CSS and Javascript-related posts</a> is still in the top10 of most visited pages on CwS.</p>
<p>As much as I would like to take a back seat, it's not in cards for the near future. In the beginning of the year, some changes of personnel lead to me pretending to be the Technical Director of Designjunction (now <a href="http://www.designscienceoffice.com/">Design Science Office</a>), a post that was made official with the rebranding in May. I thoroughly enjoyed the role, especially mentoring my junior developer, who will no doubt get very far in the future. I would've loved to stay where I was, but the economy had other plans for me.</p>
<p>So I started looking for new work and landed a very nice Systems Developer position at <a href="http://www.photobox.co.uk/">Photobox</a>, where I am finally in a position that is mostly about developing in Zend Framework, something I've been wanting to do much more for a good year. I'm sure I've projected some of my wishful thinking on Pascal during the year, which lead to the technology behind the CwS relaunch.</p>
<p>Only days after starting my new position, I also ventured on a new endeavor of a more personal kind: On November 22 my baby daughter Esmé was born, and I keep making fun of her with inappropriate comparisons in <a href="http://www.willerich.com/900days" title="900 days - a baby blog">another corner of the internet</a> ever since.</p>
<p>As a final look back, here's a list of the 15 most popular (by views) posts from 2008:</p>
<ol id="most-popular-posts">
<li><a href="http://www.contentwithstyle.co.uk/content/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps">Fixing the Back Button and Enabling Bookmarking for AJAX Apps</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/a-css-framework">A CSS Framework</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/modular-css">Modular CSS</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/clean-urls-for-a-better-search-engine-ranking">Clean URLs for a better search engine ranking</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/comments-on-comments">Comments on Comments</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/a-to-zee-with-p3p">A to Z(ee) with P3P</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/dynamic-tables-with-xslt">Dynamic tables with XSLT</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/dom-scripting-or-how-to-keep-the-code-clean">DOM scripting or how to keep the code clean</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/safari-ajax-and-the-back-button">Safari, Ajax and the back button</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/xsl-the-other-way-of-styling-up-content">XSL: the other way of styling up content</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/css-is-worthless">CSS is Worthless</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/figure-microformats">Figure microformats</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/find-your-node-advanced-xpath-commands">Find your node: Advanced XPATH commands</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/playing-nice-with-the-other-css-kids">Playing Nice with the Other CSS Kids</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/ie-conditional-comments-in-xsl">IE Conditional comments in XSL</a></li>
</ol>
<h2>Looking forward</h2>
<p>If you've been a long-term follower of CwS or know us personally, you'll probably remember some of our pledges and promises for the future that never made it.</p>
<p>To avoid this, I simply won't promise anything :-). I'd rather tell you about the situation we're in, and some of the things that are up our sleeves as you read this:</p>
<p>We're still looking for collaborators! There are several good intentions and ideas been floating around for new authors on CwS, but so far there are no real commitments or even drafts that might go out any minute. So, if you're looking for a well-read platform to spread the word, drop us a line right here in the comments.</p>
<p>I've prepared a small series of articles that we'll post over the year, which will mainly deal with the next generation of web developers. While we gain seniority in our working lives, the focus of what lies ahead splits into two: Either we keep on specializing, and push the envelope of what's possible in our fields of expertise, or we do more and more strategy and managerial work as team leads, telling our juniors how to do it right. The series is about handing down working patterns that are second nature to us, that speed up our work dramatically.</p>
<p>On the other hand we want to keep doing what we've always done: Write posts about the technologies we use every day right now, and provide useful nuggets of information, small and large, to expand the horizon or pick the brains of our readers.</p>
<p>Finally you can see from our higher rate of short posts, that we started using CwS not only as means to provide information for you, but also as our little black book of quick notes, details we come across once and want to offload our brains for future use. As a side effect we hope to give a little insight into how we work.</p>
<h2>Wrap it up already...</h2>
<p>All that's left for me now is to wish you all a Happy New Year 2009. May your lives and work pan out nicely, even in uncertain times like this. See you around, and all the best,</p>
<p>from all of us at Content with Style</p>GET parameters and caching2009-01-07T16:55:27+00:00http://www.contentwithstyle.co.uk/content/get-parameters-and-caching/get-parameters-and-caching<p>
Once you do care about caching and set expiry headers for static files in the far future, you'll have to make sure that crucial new functionality is not pulled from the cache. But a GET parameter is NOT the way to do this ....
</p>
<p>
Have a look into the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9">HTTP protocol 1.1 specs about caching</a>, and it becomes obvious why:
</p>
<blockquote>
<p>
We note one exception to this rule: since some applications have traditionally used GETs and HEADs with query URLs (those containing a "?" in the rel_path part) to perform operations with significant side effects, caches MUST NOT treat responses to such URIs as fresh unless the server provides an explicit expiration time. This specifically means that responses from HTTP/1.0 servers for such URIs SHOULD NOT be taken from a cache.
</p>
</blockquote>
<p>
<a href="http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/">People have tested this</a>, and the bottom line is that you should change the filename instead. Interestingly (I didn't verify this, btw.) <a href="http://stackoverflow.com/questions/131061/do-fancy-mvc-urls-affect-how-caching-is-done#133160">it seems that Firefox and IE just ignore this part of the spec sometimes, while Safari does it the right way</a>.
</p>XHTML Validation with the W3C validator and PHP2009-01-04T12:25:44+00:00http://www.contentwithstyle.co.uk/content/xhtml-validation-with-the-w3c-validator-and-php/xhtml-validation-with-the-w3c-validator-and-php<p>
Amongst other changes, I am working on getting this blog over to use application/xhtml+xml as the content type. Of course this calls for a much stricter validation before content can be put live, otherwise users will be confronted with a broken page. The W3C validator and Zend_Http_Client make validation in PHP easy.
</p>
<p>
People that remember my <a href="http://www.contentwithstyle.co.uk/content/a-mass-validation-shellscript">validation shell script</a> from last year (cough) already know that <a href="http://validator.w3.org/feed/docs/soap.html">there is a SOAP-like response format available from the w3c validator</a>. Like other people I am <a href="http://yoast.com/w3c-validator-api/">unhappy that the whole thing is not really a proper SOAP endpoint</a>, but merely the same script that returns a SOAP envelope when the post parameter 'output' is set to 'soap12'.
<br />
This is very unfortunate. I wasn't able to use <a href="http://framework.zend.com/manual/en/zend.soap.client.html">Zend_Soap_Client</a> to construct the request, since the passed parameters are wrapped into a SOAP envelope as well, which the validator doesn't interpret.
</p>
<p>
Instead I used the <a href="http://framework.zend.com/manual/en/zend.http.html#zend.http.client.parameters">Zend_Http_Client to do a POST request</a>, which works neatly, but requires processing of the SOAP response as XML document. Below is an example of a validation controller that validates a URL and handles the SOAP response:
</p>
<pre><code>
<?php
class Admin_ValidationController extends Zend_Controller_Action
{
public function indexAction() {
$url = $this->_request->getParam('url');
$client = new Zend_Http_Client($url);
$response = $client->request();
$fragment = $response->getBody();
$params = array(
'fragment' => $fragment,
'output' => 'soap12',
);
$client = new Zend_Http_Client('http://validator.w3.org/check');
$client->setParameterPost('fragment', $fragment);
$client->setParameterPost('output', 'soap12');
$validator_response = $client->request('POST');
$soap_response = $validator_response->getBody();
$xml = new DomDocument();
@$xml->loadXML($soap_response);
$xpath = new DOMXpath($xml);
$xpath->registerNamespace("m", "http://www.w3.org/2005/10/markup-validator");
$elements = $xpath->query("//m:errorcount");
$error_str = '';
if($elements->item(0) && $elements->item(0)->nodeValue > 0) {
$errors = $xpath->query("//m:errors/m:errorlist/m:error/m:message");
foreach ($errors as $node) {
$error_str .= $node->nodeValue. "\n";
}
}
if(!empty($error_str)) {
$this->view->message = $error_str;
} else {
$this->view->message = 'Validation of ' . $url . ' passed without errors.';
}
}
</code></pre>
<h2>No Zend_Http_Client?</h2>
<p>
For people that cannot or don't want to use Zend Framework at all (or need a facility to encode post parameters as multipart form data), maybe it's worth having a look at the <a href="http://uk2.php.net/manual/en/ref.curl.php">cURL functions in PHP</a>. They provide another easy interface to do HTTP and even FTP requests. A possible snippet could look like this:
</p>
<pre><code>
$params = array(
'fragment' => '<html />',
'output' => 'soap12',
);
$url = 'http://validator.w3.org/check';
$recieved_headers = "";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params); // multipart encoding
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_REFERER,'');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_TIMEOUT,30);
$recieved_headers = curl_exec($ch);
if (curl_errno($ch)) {
print curl_error($ch);
} else {
curl_close($ch);
}
echo $recieved_headers;
</code></pre>
<p>In this example I didn't include the handling of the SOAP response, but you can easily grab that from the previous example.</p>
<p>Happy validating everyone!</p>Conditional get in PHP - with some problems on the way2009-01-01T18:05:03+00:00http://www.contentwithstyle.co.uk/content/conditional-get-in-php-with-some-problems-on-the-way/conditional-get-in-php-with-some-problems-on-the-way<p>Happy new year everyone. If you're anything like me, the credit crunch doomsday news and the gross overeating of the past festivities make you want to slim down and become more resourceful. Using Caching, <a href="http://en.wikipedia.org/wiki/HTTP_ETag">ETags</a> and the <a href="http://www.checkupdown.com/status/E304.html">304 HTTP code</a> can help to cut down on traffic. But working on a conditional get came with some problems ...</p>
<p>The first issue I had is that Firefox didn't send the proper headers. I was't entirely sure why, but after a long google search and trial and error it turned out that it was happening as long as there weren't the correct Cache-Control or Pragma directives. Setting Cache-Control to 'public, must-revalidate' and Pragma to 'cache' got it finally working.</p>
<p>However, the PHP snippets that <a href="http://annevankesteren.nl/2005/05/http-304">I looked at</a> <a href="http://simonwillison.net/2003/Apr/23/conditionalGet/">for reference</a> utilize the HTTP_IF_MODIFIED_SINCE and HTTP_IF_NONE_MATCH entries in the $_SERVER array. Something that I could not get to work at all. Using the <code>apache_request_headers()</code> circumnavigates this issue, and the headers If-None-Match and If-Modified-Since are pulled correctly.</p>
<p>So here's the code that works for me as it stands:</p>
<pre><code>
function checkBrowserCache($identifier, $last_modified) {
$arr = apache_request_headers();
$etag = '"' . md5($last_modified . $identifier) . '"';
$client_etag = @$arr['If-None-Match'] ? trim(@$arr['If-None-Match']) : false;
$client_last_modified_date = @$arr['If-Modified-Since'] ? trim(@$arr['If-Modified-Since']) : false;
$client_last_modified = date('D, d M Y H:i:s \G\M\T', strtotime($client_last_modified_date));
$etag_match = true;
if(!$client_last_modified || !$client_etag) {
$etag_match = false;
}
if($etag_match && $client_last_modified > $last_modified) {
$etag_match = false;
}
if($etag_match && $client_etag != $etag) {
$etag_match = false;
}
header('Cache-Control:public, must-revalidate', true);
header('Pragma:cache', true);
header('ETag: '.$etag);
if($etag_match) {
header('Not Modified',true,304);
die();
}
header('Last-Modified:'.date('D, d M Y H:i:s \G\M\T', $last_modified));
}
</code></pre>
<p>Still, one problem remained: Firefox refuses to do as all other browsers that I tested. After getting a 304 correctly, it will pull the page again in its entirety. This means a full 200 OK response every second time Firefox does a request.</p>Merry Xmas everyone2008-12-19T13:21:18+00:00http://www.contentwithstyle.co.uk/content/merry-xmas-everyone/merry-xmas-everyone<p>
Now that the year has come to an end, I just wanted to wish everyone a <a href="#" onclick="(function(){var k=document.getElementsByTagName('body')[0];var i=document.createElement('canvas');var p=i.getContext('2d');var j,g=50,o=1,l,f,d;i.style.cursor='pointer';i.style.zIndex='999999';i.style.left=0;i.style.top=0;i.style.position='absolute';k.appendChild(i);i.onclick=function(){i.parentNode.removeChild(i)};window.onresize=function(){window.clearTimeout(d);n()};function n(){l=window.innerWidth;f=window.innerHeight;i.setAttribute('width',l);i.setAttribute('height',f);j=[];for(var b=0;b<g;b++){j.push({x:Math.random()*l,y:((Math.random()*f)-o),})}a()}function e(b,c){p.fillStyle='#ffffff';p.beginPath();p.arc(b,c,5,0,Math.PI*2,true);p.closePath();p.fill()}function m(b,c){p.fillStyle='white';p.strokeStyle='black';p.globalAlpha='1.0';p.lineWidth='1';p.lineCap='butt';p.lineJoin='round';p.mitterLimit='1';p.font='normal normal 12 Courier';p.fillStyle='#cf7122';p.beginPath();p.moveTo(190.5+b,110+c);p.bezierCurveTo(212.76+b,137.04+c,228.7+b,183.28+c,201+b,214.5+c);p.bezierCurveTo(177.76+b,240.7+c,137.26+b,243.54+c,115+b,216.5+c);p.bezierCurveTo(92.74+b,189.46+c,95.24+b,144.54+c,117.5+b,117.5+c);p.bezierCurveTo(139.76+b,90.46+c,168.24+b,82.96+c,190.5+b,110+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#dd381b';p.beginPath();p.moveTo(164+b,125+c);p.bezierCurveTo(189.77+b,161.32+c,206.77+b,222.68+c,181+b,259+c);p.bezierCurveTo(155.23+b,295.32+c,77.77+b,296.32+c,52+b,260+c);p.bezierCurveTo(26.23+b,223.68+c,36.21+b,158.1+c,66+b,125+c);p.bezierCurveTo(93+b,95+c,138.23+b,88.68+c,164+b,125+c);p.closePath();p.fill();p.fillStyle='#f8d5a1';p.beginPath();p.moveTo(48+b,169.5+c);p.bezierCurveTo(52.06+b,174.75+c,44.75+b,178+c,45.82+b,190.33+c);p.bezierCurveTo(46.7+b,200.46+c,38.15+b,193.63+c,33.92+b,188.06+c);p.bezierCurveTo(29.69+b,182.48+c,29.69+b,173.45+c,33.92+b,167.88+c);p.bezierCurveTo(38.15+b,162.3+c,43.94+b,164.25+c,48+b,169.5+c);p.closePath();p.fill();p.beginPath();p.moveTo(184.25+b,209+c);p.bezierCurveTo(179.33+b,204.25+c,187.98+b,205.19+c,186.25+b,190.5+c);p.bezierCurveTo(184.87+b,178.73+c,189.51+b,181.16+c,193.03+b,186.04+c);p.bezierCurveTo(196.56+b,190.93+c,197.1+b,199.29+c,194.24+b,204.71+c);p.bezierCurveTo(191.38+b,210.14+c,189.17+b,213.75+c,184.25+b,209+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#ffd5a2';p.beginPath();p.moveTo(142.82+b,38.47+c);p.bezierCurveTo(160.39+b,56.44+c,160.39+b,85.56+c,142.82+b,103.53+c);p.bezierCurveTo(125.25+b,121.49+c,96.75+b,121.49+c,79.18+b,103.53+c);p.bezierCurveTo(61.61+b,85.56+c,61.61+b,56.44+c,79.18+b,38.47+c);p.bezierCurveTo(96.75+b,20.51+c,125.25+b,20.51+c,142.82+b,38.47+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.strokeStyle='#000000';p.lineWidth='1.000000';p.lineJoin='miter';p.beginPath();p.moveTo(-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.bezierCurveTo(-122+b,-146+c,-122+b,-146+c,-122+b,-146+c);p.closePath();p.stroke();p.fillStyle='#4f4f4f';p.lineWidth='1';p.lineJoin='round';p.beginPath();p.moveTo(100.03+b,54.81+c);p.bezierCurveTo(101.59+b,56.42+c,101.59+b,59.03+c,100.03+b,60.64+c);p.bezierCurveTo(98.47+b,62.25+c,95.93+b,62.25+c,94.37+b,60.64+c);p.bezierCurveTo(92.81+b,59.03+c,92.81+b,56.42+c,94.37+b,54.81+c);p.bezierCurveTo(95.93+b,53.2+c,98.47+b,53.2+c,100.03+b,54.81+c);p.closePath();p.fill();p.beginPath();p.moveTo(128.83+b,54.81+c);p.bezierCurveTo(130.39+b,56.42+c,130.39+b,59.03+c,128.83+b,60.64+c);p.bezierCurveTo(127.27+b,62.25+c,124.73+b,62.25+c,123.17+b,60.64+c);p.bezierCurveTo(121.61+b,59.03+c,121.61+b,56.42+c,123.17+b,54.81+c);p.bezierCurveTo(124.73+b,53.2+c,127.27+b,53.2+c,128.83+b,54.81+c);p.closePath();p.fill();p.fillStyle='#ffffff';p.beginPath();p.moveTo(135.06+b,89.53+c);p.bezierCurveTo(142.85+b,107.86+c,130.06+b,138.27+c,123.51+b,157.06+c);p.bezierCurveTo(110.97+b,192.98+c,111.61+b,192.98+c,95.27+b,157.06+c);p.bezierCurveTo(87.02+b,138.91+c,77.83+b,108.48+c,85.62+b,90.14+c);p.bezierCurveTo(93.42+b,71.81+c,127.26+b,71.19+c,135.06+b,89.53+c);p.closePath();p.fill();p.fillStyle='#4f4f4f';p.beginPath();p.moveTo(117.93+b,97.68+c);p.bezierCurveTo(122.03+b,95.92+c,122.03+b,93.08+c,117.93+b,91.32+c);p.bezierCurveTo(113.82+b,89.56+c,107.18+b,89.56+c,103.07+b,91.32+c);p.bezierCurveTo(98.97+b,93.08+c,98.97+b,95.92+c,103.07+b,97.68+c);p.bezierCurveTo(107.18+b,99.44+c,113.82+b,99.44+c,117.93+b,97.68+c);p.closePath();p.fill();p.fillStyle='white';p.strokeStyle='black';p.fillStyle='#dc381b';p.beginPath();p.moveTo(127.25+b,12.09+c);p.bezierCurveTo(146.48+b,27.35+c,158.99+b,59.74+c,147+b,52.5+c);p.bezierCurveTo(123+b,38+c,92+b,40+c,72.5+b,54.5+c);p.bezierCurveTo(65.64+b,59.6+c,77+b,29.5+c,89.5+b,16+c);p.bezierCurveTo(103.44+b,0.94+c,109.5+b,-2+c,127.25+b,12.09+c);p.closePath();p.fill()}function a(){p.clearRect(0,0,l,f);m(100,100);for(var b in j){var c=j[b];c.y+=o;if(c.y>f){c.y=-10;c.x=Math.random()*l}e(c.x,c.y)}d=window.setTimeout(a,20)}n()})();return false;">merry merry christmas</a>.
</p>A mass validation shellscript2008-12-14T19:45:09+00:00http://www.contentwithstyle.co.uk/content/a-mass-validation-shellscript/a-mass-validation-shellscript<p>I was looking for a CLI script that validates a whole site for me, but I couldn't find one that would work without installation issues. So I hacked together an example shell script that does the job for me by downloading the whole site and then running the files through a validation.</p>
<h2>Prerequisites</h2>
<p>The shell script uses <a href="http://curl.haxx.se/">CURL</a> and <a href="http://www.gnu.org/software/wget/">WGET</a> (<a href="http://www.statusq.org/archives/2008/07/30/1954/">WGET for OSX</a>, in my case), plus the "SOAP API" of the w3c validator.</p>
<p>I am putting "SOAP API" in quotation marks, because it is not really supporting SOAP calls, but only wraps the response into a Soap Envelope. That's why I am using CURL to post the files.</p>
<p>For this example I installed <a href="http://habilis.net/validator-sac/">Validator S.A.C.</a> and followed the <a href="http://habilis.net/validator-sac/#advancedtopics">instructions to get it running as local service</a>. Of course, if you are on linux, you can <a href="http://validator.w3.org/source/">install it from source</a> or as package. Alternatively you can change the script to use validator.w3.org/check instead of localhost/w3c-validator/check, but it might run pretty slow and create a lot of traffic.</p>
<h2>The script</h2>
<p>Also a word of warning: The script creates a temp directory and a log.txt file, which it deletes before creating them. I am in no way responsible for any of your stuff getting deleted by running this.</p>
<p>But hey: Feel free to alter this to fit your needs (and maybe post improvements in the comments, for example for my sloppy way of detecting whether it is an HTML file).</p>
<pre><code>
#!/bin/sh
#
# Script to validate files in directory
#
is_html() {
file=$1
htmlstart=`grep '<html' $1`
if [ "$htmlstart" != "" ];
then
echo "1";
fi
}
validate_file() {
curl -s -F uploaded_file=@$1 -F output=soap12 localhost/w3c-validator/check
}
download_site() {
cd temp
echo 'downloading files ...'
wget -r -q -k -x -E -l 0 $1
echo 'done downloading files'
cd ..
}
setup() {
rm -f log.txt
rm -Rf temp
mkdir temp
touch log.txt
}
run_validation() {
for file in `find $1`;
do
htmltrue=`is_html $file`
if [ "$htmltrue" = "1" ];
then
echo "request validation: $file"
rpc=`validate_file $file`
echo "checking response: $file"
noerror=`echo $rpc | grep '<m:errorcount>0</m:errorcount>'`
if [ "$noerror" = "" ];
then
echo "Error in file $file"
echo "----------------" >> log.txt
echo "Error in file $file\n" >> log.txt
echo $rpc >> log.txt
echo "\n" >> log.txt
echo "----------------" >> log.txt
fi
fi
done;
has_errors=`cat ./log.txt | grep Error`
if [ "$has_errors" = "" ];
then
echo "no errors found\n" >> log.txt
fi
}
setup
download_site $1
run_validation ./temp/
</code></pre>
<h2><em>Update</em></h2>
<p>I slightly modified it, so I do get better error messages. I use <a href="http://xmlsoft.org/XSLT/xsltproc2.html">xsltproc</a> for parsing the <a href="http://validator.w3.org/docs/api.html#soap12format">SOAP envelope returned by the validator</a>. Here is the updates script:</p>
<pre><code>
#!/bin/sh
#
# Script to validate files in directory
#
is_html() {
file=$1
htmlstart=`grep '<html' $1`
if [ "$htmlstart" != "" ];
then
echo "1";
fi
}
validate_file() {
curl -s -F uploaded_file=@$1 -F output=soap12 localhost/w3c-validator/check
}
download_site() {
cd temp
echo 'downloading files ...'
wget -r -q -k -x -E -l 0 $1
echo 'done downloading files'
cd ..
}
setup() {
rm -f log.txt
rm -Rf temp
mkdir temp
touch log.txt
}
run_validation() {
for file in `find $1`;
do
htmltrue=`is_html $file`
if [ "$htmltrue" = "1" ];
then
echo "request validation: $file"
rpc=`validate_file $file`
echo "checking response: $file"
noerror=`echo $rpc | grep '<m:errorcount>0</m:errorcount>'`
if [ "$noerror" = "" ];
then
filelocation=`echo $file | sed "s/\/\//\//g"`
echo $rpc > temp_error.xml
xsltproc --stringparam location $filelocation error_template.xsl temp_error.xml >> log.txt
rm temp_error.xml
echo "Error in file $file"
fi
fi
done;
has_errors=`cat ./log.txt | grep Error`
if [ "$has_errors" = "" ];
then
echo "no errors found\n" >> log.txt
fi
}
setup
download_site $1
run_validation ./temp/ $1
</code></pre>
<p>As you can see, we need a file called error_template.xsl as well, here is an example file:</p>
<pre><code>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/TR/xhtml1/strict"
xmlns:m="http://www.w3.org/2005/10/markup-validator"
xmlns:env="http://www.w3.org/2003/05/soap-envelope"
>
<xsl:output
method="text"
omit-xml-declaration="yes"
/>
<xsl:param name="location" />
<xsl:template match="/">
<xsl:call-template name="divider" />
<xsl:value-of select="//m:errorcount" />
<xsl:text> Errors in </xsl:text>
<xsl:value-of select="$location" />
<xsl:call-template name="lb" />
<xsl:apply-templates select="//m:error" />
</xsl:template>
<xsl:template match="m:error">
<xsl:text> Line </xsl:text>
<xsl:value-of select="m:line" />
<xsl:text>, Col </xsl:text>
<xsl:value-of select="m:col" />
<xsl:text>:</xsl:text>
<xsl:call-template name="lb" />
<xsl:value-of select="m:message" />
<xsl:call-template name="lb" />
</xsl:template>
<xsl:template name="lb"><xsl:text>
</xsl:text></xsl:template>
<xsl:template name="divider">
<xsl:text>--------------</xsl:text><xsl:call-template name="lb" />
</xsl:template>
</xsl:stylesheet>
</code></pre>
<p>I think this would be easily adaptable to produce XML or HTML files. I'd like to figure out where WGET did download the file from, so I could insert that into the output generation, as hyperlink for example. But apart from that I think it performs pretty neat.</p>A quick look at XInclude and XLink2008-12-13T16:47:26+00:00http://www.contentwithstyle.co.uk/content/a-quick-look-at-xinclude-and-xlink/a-quick-look-at-xinclude-and-xlink<p>Part of my current project is to transform thousands of static XML files, all interlinked and some of them referencing others as data subset that I need for the transformation. Time to have a quick look at XInclude and XLink.</p>
<h2>XLink</h2>
<p>
<a href="http://en.wikipedia.org/wiki/XLink">Xlink</a> is an XML application that defines hyperlinks in XML documents. While <a href="http://www.w3.org/TR/xlink/#extended-link">XLink supports extended links</a>, which are able to define multiple hyperlinks from one XML node, the form that is easy to deal with straight away is the one defined by <code>type="simple"</code>.
</p>
<p>
Like with all XML applications that use certain definitions, don't forget to include the namespaces in the XML and XSL document, and if you generate HTML, exclude them from being outputted by the transformation using the <code>exclude-result-prefixes</code> attribute.
<br />
Below is an example for a transformation in XSL.
</p>
<pre><code>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[@xlink:type = 'simple' and @xlink:href]">
<a href="{@xlink:href}"><xsl:apply-templates/></a>
</xsl:template>
</xsl:stylesheet>
</code></pre>
<p>As for the extended type of XLink references, this could come in handy to generate link lists for certain text elements and similar stuff where a 1:N relationship is needed.</p>
<h2>XInclude</h2>
<p><a href="http://en.wikipedia.org/wiki/XInclude">XInclude</a> is an inclusion mechanism for XML, think SSI or PHP's <code>include()</code>. Just that it is that bit more powerful, because it can use <a href="http://en.wikipedia.org/wiki/XPointer">XPointer</a> to identify the nodes that it needs to include from the referenced document, and it provides a <a href="http://www.w3.org/TR/xinclude/#fallback_element">fallback element</a> for when the inclusion fails.</p>
<pre><code>
<xi:include href="my.xml#xpointer(//node)">
<xi:fallback>
<fb>error</fb>
</xi:fallback>
</xi:include>
</code></pre>
<p>Sadly this is not natively supported in browsers, but various DOM parsers have a mechanism in place that will include the referenced files, e.g. <a href="http://uk2.php.net/manual/en/function.domdocument-xinclude.php">PHP's <code>DomDocument->xinclude</code></a>:</p>
<pre><code>
$xml = new DomDocument;
$xml->load('data.xml');
$xml->xinclude();
</code></pre>
<p>Again make sure that the namespaces are included. Also, if you use nested levels of includes, depending on the DOM parser, this might not automatically get resolved. For how to address this behaviour in PHP, <a href="http://uk2.php.net/manual/en/function.domdocument-xinclude.php#44954">see the comments at PHP.net</a>.</p>XSLT and HTML 5 problems2008-12-09T17:47:46+00:00http://www.contentwithstyle.co.uk/content/xslt-and-html-5-problems/xslt-and-html-5-problems<p>Sometimes I'm really getting annoyed about the lack of control that
XSLT sometimes gives about what target formats are supported and what
output it generates</p>
<p>I'm trying to utilize a canvas tag, and <a href="http://excanvas.sourceforge.net/">excanvas</a>. Now the problem that I'm having is that excanvas is hooking up to onreadystatechange, and therefore will be executed before the ondomready event that jQuery offers.</p>
<p>Which means I have to either do inline JS, and generate the canvas tags per JS, in order to create valid HTML 4, or I have to use the HTML 5 Doctype and can write the canvas tag in there just like that.</p>
<p>Problem is: XSLT 1.0 doesn't support the HTML 5 to generate a doctype, and the output encoding meta tag that it selfishly applies is not valid in HTML 5 either. Any ideas anyone?</p>
<h2><em>UPDATE</em></h2>
<p>
Quite a fruitful discussion in the comments.
<br />
So for anyone else who's reading this: Bottom line is that, even with existing technology for XSLT, it is possible to create HTML 5.
</p>
<p>
The first issue we were discussing was the DTD. HTML 5 in its current draft caters the generation with XSLT by providing a fallback DTD:
</p>
<pre><code class="html">
<!DOCTYPE html PUBLIC "XSLT-compat">
</code></pre>
<p>
The other issue was the meta tag with the charset attribute, that HTML 5 introduces in order to target the character set:
</p>
<pre><code class="html">
<meta charset="..." />
</code></pre>
<p>
It is just not possible to generate exactly that with libXSL, because libXSL forcefully replaces it with an HTML 4 style meta tag.
</p>
<pre><code class="html">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</code></pre>
<p>
This is not a problem though, as the old meta tag in its encoding state is a valid declaration of the character set, too.
</p>Random thoughts on: SVG, JS toolkits and more2008-12-07T02:53:04+00:00http://www.contentwithstyle.co.uk/content/random-thoughts-on-svg-js-toolkits-and-more/random-thoughts-on-svg-js-toolkits-and-more<p>For various reasons I seem to do much more reading on new techniques
and web stuff lately, and today brought up some for me fascinating
finds that I wanted to share.</p>
<p>After discovering the merits of the canvas tag last week, and getting myself into rendering stats, I continued reading up on web based vector graphics. <a href="http://starkravingfinkle.org/blog/2006/09/canvassvgvml-drawing-roundup/">Mark Finkle's roundup on Canvas/SVG/VML</a> was a good start point, and triggered some interest in his list of web based drawing tools. Are there any good ones based on canvas, rather than SVG? Very interesting to me is the little <a href="http://annevankesteren.nl/2006/08-paintr21">Paintr</a> script, especially becasue of the use of <a href="http://cow.neondragon.net/index.php/681-Canvas-Todataurl">toDataURL()</a>.</p>
<p>Also, a very good overview are the <a href="http://people.mozilla.com/%7Evladimir/xtech2006/">slides by Vladimir Vukićević</a> covering SVG and canvas. Hey, is there a SVG to canvas importer, where I can load a file and then display it on canvas, or is that a completely ridiculous idea? Or how about <a href="http://weblogs.mozillazine.org/roc/archives/2008/06/applying_svg_ef.html">boosting your CSS stuff with SVG FX</a>, will that really take off?</p>
<p>On to toolkits! There are so many out there. The so far unknown ones are <a href="http://www.mochikit.com/">Mochikit</a>, which I discovered through <a href="http://www.liquidx.net/plotkit/">Plotkit</a>, and a pretty obscure one called <a href="http://www.uize.com/">UIZE</a>, as so often discovered <a href="http://ajaxian.com/archives/uize-javascript-ui-toolkit">via the almighty Ajaxian</a>.</p>
<p>Sure, diversity sparks politics, and it is always interesting to read project politics, as seen in <a href="http://mootools.net/blog/2008/12/04/sizzle/#more-115">this post about why mootools would not use Sizzle</a>.
Discussions like this show so much that we are all a bit self centric
and ego driven, when we expose ourselves on the web through
contribution and publication, really.</p>
<p>Mochikit, by the way, is part of <a href="http://turbogears.org/about/mochikit.html">Turbogears</a>, a mainly python based rapid development framework. Myself being focussed on <a href="http://framework.zend.com/">Zend Framework</a> for the moment, I am always curious whether I should move on to the next tool and get into something completely different? I had a little play around with <a href="http://www.djangoproject.com/">Django</a> and <a href="http://www.rubyonrails.org/">RoR</a>, but in the end I didn't really wanted to invest the time to get that darn mod_python or to learn a whole new language. Unless someone wants me to do it while I get paid for it, that is ... anyone? </p>
<p>However, if you already struggle with the diversity of js toolkits, it gets even worse when you look at the vast amount of MVC style frameworks that run on the server. At the moment I am working together with a team of Perl developers, and they use <a href="http://www.catalystframework.org/">Catalyst</a>, a Perl based one that I hadn't heard of before, either. </p>
<p>But the choices are endless, the egos fired up, and <a href="http://grails.org/">some people even say that the search is over</a>. Well, until the next fashionable paradigm comes up, that is ... but before we get there, <a href="http://www.railsenvy.com/2007/9/10/ruby-on-rails-vs-django-commercial-7">we can watch the frameworks battle each other</a>.</p>Onenaught on jQuery, testability and events2008-12-05T11:11:02+00:00http://www.contentwithstyle.co.uk/content/onenaught-on-jquery-testability-and-events/onenaught-on-jquery-testability-and-events<p>Over at <a href="http://www.onenaught.com">OneNaught</a> Anup Shah gives some <a href="http://www.onenaught.com/posts/85/turn-your-jquery-code-into-a-richer-unit-testable-plugin">hints about how to utilize Events to make your jQuery plugins ready for unit testing</a>.</p>
<p>Anup is one of the head developers at <a href="http://www.ivisgriup.com">IVIS</a>, a company that I was contracting for in the past. They heavily use XSLT, and very often Anup steps up as the main advocate for standards based techniques in an environment where not every middle-tier developer cares about quality markup and accessibility.</p>Looking at free charts and stats APIs2008-12-04T14:28:06+00:00http://www.contentwithstyle.co.uk/content/looking-at-free-chart-and-stat-apis/looking-at-free-chart-and-stat-apis<p>At the moment I am looking for a nice way to convert HTML tables into stats. I am thinking along the lines of <a href="http://www.mikeindustries.com/blog/archive/2004/12/sifr-2.0-release-candidate-2">SIFR</a>, where flash replaces markup and displays dynamic data. But first I need something that displays dynamic stats ...</p>
<h2>Flash Based:</h2>
<p>The first thing that I found was <a href="http://www.maani.us/xml_charts/">XML/SWF Charts</a>, which did look brilliant at first glance. Unfortunately the free version includes a link to their homepage, whenever one clicks on the object, and of course that's out of discussion.</p>
<p>I then looked at <a href="http://www.fusioncharts.com/Free/">FusionCharts Free</a>, but didn't find a way to quickly modify the shadows for the bar charts. Another Google search lead me to <a href="http://teethgrinder.co.uk/open-flash-chart-2/bar-chart.php">Open Flash Chart 2</a>, which pretty much does want I want it to.</p>
<p>All of the above are meant to work with a datafile that is loaded, like XML or JSON, and to get them to work with inline data requires a bit of JavaScript trickery, as this example for OpenFlashChart illustrates:</p>
<pre><code class="js">
function get_data(id) {
return $.toJSON(data_obj);
}
var data_obj = {
"elements": [ { "type": "bar", "values": [ 9, 8] } ],
"title": { "text": "Thu Dec 04 2008" }
};
var swf_config = { "get-data": "get_data", "id": "my_chart"};
$.swfobject.embedSWF("../3rdparty/open_flash_chart_2/open-flash-chart.swf", "my_chart", "200", "200", "9.0.0", "expressInstall.swf", swf_config);
</code></pre>
<p>
Not ideal in my books, I thought to myself, but I could work around this somehow. Some further Google search (and at the same time, Matthias), however, pointed me towards solutions based on the canvas tag.
</p>
<h2>Canvas Based:</h2>
<p>The lack of support for canvas in IE was one of the reasons for me to pretty much ignore it. Probably the reason why I missed the ventures that try to work around the lack of support, namely a<a href="http://ajaxian.com/archives/using-canvas-in-ie-via-flash"> flash based canvas integration</a>, but also an emulated <a href="http://me.eae.net/archive/2005/12/29/canvas-in-ie/">canvas for IE</a> implementation based on SVG, and its successor, <a href="http://sourceforge.net/projects/excanvas/">excanvas</a>.</p>
<p>Some tools utilizing this technique are <a href="http://www.liquidx.net/plotkit/">PlotKit</a>, <a href="http://webfx.eae.net/dhtml/chart/chart.html">WebFX Chart</a>, its rewrite <a href="http://www.sprymedia.co.uk/article/Canvas+Charts#download">Canvas Charts</a> and <a href="http://bluff.jcoglan.com/">Bluff</a>.</p>
<p>I managed to quickly get something off the ground with Bluff, and not only does the built in facility to use an HTML table as data source works great towards what I want to do, but the whole script looks considerably more tidy as well:</p>
<pre><code class="js">
window.onload = function() {
var g = new Bluff.Mini.Bar('example', 400);
g.theme_keynote();
g.minimum_value = 0;
g.maximum_value = 10;
g.data_from_table('data');
g.draw();
}
</code></pre>
<h2>Others:</h2>
<p>Of course there is a variety of other techniques and APIs to generate charts and stats. From the <a href="http://www.smashingapps.com/2008/01/10/the-google-chart-api-lets-you-dynamically-generate-free-charts-in-your-website.html">Google Charts API</a> down to <a href="http://search.cpan.org/dist/SVG-TT-Graph/">Perl based CPAN modules that generate SVG</a>. But most of those won't work on the client, but will require some server action, and client based rendering was one of my initial requirements.</p>
<h2>Bottom Line:</h2>
<p>It will canvas rather than SWF based charts, I think.</p>
<p>Also it seams that canvas offers better answers to the problems that we were facing before, like the image replacement for headlines, nicely tackled by <a href="http://css-tricks.com/typefacejs-a-sifr-alternative/">Typface.js</a>.</p>
<p>While for some of you web folks this might all be yesterdays news, I am amazed about how far the dynamic client side has come now. The <a href="http://squeedlyspooch.com/firelight/xaml.xhtml">Silverlight implementation without Silverlight</a>, for example, is a step that goes so far beyond anything that I'd have ever expected, that I can only say: Amazing stuff.</p>
<h2>Related:</h2>
<ul>
<li><a href="http://ajaxian.com/archives/round-the-web-vector-graphics">Nice little vector graphics roundup by the Ajaxian</a></li>
<li><a href="http://ajaxian.com/by/topic/canvas">Canvas tag on Ajaxian</a></li>
<li><a href="https://developer.mozilla.org/en/Canvas_tutorial">Canvas tutorial at the Mozilla Developer Center</a></li>
<li><a href="http://free-wiz.blogspot.com/2008/07/best-free-chart-apis.html">Free Wiz: 10 Best Free Chart APIs</a></li>
</ul>
<h2><em>Update</em></h2>
<p>
The longer I do search around the web, the more I discover:
</p>
<ul>
<li><a href="http://www.phpied.com/canvas-pie/">Canvas Pie</a></li>
<li><a href="http://blogs.atalayasec.org/dev/2008/02/a-jquery-plugin-for-canvas-pies/">Canvas Pie jQuery plugin</a></li>
<li><a href="http://code.google.com/p/flot/">Flot</a></li>
<li><a href="http://www.filamentgroup.com/lab/creating_accessible_charts_using_canvas_and_jquery/">fgCharting</a></li>
</ul>MySQL client output logging with tee2008-12-03T13:44:08+00:00http://www.contentwithstyle.co.uk/content/mysql-client-output-logging-with-tee/mysql-client-output-logging-with-tee<p>James, the sys admin guy here at <a href="http://www.sportingstatz.com/">Opta</a>, where I am working at the moment, brought this up, and it is one of those neat tricks that can make life very easy but I still didn't know about.</p><p>A look in the <a href="http://www.aip.de/%7Eweber/doc/mysql/manual_Client-Side_Scripts.html">manual</a> reveals that in the command line 'tee /path/to/file.txt' sets the output file, and 'notee' unsets it and therefore stops the logging.</p><p>Alternatively \T and \t can be used.</p><p>Happy logging.</p>Hello there, and congratulations ...2008-12-02T23:23:04+00:00http://www.contentwithstyle.co.uk/content/hello-there-and-congratulations/hello-there-and-congratulations<p>The last week was full of events for two of us. <a href="http://www.willerich.com/900days/2008/12/crossing-the-start-line/">While Matthias has just started the most amazing start-up one can think of</a>, I went past my use-by date to hit the magic number: 30.</p>
<p>And in order to avoid the struggle with loads of guests at some weird party that makes me wanna be 18 again, I tried to escape by going to Big Apple and celebrate thanksgiving with a 19 lbs turkey.</p>
<p>I felt a bit drowsy thereafter, and couldn't get myself to write anything ... Matthias sure had better reasons not to write anything, <a href="http://www.contentwithstyle.co.uk/content/charles-proxy-review-by-snookca">but somehow he managed</a>. I hope he's got some sleep, finally. In any case, forgive us dear reader. We'll be on the case again soon.</p>Charles proxy review by Snook.ca2008-12-02T22:24:46+00:00http://www.contentwithstyle.co.uk/content/charles-proxy-review-by-snookca/charles-proxy-review-by-snookca<p>I've been mentioning <a href="http://www.charlesproxy.com/">Charles</a> in comments here and on other sites for a good 3 years now. It's been a great companion in debugging server communication with flash and ajax applications, especially before firebug had its network monitoring capabilities.</p><p>Jonathan Snook, who has been writing a consistently amazing blog for many years now, has written <a href="http://snook.ca/archives/review/charles-http-monitoring/">a post about how he monitors his HTTP traffic with Charles</a>, and he seems to use a setup very similar to what I use; Charles as a weapon of choice, when you're not 100% on what the server/client sends, when you need to resend requests to the server, etc. and the Firefox extension <a href="http://livehttpheaders.mozdev.org/">Live HTTP Headers</a> when you want to see what the result does in your browser.</p><p>He writes: </p><p>"Charles ia a cross-platform desktop application that acts as a proxy
for all web requests, no matter what browser they come from; it can
even inspect Flash-originated requests and Adobe AIR application
requests. As the proxy, it's able to see all the information flowing
back and forth and provide a detailed peek at every request and
response. </p><p>One of my favourite features — and where it stands out from the
Firefox addons — is the ability to view XML, JSON and even AMF data
structures. It's easy to see right away if a request was successful or
not and you get access to all of the request and response headers."</p><p>I can only recommend that you give it a spin.</p>XSL: Recursive template for counting up2008-11-20T11:20:29+00:00http://www.contentwithstyle.co.uk/content/xsl-recursive-template-for-counting-up/xsl-recursive-template-for-counting-up<p>Thought I'd share this with you, even though it's nothing special: A simple XSL template that recursively increments a counter, so you can do a set number of operations.</p>
<pre><code class="xml">
<xsl:template name="empty-score">
<xsl:param name="target_cnt" />
<xsl:param name="current_cnt" />
<xsl:if test="$target_cnt > $current_cnt">
<p>
Target Cnt: <xsl:value-of select="$target_cnt" />
Current Cnt: <xsl:value-of select="$current_cnt" />
</p>
<xsl:call-template name="empty-score">
<xsl:with-param name="target_cnt" select="$target_cnt" />
<xsl:with-param name="current_cnt" select="$current_cnt + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</code></pre>Zend Framework: XSL and self-serializing Views2008-11-17T01:39:34+00:00http://www.contentwithstyle.co.uk/content/zend-framework-xsl-and-self-serializing-views/zend-framework-xsl-and-self-serializing-views<p>A long time ago I argued that MVC style frameworks should use XSL instead of inline PHP code and so on. That's why I knocked together a little proof of concept for Zend Framework where the views files are XSLs and the View object serializes itself into XML for rendering.</p>
<h2>Basic MVC structure</h2>
<p>I just created a demo layout, utilizing the standard MVC structure of Zend_Controller:</p>
<pre> |-application
|---default
|-----controllers
|-----models
|-----views
|-------filters
|-------helpers
|-------scripts
|---------index
|---------test
|-library
|---demo
|---zendframework_1.6.2
|-webroot
</pre>
<p>Of course now we need a bootstrap file:</p>
<pre><code class="php">set_include_path('.'
. PATH_SEPARATOR . '../library/zendframework_1.6.2/'
. PATH_SEPARATOR . '../library/demo/'
. PATH_SEPARATOR . '../application/default/controllers'
. PATH_SEPARATOR . get_include_path());
require_once('Zend/Loader.php');
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Controller_Action_Helper_ViewRenderer');
$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory(array(
'default' => '../application/default/controllers',
));
require_once 'View_Xslt.php';
$view = new View_Xslt;
$options = array();
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view, $options);
$viewRenderer->setViewSuffix('xsl');
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
$frontController->dispatch();</code></pre>
<p>Note that I have provided a new viewRenderer and view object, which is called View_Xslt.php and is located in the library/demo folder.
Also I set the view suffix to xsl.</p><p><a href="http://www.contentwithstyle.co.uk/resources/xslt_views_demo.zip">A ZIP file containing the whole demo (excluding the Zend Framework files) can be downloaded here</a>. </p>
<h2>The View Object</h2>
<p>
The view object itself needs to be a class that extends Zend_View_Abstract. The rendering of the views happens in the _run method, and the view file will be passed as the first argument. However, this argument needs to be accessed with func_get_arg, otherwise we're confronted with a neat error message that our declaration is incompatible with Zend_View_Abstract.</p><p>In order to make my view object self-serializing later, I also added the Serializer in the constructor magic method, plus I added a private function that serializes the view into XML using the Serializer just created.</p>
<pre><code class="php">require_once('Serializer.php');
class View_Xslt extends Zend_View_Abstract
{
private $serializer;
private $rootName;
public function __construct($data = array()) {
$this->serializer = new Serializer();
parent::__construct($data);
}
public function setRootName($name) {
$this->rootName = $name;
}
protected function _run() {
$template = func_get_arg(0);
$xslDoc = new DOMDocument();
$xslDoc->load($template);
$xmlDoc = $this->toXml();
$proc = new XSLTProcessor();
$proc->importStylesheet($xslDoc);
echo $proc->transformToXML($xmlDoc);
}
private function toXml() {
$xml_str = $this->serializer->Serialize($this, $this->rootName);
return $xml_str;
}
}</code></pre>
<h2>The Serializer</h2>
<p>So what does this Serializer do? It utilizes the <a href="http://uk2.php.net/manual/en/language.oop5.reflection.php">Reflection</a> functionality to serialize the object into an XML string. This enables us to normally assign variables to the view from within our controller actions, just by saying <code>$this->foo = 'bar'</code>.</p><p><a href="http://www.contentwithstyle.co.uk/content/xml-serialization-and-php-reflection">I did a quick post on XML Serialization before</a>, and the Serializer I have provided is inspired by what I have found there. DISCLAIMER: Keep in mind that this is just a proof of concept, and to get this working perfectly it probably needs a bit more work.</p>
<pre><code class="php">class Serializer
{
private $xmlDoc;
public function __construct() {
$this->xmlDoc = new DOMDocument();
}
public function Serialize($inst, $nodeName=null) {
if(is_object($inst))
{
$nodeName = ($nodeName == null) ? get_class($inst) : $nodeName;
$root = $this->xmlDoc->createElement($nodeName);
$this->xmlDoc->appendChild($root);
$this->SerializeObject($inst, $nodeName, $root);
} else if(is_array($inst)) {
$nodeName = ($nodeName == null) ? get_class($inst) : $nodeName;
$root = $this->xmlDoc->createElement($nodeName);
$this->xmlDoc->appendChild($root);
$this->SerializeArray($inst, $nodeName, $root);
}
return $this->xmlDoc;
}
private function SerializeObject($inst, $nodeName, $parent) {
$obj = new ReflectionObject($inst);
$properties = $obj->getProperties();
foreach($properties as $prop) {
if(!$prop->isPrivate()) {
$elem = $this->SerializeData($prop->getName(), $prop->getValue($inst), $parent);
}
}
}
private function SerializeArray($array, $nodeName, $parent) {
foreach($array as $key => $val) {
$keyStr = (is_numeric($key)) ? 'ArrayValue' : $key;
$elem = $this->SerializeData($keyStr, $val, $parent);
if(is_numeric($key)) {
$elem->setAttribute('index', $key);
}
}
}
private function SerializeData($key, $val, $parent) {
if(is_object($val)) {
$propNodeName = get_class($val);
$elem = $this->xmlDoc->createElement($propNodeName);
$parent->appendChild($elem);
$this->SerializeObject($val, $propNodeName, $parent);
$elem->setAttribute('type', 'object');
} else if(is_array($val)) {
$elem = $this->xmlDoc->createElement($key);
$parent->appendChild($elem);
$this->SerializeArray($val, $key, $elem);
$elem->setAttribute('type', 'array');
} else {
$elem = $this->xmlDoc->createElement($key, $val);
$parent->appendChild($elem);
$elem->setAttribute('type', 'property');
}
return $elem;
}
}</code></pre>
<h2>Controller and View files</h2><p>Nearly there. We'll just need some XSL file and a controller with an action to get the demo running. First the controller and action. I included a little Demo class, so we can see the Serializer in action:</p>
<pre><code class="php">class IndexController extends Zend_Controller_Action {
public function indexAction() {
$this->view->setRootName('DataObject');
$this->view->foo = 'bar';
$this->view->super = array(
'here' => 'there', 'foo' => array(1,2,'test'),
);
$this->view->testObject = new DemoObject();
$this->view->testObject->var = 'testObjectVar';
}
}
class DemoObject {}</code></pre>
<p>Then the view file(s). You could get away with just one, but because I wanted to imitate Zend_Layout, I did utilize xsl:import in order to do something similar.</p>
<pre><code class="xml"><?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="../layout.xsl"/>
<xsl:template match="DataObject">
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="*">
<div>
<h2><xsl:value-of select="name()" /></h2>
<xsl:apply-templates select="text()" />
<xsl:apply-templates select="*" />
</div>
</xsl:template>
</xsl:stylesheet></code></pre>
<pre><code class="xml"><?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="ISO-8859-1" omit-xml-declaration="no" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" indent="yes" />
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<xsl:apply-templates select="/*" />
</body>
</html>
</xsl:template>
</xsl:stylesheet></code></pre>
<h2>Result</h2>
<p>And that's it! Rendering the index page should give you an output looking somehow like this:</p>
<pre><code class="xml"><?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Test</title>
</head>
<body>
<div><h2>foo</h2>bar</div>
<div>
<h2>super</h2>
<div><h2>here</h2>there</div>
<div>
<h2>foo</h2>
<div><h2>ArrayValue</h2>1</div>
<div><h2>ArrayValue</h2>2</div>
<div><h2>ArrayValue</h2>test</div>
</div>
</div>
<div>
<h2>DemoObject</h2>
</div>
<div><h2>var</h2>testObjectVar</div>
</body>
</html></code></pre>Be Box CLI and security issues2008-11-15T18:43:04+00:00http://www.contentwithstyle.co.uk/content/be-box-cli-security-issues/be-box-cli-security-issues<p>An interesting insight into the Be Box, and how much functionality is lurking behind the simple face <a href="http://rory.allford.net/2007/03/30/bebox-got-root/">is what I found here</a>,
stating that the box is a dressed down version of a Speedtouch 780WL,
and therefore much more capable than the web interface suggests.</p>
<p>This made me curious, and I did a google search on the topic, and I found a <a href="http://www.beforum.co.uk/showthread.php?t=6667">variety of CLI commands and some good explanations on the adsl2forum</a>. Finally I took a look into the <a href="http://rory.allford.net/wp-content/uploads/2007/03/st-780wl_cli.pdf">CLI reference for the 780WL</a>, which the Be Box is based on. This does reveal the whole complexity of the commands that the little box offers.</p>
<p>So far so good. But careful readers of above links will also discover that there are a couple of weird security issues which are hidden in the default settings of the router. The services ftp, https and telnet are exposed to the WAN by default, and on top of that there are some users stored in the settings, which are not visible through the web interface:</p>
<pre><code>
_{Administrator}=>user list
User Flags Role
---- ----- ----
Administrator U Administrator
tech R TechnicalSupport
BeTech TechnicalSupport </code></pre>
<p>The suspicion that <em>tech</em> and <em>BeTech</em> always have the same password, so the Be guys can telnet into your router quietly, might not be to far off? This means that the door is open to an easy attack for everyone.</p>
<p>To anyone who's using the BeBox, I recommend to remove those users, change the Administrator password and, if not needed, disable the telnet, ftp and https services for the WAN. The easiest way to do this is exporting the settings to an ini file, edit them and then re-import them.</p>Reglib, an Event Delegation based Framework2008-11-14T22:50:27+00:00http://www.contentwithstyle.co.uk/content/reglib-an-event-delegation-based-framework/reglib-an-event-delegation-based-framework<p>Via the <a href="http://ajaxian.com">ajaxian</a> I came across the javascript based framework <a href="http://blogs.sun.com/greimer/entry/reglib_javascript_library_now_available">reglib</a>, written by Gregory Reimer. It is based on some very clever thoughts and makes heavy use of <a href="http://icant.co.uk/sandbox/eventdelegation/">Event Delegation</a>.</p><p>Event Delegation is a technique I used quite a lot at Tesco. Back then <a href="http://www.linkedin.com/in/matthewjdunn">Matt Dunn</a>, who was the lead UI developer there, introduced me to its benefits and how it <a href="http://cherny.com/webdev/70/javascript-event-delegation-and-event-hanlders">helps to keep the memory footprint down</a>.</p><p>reglib goes a step further. It utilizes the body element as single entry point for all events, and identifies the nodes that triggered the event so it can match them with functions that were assigned through the library and execute them.</p><p>This cuts out the wait for until the DOM is<span> Loaded by the browser, something that is pretty much essential and implemented into most other frameworks like <a href="http://docs.jquery.com/Events/ready">JQuery</a> or <a href="http://mootools.net/docs/Utilities/DomReady">MooTools</a></span>. Gregory Reimer, who has written reglib based on his work for <a href="http://www.sun.com">sun.com</a>, seems to dislike this and even <a href="http://blogs.sun.com/greimer/entry/on_the_inelegance_of_modern">describes this as anti-pattern</a>.</p>Controller separation with ActionStack and Zend_Layout2008-11-06T22:47:11+00:00http://www.contentwithstyle.co.uk/content/controller-separation-with-actionstack-and-zend-layout/controller-separation-with-actionstack-and-zend-layout<p>Thinking about my past projects, I realized that previously my
controllers where doing way too much that they shouldn't, and quickly
the comments controller would update user content and so on. I had
a good look at how I wanted to structure my code, and
decided that the controllers only should do things that belong to them,
but call other controllers if they needed stuff from them.</p>
<h2>A Built In Solution</h2>
<p>The Zend view renderer actually gives you a mechanism for this. In
every action you can set the response segment that you are targeting.
In combination with the action stack helper, this enables you to call
all the actions that you need and then output all the response segments
you have created where you'd need them within the layout, separating
bits of markup into single actions.</p>
<p>Let's have a look at a code example:</p>
<pre><code>
class FooController extends BaseController {
public function indexAction() {
$this->_helper->actionStack('list', 'foo');
$this->_helper->actionStack('foo', 'bar');
$this->_helper->actionStack('bar', 'bar');
}
public function listAction() {
$this->_helper->viewRenderer->setResponseSegment('list');
}
}
class BarController extends BaseController
{
public function fooAction() {
$this->_helper->viewRenderer->setResponseSegment('foo');
}
public function barAction() {
$this->_helper->viewRenderer->setResponseSegment('bar');
}
}
</code></pre>
<p>Then in the layout:</p>
<pre><code>
<?php
echo $this->layout()->bar;
echo $this->layout()->foo;
echo $this->layout()->list;
?> </code></pre><h2>Further Reading</h2><ul>
<li><a href="http://framework.zend.com/manual/en/zend.controller.actionhelpers.html#zend.controller.actionhelpers.actionstack">Zend Action Stack Helper</a></li>
<li><a href="http://www.nabble.com/Zend_Layout-and-ActionStack-td15788789.html">Zend_Layout and ActionStack</a></li>
</ul>The credit crunch deja vu2008-11-05T10:25:18+00:00http://www.contentwithstyle.co.uk/content/the-credit-crunch-deja-vu/the-credit-crunch-deja-vu<p>Of course everyone follows the news about the financial sector
crisis right now, and from a personal point of view I feel reminded
about the time around 9/11. Back then many many agencies in Germany,
where I am born and was doing my apprenticeship at the time, got hit hard and
had to lay off people.</p>
<p>I remember group meetings, strong worries, and as the company that I worked for was directly dependent on the financial markets (doing websites for fund management organizations and other financial sector companies), they pretty much got hit immediately and did lay off about 20 people all together. I was worried whether my apprenticeship was worthwhile, or if I even was able to finish with it.</p>
<p>I then switched to another company based in Berlin. They sounded a bit more optimistic in the interviews, so I joined them just to find that after three months of working there the same cost-cutting process was happening all over again.</p>
<p>German economy in general went through tough times until about 2006, way past the date that I decided it's time to move somewhere else, and has just recently come out of the pessimism and fear that was so dominant through this whole time. The style that a lot of agencies, many of them new and inexperienced with tough situations, handled the layoffs was often borderline cruel, and where I worked relationships between management and staff became bitter and fearful. Websites like dotcomtod (now <a href="http://www.boocompany.com/">boocompany [german]</a>) sarcastically documented companies swiveling towards going bust and cheered every time it happened.</p>
<p>Maybe having seen times like this is one reason for the German IT magazine <a href="http://www.heise.de/jobs/Die-IT-Branche-und-die-Krise-Sie-kommt-sie-kommt-nicht-sie-kommt--/news/meldung/118260">"Heise" to sound so more pessimistic than everything else I read before [german]</a> about the credit crunch, and one director of a german agency that I did work for verbally expressing something like "the fat years are over now, that's for sure".</p>
<p>Chris Heilmann of Yahoo <a href="http://www.wait-till-i.com/2008/10/31/working-in-the-now/">seems to have a similar feeling of deja vu</a>, and I can imagine that the fact that <a href="http://www.nytimes.com/2008/01/30/technology/30yahoo.html?_r=3&ei=5088&en=e797ae37305e5c8f&ex=1359435600&adxnnl=1&oref=slogin&partner=rssnyt&emc=rss&adxnnlx=1225785754-1uaunmzdvJZCW47tKsu88g">Yahoo was one of the first IT companies that came up in the news with reports of staff reduction</a> does make his outlook more pessimistic than others, but not necessarily inaccurate. </p>
<p>This stands in big contrast to many other people that think (or maybe they're just wishing) that the IT industry is somehow the exception of economy, and will do well despite the downturn. But already I hear contractors asking about how the market does now, and I'm worried to find out for myself in December, when I need to get contracting work sorted once more.</p>
<p>Of course, we Germans are pessimists, and the outlook that <a href="http://www.computerweekly.com/Articles/2008/10/10/232620/credit-crunch-reverberates-across-it-industry.htm">Computer Weekly has on the topic</a> is talking of "chances" and "positive impact of the credit crunch", but at the same time it points out that there will be drastic changes in IT investment strategies and what investors ask for as return value for these investments.</p>Be box and iplayer = crash?2008-11-03T22:28:47+00:00http://www.contentwithstyle.co.uk/content/be-box-and-iplayer--crash/be-box-and-iplayer--crash<p>I thought I was going mad: Everytime I watched some iplayer content,
my wireless dropped out and it looked like the router reset itself.
</p>
<p>Then, last time I tried it was all fine, and I kind of forgot about it. But <a href="http://www.guardian.co.uk/technology/askjack/2008/oct/30/vista-wifi">a post on the Guardians "Ask Jack"</a> blog about trouble with wireless remined me of my troubles. Curiosity prompted me to look in the <a href="https://www.bethere.co.uk/">be</a> forums now, and I found <a href="http://www.theregister.co.uk/2008/08/28/bt_router_bbc_iplayer_bug/">a BBC article on it</a>, that a) tells me I'm not crazy and b) they fixed it. YAY!</p>JS custom events roundup2008-11-02T20:06:22+00:00http://www.contentwithstyle.co.uk/content/js-custom-events-roundup/js-custom-events-roundup<p>With independent UI components, that not always know about each other, custom events come in handy. They enable us to code components that will populate hooks for other objects that then can be used to trigger different behaviours. Even better, the various objects that do pick up on our custom events don't necessarily have to be anticipated, but can subscribe to the provided events at any point throughout the development process. <a href="http://ajaxian.com/archives/enjoying-the-observer-pattern-with-custom-events">The observer pattern</a> is a good example for this.</p>
<p>But with different implementations in <a href="http://msdn.microsoft.com/en-us/library/ms536343%28VS.85%29.aspx">IE</a> and <a href="https://developer.mozilla.org/En/DOM/Element.addEventListener">Firefox</a> to start with, scripting you own custom events hasn't been easy from the start.</p>
<p>Thankfully, a while ago some folk did good work in <a href="http://www.truerwords.net/articles/web-tech/custom_events.html">wrapping up the two implementations into a single class</a> for custom events.</p>
<p>Nowadays, most major javascript toolkits have some sort of custom event facility.</p>
<p>From YUI I am familiar with the CustomEvents class, which enables us to create custom events, then fire them or subscribe to them</p>
<pre><code>var foo = new YAHOO.util.CustomEvent('foo event');
foo.subscribe(function(type, args){}, true);
foo.fire();
</code></pre>
<p>The way JQuery uses custom events is by attaching custom events with
the bind function to a DOM element wrapped in the $ function, which
then can be triggered:</p>
<pre><code>
$('#foo').bind('bar_event', function(event, param){});
$('#foo').trigger('bar_event');</code></pre>
<p>I didn't read up on this yet, but I am sure that other toolkits like prototype, dojo and mootools will have similar event models at hand.</p><h2>Further reading</h2>
<ul>
<li><a href="http://developer.yahoo.com/yui/event/#customdefine">YUI Documentation for CustomEvent</a></li>
<li>The <a href="http://www.yuiblog.com/blog/2007/09/13/bubbling-library-by-caridy/">YUI blog</a> for a more detailed reading on YUI custom events and event bubbling</li>
<li><a href="http://www.dustindiaz.com/custom-events/">Dustin Diaz - Publishing Custom Events in JavaScript</a></li>
<li><a href="http://docs.jquery.com/Events">JQuery Events documentation</a></li>
<li>Ajaxian on the <a href="http://ajaxian.com/archives/enjoying-the-observer-pattern-with-custom-events">Observer Pattern</a></li>
</ul>DNS clustering in WHM/Cpanel accounts2008-11-01T16:00:10+00:00http://www.contentwithstyle.co.uk/content/dns-clustering-in-whmcpanel-accounts/dns-clustering-in-whmcpanel-accounts<p>Dealing with multiple CPANEL/WHM installs, DNS clustering is a way
to use redundant nameservers in order to keep your zones created by
CPANEL/WHM in sync.</p><p>In order to set it up, you need to go into "Cluster/Remote Access >> Configure Cluster" and check the radio button "Enable Dns Clustering". Then you have to add the remote servers and establish a trust relationship between the remote and the local server, by storing an authorization key.</p><p>Also, you have to make sure that the DNS role is set to "Synchronize changes".</p>
<p>Do this on every installation that should be able to receive upodates from other DNS servers.</p><p>Once this is done, you might have to restart the BIND installations. Now you should be able to create an account or modify a DNS zone and afterwards synchronize the servers with the functions found in "<span>DNS Functions</span> >> <span>Synchronize DNS Records</span>".</p><p>If all zones are available on both servers, you will now be able to use nameservers on both installations as DNS entries for your domains.</p><p>The process with screenshots is described in the <a href="http://www.cpanel.net/docs/dnsclustering-quickstart/">CPANEL dns clustering quickstart guide</a>.</p><p></p>Random thoughts on: Cloud computing2008-11-01T15:14:32+00:00http://www.contentwithstyle.co.uk/content/reading-up-on-cloud-computing/reading-up-on-cloud-computing<p><a href="http://en.wikipedia.org/wiki/Cloud_computing">Cloud computing</a>, as in <a href="http://en.wikipedia.org/wiki/Software_as_a_service">Software as a Service</a> and web based applications and APIs, is hailed as the <a href="http://www.techcrunchit.com/2008/08/01/welcome-to-web-30-now-your-other-computer-is-a-data-center/">Web 3.0</a> and might also be the <a href="http://www.readwriteweb.com/archives/google_apps_serious_threat_to_microsoft_office.php">nail in the coffin for vendors that sell expensive office suites</a>, at least it's trying to be.</p><p>And they are brilliant, those apps. The fact that pretty much everyone I ask uses Gmail, just to handle the spam volume, and finds that it works much better than Thunderbird or Outlook, speaks for itself. Providers of large scale services of course have more money than we ever imagine, more man power to refine their apps, more hardware, more redundancy, more you name it...</p><p>And who could overhear the amazing stories behind all the mashups, <a href="http://googlemapsmania.blogspot.com/2006/12/50-things-to-do-with-google-maps.html">that use google maps for example</a>, or some other cool API, to do something amazing that otherwise would have been unachievable or at least very expensive and therefore not happening at all?
</p>
<p>Of course, harvesting the benefits of cloud computing means that I surrender the power to control my information, and its privacy, to someone else, often more powerful and of trustworthy intentions. Also, the more I trust external services the more I am building up dependencies that could really rock my boat, if one of them stops provision of services for whatever reason, like in the case of poor <a href="http://www.guardian.co.uk/technology/blog/2008/oct/20/google-cloudcomputing">Loren Baker</a>, who got locked out of his Google accounts and therefore <a href="http://www.guardian.co.uk/technology/blog/2008/oct/20/google-cloudcomputing">couldn't access any of his business email or documents</a>.</p><p>Quite worrying indeed,
and while many people ask something like "well, what did he do in
order to be locked out, the poor sod" and others, like GNU guru <a href="http://www.stallman.org/">Richard Stallman</a>, feel that <a href="http://www.guardian.co.uk/technology/2008/sep/29/cloud.computing.richard.stallman">cloud computing is a trap</a> that we're falling into, as soon as we give up control.</p><p>But how safe are private computers, really? Who didn't have a failed hard
drive here, please raise your hand now? <a href="http://ask-leo.com/why_dont_people_back_up.html">How good is your backup
strategy</a>? And how different is renting a mailserver from a company to do your business from using Gmail to the same?</p><p>My guess is that, if you have a root server somewhere, at least you can point at the contract and say: "I am renting this service from you, so as long as I pay you have no right to change the mode of provision". </p><p>Otherwise, with major companies changing their minds about strategies, about what they do want to support and what they don't, all we are left with is their good will and kindness, something that we cannot always count on, <a href="http://news.cnet.com/2100-1040-946379.html?tag=fd_top">as seen with apples .Mac </a>service, which was promised to us as the email that was "<a href="http://www.macobserver.com/editorial/2002/07/21.1.shtml">free for life</a>".</p><p>Also, if things like this happen, how do you get the data out of these services? It seems immoral to introduce changes and not offer a transition period where some sort of migration should be possible. But I haven't yet found "export all emails to other imap server" function in Gmail (only the other way round is instantly possible), even though this might be exactly what's needed to make cloud computing a fair deal for the user. </p><p>So for all of you out there, that base their businesses entirely on other businesses APIs, what is your strategy to overcome issues like this? Is there a fallback strategy, when one service that you're utilizing drops out?</p>Why the relaunch?2008-10-28T07:03:29+00:00http://www.contentwithstyle.co.uk/content/why-the-relaunch/why-the-relaunch<p>Ok, as <a href="http://www.contentwithstyle.co.uk/content/a-new-style-for-our-content#comment-4378" title="Comment by Matthias">Matthias already said</a>, I'd like to give an insight into what has changed in this relaunch, but above all I want to share what we're trying to achieve.</p>
<h2>Where we're coming from</h2>
<p>If you still remember our old layout in its first version, you'll remember the three columns on the homepage: Articles, Elsewhere and Events. Also you'll remember that at some point the Elsewhere column has been replaced with the Blogs column.</p>
<p>Historically we planned to write long and elaborate how-tos and detailed articles on what we were dealing with in our jobs on a day to day basis. Also we were planning to give you UK related web events in a calendar-like style. Great ideas, both of them, but after the initial honeymoon period we all got very busy in our private lives, and the first thing that we realized was that the Events list was much more work than we expected, with very little interest from our audience. The second thing, however, had a much bigger impact: Our article releases were going down to only a few per year. Not enough to keep anyone reading our output, really.</p>
<p>This is when the Blog section came to life. Replacing the Events list found on the homepage, it enabled us to quickly share some thoughts and observations, without having the "overwhelming" task of writing a proper article. This somehow revived the site's publishing frequency, but since the original layout was article centric, the Blog section seemed a bit ... well ... misplaced.</p>
<p>This is why the main goal for this relaunch was to get rid of the distinction between Articles and Blog. The site is a blog, and everything should be worth reading, no matter how long it is.</p>
<h2>Ancient technicalities</h2>
<p>Another thing that got to my head, especially, was the fact that the old site was running on a pre 1.0 version of Textpattern. Certain things were never really possible to do, it took quite a toll on the server, the database log took up something like 3GB, and two times we had to move hosting providers because we found out that the small print of their contracts enables them to kick you off their shared servers without warning, even though you neither exceed the stated space or bandwidth.</p>
<p>Even worse, it was really hard to implement new functionality properly, and when we did, we were hacking the system so much that we'd never be able to port it. When we tried to port it over to wordpress, which should be possible when using an newer version of Textpattern, we miserably failed.</p>
<h2>Did we do the right thing?</h2>
<p>This, dear reader, is something that time will tell. The things to be judged by are the amount of writing we'll do in the future, but also whether you folks out there do like the new look of the relaunch and find it easy enough to use. Also we're planning to slowly jazz it up here and there, and the new Zend Framework based system should make it quite simple to plug new functionality in.</p>
<h2>Technical details to come!</h2>
<p>Since the sytem is a complete custom rewrite, there are a few code snippets that might be interesting. But this is a pretty long post already, so I'll save writing about the technical details of the relaunch for a follow up post. In the meantime, you can just subscribe to <a href="http://www.contentwithstyle.co.uk/feeds/rss">our RSS feed</a> to make sure you're gonna be the first to find out.</p>A new style for our content2008-10-26T18:02:05+00:00http://www.contentwithstyle.co.uk/content/a-new-style-for-our-content/a-new-style-for-our-content<p>We've announced it a couple of times, and here we are: A new face for Content with Style is live.</p>
<p>So we're happy to say that we didn't lie, we've just been slow. Forgive us if the whole thing doesn't work perfectly from the start, but let us know what's broken and you'll help us to fix it quicker. Thanks for your patience.</p>You've got new mail. 3500 actually.2008-10-21T13:48:53+01:00http://www.contentwithstyle.co.uk/content/youve-got-new-mail-3500-actually/youve-got-new-mail-3500-actually <p>Today my folder for mailing list digests hit the mark of 3500 unread mails. Time to cut them off, I think.</p>
<p>Already checking the uk-usability list, I subscribed to 5 more of them at around the time Content with Style went online, so that’s May 2005 or so. And I used to read a lot of the digests, sometimes helping a lost developer on the way, sometimes pimping our site.</p>
<p>Maybe three times I actually asked a technical question in the more or less anonymous round, but as far as I remember, never got a useful reply (so I had to wait until shaun inman promoted the obvious, using opacity to style the file input/submit button <strong>without</strong> deleting the value). What I got instead is an average of 10 spam mails a day. </p>
<p>It’s not all bad, though. First of all there were a lot of hacks and small insights learnt on every list, not so much the “how to do it”, but rather the “how to do it better” or “how to do it differently”. Then there’s the exception to all the others, the rest-discuss list on yahoo groups. I don’t know how many times I’ve seen questions replied that would make people understand the possibilities and concept of REST, also there’s not a lot of comments that tell you to use google, RTFM or what else.</p>
<p>But enough is enough, and now is the time to cut down on this mess. It’s all archived online, so I don’t need to do that. Before I delete this part of my career development, I want to let you know my recommendations for your development needs, whenever google lets you down:</p>
<ul>
<li><a href="http://www.chinwag.com/lists/uk-usability">uk usability</a>: pretty silent most of the time, but reasonable questions are usually discussed instead of given the silent treatment</li>
<li><a href="http://news.php.net/php.i18n">php-i8n</a>: One list that helped me close the serious gap in my programming knowledge about how to deal with planning and building websites with different languages and character encodings.</li>
<li><a href="http://news.php.net/php.db">php-db</a>: This one seems to have more of a junior crowd appeal, for folks new to dealing with the server side of things. No use for me now, not bad back then. There’s <a href="http://news.php.net/">tons more specialized php mailing lists</a>, by the way.</li>
<li><a href="http://lists.evolt.org/">thelist@evolt</a> and </li>
<li><a href="http://www.webdesign-l.com/">webdesign-l</a>: Both the evolt list and webdesign-l are very likeable, each have a reasonably sized group of returning and helpful contributors. Member of those lists were so kind to help me (mostly off-list) when I started out as freelancer, and I’d love to contribute more than my current total of maybe half a dozen replies if I ever have a less time-demanding job. Another <a href="http://alistapart.com/articles/tenyears">authority on web development</a> recently bigged them up, too.</li>
<li><a href="http://tech.groups.yahoo.com/group/rest-discuss/">rest-discuss@yahoogroups</a>: As I said above, a brilliant resource, and a brilliant discussion forum. Definitely sign up when you grind your teeth on REST. I think I still owe them a long announced, never completed Ajax-to-REST demo.</li>
</ul>
<p>Never forget the etiquette: Try googling it first, also look into the archive of the list you’d like to ask, and try to understand the issue at hand. Be nice, be concise, and help others if you can.</p>
Friday fun: Sokoban2008-10-17T06:31:02+01:00http://www.contentwithstyle.co.uk/content/friday-fun-sokoban/friday-fun-sokoban <p>I had this great moment in my kitchen recently, when we had a new washing machine and fridge delivered.<br />
The delivery guys were in a bit of a rush, and our road as well as our hallway are fairly narrow, which added to the tension. So, instead of doing the logical thing of taking the old appliances out first, they decided to roll the new boxed up ones in right away.<br />
This created a comedy scene I’ll never forget: 4 people in the kitchen, one big kitchen table, and 4 boxes that had to be moved in a specific order into a specific place. Real life Sokoban! Needless to say, I cracked up laughing, leaving my wife puzzled and one of the delivery guys visibly angry.</p>
<p>For those who don’t know, Sokoban is an old computer game <br />
<cite>in which the player pushes boxes around a maze, viewed from above, and tries to put them in designated locations. Only one box may be pushed at a time, and boxes cannot be pulled. The puzzle is usually implemented as a video game.</cite> (source: <a href="http://en.wikipedia.org/wiki/Sokoban">wikipedia</a>)</p>
<p>After that I felt the urge to play Sokoban again. Finding the original on the web wasn’t too hard, although I won’t give you a link here. Use google wisely and live with the fact that you’re illegally downloading a random collection of exe files that might or might not unleash hell on your machines.</p>
<p>I felt that it’s a great time waster and banned it from my work, instead I tried and bought <a href="http://www.gtsystem.eu/naboko/">Naboko</a> for the iPhone, which is amazing. The shear fact that it has something like 2400 levels, made by a handful of bright mathematical-type brainiacs is astonishing. One of them is <a href="http://sapiens.ya.com/sokobanhomz/intro.html">Marti Homs</a>, who has contributed a ton of levels to Naboko, but also set them up online to play in a javascript version, built by another, <a href="http://sokoban.e-contento.com/en.index.php">Juan Antonio Jiménez</a>, who has done the same with his levels as well as the original levels the game came.</p>
<p>Enjoy!</p>
Remove the mac sync icon in Leopard2008-10-03T02:27:17+01:00http://www.contentwithstyle.co.uk/content/remove-the-mac-sync-icon-in-leopard/remove-the-mac-sync-icon-in-leopard <p><a href="http://www.kianhean.com/">Lim Kien Hean</a> describes the <a href="http://www.kianhean.com/2007/10/30/remove-that-mac-sync-icon-in-leopard/">removal of the .mac sync icon from the OSX top bar</a> as a small but important trick.<br />
I happen to agree! Thanks.</p>
Over @Natbat.net: CSS systems for writing maintainable CSS2008-10-01T11:44:18+01:00http://www.contentwithstyle.co.uk/content/over-natbatnet-css-systems-for-writing-maintainable-css/over-natbatnet-css-systems-for-writing-maintainable-css <p>Very very good slides for download at <a href="http://natbat.net/2008/Sep/28/css-systems/">natbat.net</a> , and a nice discussion in the comments as well.</p>
<p>Interestingly Natalie Downe argues against CSS frameworks as such, but for a structured and reusable approach. Myself, I have never utilized a CSS framework, and apart from a little toolset for clearfix and inline-block I write every CSS for a site from scratch.</p>
<p>I like the fact that she takes version control into consideration when she chooses against multiple rule statements in one line, which is something that I have to argue again and again …</p>
Command line search and replace?2008-09-30T06:24:54+01:00http://www.contentwithstyle.co.uk/content/command-line-search-and-replace/command-line-search-and-replace<p>
So I'm asking around for the best way to do search and replace in command line ... I have to copy files into another folder, but replace one word.
</p>
<p>
<a href="http://www.cyberdummy.co.uk/">Tom</a> pointed me towards using perl:
</p>
<pre><code>
cp ./source/* ./destination/
cd ./destination/
perl -pi -e 's/foo/bar/' *
</code></pre>
<p>
<a href="http://www.redwiredesign.com/profile/justin_finkelstein.html">Justin</a> in contrast uses sed, and a loop:
</p>
<pre><code>
cd ./source
for A in `ls`; do echo sed 's/foo/bar/' $A > ./destination/$A; done;
</code></pre>
<p>
Which brought me to finally look into the man pages for <a href="http://lowfatlinux.com/linux-sed.html">sed</a> , the linux stream editor.
Apparanetly the -i flag enables in-place editing there as well, and you could do something like tom did with sed:
</p>
<pre><code>
cp ./source/* ./destination/
cd ./destination/
sed -i '' 's/foo/bar/' *
</code></pre>
<p>
Initially, trying the -i flag, I got weird warnings like this:
<br />
sed: 1: "index.shtml": command i expects followed by text
</p>
<p>
This is due to the missing extension that you need to provide for the -i flag, in the case above I used '' to not create any backup files.
If you provide '.bak' it will create backup files, i.e. index.shtml.bak before editing.
</p>
<p>
Happy search and replace!
</p>More background image fun2008-09-22T14:21:56+01:00http://www.contentwithstyle.co.uk/content/more-background-image-fun/more-background-image-fun <p>Tying in with Pascal’s recent <a href="http://www.contentwithstyle.co.uk/Blog/183/css-background-image-on-html-image-element/">post about background images for images</a>, here is more image-related css magic, this time combined with a little jQuery, to create beautifully animated rollover effects (or whatever trigger you can think of, progress bars etc). Yay, eyecandy!</p>
<p>If you like it or not is of course personal preference, but I think Jonathan Snook’s <a href="http://snook.ca/archives/javascript/jquery-bg-image-animations/">examples of utilizing jQuery to animate background images</a> are very slick, especially because it’s only using fairly obvious techniques.</p>
<p>His work is based on the <a href="http://alistapart.com/articles/sprites2">ideas of Dave Shea</a> in an article in the late August issue of A List Apart, but he cleans up and improves the readability of the css by miles.</p>
Textmate search & reg exp2008-09-17T11:53:02+01:00http://www.contentwithstyle.co.uk/content/textmate-search-reg-exp/textmate-search-reg-exp <p>One of the nicest things about <a href="http://macromates.com/">Textmate</a> is the that you keep discovering things even after a year of using it. The project wide search and replace functionality already helped me, but I kept ignoring that little check box saying “Regular expression”.</p>
<p>Of course, for someone that quite likes regular expressions, using search and replace with regular expression matches is a very powerful I gave it a try, fired up the project wide search (cmd+shift+F) and changed CSS file names in various locations and folders by searching for </p>
<p>href=”(.+)foo.css” </p>
<p>, then replaced it with </p>
<p>href=”$1bar.css”</p>
<p>and, after double checking for what it did, just clicked on File->Save All (alt+cmd+S).</p>
<p>Very powerful, very neat!<br />
One thing that I am still missing, though, is the muti-line search and replace field found in BBEdit or Homesite, which comes in handy when searching for whole blocks of HTML. Dealing with line breaks in the Textmate search is just too fiddly, and maybe there’s a better way but I haven’t found it yet? </p>
CSS Background image on html image element?2008-09-15T07:01:26+01:00http://www.contentwithstyle.co.uk/content/css-background-image-on-html-image-element/css-background-image-on-html-image-element <p>My co-worker <a href="http://fuzzyoutline.com/">Paul</a> showed how helpful it is to question the obvious: Can you apply a background image to an image?</p>
<p>And, as he found out, the answer is: Yes!</p>
<p>Switch the image to display: block, apply a padding, and there you go, you can set up backgrounds for images …</p>
<p>See the <a href="http://www.contentwithstyle.co.uk/resources/img_bg_demo/index.html">little demo</a> I’ve built to achieve a paralax like scrolling with just a div and an img.</p>
Firefox 3 keeps logging me out. Not any more it's not.2008-09-01T09:51:33+01:00http://www.contentwithstyle.co.uk/content/firefox-3-keeps-logging-me-out-not-any-more-its-not/firefox-3-keeps-logging-me-out-not-any-more-its-not <p>Like many others I’ve been enjoying Firefox 2’s ongoing session functionality that had me logged in on frequently used sites. This recently changed with the Firefox 3 upgrade. All of a sudden Basecamp and some other random sites kept asking for my login. </p>
<p>At first I thought that maybe Basecamp changed policies, or that some rolled out code went wrong. That went until last Friday, when I stopped ignoring the “remember me” checkbox, which also failed and an old client of mine contacted me and stated the same problem for a website I built for them ages ago. Now, when other people’s code breaks, that’s one thing, but of course I couldn’t let it lie if it’s my stuff, legacy or not.</p>
<p>Today I got around researching the problem and found this <a href="http://forums.mozillazine.org/viewtopic.php?p=3561225#p3561225">all-explaining forum post on mozillazine</a> . Seems that some people’s upgrade has corrupted the cookies database, which moved from cookies.txt to cookies.sqlite, with varying and downright weird consequences, like the guy who seemed to be able to log into 10 sites, and the 11th killed them all.</p>
<p>Some of the suggested solutions involve safe-mode and digging through add-ons (as some apparently trigger said safe-mode or break other functionality), others claim it has something to do with the way you quit firefox, but mine was as simple as this:</p>
<p><cite>I shut down my browser and deleted both my cookies.txt and cookies.sqllite files, then restarted the browser. Worked like a charm. Apparently the files had gotten corrupted somehow, and session information was not being saved correctly. It took a complete purge to start again from scratch.</cite></p>
<p>That’s all, and not a grey hair in sight. </p>
SSI trouble in parent directories2008-08-28T10:32:15+01:00http://www.contentwithstyle.co.uk/content/ssi-trouble-in-parent-directories/ssi-trouble-in-parent-directories <p>Sometimes it helps to read the manual, especially when one makes a really silly mistake.</p>
<p>For example today I tried to figure out which permission prevented my SSI based pages to work if the include file was to sit in the parent directory.</p>
<p>Of course, when one reads a <a href="http://russell.dyerhouse.com/cgi-bin/article.cgi?article_id=84">manual</a> and looks for “relative paths” one will find that there is the “file” attribute in the include statement which resolves system paths, but also there is the “virtual” attribute which resolves relative paths. DOH!</p>
<p>P.S.: Classic example for “Dealt with a problem many times but always keep forgetting the solution” ...</p>
JS and objects2008-08-28T10:22:43+01:00http://www.contentwithstyle.co.uk/content/js-and-objects/js-and-objects<p>Just a quick moment trying to explain the different approaches to using objects in Javascript to a fellow co-worker:</p>
<pre><code>
// Normal object with new keyword
var myClass = function() {
var privateMethod = function() {
}
this.publicFunc = function() {
}
}
var myObj1 = new myClass();
var myObj2 = new myClass();
var myObj3 = new myClass();
// Singleton with new keyword
var mySingleton = new function() {
this.foo = function() {}
}
mySingleton.foo();
// Singleton without assignment - YUI style
(function(){
})();
// Object literal
var myObjLiteral = {
'foo' : function() {},
'bar' : function() {}
}
</code></pre>
<p>I am sure that I missed out some important ones, but feel free just to put them into the comments.</p>
How to delete files when argument list is to long ...2008-08-12T11:42:11+01:00http://www.contentwithstyle.co.uk/content/how-to-delete-files-when-argument-list-is-to-long-/how-to-delete-files-when-argument-list-is-to-long- <p>I had trouble with deleting files, because a simple</p>
<pre><code>rm -Rf *</code></pre>
<p>would fail because the argument list was to long.<br />
<a href="http://www.cyberdummy.co.uk">Tom</a> , my former co-worker from <a href="http://www.redwiredesign.com">Redwire</a> times, pointed out that I should utilize xargs.</p>
<p>A simple </p>
<pre><code>ls | xargs rm -f</code></pre>
<p>took quite a while, but did the trick!</p>
PHP Deployment With Capistrano2008-08-11T08:33:27+01:00http://www.contentwithstyle.co.uk/content/php-deployment-with-capistrano/php-deployment-with-capistrano <p>Today I’d like to share 2 links with you that have helped me quite a bit recently.<br />
I finally found the time at work to move a couple of further-ran projects from the initial “building it” stage to “maintaining it”. Using this opportunity I followed <a href="http://www.simplisticcomplexity.com/2006/08/16/automated-php-deployment-with-capistrano/">Jon Maddox’ post</a> about utilizing my installed (and older) version of <a href="http://www.capify.org/">Capistrano</a> to get more control over deploying PHP projects. As most of them are Wordpress installations, this Made By Many post about <a href="http://www.madebymany.co.uk/using-capistrano-with-php-specifically-wordpress-0087">Capistrano and Wordpress</a> was also of good use.</p>
<p>The biggest criticism I come across when mentioning this is “why bother with this when I can use shell scripts”, referring to scp, ssh, rsync, and all the rest. I find <a href="http://www.simplisticcomplexity.com/2006/08/16/automated-php-deployment-with-capistrano/#comment-10668">this is a valid point</a>, and except for the “maybe I can’t be bothered writing a shell script containing all that” argument, I’m clueless. Is it simply the collection of all this functionality and presenting it in an apparently easy way? Maybe someone can enlighten me here or over on the other side?</p>
<p>For me, I just like to have the good feeling that, after configuring it, I can do my changes locally, test them, check in the changes once I’m happy, hit “cap deploy” and go for lunch/dinner/over to more exciting things in life than uploading files. And if I really screw up (because I secretly make sure it’s fine live as well, and it’s not), i can simply roll back. <br />
With a little scripting magic wrapped into the configuration I can sync databases and files back and forth (yeah, yeah, why not do this in the console to start with?). And, paths amended for a different machine (if necessary), it’s portable, too. </p>
View variables in Zend Framework Partial Loop2008-07-30T10:58:50+01:00http://www.contentwithstyle.co.uk/content/view-variables-in-zend-framework-partial-loop/view-variables-in-zend-framework-partial-loop <p>One of the fun things about Zend Framework is the possibility to extend the Framework classes with extras functionality.<br />
I got slightly annoyed that in a partialLoop I couldn’t access the view variables. here’s how I fixed it.</p>
<p>First of all we need to understand that the Helpers get overwritten, if we declare custom ones with the same name.</p>
<p>Therefore, if I create a helper class partialLoop and load it via declaring a helper path, it will overwrite the Zend Framework one.</p>
<p>In my case I just added this into the init function of the Base Controller:</p>
<pre><code>
public function init() {
$this->view->addHelperPath('/path/', 'Extended_Helper');
}
</code></pre>
<p>Within the declared path I now have to add a file called PartialLoop.php</p>
<pre><code>
<?php
require_once 'Zend/View/Helper/PartialLoop.php';
class Extended_Helper_PartialLoop extends Zend_View_Helper_PartialLoop {
public $view;
public function setView($view) {
$this->view = $view;
$this->config = Zend_Registry::get('config');
}
public function partialLoop($path, $array) {
return parent::partialLoop($path, $array);
}
public function partial($name, $module, $item) {
$item['view'] = $this->view;
$item['config'] = $this->config;
return parent::partial($name, $module, $item);
}
}
</code></pre>
<p>And voila, we now can access the view from within a partialLoop.</p>
JS and CSS minification with YUI2008-07-25T10:49:51+01:00http://www.contentwithstyle.co.uk/content/js-and-css-minification-with-yui/js-and-css-minification-with-yui <p>At the moment I am working on a large website project for <a href="http://uktv.co.uk">UKTV</a>, where for maintainability reasons I keep files separated and pretty verbose.<br />
Every box and page type would get its own CSS file, and all would be included through one big CSS, which looks somehow like this:</p>
<pre><code>
@import url('../global/tools.css');
@import url('../global/foo.css');
@import url('../global/bar.css');
@import url('../content/foo.css');
@import url('../content/bar.css');
@import url('../sidebar/foo.css');
@import url('../sidebar/bar.css');
</code></pre>
<p>The same would go for the JS files, which were broken down into class files and then included one by one:</p>
<pre><code>
<script type="text/javascript" src="/js/project.js"></script>
<script type="text/javascript" src="/js/project/foo.js"></script>
<script type="text/javascript" src="/js/project/bar.js"></script>
</code></pre>
<p>All together this generated more than different 35 hits, something that inevitably makes the rendering of an HTML page very slow.</p>
<p>The solution came to me when my friend Simon from <a href="http://seventytwo.co.uk/">Seventytwo</a> highlighted the <a href="http://developer.yahoo.com/yui/compressor/">YUI compression tools</a> to me, first just as a <a href="http://bundleforge.com/">Textmate bundle</a>, but then he pointed out to me that there is <a href="http://www.julienlecomte.net/yuicompressor/">a jar file</a> for it as well.</p>
<p>20 minutes and one little shellscript later I have one CSS files and one JS file, all nicely minified, plus an IE6 and IE7 stylesheet in conditional comments. </p>
<pre><code>
cat
./templates/css/global/tools.css
./templates/css/global/layout.css
./templates/css/global/foo.css
./templates/css/global/bar.css
./templates/css/global/flyouts.css
./templates/css/content/foo.css
./templates/css/content/bar.css
./templates/css/sidebar/foo.css
./templates/css/sidebar/bar.css
> ./templates/css/shared_concated.css
java -jar yuicompressor-2.3.5.jar
./templates/css/shared_concated.css >
./templates/css/shared_minified.css
rm ./templates/css/shared_concated.css
cat
./templates/js/project.js
./templates/js/project/foo.js
./templates/js/project/bar.js
> ./templates/js/project_concated.js
java -jar yuicompressor-2.3.5.jar
./templates/js/project_concated.js >
./templates/js/project_minified.js
rm ./templates/js/project_concated.js
</code></pre>
<p>Neat, me thinks!</p>
Net neutrality rant2008-06-19T12:07:24+01:00http://www.contentwithstyle.co.uk/content/net-neutrality-rant/net-neutrality-rant <p>So I’m reading the guardian technology section today, and a rant about <a href="http://www.guardian.co.uk/technology/2008/jun/19/digitalvideo.internet">how net neutrality seemingly incapacitates the internet</a> by making it possible for content providers like youtube to eat up the whole bandwidth.</p>
<p>Makes me wonder how much the author considered that net neutrality also ensures that the little mans blog site gets the same priority as any other company big wig paid download service?</p>
<p>While he’s calling for more regulation to “free up the pipes”, I also wonder how often he has been annoyed with blocked ports to services like SMTP, to force you using the ISPs SMTP server.</p>
<p>And while some ports might indicate a certain type of service, and therefore be blocked, you might have recently put your servers’ ssh access on there.</p>
<p>Come on, the most likely scenario here is that you get Youtube and Ebay with perfect speed, and that the Itunes shop works like a charm, but independent small businesses will have to pay extra to be able to run a web shop, just to fall into the priority band.<br />
And the next thing you know is that any private website will dribble with 5k per second, you cannot access any port other than 80 and 225, and you’d better forget about using any open source VOIP software but skype …</p>
<p>... Now that’s better!</p>
XML Serialization and PHP Reflection2008-06-18T09:56:05+01:00http://www.contentwithstyle.co.uk/content/xml-serialization-and-php-reflection/xml-serialization-and-php-reflection <p>Browsing the web for a way to serialize PHP objects into XML I came across this <a href="http://groups.google.com/group/Professional-PHP/browse_thread/thread/2b0623bfc46cc336">interesting thread</a> </p>
<p>The most interesting thing to have a look at is the use of the <a href="http://uk2.php.net/manual/en/language.oop5.reflection.php#language.oop5.reflection.reflectionclass">Reflection functionality in PHP</a> , which gives you access to the objects, their methods and members.</p>
PHP Benchmark2008-06-02T12:17:26+01:00http://www.contentwithstyle.co.uk/content/php-benchmark/php-benchmark <p>Via <a href="http://magnetikonline.com/">Peter Mescalchin</a> I got hold of this <a href="http://www.phpbench.com/">PHP benchmark</a>.</p>
<p>I was particularly surprised that while loops runs o much quicker than foreach and for loops. Also it turns out that one can safely use double quotes if one likes to, without loosing much performance at all. Definitely worth a look.</p>
likes and don't likes2008-05-14T06:30:46+01:00http://www.contentwithstyle.co.uk/content/likes-and-dont-likes/likes-and-dont-likes <p>Just because I’m working with things …</p>
<p>I do Like:</p>
<ul>
<li><a href="http://framework.zend.com/manual/en/zend.config.adapters.ini.html">Zend_Config_Ini</a></li>
<li><a href="http://framework.zend.com/manual/en/zend.layout.html">Zend_Layout</a></li>
<li><a href="http://framework.zend.com/manual/en/zend.controller.html">Zend_Controller</a></li>
<li><a href="http://www.notepad2.com/">Notepad2</a></li>
<li><a href="http://developer.yahoo.com/yui/editor/">YUI Editor</a></li>
<li><a href="http://www.en.wampserver.com/">WAMP</a></li>
</ul>
<p>I don’t like:</p>
<ul>
<li><a href="http://www.twinhelix.com/css/iepngfix/">.htc files</a></li>
<li><a href="http://www.json.org/">JSON</a> to render content, instead of having real markup</li>
<li><a href="http://en.wikipedia.org/wiki/Duplicate_code">Duplicate Code</a></li>
<li><a href="http://en.wikipedia.org/wiki/God_object">God objects</a></li>
<li>Mixing <a href="http://en.wikipedia.org/wiki/Hungarian_notation">Hungarian</a> with other coding notations</li>
</ul>
Javascript libraries2008-04-25T03:06:58+01:00http://www.contentwithstyle.co.uk/content/javascript-libraries/javascript-libraries <p>If you came across some of my comments in other blogs about this subject, you probably know already that I’m a moo-man.<br />
<a href="http://moofx.mad4milk.net/">moo.fx</a>, the addons moo.dom and moo.ajax, I found them really easy to use. <br />
They have been derived from the early releases of <a href="http://prototype.conio.net/">prototype</a> (which I initially used as well), and the mentioned libraries come with a lite version of that, stripped to the bear essentials. Now <a href="http://mad4milk.net/">mad4milk</a> has released their <a href="http://mootools.net/">mootools</a>, basically a rewrite of the existing libraries, and a nice downloads page where you can mix and match modules for your needs, compiled into one handy file.<br />
If you want to check or extend the code, download an unpacked version for your perusal, only to use a packed version when going live. So many things can be done with such little files. Crazy. <br />
It also incorporates a version of <a href="http://dean.edwards.name/weblog/2006/06/again/">Dean Edwards’ DOMContentLoaded</a>.</p>
<p>Mike’s library of choice is <a href="http://jquery.com/">jQuery</a>, which itself stems from prototype (and it’s <a href="http://www.eweek.com/article2/0,1895,2010602,00.asp">partly based</a> on <a href="http://www.mad4milk.net/entry/links-of-interest">moo.fx</a>, too). It came up with the same concept of keeping the filesize low, and it’s selling point must be the <a href="http://www.visualjquery.com/">excellent documentation</a>. It possibly used to be the pick and mix download panel, but that’s “borrowed” back and improved by mootools now.<br />
They have in turn released the first issue of <a href="http://www.visualjquery.com/magazine/">Visual jQuery magazine</a> to help their and their developer community’s cause.<br />
But the biggest present for their own first birthday must be the release of <a href="http://jquery.com/blog/2007/01/14/jquery-birthday-11-new-site-new-docs/">jQuery 1.1</a> a couple of days ago. The improvements seem massive, and I’m very tempted to switch… </p>
<p>Both of these come from prototype, which I got to know around the same time as the <a href="http://dojotoolkit.org/">Dojo Toolkit</a>. Prototype is very well integrated into Ruby on Rails, and liked by many in combination with the <a href="http://www.ujs4rails.com/">UJS Rails plugin</a>. Sadly, both prototype and dojo are, compared to the aforementioned libraries, quite heavy, and I don’t need their particular features in my projects. </p>
<p>Content with Style’s own Pascal uses mainly moo.ajax and <a href="http://bennolan.com/behaviour/">Behaviour</a> to easily register, well, behaviours on dom elements. He extends those with custom written object literals in order to ensure separated scopes.<br />
All of us tend to pick and mix for our needs, extending where necessary, always with an eye on filesize.</p>
<p>Of course there’s <a href="http://edevil.wordpress.com/2005/11/14/javascript-libraries-roundup/">a million more javascript libraries</a>, and a bazillion ways to use them. That leads me to the question: What do you use? Is it in commercial projects, and where do you see the importance of a filesize/used code ratio? Have you abandoned other frameworks because you found something more tailored to your needs?</p>
MVC explanations - I love mailing lists2007-12-21T06:07:22+00:00http://www.contentwithstyle.co.uk/content/mvc-explanations---i-love-mailing-lists/mvc-explanations---i-love-mailing-lists <p>I came across this <a href="http://mail.python.org/pipermail/python-list/2006-January/360901.html">very elaborate explanation of MVC</a> in a python mailing list.<br />
Just want to take this as an occasion to say thank you to all people that take time to explain things in mailing lists.</p>
Hi guys, what's happening with CwS?2007-11-20T09:03:26+00:00http://www.contentwithstyle.co.uk/content/hi-guys-whats-happening-with-cws/hi-guys-whats-happening-with-cws <p>Good question! We were about to ask ourselves the same question.<br />
Even though Matthias came up with a <a href="http://www.contentwithstyle.co.uk/Articles/161/figure-microformats/">new article</a> today, we cannot deny that not much that has happened here in the past couple of months. We’re basically facing some descisions wether to leave CwS to die quietly, or to keep it alive … </p>
<p>... and the bad news is: We’re all awfully busy at the moment. In fact we’re so busy, that with the way the site is set up currently, we’re sure we cannot make it a more lively place, as none of us really has the time to write full fledged articles. And since we didn’t even get around to set up a pagination yet, the Blog hasn’t really got the capacity to grow much more, either …</p>
<p><strong>BUT:</strong></p>
<p>We don’t want CwS to die like this. We’ve made up our minds, and we decided to invest some serious time into a brand new CwS, wich will happen somewhen in the beginning of 2008. We’re going to have a new design, we’ll migrate to a new platform, we’re going to focus on the blog more, with many more authors, still have the occasional article, we’re going to introduce a new categorizations, and we’re pondering what other features we should bring to you, dear reader.</p>
<p>So why don’t you let us know? Any suggestions/criticism/feature requests are really appreciated, so please don’t hesitate.<br />
We’re looking forward to hear from you.</p>
Figure microformats2007-11-20T08:28:44+00:00http://www.contentwithstyle.co.uk/content/figure-microformats/figure-microformats <p>Recently I’ve built a <a href="http://www.iwm.org.uk/myboyjack">very small and compact project</a>, an 8-page static website actually (first time I’ve built anything static in about 7 years!), and I took the opportunity to get a bit deeper into the microformat madness that seems to be so en vogue since the last year or so.</p>
<h2>hCard yay, figure nay?</h2>
<p>While the whole hCard/vCard thing for contacts is a very nice and half-decent working solution, the <a href="http://microformats.org/wiki/figure-examples">discussion for image/figure markup</a> is just confusing.</p>
<p>The issues are that some solutions are worrying mostly about positioning, others about specific output formats, <a href="http://www.pearsonified.com/2007/06/how-to-format-images-for-feed-readers.php">for example for feed readers</a> (<a href="http://www.simplebits.com/notebook/2005/07/10/figures.html">also here</a>).</p>
<p>And then of course there’s <a href="http://edward.oconnor.cx/2007/04/marking-up-figures">the solution by Edward O’Connor</a> that wants to follow the html 5 draft as closely as possible, which is the reasoning I could follow the easiest, because it’s also, in my eyes, the one closest to the concept that was used for the hCard.</p>
<h2>Break it, fix it</h2>
<p>I started using a variation of it, and the first thing that annoyed me was that I did want to mark-up caption and credit individually, but I needed to position it quite freely as a block. The <a href="http://microformats.org/wiki/figure-examples#Revenue_Watch_Institute_.28website_forthcoming.29">suggestion on the microformats wiki</a> would allow for individual positioning, the <a href="http://www.alistapart.com/articles/figurehandler">solution at A List Apart</a> seemed a bit over the top, so I adjusted the original idea as follows:</p>
<pre>
<div class="figure">
<img src="look_up_dere__small.jpg" width="400" height="602" alt="Matthias and lots of blue sky" />
<div class="legend">
<p class="caption">
Matthias and lots of blue sky
</p>
<p class="credit">
<abbr class="type" title="Photograph">Photo</abbr>
&copy; <cite>Matthias Willerich</cite>
</p>
</div>
</div>
</pre>
<p>This follows the html 5 draft as per suggestion on one hand, even more so because “legend” is used, on the other it allows me to specify caption and copyright individually, but I have the freedom to style and position it anywhere together or individually.</p>
<p>It took me a while to understand what Edward meant by “This has block-level elements with inline-level siblings, which is gross”, until I read Anne van Kesteren’s opinion about <a href="http://annevankesteren.nl/2005/05/content-models">Markup content models</a>.</p>
<p>I would’ve followed what he’s saying, but at that point my project was already out the door. Nevermind, here’s a suggested amendment:</p>
<pre>
<div class="figure">
<p class="image"><img src="look_up_dere__small.jpg" width="400" height="602" alt="Matthias and lots of blue sky" /></p>
<div class="legend">
<p class="caption">
Matthias and lots of blue sky
</p>
<p class="credit">
<abbr class="type" title="Photograph">Photo</abbr>
&copy; <cite>Matthias Willerich</cite>
</p>
</div>
</div>
</pre>
<h2>what’s the benefit?</h2>
<p>With all this highbrow talk I don’t want to miss the point that I’ll never be in the situation again where I’m creating a template with text information associated to an image and I just don’t know how to do it <em>right</em>. This had been bugging me forever! With all the other concepts I just default to when I see a screen design the first time, this one fits right in. Here’s a <a href="http://www.willerich.com/demo/figure_microformats/">little example page</a> to have a look at how flexible this can be used.</p>
<h2>Back to the highbrow future</h2>
<p>Still, it left me wondering if I’m breaking a structure that’s used in other places; but I couldn’t find any (if you disagree, now is the time). If there was nothing using the figure markup, hell, why think about it in the first place, and not just throw together any old image and paragraph? Now, I was at that point with the hCards a while ago, but since then an hCard extension for firefox has come along, and with the current version it’s actually reasonably useful. I also agree very much with <a href="http://www.simplebits.com/notebook/2005/07/10/figures.html">Dan Cederholms feedreader train of thought</a> about how it <em>could</em> be used. While his article is already 2 years old, and not much technical seems to have happened in between, it doesn’t mean that its theoretical meaning isn’t picked up and formatted or used otherwise in the future.</p>
<h2>Homework</h2>
<p>Although they’re spread out all over the article, here’s the reading list that helped me with my conclusion. Have fun reading and let me know what you think.</p>
<ul>
<li><a href="http://microformats.org/wiki/figure-examples">Microformats figure examples</a>, find more links I’ve also mentioned here <a href="http://microformats.org/wiki/figure-examples#Further_discussion">at the bottom of the article</a></li>
<li>Edward O’Connor, <a href="http://edward.oconnor.cx/2007/04/marking-up-figures">Marking up figures</a> and <a href="http://edward.oconnor.cx/2007/08/figure-markup-redux">Figure markup redux</a></li>
<li>Anne van Kesteren, <a href="http://annevankesteren.nl/2005/05/content-models">Markup content models</a></li>
<li>SimpleBits, <a href="http://www.simplebits.com/notebook/2005/07/10/figures.html">When Floated Figures Attack!</a></li>
</ul>
Meebo firefox extension2007-10-24T05:27:10+01:00http://www.contentwithstyle.co.uk/content/meebo-firefox-extension/meebo-firefox-extension <p>Since I’m a great fan of <a href="http://www.meebo.com">meebo</a> and consider it one of the best examples for an AJAX application, I am pretty thrilled to see the <a href="https://addons.mozilla.org/en-US/firefox/addon/5700">meebo Firefox extension</a> .</p>
<p>It adds popup alerts, i.e. for incoming messages, and a neat sidebar that shows the contact list. Well done, meebo!</p>
Security: Even the simple stuff is hard it seems!2007-08-14T08:23:59+01:00http://www.contentwithstyle.co.uk/content/security-even-the-simple-stuff-is-hard-it-seems/security-even-the-simple-stuff-is-hard-it-seems <p>So I am reading about <a href="http://blog.washingtonpost.com/securityfix/2007/08/ftp_files_expose_web_site_cred.html">the bug in IE that puts your FTP details into the comments plaintext</a> ...</p>
<p>And I must say, it makes me chuckle. A while ago one of my friends sent a bulk mail to all of his contacts, containing an FTP link that he dragged from the address bar of IE into the mail in outlook. Needless to say that this contained username, password and host. </p>
<p>A comment about maybe changing the password or so was shrugged off, and when I demonstrated how fast the index.html was changed into a page stating that one should think about security, he was asking me to change it back … but still couldn’t be bothered to change the password.</p>
<p>What I want to say with this little story is, that even just the very concept of basic security measures doesn’t really touch the average folk. They are completely oblivious of the world around them being able to exploit private data or damaging their business.</p>
<p>Even worse, that, on various occasions when asking for what kind of authentication I should implement and pointing out that the chosen one is really bad, I have been told off for “overcomplicating things” and there were ovious security issues in the final application that I wasn’t allowed to fix, because the client was not being expected to be able to remember a different password to his own username.</p>
<p>I think it sometimes is a matter of responsibility for developers to protect people from themselves, making username/password combinations like myname/myname impossible, etc.</p>
Unitless line height in CSS2007-07-09T05:11:16+01:00http://www.contentwithstyle.co.uk/content/unitless-line-height-in-css/unitless-line-height-in-css <p>For a couple of years now I keep stressing the fact that line-height works best when you don’t provide a unit. And I am not saying this for no reason.</p>
<p>As <a href="http://meyerweb.com/eric/thoughts/2006/02/08/unitless-line-heights/">neatly explained by Eric Meyer</a> the unitless line height doesn’t enforce it’s computed size onto the inheriting element.</p>
PHP & Mysql socket error2007-07-07T19:58:36+01:00http://www.contentwithstyle.co.uk/content/php-mysql-socket-error/php-mysql-socket-error <p>I keep coming across this whenever I set up MySQL and PHP on a new mac: mysql_connect() fails throwing an error …</p>
<pre><code> mysql_connect() [function.mysql-connect]: Can't connect to local
MySQL server through socket '/var/mysql/mysql.sock'</code></pre>
<p>And everytime I forget to remember what the solution was:<br />
This can be fixed by changing settings in /etc/php.ini:</p>
<pre><code>
sudo cp /etc/php.ini.default /etc/php.ini
sudo vim /etc/php.ini
</code></pre>
<p>Then set the mysql default socket to the temp folder:</p>
<pre><code>
mysql.default_socket = /tmp/mysql.sock
</code></pre>
<p>Quite annoying, but then again: Hey, I’ve got a new Mac!!!</p>
Sorting by date with XSL2007-07-06T04:48:16+01:00http://www.contentwithstyle.co.uk/content/sorting-by-date-with-xsl/sorting-by-date-with-xsl <p>XSLT architect <a href="http://migueldemelo.blogspot.com/">Miguel de Melo</a> gives an insight into the sorting capabilities of XSL in his article <a href="http://migueldemelo.blogspot.com/2007/06/sort-by-date-greater-than-other-date.html">Sort by date greater than other date</a></p>
<p>The other blog entries are well worth a look, as they contain helpful examples of real-life XSL code. His blog is meant to be capturing the most interesting output of his replies to various mailing lists around he replies to.</p>
Firebug can make life easier2007-06-28T11:06:51+01:00http://www.contentwithstyle.co.uk/content/firebug-can-make-life-easier/firebug-can-make-life-easier <p>Today I was pointed to a couple of handy functionalities in the <a href="http://www.getfirebug.com/">Firebug plugin</a> by <a href="http://www.magnetikonline.com">Peter Mescalchin</a>, who’s one of the UI developers I am currently working with.</p>
<p>The highlight here is the <code>console.log()</code> function for debugging. This first of all gets rid of all those alerts that pause the application till we click the button of the alert box.</p>
<p>When we pass a dom node to <code>console.log()</code> instead of a string, we’ll have the dom node in the console, that on rollover highlights it’s position on the page.</p>
<p>Amazing is that this is even possible with an array of nodes, i.e. out of <code>getElementsByTagName()</code> or something similar.<br />
The output will be a nice little list of nodes that on rollover can be identified in the browser window.</p>
<p>Changing <code>console.log()</code> to <code>console.dir()</code> gives an even more detailed view on the object, with properties and so on, vaguely similar to a print_r or vardump in PHP, just that the console lets you collapse the single nodes etc.</p>
<p>Another great feature is the possibility to set breakpoints and debug your javascript application line by line.</p>
<p>For a deeper insight check out the <a href="http://yuiblog.com/blog/2007/01/26/video-hewitt-firebug/">video featuring Joe Hewitt</a> or read the <a href="http://www.getfirebug.com/console.html">reference for the firebug console API</a></p>
<p>Thanks for the tip Pete!</p>
How long have I been waiting for this?2007-06-05T17:48:27+01:00http://www.contentwithstyle.co.uk/content/how-long-have-i-been-waiting-for-this/how-long-have-i-been-waiting-for-this <p>It must be since I heard the word entity first. Check otu this <a href="http://leftlogic.com/lounge/articles/entity-lookup/">brilliant entity reference</a>. Via <a href="http://www.ajaxian.com">ajaxian</a>. </p>
<p>It’s AJAX powered, it’s slick and it brings up what you need. Definitely one of my favorites …</p>
DOs and DONTs in XSLT2007-06-04T11:43:01+01:00http://www.contentwithstyle.co.uk/content/dos-and-donts-in-xslt/dos-and-donts-in-xslt <p>A nice little roundup of how to efficiently use XSLT can be found <a href="http://www2.sims.berkeley.edu/academics/courses/is290-8/s04/lectures/5/dragons/allslides.html">here</a></p>
<p>I came accross this link when reading about <a href="http://copia.ogbuji.net/blog/2005-06-10/Push_vs_pu">Push vs Pull XSL</a> , something thatw ill change my approach on XSL quite a lot. Reading up on modes, push vs pull and so on basically gave me one of these moments that you had when you realized that tables and the way you were using them where not really appropriate in HTML.</p>
<p>Quite a <a href="http://www.biglist.com/lists/xsl-list/archives/200102/msg01119.html">drastic quote</a>, but true if I consider how I did my XSL code until now, I can only say he’s right:</p>
<blockquote><p>
Whether your favorite conceptual module is pipes and filters, tuple spaces, or just good ol’ lambdas, a fundamental understanding of push techniques is essential if you want to ever do any serious development in XSLT. New arrivals to this field take short-cuts only to get lost later. From a purely practical point of view, I think it’s important to teach apply-templates, modes and friends well before for-each, and bitchin’ value-of tricks.
</p></blockquote>
Hip hip, hooray2007-06-04T09:22:38+01:00http://www.contentwithstyle.co.uk/content/hip-hip-hooray/hip-hip-hooray <p>We did it. After more than one year we have a new article online.</p>
<p>I wrote <a href="http://www.contentwithstyle.co.uk/Articles/151/templates-from-babel/">Templates from Babel?</a> after being involved in the development of an XSL driven CMS for <a href="http://www.redwiredesign.com">Redwire</a> and then developing two small projects for <a href="http://www.sennep.com">Sennep</a> using the cakePHP framework.</p>
<p>It is meant as a careful critical remark rather than anything else, and any comments would on the article are most welcome.</p>
Templates from Babel?2007-06-04T09:16:11+01:00http://www.contentwithstyle.co.uk/content/templates-from-babel/templates-from-babel<h2>MVC frameworks are in!</h2>
<p>With the advent of the <a href="http://www.google.co.uk/search?hl=en&client=firefox-a&rls=org.mozilla:en-GB:official&hs=YPP&sa=X&oi=spell&resnum=0&ct=result&cd=1&q=Ruby+on+Rails+hype&spell=1">MVC framework hype</a> and all its <a href="http://forevergeek.com/programming/ruby_on_rails_not_so_productive.php">sceptical voices</a> and <a href="http://www.youtube.com/results?search_query=ruby+on+rails&search=Search">fan videos</a> side-effects we now see the MVC framework becoming more and more popular to quickly knock out applications. Application scaffolding and various out-of-the-box functions seem to be able to improve development drastically.</p>
<p>I have been using <a href="http://www.cakephp.org/">cakePHP</a> recently on 2 professional projects and it definitely has some great advantages, speeding up my development drastically. But some stuff made me feel a bit puzzled, if not disappointed, and that’s the motivation for this article.</p>
<h2>Personal observations</h2>
<p>First of all, there was a bit of a lost control over the output stream. Views get rendered through an application lifecycle and a flush, for example, just didn’t have the effect that I expected.</p>
<p>Second, I felt that the SQL performance of the database abstraction models just wasn’t that great. I mean, let’s think about this: We do an explain on the table, then we get all the stuff back, then we return the result and use it for rendering. And if there’s a relationship between them, then you have selects for every row to join the additional data in. Doesn’t really cut the mustard, does it?</p>
<p>But what really struck me bad, and this is what I am going to write about in this article, was the views and how the rendering gets done.</p>
<h2>But first, how do these frameworks work?</h2>
<p>MVC frameworks usually break up the application into database abstraction (model), business logic (controller) and output rendering (view). This, in most cases, is done with a strict naming convention that has been put in place to make changes easier and the development process faster. To find out more you can read the <a href="http://en.wikipedia.org/wiki/Model-view-controller">Wikipedia article</a> on this subject or <a href="http://justfuckinggoogleit.com/?q=mvc">just google for more</a></p>
<h2>Why Layer Separation?</h2>
<p>So why did people come up with layer separation and what do they usually think it is good for?</p>
<ul>
<li>Each layer can be maintained without changes affecting the other parts of the application layers</li>
<li>Separate testing of each layer is possible, i.e. through Unit tests</li>
<li>It is possible to swap a layer for something different, i.e. MySQL against PostgreSQL</li>
<li>It often is easier to find the individual files when they need to be changed (Ruby on Rails calls this “Convention over configuration”)</li>
</ul>
<h2>A look into how we are rendering the view layer</h2>
<h3>We look around …</h3>
<p>Starting my look around with cakePHP, I found that the views are embedding inline PHP that is able to call any framework method or custom PHP script. And that’s a lot. Have a look into the <a href="http://cakephp.org/files/cakesheet.pdf">cakePHP cheat sheet</a> and you’ll see what I mean.</p>
<p>Researching other frameworks I found that the same approach goes for most of them. Let’s have a look at a Ruby on Rails example, found on <a href="http://www.onlamp.com/pub/a/onlamp/2007/01/05/revisiting-ruby-on-rails-revisited-2.html">ONLamp.com</a> :</p>
<pre><code>
<%= select("recipe", "category_id", Category.find(:all).collect
{|c| [c.name, c.id] }) %>
</code></pre>
<p>The same goes for <a href="http://www.djangoproject.com/documentation/templates/">Django</a>, a python based framework, this time using curly brackets:</p>
<pre><code>
{% block content %}
<h1>{{ section.title }}</h1>
</code></pre>
<p>Discussing this amongst the CwS writers and other developers reveals that most frameworks offer some kind of mechanism to push data to the template from the controller so that direct method calls can be avoided. Examples would be the <a href="http://rails.rubyonrails.com/classes/ActionView/Partials.html">Partials in RoR</a> or the $controller->set() method in cakePHP.</p>
<p>Still, those mechanisms are language and framework specific.</p>
<h3>... and find a lot</h3>
<p>So the situation we’re facing is that every individual framework has an different way of rendering the view templates.</p>
<p>
Most solutions either incorporate the scripting language directly (i.e. cakePHP using inline PHP calls), are framework/CMS specific (i.e. TypoScript) or non-standard (i.e. Smarty).</p>
<p>Just the evaluation of these is already a pain, even when we restrict ourselves to a somehow narrowed down field of research, i.e. “MVC based frameworks in PHP”. </p>
<h3>We want power …</h3>
<p>Having script languages available makes template rendering extremely powerful. And there are good reasons for why we find those powerful mechanisms in the view rendering process:
To introduce script calls into the template rendering itself usually happens because there is a need for more than just straight output, but somehow more complex processing like conditions, loops, includes or sorting.</p>
<h3>... but get confusion?</h3>
<p>As we could see earlier, when we looked into different frameworks, we often have function calls directly from the view itself. Sure, it is a powerful mechanism to render templates, but is it a really clean approach?</p>
<p>One of the most common <a href="http://www.43things.com/entries/view/1487677">arguments against using PHP</a> is that its leading to <a href="http://en.wikipedia.org/wiki/Spaghetti_code">spaghetti code</a> . Most people that bash PHP pull out the “it’s just HTML with messy script tags” card first thing, and in my opinion they are right to say that this leads to an unmaintainable state.</p>
<p>Introducing inline code into the view rendering can cancel out the benefits of layer separation. If, for example, during an update process, parameter counts or method names get changed, we have two revise the views as well, otherwise the view could lead to a script error.</p>
<p>Even more drastic would be a port to another language. If we use a native scripting language like inline PHP or inline Ruby, we basically have to bin the whole application and do a complete rewrite. The view will not be reusable at all.</p>
<h3>There are other ways. </h3>
<p><a href="http://onsmalltalk.com/programming/smalltalk/rails-vs-seaside/">Seaside</a> for example, a Smalltalk based Framework that follows a very different paradigm, doesn’t contain any templates, but renders markup through methods only. This definitely seems drastic to most people, so it does for me, but I have to admit that it is a very clean approach. </p>
<h3>Are standards an alternative?</h3>
<p>Talking standards there are only a couple of techniques that spring to mind that offer interchangeable and platform-independent functionality… These being <a href="http://www.w3.org/TR/xslt">XSLT</a> and <a href="http://www.xml.com/pub/a/2002/03/20/xsl-fo.html">XSL-FO</a>.</p>
<p>XSL Transformations offer the power to do processing of any complexity if needed. XSLT parsers are available for most platforms out there, including PHP, ASP, ASP.NET, Python and many more.</p>
<h2>So what do I think?</h2>
<p>What I don’t really understand is why, after going to the trouble of creating system where model, view and controller are nicely separated, one would come up with a mixture of controller functions and markup in the view, that renders all the valid points above obsolete. </p>
<p>I am not sure if this is a misconception on my side when I digested the term “Layer separation”, but embedding inline PHP or script calls to controller methods in my eyes is a bit similar to putting inline styles into XHTML pages. They become less maintainable.</p>
<p>As mentioned above, we have the W3C standards that offer all the power we need for more intelligent template rendering. XSLT cuts out the learning curve for another proprietary template language, it’s powerful and it’s cross platform. Yet I see hardly any serious CMS or Frameworks embracing those official W3C standards available outside of the enterprise sector.</p>
<p>The only XSL based free blogging system I currently know about is <a href="http://21degrees.com.au/products/symphony/">Symphony</a> but there might be others.</p>
<h3>Where is the web going?</h3>
<p>I recently ask myself this question more often than usual. Whereas a couple of years ago the web seemed to be aiming for better standards, with XHTML and XML/XSLT being the way out of the mess, the outlook nowadays seems much more clustered, diverse and maybe even confusing.</p>
<p>The current focus seems to be rapid development and AJAX integration, the new buzz word is Web 2.0, coming along with whole new variety of Frameworks, Libraries and Communities that bring new life to the web community. All this is very good. </p>
<p>But I think that in a bigger question we have to ask ourselves is:
Why forget the old merits? Sure, frameworks and libraries can help to cut down development time, but can they cut out knowledge of the core techniques?</p>
<p>And yes, a big variety of techniques to choose from is great. Yet, is this the reason to disregard well defined standards and end up with a babble of languages and approaches, all adding to the initial learning curve?</p>
<p>If we go for a certain framework and technique now, but later the popular paradigm changes into something else, will we have to abandon most of what we’re using… or can we carry some standardised parts over to the new platform?</p>
<p>In any case, what we work with is down to each individual developer and the requirements of the individual project.</p>
<p>I hope I have highlighted some valid points in this article and am looking forward to your feedback.</p>Unit Tests in JavaScript2007-05-31T17:52:32+01:00http://www.contentwithstyle.co.uk/content/unit-tests-in-javascript/unit-tests-in-javascript <p>I don’t know how many of you guys out there are seriously testing their javascript applications, and how you do approach tesing them.<br />
I, until now, have been doing the good old step by step alert thing …</p>
<p>But my recent reading and the changes in my PHP development, where I’m using <a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a> more and more, made me shop around for finding something similar for JavaScript.</p>
<p>The one I want to start using from now on to debug my scripts and trace their output, is called <a href="http://jsassertunit.sourceforge.net/">jsassertunit</a></p>
<p>A port of the xUnit framework (know from <a href="http://www.junit.org/index.htm">JUnit</a> or <a href="http://phpunit.sourceforge.net/">PHPUnit</a>) is <a href="http://www.jsunit.net/">JsUnit</a></p>
<p>Browsing around on the general subject of unit testing, I even came accross a <a href="http://xsltunit.org/">test framework for XSLT</a>. That left me quite surprised, and I wonder how many people really do use things like this. But then again, XSLT is a <a href="http://en.wikipedia.org/wiki/Turing_complete">turing complete</a>, so you can do potentially anything computable with it.</p>
<p>If anyone has suggestions or helpful examples on using unit tests in javascript, please drop a comment.<br />
Also, I’d like to ask you, dear reader, to comment about your general testing practice and approach to debugging, not only JavaScript but everything you code.</p>
301 redirect with mod_rewrite2007-04-25T09:10:39+01:00http://www.contentwithstyle.co.uk/content/301-redirect-with-modrewrite/301-redirect-with-modrewrite <p>
So I changed my domain name from .de to .com. But wasn’t there something whereas Google would punish double-posted content with pagerank 0? That’s where a 301 redirect comes in, as neatly explained on <a href="http://www.gnc-web-creations.com/301-redirect.htm">this tutorial by GNC Web Creations</a>. The straight forward way to point an old domain to a new one would be putting this into the .htaccess:
</p>
<pre><code>
Redirect 301 /foo http://foobar.com/foo
</code></pre>
<p>OR</p>
<pre><code>
Redirect permanent /foo http://foobar.com/foo
</code></pre>
<p>
Now in my particular setting it was a bit more difficult, the domain was pointing to the same directory on the same server. And all that on a GUID managed system, so no fiddling around in the httpd.conf files.</p>
<p>
Thankfully the whole issue is easily solved utilizing the reg exp based rules of mod-rewrite, examining the HTTP_HOST of the request:<br />
</p>
<pre><code>
RewriteEngine ON
RewriteCond %{HTTP_HOST} ^(www.)?example.de
RewriteRule ^(.*) http://www.example.com/$1 [R=301,L]
</code></pre>
mod_deflate and IE6 bug2007-04-20T12:46:50+01:00http://www.contentwithstyle.co.uk/content/moddeflate-and-ie6-bug/moddeflate-and-ie6-bug<p>
This one was very strange. IE 6.0.29 would randomly come up with white pages. On the local servers it would come up fine, and so it did in any other version of IE, nor when using the <a href="http://tredosoft.com/Multiple_IE">multiple IE installer</a>.
</p>
<p>
Trying to use <a href="http://www.xk72.com/charles/">Charles</a> to sincerely scrutinize the headers and output for dodgy characters made the problem just go away.
</p>
<p>
Turns out that the gzip compression added by mod_deflate just wasn't well digested. The following lines in the http.conf caused the misery.
</p>
<pre><code>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/x-javascript
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch bMSIE !no-gzip !gzip-only-text/html
</code></pre>
<p>
Changing the last two lines would turn off the mod_deflate compression for IE6 but leave it in for IE7:
</p>
<pre><code>
BrowserMatch ^Mozilla/4.[0678] no-gzip
BrowserMatch bMSIEs7 !no-gzip !gzip-only-text/html
</code></pre>
<p>
For testing the HTTP headers that come through one can use the <a href="http://www.blunck.se/iehttpheaders/iehttpheaders.html">ieHTTPHeaders plugin</a> for IE or <a href="http://www.rexswain.com/httpview.html">Rex Swain's HTTP viewer</a>.
<br />
<strong>Happy Debugging!</strong>
</p>You might have noticed: There are some ads now.2007-04-13T12:50:33+01:00http://www.contentwithstyle.co.uk/content/you-might-have-noticed-there-are-some-ads-now/you-might-have-noticed-there-are-some-ads-now <p>I kept trying to avoid them, but in the end of the day I cannot see why bandwidth gets burned up without any cost return. And everyone else does seem to do it.</p>
<p>I hope your eyes are not massively insulted. If you have any better ideas then this, please let me know. </p>
<p>Now we just need some new articles to go along with it, in order to really raise some money for the poor server monkeys of CwS!</p>
Swapping nodes in a DOM tree2007-04-12T09:34:02+01:00http://www.contentwithstyle.co.uk/content/swapping-nodes-in-a-dom-tree/swapping-nodes-in-a-dom-tree<p>
I was quite surprised to find out that the insertBefore method actually does most of the job for you.
<br />
A detail in <a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-952280727">the manual</a> offers us insight in how: "If the newChild is already in the tree, it is first removed."
<br />
<br />
Hence:
</p>
<pre><code>
myNode.parentNode.insertBefore(myNode, myNode.nextSibling);
</code></pre>php: array_diff_assoc and the order of parameters2007-04-12T03:55:51+01:00http://www.contentwithstyle.co.uk/content/php-arraydiffassoc-and-the-order-of-parameters/php-arraydiffassoc-and-the-order-of-parameters<p>
This took a while till I found out: They actually do matter!
<br />
Even worse: The manual says so!!!! Just that I was to stupid to read it.
<br />
Lesson learned. No more guessing, no more quick reading!
</p>
<pre><code>
<?
$a = array(
'x' => 'x',
'y' => 'y',
'z' => 'z',
't' => 't',
);
$b = array(
'x' => 'x',
'y' => 'y',
'z' => 'z',
't' => 't',
'g' => 'g',
);
print_r(array_diff_assoc($a, $b));
print_r(array_diff_assoc($b, $a));
?>
</code></pre>
<p>
This will output:
</p>
<pre><code>
Array
(
)
Array
(
[g] => g
)
</code></pre>Blog section opened2007-04-07T06:53:45+01:00http://www.contentwithstyle.co.uk/content/blog-section-opened/blog-section-opened <p>Welcome to The new blog section on the CwS website.<br />
It will basically replace the events and offer us a bit more freedom than the links currently shown in the “Elsewhere” section, but also it’ll contain ideas that didn’t make it into the articles because … guess what … we’re just to busy.</p>
generate PDF with XSL-FO and FOP2007-04-03T08:57:25+01:00http://www.contentwithstyle.co.uk/content/generate-pdf-with-xsl-fo-and-fop/generate-pdf-with-xsl-fo-and-fop <p>A lot of 3-letter abbrevations here, I wanted to share some notes about this.<br />
So, the scenario I was faced with was to create a pdf with some dynamic content (text, pixel-based image). As usual, nobody wants to spend any money and the “open-source” card is played early.<br />
So, after a short look I find <a href="http://xmlgraphics.apache.org/fop/index.html">FOP</a>, an apache project that renders pdf and other formats out of a <a href="http://www.w3schools.com/xslfo/default.asp">XSL-FO</a> source.<br />
XSL-FO is an XML subset, that allows you to mark up content in order to generate print formats, such as pdf, rtf or postscript.</p>
<p>My trouble started when I had to embed a font, as pdf only comes with <a href="http://xmlgraphics.apache.org/fop/0.20.5/fonts.html#Base-14+Fonts">a small preconfigured set</a> :<br />
<cite>The Adobe PDF Specification specifies a set of 14 fonts that must be available to every PDF reader: Helvetica (normal, bold, italic, bold italic), Times (normal, bold, italic, bold italic), Courier (normal, bold, italic, bold italic), Symbol and ZapfDingbats.</cite></p>
<p>For FOP you could create a metrics file needed to embed a font with an accompanying tool. On OS X I discovered that Preview did not display the text using the font. Luckily that was fixed quickly when I tried out the <a href="http://xmlgraphics.apache.org/fop/0.93/fonts.html#truetype-metrics">-enc ansi</a> option. I’m not sure why there are 2 ways to create a metrics file, but hey, the ansi version works with Preview, the default one (CID-keyed) doesn’t.<br />
Everything seemed fine.</p>
<p>Until I realised that this version only creates 72dpi output, regardless what you specify. Not great when the pdf is meant to be printed out.<br />
So I went for the most recent release, <a href="http://xmlgraphics.apache.org/fop/0.93/index.html">fop 0.93</a>. While I could now generate 150dpi output (or any kind of resolution, for that matter), it didn’t recognize my previously generated metrics file, or font, and gave me grief with some warnings similar to this:</p>
<pre>
SEVERE: Failed to read font metrics file null
java.io.EOFException: Reached EOF, file size=5104 offset=5104
</pre>
<p>After some international swearing, a lunch-break and some research I found <a href="http://www.mail-archive.com/fop-users@xmlgraphics.apache.org/msg06266.html">this little gem</a> in their mailing list. It kinda reads as if they literally forgot to test with their own test fonts before release. But who am I to complain, I didn’t spend a minute developing it, did I? <br />
So, after downloading an <a href="http://svn.apache.org/viewvc?view=rev&revision=496860">updated font file reader</a> together with ant and the source package of the 0.93 release, I compiled the whole thing again, without any problems, and started rendering away happily.</p>
Browser based vector rendering2007-03-12T13:54:40+00:00http://www.contentwithstyle.co.uk/content/browser-based-vector-rendering/browser-based-vector-rendering <p>Today <a href="http://www.redwiredesign.com/profile/justin_finkelstein.html">Justin</a> came across a demo for the <a href="http://ajax3d.sourceforge.net/">Ajax3d engine</a>, an entirely JavaScript based 3d engine that utilizes the canvas tag for rendering vector based graphics.</p>
<p>Canvas tag? A little <a href="http://en.wikipedia.org/wiki/Canvas_%28HTML_element%29">research</a> reveals that this is yet another proprietary tag, this time introduced by Apple.<br />
With all of us in the office Browsing the web a bit more, <a href="http://www.redwiredesign.com/profile/tom_westcott.html">Tom</a> finds a <a href="http://testzone.danieljmarra.com/canvas/canvaspong/">pong game</a> and various other stuff on the web, all done using the canvas tag.</p>
<p>One big downside, though: It is not supported in IE!</p>
Wine, Photoshop & "save for web"2007-02-05T08:25:25+00:00http://www.contentwithstyle.co.uk/content/wine-photoshop-save-for-web/wine-photoshop-save-for-web <p><a href="http://www.redwiredesign.com/profile/tom_westcott.html">Tom</a> did find out about this one for me:<br />
The trick is to execute Photoshop with quotes around the path and backslashes in it.</p>
<p>Via <a href="http://www.winehq.org/pipermail/wine-bugs/2006-June/032059.html">winehq</a></p>
<pre>
Save for Web works with:
wine "c:\program files\adobe\Photoshop 7.0\Photoshop.exe"
Save for Web WILL NOT work with:
wine "c:/program files/adobe/Photoshop 7.0/Photoshop.exe"
</pre>
Ndiswrapper and Fedora Core 62007-01-18T10:30:02+00:00http://www.contentwithstyle.co.uk/content/ndiswrapper-and-fedora-core-6/ndiswrapper-and-fedora-core-6 <p>For me, the key to get this working is to NOT attempt to create the ath0 Interface, but <a href="http://ndiswrapper.sourceforge.net/mediawiki/index.php/Fedora#Configuration_using_script_system">using ethX as interface</a> instead.</p>
<p>1) First I updated the whole system to use the latest rpm’s</p>
<pre><code>sudo yum update</code></pre>
<p>2) I set up my yum to use the <a href="http://rpm.livna.org/rlowiki/UsingLivna">livna rpms</a></p>
<pre><code>rpm -ivh http://rpm.livna.org/livna-release-6.rpm</code></pre>
<p>3) I installed ndiswrapper using YUM:</p>
<pre><code>sudo yum install ndiswrapper</code></pre>
<p>4) I edited my modprobe.conf adding the following lines:</p>
<pre><code>
options ndiswrapper if_name=eth1
alias eth1 ndiswrapper
</code></pre>
<p>5) I restarted</p>
<p>6) I downloaded the <a href="ftp://ftp.dlink.co.uk/wireless/dwl-g650+_rev_Bx">XP drivers for the card</a> and unzipped the file, then loaded the driver into ndiswrapper:</p>
<pre><code>sudo ndsiwrapper -i WinXP/GPLUS.inf</code></pre>
<p>7) Finally I executed the modprobe command to load ndsiwrapper as driver:</p>
<pre><code>sudo modprobe ndsiwrapper</code></pre>
<p>And finally I was able to go to the network administration panel in GNOME and change the settings for the card like for any other network interface.</p>
Outlook 2007 and those bloody HTML emails2007-01-18T08:07:20+00:00http://www.contentwithstyle.co.uk/content/outlook-2007-and-those-bloody-html-emails/outlook-2007-and-those-bloody-html-emails <p>Microsoft comes up with a great way of rendering emails in Outlook 2007 which might cause Matthias to refuse doing any HTML formatted emails in the future. </p>
<p>David Greiner posted this interesting article on <a href="http://www.campaignmonitor.com/blog/archives/2007/01/microsoft_takes_email_design_b.html">campaignmonitor.com</a><br />
stating that Microsoft just threw back the development of HTML emails for about 5 years, and even though <a href="http://www.molly.com/2007/01/18/what-happened-with-html-and-css-in-outlook-2007/">Molly Holzschlag</a> seems to be more pragmatic about the whole thing than David, saying that we should dry our tears and start dealing with the problem, the problem itself remains:</p>
<p>It will not be possible to use accessible, standard-compliant and best-practice HTML to format emails anymore.<br />
Outlook 2007 will mess everything up and cripple anything that uses floats and other sophisticated CSS commands.</p>
<p>While many people get really worked up over this I start secretly smiling and telling myself my honest opinion over and over again:<br />
“There shouldn’t be ANY HTML in emails. They’re supposed to be plain text and maybe some attachments!” </p>
fulltext & weighted relevance2007-01-09T09:14:37+00:00http://www.contentwithstyle.co.uk/content/fulltext-weighted-relevance/fulltext-weighted-relevance <p>Via JV Multimedia:</p>
<p>A quick and neat way to use MySQL fulltext search with weighted relevance modificators.</p>
<pre><code>
SELECT
*,
(MATCH (category) AGAINST ('$query' IN BOOLEAN MODE)*100) +
(MATCH (title) AGAINST ('$query' IN BOOLEAN MODE)*10) +
MATCH (body) AGAINST ('$query' IN BOOLEAN MODE) AS rating
FROM
stories
WHERE
MATCH (title,category,body) AGAINST ('$query' IN BOOLEAN MODE)
ORDER BY
rating DESC
LIMIT 0,10
</code></pre>
<p><a href="http://www.jvmultimedia.com/portal/node/61">http://www.jvmultimedia.com/portal/node/61</a></p>
CwS 2006 - a little summary2006-11-30T07:46:03+00:00http://www.contentwithstyle.co.uk/content/cws-2006---a-little-summary/cws-2006---a-little-summary <p>With 2006 nearly over it’s time for a little look back on what happened with CwS throughout the year.</p>
<p><strong>2006 came with some major difficulties and changes for us.</strong></p>
<p>We were offline for ages, being kicked out of our shared hosting platform and <a href="http://www.contentwithstyle.co.uk/Blog/85/offtime-moving-and-some-changes/">had to sort out some hosting solution</a> that wouldn’t cause the same trouble again.</p>
<p>A bit on the negative side as well is our article output, which is just two articles throughout the whole year.</p>
<p>We had a <a href="http://www.contentwithstyle.co.uk/Blog/107/spam-and-some-other-things-/">mad increase of spam being dropped in our comments</a>, and a couple of insults came up as well. </p>
<p>Plus, we got rid of the Events section because we realized it was to much to manage in a way that wouldn’t come across as half-hearted.</p>
<p><strong>On the other hand it wasn’t all just negative.</strong></p>
<p>We did successfully introduce the <a href="http://www.contentwithstyle.co.uk/Blog/">Blog section</a>, where we published quite a lot of things, from general things to little code snippets, and sparked some interesting discussions as well.</p>
<p>We received quite a lot of attention, i.e. Mike being asked to predict the future in .Net magazine.</p>
<p>Our stats look good, really. With around 20k unique visitors each month and something like 3 million page impressions throughout this year we’re quite pleased to have that many readers.</p>
<p>And last but not least, some people dropped some nice comments and made us feel like we contributed something.</p>
<p><strong>As a little outlook for 2007</strong> I cannot really offer anything more than some of the things we’d fancy to do, if we ever find the time to tackle them:</p>
<p><em>We’d like to do a redesign and relaunch</em><br />
<em>We’d like to write some proper articles</em><br />
<em>We’d like to have others writing some articles for us</em></p>
<p><strong>So there’s just one more thing to do then:</strong><br />
We wish you a great rest of 2006 and a good start into 2007. Enjoy the holidays and a happy new year, or, as the Germans say “Frohe Weihnachten und einen guten Rutsch”!</p>
XML validation in PHP2006-11-08T06:41:10+00:00http://www.contentwithstyle.co.uk/content/xml-validation-in-php/xml-validation-in-php <p>Working with user input that needs to be valid XML, it turns out that PHP5 has a built-in <a href="http://www.php.net/manual/en/function.dom-domdocument-schemavalidate.php">validation</a> function.</p>
<p>For just checking if the XML is well-formed you can just leave the parameter with the filename of the xsd blank.</p>
<p>See the following example code for a possible application:</p>
<pre><code>
class XML
{
public static function validate($xml)
{
libxml_use_internal_errors(true);
$doc = new DOMDocument('1.0', 'utf-8');
$doc->loadXML($xml);
$errors = libxml_get_errors();
if (empty($errors))
{
return true;
}
$error = $errors[ 0 ];
if ($error->level < 3)
{
return true;
}
$lines = explode("r", $xml);
$line = $lines[($error->line)-1];
$message = $error->message.' at line '.$error->line.':<br />'.htmlentities($line);
return $message;
}
}
</code></pre>
IE7 and how it changes the web2006-10-29T12:32:05+00:00http://www.contentwithstyle.co.uk/content/ie7-and-how-it-changes-the-web/ie7-and-how-it-changes-the-web <p>Simon Griffin from <a href="http://www.etre.com/">etre</a> sent us a rather interesting email about some research they’ve done on IE7 …</p>
<blockquote>
<p>Hi,</p>
<p>I was just browsing your site and thought you might be interested in a study we just published.</p>
<p>As I’m sure you know, prior to its release last week, many were predicting that Internet Explorer 7.0 would break the internet. Indeed, Microsoft itself admitted that certain sites that worked well in IE6 would fall apart in IE7. Unfortunately, no one seemed to know exactly how many sites would be affected by the launch of the new browser. So to get an idea, we decided to fire up a couple of machines and compare the homepages of a hundred different corporate websites in both IE6 and IE7. The results are available on our blog at: <a href="http://www.etre.com/blog/2006/10/ie7_were_they_ready/">http://www.etre.com/blog/2006/10/ie7_were_they_ready/</a> </p>
<p>If our findings are indicative of websites in general (which I admit requires a bit of a leap of faith!), they would suggest that around 12.7 million websites in need of a little TLC as a result of the introduction of IE7!</p>
<p>Anyway, hope this is of interest to you.</p>
<p>Kind regards,</p>
<p>Simon Griffin.</p>
</blockquote>
<p>Thanks Simon for that info.<br />
I am actually quite pleased to hear this. Turns out to be a great source of revenue for the web industries. <br />
From a bit of a sarcastic point of view: It often seems to be a good excuse for doing a complete relaunch of the whole content management system and everything else, when the logo’s not in place anymore. Not that the other bad aspects weren’t urgent, but this one’s unbearable …</p>
XSL:TEXT, CDATA and PHP52006-10-20T07:53:06+01:00http://www.contentwithstyle.co.uk/content/xsltext-cdata-and-php5/xsltext-cdata-and-php5 <p>Right, so I am applying disable-output-escaping="yes" on an xsl:text element containing some CDATA, but the output is still coming with escaped entities …</p>
<pre><code>
<xsl:text disable-output-escaping="yes">
<![CDATA[This is a <p> tag]]>
</xs:text>
</code></pre>
<p>This is actually a <a href="http://bugs.php.net/bug.php?id=29837&edit=1">glitch</a> in the PHP5 built in transformation engine, and the good news is that there is a fix for this. The predefined constant <a href="http://us3.php.net/manual/en/ref.libxml.php#libxml.constants">LIBXML_NOCDATA</a> will fix this behaviour to the one you expect:</p>
<pre><code>
$xsl_dom->loadXML($xsl, LIBXML_NOCDATA);
or
$xsl_dom->load($xsl_file_location, LIBXML_NOCDATA);
</code></pre>
Find and Replace and some more VIM tuning2006-10-17T08:40:44+01:00http://www.contentwithstyle.co.uk/content/find-and-replace-and-some-more-vim-tuning/find-and-replace-and-some-more-vim-tuning <p>Awkward at first glance <a href="http://www.vim.org/">VIM</a> has become my editor of choice. A modified .vimrc file based on the one found at <a href="http://schlitt.info/applications/blog/index.php?/archives/331-Comfortable-PHP-editing-with-VIM-3.html">schlitt.info</a><br />
enables quite a couple of extras that make work fast and efficient.</p>
<p>Handy as well is the functionality to add comments, again, found at <a href="http://schlitt.info/applications/blog/index.php?/archives/488-Comfortable-PHP-editing-with-VIM-5.html">schlitt.info</a> and available for download at <a href="http://www.vim.org/scripts/script.php?script_id=1355">vim.org</a></p>
<p>Neat: <a href="http://www.vim.org/tips/tip.php?tip_id=31">Search and Replace in VIM</a> works by utilizing the <a href="http://www.cornerstonemag.com/sed/">sed</a> stream editor.</p>
Checking for object literals2006-10-03T07:46:03+01:00http://www.contentwithstyle.co.uk/content/checking-for-object-literals/checking-for-object-literals <p>Such a basic thing to do, but still, I didn’t have to do it until now:<br />
Checking if an object literal is defined.</p>
<pre><code>
var my_object_literal
{
'my_property' : 'I exist'
};
if (window.my_object_literal)
{
alert(my_object_literal.my_property);
}
</code></pre>
<p>via <a href="http://www.faqts.com/knowledge_base/view.phtml/aid/16419">FAQTS</a></p>
IE Conditional comments in XSL2006-09-27T05:49:41+01:00http://www.contentwithstyle.co.uk/content/ie-conditional-comments-in-xsl/ie-conditional-comments-in-xsl <p>Just a small bit of code, but not obious at first glance …</p>
<pre><code>
<xsl:comment><![CDATA[[if gte IE 6]>
<link rel="stylesheet" type="text/css" media="screen" href="ie.css" />
<![endif]]]></xsl:comment>
</code></pre>
PHP Script: Timeshift in Subtitle Files2006-09-16T10:30:58+01:00http://www.contentwithstyle.co.uk/content/php-script-timeshift-in-subtitle-files/php-script-timeshift-in-subtitle-files<p>
Have you ever tried to watch a movie with a .srt file, and the subtitle was more or less out of time? You can use this little PHP script through commandline and fix it by providing an offset.
</p>
<p>
This obviously is just a quick script, nothing fancy, but i thought I might still share it. Save the file as “subtitle_timeshift.php” and execute it with PHP in the commandline. It will then save your file with the prefix “parse_” so you can check if it works or not.
<br />
<br />
Enjoy!
</p>
<pre><code>
<?
if(empty($argv[1]) || empty($argv[2]))
die('Syntax: php subtitle_timeshift.php filename seconds'. "n");
if(!file_exists($argv[1]))
die('file not found' . "n");
$fc=file($argv[1]);
$f = fopen('parse_' . $argv[1], 'w+');
foreach($fc as $ln)
{
if(strpos($ln, ' --> ') !== false)
{
$parts = explode(' --> ', $ln);
$start = substr($parts[0], 0, 8);
$end = substr($parts[1], 0, 8);
$start = strtotime("2000-01-01 " . $start) + intval($argv[2]);
$end = strtotime("2000-01-01 " . $end) + intval($argv[2]);
$start = date("H:i:s", $start) . substr($parts[0], 8);
$end = date("H:i:s", $end) . substr($parts[1], 8);
$ln = $start . ' --> ' . $end;
}
fputs($f, $ln);
}
fclose($f);
?>
</code></pre>PHP and MP3s2006-09-11T08:12:57+01:00http://www.contentwithstyle.co.uk/content/php-and-mp3s/php-and-mp3s <p>While setting up a media server for the <a href="http://www.netaudiolondon.cc">Netaudio Festival</a> next weekend I had not only to set up a dedicated machine with samba shares, Webserver and Web based interface, but also to play around with mp3s and ID3 tags.</p>
<p>First I wanted to use the <a href="http://pecl.php.net/package-info.php?package=id3&version=0.2">id3 package</a> . But after getting errors using the PEAR installer (phpize first was missing, then still failed when reading the package) I eventually gave up and went with the fantastic <a href="http://getid3.sourceforge.net/">GetID3</a> script.<br />
Very cool, especially because the MySQL Demo already features a full data structure and recursive indexing of directories.</p>
<p>I’ll keep you updated on this one as I want to make the script I wrote Open Source for everyone to enjoy, but it needs a bit of tidying up.</p>
Register PHP functions in XSL2006-09-11T07:55:48+01:00http://www.contentwithstyle.co.uk/content/register-php-functions-in-xsl/register-php-functions-in-xsl <p>Working with PHP5 and the built-in XSLT processor I came accross the method <a href="http://uk2.php.net/manual/en/function.xsl-xsltprocessor-register-php-functions.php">registerPHPFunctions</a> that lets you register PHP functions in XSL. Quite a neat feature. Wonder how much that slows the transformation down?</p>
Hybrid Flash Developers: A unique species2006-09-05T11:56:37+01:00http://www.contentwithstyle.co.uk/content/hybrid-flash-developers-a-unique-species/hybrid-flash-developers-a-unique-species <p>Check out this <a href="http://www.thefwa.com/articles/flashdevs0906.html">article</a> by <a href="http://www.velloff.com/">Nick Velloff</a>. (via <a href="http://blog.lessrain.com/">lessrain blog</a>)</p>
<p>We just discussed this a little bit in the office, with different opinions. I think there’s a flaw in the article, as to me it reads that the hybrid flash developer is the way to go. I wonder: Has a hybrid developer, or a “generalist” as some people call it, ever been out of fashion? Will it lose eventually against the “specialist”?</p>
<p>I don’t believe in that. The essence I take out of this article is in this sentence:</p>
<p><cite>This developer generally has an excellent rapport and mutual level of respect with designers.</cite></p>
<p>But it really doesn’t read as if the article focusses on this point. Further down Nick starts a list with ideas of how to make yourself be more design-aware. Wow: “Pay attention to design comps” and “Try and be creative”. Is he serious?</p>
<p>Sure, when I “Try to sit near the designers”, I’m really after an exchange, and that helps understanding each other. Understanding how the other one thinks, what excites them and what makes them tick. Nick is after the right result, but his viewpoint is confusing me.</p>
<p>In small creative agencies you’ll always need hybrid developers of any kind; there’s different possibilities, e.g. one person that designs and animates, another one that does the heavy actionscript coding; or one designer that creates templates down to html and css, and a programmer that breathes life into the templates. Or the other way round: I myself am responsible for front and back-end coding, HTML and PHP mostly, and I mostly treat it as 2 jobs. There’s many possibilities, and small companies rely on them. <br />
Bigger companies will look for very specialized employees, so that one only does the job they’re best at.<br />
But what stays is the need for mutual respect, the rough understanding of the person doing their job before or after you. Or their job, rather. Preparing your work so that they have an easier life once they start their task.<br />
That doesn’t make the hybrid developer better or worse than one specializing in one or the other field. So a call to push yourself to become a hybrid developer is essentially only for folks that want to work in a small team. The respect for your teammates is something you can utilise anywhere you work. </p>
Spam and some other things ...2006-08-25T07:10:03+01:00http://www.contentwithstyle.co.uk/content/spam-and-some-other-things-/spam-and-some-other-things- <p>Sadly we lately have to deal with unpleasant things. One is the comment spam, that actually is getting worse and worse. But since Mike did write his latest article with a bit of a tabloid title we have to deal with some other things as well …</p>
<p>Which are insults. A bit disappointing for me, really, since I invest spare time and money in this project without asking for anything in return. I think that we already successfully sparked some interesting discussions with our articles, and to drop a line like “You are loosers” or something similar just doesn’t do us right I think.</p>
<p>Please note that we moderate the comments. Every single one will be reviewed, and no insult or spam be switched visible. The same goes for really off-topic comments, that we will answer in an email though.</p>
<p>So much for our comments and our problems with them. </p>
<p>On the other side of things we had nearly 30.000 unique visitors a month, our traffic (and that is text data mostly) ate up about 4.84 Gigabytes. A massive 284604 page impressions have been logged and all together that adds up to more than 900000 hits.</p>
<p>That is a big success for us and we are happy that the site is well percieved. Hopefully we will come up with some more articles in the near future, but again a quick reminder: This website is done in our spare time, and we can only write when we have spare time.</p>
<p>If you think you have an idea for an article then feel free to send us a draft. We would welcome more writing on here.</p>
Netaudio'06: London Netlabel Festival2006-08-25T07:09:42+01:00http://www.contentwithstyle.co.uk/content/netaudio06-london-netlabel-festival/netaudio06-london-netlabel-festival <p>CwS supports London’s first netlabel festival with more than 30 artists from all over the world. Netaudio’06 takesplace on the 15th and 16th of September 2006 at Candid Arts Gallery and Electrowerkz, next to Angel tube station, London.</p>
<p><a href="http://www.netaudiolondon.cc/"><img src="http://www.netaudiolondon.cc/images/116.gif" alt="" /></a> </p>
<p>Make sure to pop down and check out a fine selection of netlabel music,<br />
audiovisual arts, take part in the knowledge fair and coffe table discussions.</p>
<p>The daytime event will be for free, tickets for both nighttime events <br />
are now available for £15 through <a href="http://www.ticketweb.co.uk/user/?region=gb_london&query=detail&event=174367">Ticketweb</a></p>
UTF-8 based transformation output in .Net2006-08-17T11:38:46+01:00http://www.contentwithstyle.co.uk/content/utf-8-based-transformation-output-in-net/utf-8-based-transformation-output-in-net<p>
Using XSL transformations in .Net I came accross the weird behaviour that my transformations would be UTF-16 encoded even though I specified UTF-8 in the <code><xsl:output /></code> tag.
</p>
<p>
This left me a bit speechless, and I was assuming that this only could be a .Net bug. After a bit of research, however, I found this to be a result of .Net being very specific about character encodings.
</p>
<p>
In my following example the StringWriter has the property Encoding set to System.Text.Encoding.UTF-16, hence the output charset will be UFT-16 as well, no matter what I specify as character set in the XSL.
</p>
<pre><code>
XslTransform xslt = new XslTransform();
StringWriter output = new StringWriter();
xslt.Transform(xml, args, output);
String code_transformed = output.ToString();
</code></pre>
<p>
<a href="http://www.dotnet247.com/247reference/msgs/41/205082.aspx">Steven Livingstone pointed out</a> that, since the encoding property of System.IO.StringWriter is a read only property, one has to provide a different Stream object to recieve the transformation output, if this is to be encoded in UTF-8:
</p>
<pre><code>
XslTransform xslt = new XslTransform();
MemoryStream ms = new MemoryStream();
xslt.Transform(xml, args, ms);
ms.Position = 0;
StreamReader sr = new StreamReader(ms, Encoding.UTF8);
String code_transformed = sr.ReadToEnd();
</code></pre>
<p>
Another possibility would be to extend the StringWriter class in order to make a different encoding possible, as suggested on Robert McLaws <a href="http://weblogs.asp.net/rmclaws/archive/2003/07/31/22080.aspx">FunWithCoding.Net</a> which would read as follows:
</p>
<pre><code>
using System;
using System.IO;
using System.Text;
namespace MyAwesomeNamespace
{
public class StringWriterWithEncoding : StringWriter
{
private Encoding _enc;
public StringWriterWithEncoding(Encoding NewEncoding) : base()
{
_enc = NewEncoding;
}
public override System.Text.Encoding Encoding
{
get
{
return _enc;
}
}
}
}
</code></pre>CrunchBoard2006-08-04T08:24:39+01:00http://www.contentwithstyle.co.uk/content/crunchboard/crunchboard <p>My current favourite website and VC’s darling, TechCrunch, has just launched a jobs board: <a href="http://www.crunchboard.com/">CrunchBoard</a>. It’s not cheap to post a job so the quality of the ones on offer seems pretty high. </p>
Safari, Ajax and the back button2006-07-10T09:42:58+01:00http://www.contentwithstyle.co.uk/content/safari-ajax-and-the-back-button/safari-ajax-and-the-back-button <p>A while ago David Bloom posted a <a href="http://bloomd.home.mchsi.com/histapi/test.html" title="and others">back button, bookmarking and so on fix for Safari</a>, which, aside from some funny character garbage at the end, works reliably enough to be used for all your Ajax and Flash needs.</p>
<p>I’ve got the feeling that one reason it didn’t get its deserved attention is that he called it “Fragment identifier history”, which might be the right terminology, but doesn’t help much in the publicity department.</p>
<p>I didn’t post this before, as I wanted to compare and integrate his solution with <a href="http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps/">Mike’s approach</a> and the <a href="http://www.blog.lessrain.com/index.php?s=back+button">work of others</a>. But time is of the essence and I didn’t have any so far, not even for the <a href="http://www.blog.lessrain.com/?p=357">solution we currently use at Less Rain</a>. This one fixes IE by using a client-side VB script, how weird is that?</p>
<p>So, I wonder, has anyone seen a solution that is publicly available, documented and works on Safari and possibly Opera as well? Or is it just not fashionable any more to incorporate this kind of solution?</p>
Ctrl+Shift+F at the developer toolbar party2006-07-06T08:14:54+01:00http://www.contentwithstyle.co.uk/content/ctrlshiftf-at-the-developer-toolbar-party/ctrlshiftf-at-the-developer-toolbar-party <p>This is my find of the day:<br />
Hit Ctrl + Shift + F when using <a href="http://chrispederick.com/work/webdeveloper/">Chris Pederick’s developer toolbar</a>, or “display element information” under Information. I’m sure I’m so late to this party, that the only ones left are some crying girl with an empty bottle of spirits and a sad sausage swimming in lukewarm water.</p>
<p><img src="http://www.contentwithstyle.co.uk/resources/old_images/20.gif" alt="" width="405" height="280" /></p>
<p>Now more seriously: You’ll find a nice summary including attributes, ancestors and children as well as position and style information for every element you roll over. </p>
<p>Of course you can get similar results (depending on what you’re after) with with CSS->View style information, Outline block elements or by using <a href="http://www.joehewitt.com/software/firebug/">Firebug’s</a> inspector; but it doesn’t claim as much space, and the information is presented very nicely.</p>
REST and some unknown verbs2006-06-28T06:27:21+01:00http://www.contentwithstyle.co.uk/content/rest-and-some-unknown-verbs/rest-and-some-unknown-verbs <p>I started off with a little <a href="http://www.google.co.uk/search?q=REST+urls&hl=en&hs=BW1&lr=&client=firefox-a&rls=org.mozilla:en-US:official&start=10&sa=N">google search</a> and had a read about <a href="http://www.xfront.com/REST-Web-Services.html">Building Web Services the REST Way</a> which made clear to me that you could POST something to its already given URL as a natural way of updating ressources.</p>
<p>A bit further down the list there was a very interesting article called <a href="http://naeblis.cx/rtomayko/2004/12/12/rest-to-my-wife">How I explained REST to my wife…</a> that initially came across a bit stupid, but then in the end flagged up some very true points about the benefits that HTTP and REST has, mentioning GET, POST and PUT … </p>
<p>Hang on, PUT? What the heck is that? Yet another google search and I knew a bit more:</p>
<blockquote cite="http://www.apacheweek.com/features/put"><p>The “PUT” method is similar to the POST method in that it can cause information to be updated on the server. The difference is that the POST method is normally handed a script which is explicitly named by the resource (that is, something that already exists), while a PUT request could be directed at a resource which does not (yet) exist. Another difference is that the POST method can be used in response to a form, while the PUT method can only contain a single data item. The PUT method is suited for publishing pages.</p></blockquote>
<p>Seems like I discovered a verb that was unknown to me up to this point. Same was DELETE as http method mentioned in this article.<br />
Time to look up the whole family I thought, and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">according to the w3c</a> you have even more methods.<br />
Also there is a <a href="http://perso.ec-lyon.fr/lyonel.vincent/apache/mod_put.html">mod_put</a> for apache utilizing the PUT and DELETE method.</p>
<p>Amazingly I never came accross them, and that is in 5 years+ of proramming web applications. Am I the only guy missing out on these basic HTTP definitions?</p>
UTF-8: Documents with a lot of character2006-06-21T05:21:40+01:00http://www.contentwithstyle.co.uk/content/utf-8-documents-with-a-lot-of-character/utf-8-documents-with-a-lot-of-character<h2>Sloppy?</h2>
<p>
Did you ever built a webpage in Homesite and then you didn't encode the html-entities? Then, probably when the client has a look on it, all the german Umlaut characters look awkward on a mac?
And did you figure out why? It's because of the charsets and the encoding of the characters in the saved file!
</p>
<h2>Charset?</h2>
<p>
Yeah, a charset! Actually a charset is the first bit you have to be aware of when you start using any kind of characters on a computer.
Most of the time we still use <a href="http://www.asciitable.com/">ASCII</a> with the encoding type “Latin-1”, where every character is encoded by using 8 bit, means 1 byte, means 256 charachters.
Obviously this doesn't cover all european languages, that's why, when using ASCII some characters have to be escaped as <a href="http://www.htmlhelp.com/reference/html40/entities/latin1.html">entities</a>.
Or you could use another language encoding than “Latin-1”, there are <a href="http://www.iana.org/assignments/character-sets">many of them</a> for languages like turkish or polish.
</p>
<h2>Wait a minute! Did you say european?</h2>
<p>
Yeah, and what about asian languages? Or russian? Or chinese? I heard they had <a href="http://www.logoi.com/notes/how_to_study_chinese_characters.html">more than 3000 signs</a>, how does that fit into those 256 possibilities?
<br />
Here we go, good question! An you're not the first one to ask it. That's why, from the information-technology point some people moved on and created the
<a href="http://www.unicode.org/">Unicode</a> character set. And again there is many types of Unicode, but there is two really relevant ones: UTF-16 and UTF-8.
Both offer 16bit encoded characters, means 65536 possible characters. Obviously this needs more memory to store it, because suddenly a page with the same amount of characters takes up twice the memory.
That's why <a href="http://www.unicode.org/versions/Unicode4.0.0/ch02.pdf#G11165">UTF-8 goes a more intelligent way</a> and saves ASCII as single bytes and everything beyond as double-byte. This saves memory.
</p>
<h2>Alright, got the concept, but how do I use it?</h2>
<p>
If you are coding plain HTML you can put in this bit of code in the top, to ensure your browser get's it right:
</p>
<pre>
<meta http-equiv="Content-type" value="text/html; charset=utf-8">
</pre>
<p>
Once you've done that you need to save the files you worked in as UTF-8, which means that you need a text-editor that can save UTF-8, obviously. And you won't believe how rare those are!
Anyway, BBedit or Homesite should do the job. And wordpad as well. Ohh, and for those of you that decided to stick to iso Latin-1, please put in that as charset then, rather than giving the browser the possibility to use the wrong encoding.
For those who stick to what I said: From now on there's no need to escape german Umlaut characters or chinese signs anymore.
<br />
And if you use PHP you can set the content-type with the header already:
</p>
<pre>
<?
header("Content-type: text/html; charset=utf-8");
?>
</pre>
<h2>BOM trouble</h2>
<p>
A big trouble could be the <a href="http://www.unicode.org/faq/utf_bom.html#22">BOM</a>, because some older browsers don't get what it means, so they put it in as characters, which has the quite painfull effect to show this on top of the pages: <code class="inline"></code>.
My workaround for that would be a page saved in UTF-8, set as UTF-8 in the meta-tag, but without BOM. I know it sounds odd, and you have to find a way to do that. But once you use templates and dynamic data it might be worth the effort.
</p>
<h2>Where does XSLT come in?</h2>
<p>
I think that UTF-8 starts really making sense when you start using XML and XSLT. Because suddenly, whithin XSL, you can define the output-encoding:
</p>
<pre>
<xsl:output method="html" indent="no" encoding="utf-8"/>
</pre>
<p>
The good thing is, that no matter which encoding the XML has, the output will be transformed into UTF-8.
Or, if you want to have a special language-encoding like iso-8551-9 for example, you can feed UTF-8 data via XML into the transformation process
and get the correct encoding out.
</p>
<h2>Server stuff</h2>
<p>
It starts getting really interesting when a database comes on top of that. It makes perfectly sense to have a utf-8 or utf-16 compliant database, so the stored content can contain any kind of language.
<a href="http://dev.mysql.com/tech-resources/articles/4.1/unicode.html">MySQL supports utf-8 since the version 4.1</a>, The SQL-Server from Microsoft has a built-in UTF-16 support (nvarchar, ntext).
<br />
The sad bit is that in PHP dealing with different encodings is not really big fun, but the <a href="http://uk2.php.net/iconv">iconv extension</a> makes it possible to convert encodings.
In ASP the support for codepages is much stronger integrated and it is just one line of code to set the <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iissdk/iis/ref_vbom_sesopcp.asp">codepage that ASP works with internally</a> or <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iissdk/iis/ref_vbom_resopch.asp">the charset it outputs the data with</a>.
</p>CSS is Worthless2006-06-06T08:22:51+01:00http://www.contentwithstyle.co.uk/content/css-is-worthless/css-is-worthless<p>CSS does not make a site accessible. CSS does not make your site rank higher in search engines.
Good CSS does not make you an elite web programmer. I don't code CSS. CSS is misunderstood.</p>
<h2>Misconceptions</h2>
<p>I was looking at the HTML source of a website the other day and something a creative director
once said to me popped into my head:</p>
<p><q>I really like this site. It's a really interesting design and it's done with CSS so
it's accessible.</q></p>
<p>Wrong wrong wrong. CSS does not an accessible site make.</p>
<h2>The forgotten basics of CSS</h2>
<p>Let me quickly recap what cascading style sheets actually do... They allow you to redefine
the presentation of any HTML tag. This means that there is no need to be bound by how a tag displays
by default because you can change it. We can now use whatever markup makes sense and then redefine
it to fit our design. That's the key benefit of CSS – it lets you use the right HTML.</p>
<h2>CSS is misunderstood</h2>
<p>This is the biggest misunderstanding that I come across when trying to teach someone CSS:
They've heard that CSS is <em>the thing</em>; it's the way web pages should be done these days. It
lets you do away with spacer gifs, achieve pixel perfect precision and carries accessibility and SEO
benefits to boot. They want to learn CSS. I have a really hard time hammering home that CSS is only
the icing and the real benefit comes from how they do the rest of their coding... And I suspect that
an awful lot of people who say they can 'do' CSS have the same problem. </p>
<p>While consulting at a company a couple of weeks ago I was chatting to one of their developers,
trying to gauge his level with web standards, and he told me that he could do CSS1 but not CSS2. What
does that mean? I also see 'CSS2' as a requirement in a lot of job ads these days, as though knowledge of
these extra properties somehow makes for a more accomplished web developer. I do not see any distinction
(other than the obvious historical one) between CSS1 and 2 - I code my HTML properly and then use whatever
rules come to hand the redefine the behaviour of those tags to fit into the required design. </p>
<p>A given site may not use a
single property from the <a href="http://www.w3.org/TR/REC-CSS2/">CSS2 spec</a> but if the HTML is good it will have
all the benefits people associate with CSS.</p>
<h2>HTML matters</h2>
<p>Now, at the beginning of this article I said that I don't code CSS. That's not entirely true but
let me finish that sentence. I don't code CSS, I code semantic HTML. I happen to use CSS to restyle that
HTML, to make it look the way I want, but that's an aside. <strong>I only use CSS because I use semantic HTML</strong>. I
use HTML tags for their <em>appropriateness for the information they contain</em> (instead of for how they look) so I have to
<em>redefine</em> how they behave.</p>
<p>Almost all of the benefits people normally attribute to CSS are actually down to the underlying (X)HTML.
The better your markup, the better sense machines will be able to make of it – that includes both screenreaders
and search engine spiders. </p>
<p>A bit of care and attention to what your page's markup actually means will go a long way. I'm not saying
that everyone should agonise over whether to use a definition list or an unordered list for navigation – pedantry aside, either
will do pretty well, but it pays to think about the content you are wrapping up. Do the headers go
sequentially from <code>h1</code> down? Are your lists in <code>ol</code>, <code>ul</code> and <code>dl</code>s? Are you using
<code>div</code> elements unnecessarily? Is there
something more meaningful you could use instead? I get the bulk of my HTML done before I touch the CSS – if it
makes sense <a href="http://www.dustindiaz.com/naked-day/">naked</a> then I'm on the right track.</p>
<h2>Turning it around</h2>
<p>HTML can make a site accessible. HTML can make your site rank higher in search engines. Good HTML can make
you an elite web programmer. HTML is misunderstood.</p>
<h2>Read on</h2>
<ul>
<li><a href="http://www.456bereastreet.com/archive/200605/writing_good_html_is_a_craft/">Writing good HTML is a craft</a></li>
<li><a href="http://www.clagnut.com/blog/228/">Mark-up tactics</a></li>
<li><a href="http://www.simplebits.com/bits/simplequiz/">SimpleQuiz</a></li>
<li><a href="http://bazzinet.info/Semantic">Semantic HTML (or: Search Engine Optimization (SEO) Made Easy)</a></li>
<li><a href="http://www.garrettdimon.com/archives/front-end-architecture-markup-is-the-technical-foundation">Front-End Architecture: Markup is the Technical Foundation</a></li>
<li><a href="http://diveintomark.org/archives/2002/12/29/million_dollar_markup">Million dollar markup</a></li>
</ul>Narrative JavaScript2006-06-05T03:56:47+01:00http://www.contentwithstyle.co.uk/content/narrative-javascript/narrative-javascript <p>This is an extension to javascript that will be compiled into javascript afterwards, and helps you to write readable code by introducing the “blocking” operator (notated as ’->’).</p>
<p>It turns out that the blocking operator gets translated into javascript that heavily uses so called <a href="http://en.wikipedia.org/wiki/Continuation_passing_style">continuation passing style</a>. This principle allows you to keep the context of callbacks by gathering the context data and writing the exectution into a callstack.</p>
<p>Quite an interesting concept that is and for sure the only compiled javascript aplication I ever heard of. Definitely will have a deeper look at this soon.</p>
IE7 and its problems2006-06-01T09:24:53+01:00http://www.contentwithstyle.co.uk/content/ie7-and-its-problems/ie7-and-its-problems <p>As Mike was busy doing this already on his blog <a href="http://www.donotremove.co.uk/weblog/fixing-donotremove-for-ie7/">donotremove.com</a> I quickly debugged our site for the public IE7 beta 2 that came out today.</p>
<p>Like Mike pointed out correctly, most problems are gone once you updated the <a href="http://positioniseverything.net/easyclearing.html">clearfix hack</a> for IE7.</p>
<p>This is done by creating a file that contains</p>
<pre>
.clearfix
{
zoom: 1;
}
</pre>
<p>and include it with <a href="http://msdn.microsoft.com/workshop/author/dhtml/overview/ccomment_ovw.asp">conditional comments for IE</a></p>
<pre>
<!--[if gte IE 6]>
<link rel="stylesheet" type="text/css" media="screen"
href="../css/ie_standard.css" />
<![endif]-->
</pre>
<p>I spent about 45 minutes to walk through a couple of old project websites and this pretty much always did the trick. The only exceptions I came accross were when the <a href="http://www.info.com.ph/~etan/w3pantheon/style/starhtmlbug.html">star html bug</a> was used to filter out IE, i.e. on this website the backgrouds for the rounded corners did not get loaded, and I had to add in a couple of lines to tackle this behaviour.</p>
<p>So for the future I would recommend to use conditional comments straight away where possible.</p>
Comments on Comments2006-05-30T03:59:18+01:00http://www.contentwithstyle.co.uk/content/comments-on-comments/comments-on-comments<h2>Introduction</h2>
<p>The right kind of comments to speed up the development process and enable a couple of interesting possibilities to generate documentations automatically. This article tries to reflect on the pros and cons of comments and to show some interesting possibilities for automatic comment parsing.</p>
<h2>Comment Basics</h2>
<p>Comments are basically regions of written characters that don't get interpreted by the PHP parser. Instead they get stripped out, and they solely are functioning as meta data. So just in case you don't know about how to embed comments into your scripts I'll just give you a quick example. In PHP there is two ways of using comments:
</p>
<pre>
// a single line comment is preceded by a double slash
/*
A multiline comment
is enclosed by slashes
and asterisks
*/
</pre>
<p>This style of comments is used by many other languages like Java, JavaScript or ActionScript. You will find the comments in any documentation as one of the first things mentioned, so whatever language you use, adding comments will be no problem for you.</p>
<h2>Reasons to comment your code</h2>
<p>When I wrote the first drafts of what would become this article I basically reflected the traditional approach on comments: Comments are a good thing, comment your code, it will be more legible, external programmers will find it more easy to get into your code, etc. Interestingly this kicked off a little discussion about coding practices and about what happens when comments are getting out of sync. Obviously wrong comments are a huge problem, and there are some people that actually consider comments as completely redundant, if not even harmful, because they will be hard to maintain once the code gets changed and therefore misleading other developers afterwards.</p>
<p>This is quite an interesting point of view, and makes sense when the code is following naming conventions that are self explanatory and obvious, a state of coding that every coder should aim for.</p>
<p>On the other hand many people still feel the need for comments. Many feel that after a certain amount of time they can find their own thoughts being reflected in the comments more than in the raw code, and it helps them to quickly amend parts of the code without having to read it all.</p>
<p>Not sure what this conflict of beliefs would do to my article I did a little google research to find out more. I turns out that this discussion is not only going on within the CwS team, but all along the developers community (<a href="http://groups.google.co.uk/group/comp.programming/browse_frm/thread/e9e433ce84a1f290/18e831b2491f8ad3?tvc=1&q=%22leave+out%22+code+comments&hl=en#18e831b2491f8ad3">example</a>), especially when it comes to extreme programming. </p>
<p>Pragmatic as I am I think I'll stick with what the <a href="http://www.dagbladet.no/development/phpcodingstandard/#comments">PHP coding standards</a> say:<br />
<em>“Comments should document decisions.”</em></p>
<p>This means that comments should be applied within the code once the code itself is not sufficient enough to explain <strong>why</strong> it does what it does. This also means comments within the code that are redundant should be avoided. As exceptions from the “avoid redundant comments” rule I would recommend anyone to use comments for Debugging, Gotchas and Automated Documentation, some applications that at the end of the discussion the members of CwS pretty much could agree on.</p>
<h2>Comments and debugging</h2>
<p>In the middle of development process commenting code to temporarily switching and and off whole blocks is extremely helpful. It avoids deleting parts of the scripts and then by accident (your application crashed, you forgot to save the file, etc) having to rewrite them because the undo function of your editor didn't get you anywhere.</p>
<p>It also is extremely helpful if you find little commented examples of the actual script usage within the script itself or if basic setting possibilities are just commented out so removing the comments enables the debugging mode, sends the emails to the test address or includes a different language library for example. People that ever edited the <a href="http://www.ccl.net/cca/software/UNIX/apache/httpd.conf.3.1b1.shtml">apache httpd.conf file</a> know what I am talking about here and I think this is one of the most basic coding techniques to use.</p>
<h2>Gotchas</h2>
<p>Gotchas are embedded keywords that are used to point out potential issues or existing problems. Since they are quite easy to use with for example search and replace it is easy to quickly jump to all parts of the code marked with :TODO: or :BUG:.</p>
<p>An interesting aspect to gotchas is the possibility to have a robot finding them automatically. Good IDE's for example would be able to find gotchas and flag up every todo that has been marked as such. Again see the <a href="http://www.dagbladet.no/development/phpcodingstandard/#mge">PHP coding standards</a> for more on this. </p>
<h2>Automated Documentation</h2>
<p>One of the biggest benefits of doing your comments in a more sophisticated way is that it will enable you to use automated documentation tools. The most common tool for PHP is PHPDocumentator or <a href="http://www.phpdoc.org/">PHPDoc</a> which I found one of the most helpful tools ever. The PHPDoc application derived from <a href="http://java.sun.com/j2se/javadoc/">JavaDoc</a> and took over the same comment standards to extract html documentations out of the comments embedded into the PHP code.</p>
<p>The same commenting standard is used by other documentation tools like <a href="http://ccdoc.sourceforge.net/">CcDoc</a> or <a href="http://phpxref.sourceforge.net/">PHPXref</a> and other applications like the <a href="http://www.zend.com/products/zend_studio/feature_list#Phpdoc">Zend Studio IDE</a>. </p>
<p>PHPDoc also can be installed as external tool in <a href="http://www.plog4u.org/index.php/Using_PHPEclipse_:_Installation_:_Installing_the_phpDocumentor">PHPEclipse</a> and there is a variety of little plugins for other editors like <a href="http://www.vim.org/scripts/script.php?script_id=520">VIM</a> that will help you creating the PHPdoc style comments.</p>
<h3>PHDoc in use</h3>
<p>PHPDoc comments are very easy to apply and contain meta information embedded in <a href="http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.pkg.html">PHPDoc tags</a>. Let's have a look at a comment for a class and see how PHPDoc parses comments:</p>
<pre>
/**
* short desc
*
* long desc
* @package test
* @author me
* @version 1.0
* @abstract
* @copyright never
*/
class parclass
{
}
</pre>
<p>As you can see the @ triggers the PHPDoc tags, and there are various tags that can apply depending on whether you document a function, a class or an include for example. At the end of the development process the comments and PHPDoc tags can be parsed into a complete HTML documentation that will give you a full overview of every class and function in your project. If you have done the comments right during the coding process your documentation will know all required parameters, all return types and the documentation will tell a story about how the classes and functions work.</p>
<p>To get an impression of how this looks like have a look on <a href="http://phpdoc.de/phpdoc/index2.html">PHPDoc documenting itself</a>.</p>
<h3>XML comments in C#</h3>
<p>Another common example of automatically generated documentation are the <a href="http://msdn.microsoft.com/msdnmag/issues/02/06/XMLC/">XML comments used in C#</a>. </p>
<pre>
/// <summary>
/// This is the summary
/// </summary>
/// <param name="myParameter">The value passed</param>
public void MyMethod(string myParameter)
{
}
</pre>
<p>As you can see in the example above this works pretty similar to the JavaDoc/PHPDoc standard and offers a lot of flexibility by applying XSL templates to generate an HTML documentation.</p>
<p>IDE's like <a href="http://www.sharpdevelop.com/OpenSource/SD/">SharpDevelop</a> or <a href="http://msdn.microsoft.com/vstudio/">Visual Studio</a> support this natively and provide <a href="http://www.sharpdevelop.com/OpenSource/SD/Tour/094/XmlDocumentation.aspx">code completion features</a> for adding the comments to Classes and Methods.</p>
<h2>Other uses for comments</h2>
<p>Apart from the ones mentioned above, there might be various other possible applications for comments. The <a href="http://en.wikipedia.org/wiki/Comments">Wikipedia article on comments</a> for example states that syntax highlighting could be one of them, and I can think of a coupe of <a href="http://centricle.com/ref/css/filters/">CSS Hacks</a> and various pieces of ASCII art.</p>
<h2>Bottom line</h2>
<p>While before writing this article I was clearly stating that comments are an essential addition to good coding style, the discussion about my first drafts made clear that I couldn't hold this up but needed to rethink my approach on how, when and why I was using comments. Quite an interesting lesson that was, and it showed once again that using your own head rather than a learned design/behavior/technique is actually a good thing. </p>
<p>That said it becomes obvious that it's necessary to find an approach that works for the individuals involved in the development process, and if you already have found your approach on commenting code and keeping it maintainable and up to date, then in my opinion there's no need for you to change it.</p>
<p>Personally I gained a lot since I started using generated documentations. Embedding the documentation into the source code in form of comments is probably the only way to make accurate documentations of big projects possible.</p>
<p>If I missed out any useful applications for comments feel free to post them in ... well ... the comments ;)</p>Disabling text selection2006-05-11T13:03:10+01:00http://www.contentwithstyle.co.uk/content/disabling-text-selection/disabling-text-selection <p>While trying to make a table behave in an Excel fashion I came across a little problem. I wanted the table to allow the user to select a range of rows with a Shift+Click… The problem is that, by default, the browser will select the page’s text content. This isn’t something you’ll want to do very often but it is possible to get around this:</p>
<p>Firefox can do this from your CSS:</p>
<pre><code>
table {
-moz-user-select: none;
}
</code></pre>
<p>On the table element IE needs:</p>
<pre><code>
the_table_node.onselectstart = function () {
return false;
};
</code></pre>
quick MySQL nice-to-know2006-05-11T07:41:03+01:00http://www.contentwithstyle.co.uk/content/quick-mysql-nice-to-know/quick-mysql-nice-to-know <p>While dealing with a project at work, I came across a couple of database issues that you all probably already know. A standard case of RTFM (by the way, nice <a href="http://www.gotapi.com/">reference lookup service here</a> ). Nevertheless, as I never seemed to bother using them, I’m sure there’s some of you out there that’ll find this helpful. And, er, others will probably see this as an embarrassment of mine.</p>
<p>First: It’s proven: <a href="http://experts.about.com/q/MySQL-3296/inner-join.htm">INNER JOINs are more effective than cartesian products</a> . I never liked this way of abbrevating INNER JOINs, so I got suspicious and looked for a good reason not to do it. Although the explanation for it is very straight forward, I’m surprised that MySQL doesn’t optimize or translate this internally.</p>
<p>Then: I have a scenario where I’m working with primary and secondary keys, and I will more often than not insert 2 rows with the same ID and different secondary key(same content, 2 languages). In order to avoid checking for an item, then updating it, I first thought of using mysql_affected_rows() on an UPDATE. But this will return 0 if no changes were made, as well as if there was no row matching the filter. <br />
Then I found <a href="http://dev.mysql.com/doc/refman/4.1/en/insert-on-duplicate.html">INSERT…ON DUPLICATE KEY UPDATE</a> but sadly I’m stuck with MySQL 4.0. The solution came around in the form of <a href="http://dev.mysql.com/doc/refman/4.1/en/replace.html">REPLACE</a> .</p>
<p>Oh so simple. It’s an INSERT, and if the row is already there, it’s gonna be UPDATEd. As I already have the ID from a referencing table, I can merge INSERT and UPDATE for follow up items in other languages than the default one into one REPLACE statement.</p>
<p>Also: Why did I always have an ID in my joint tables? stupid. In most cases it will be enough to have both foreign keys as a combined primary key.</p>
<p>Is everyone using this? Is it working as I expect it to work? Am I gonna run into trouble with this further down the line? Time (and maybe one or the other comment) will tell.</p>
CSS Framework Design Competition2006-05-02T08:29:07+01:00http://www.contentwithstyle.co.uk/content/css-framework-design-competition/css-framework-design-competition <p>I meant to post about this a couple of weeks ago but I completely forgot… I hope I’m not too late. There’s a <a href="http://raibledesigns.com/page/rd?entry=css_framework_design_contest_only">design competition</a> going on over at Raible Designs for AppFuse templates. Why do I care? They are using the CwS <a href="http://www.contentwithstyle.co.uk/Articles/17/a-css-framework/">CSS Framework</a> as the base. I’m chuffed that people are using it and excited to see what people can come up with!</p>
<p>There’re some nice prizes in the offing for the winners: an iPod (60GB), an iPod (30GB) and a 2GB Nano.</p>
Offtime, moving and some changes2006-04-25T11:41:37+01:00http://www.contentwithstyle.co.uk/content/offtime-moving-and-some-changes/offtime-moving-and-some-changes <p>Ok, some might have noticed that this site was offline for a couple of days. That is because our previous host, hostgator.com, didn’t like the site on a reseller hosting as it was causing a bit of load. So they decided to move it to a slow dedicated server and give us a very short period of time to change to another hosting plan or be switched off, and on top of that they managed to fuck up the site and all of our email during this stunt. </p>
<p>So I decided to get a cheap dedicated solution. After a quick google I wanted to go with server4you.de and get a virtual dedicated server. I submitted the order and then I didn’t hear from ‘em for a long long time. That long, in fact, that I cancelled the contract with them before it even started. </p>
<p>I used google again, this time I looked for service and good reviews.<br />
Now we’re with <a href="http://www.servint.net">ServInt</a>. They managed to get a server up in less than half a day, and I must say this rocks.</p>
<p>I used this occasion to change a couple of things on the site:</p>
<ul>
<li>The pretty meaningless categories got binned</li>
<li>A blog section has been opened</li>
<li>The homepage welcome will be a blog entry</li>
</ul>
<p>Let me know how U like these changes and expect us to be a bit more active in here, even if it might be no article but just a small blog entry.</p>
php: array_filter and callback function2006-04-20T09:48:47+01:00http://www.contentwithstyle.co.uk/content/php-arrayfilter-and-callback-function/php-arrayfilter-and-callback-function <p>Sometimes array_pop is just not enough. You might have to strip out something from an array that is 2 dimensional or do some more complicated adjustments to the array values. In this case you can use array_filter and a callback function.</p>
<p>The following example is class based:</p>
<pre><code>
<?
class test
{
function test()
{
$arr = array();
$arr[] = array('ID' => 1, 'name' => 'first');
$arr[] = array('ID' => 2, 'name' => 'second');
print_r($arr);
$arr = array_filter($arr, array(&$this, "filter"));
print_r($arr);
}
function filter($var)
{
return($var['name'] != 'first');
}
}
$test = new test();
?>
</code></pre>
AJAX Training from Clearleft2006-04-17T10:16:33+01:00http://www.contentwithstyle.co.uk/content/ajax-training-from-clearleft/ajax-training-from-clearleft <p>Jeremy Keith is giving another of his <a href="http://www.clearleft.com/services/training/ajax.php">Ajax training workshops</a>, this time up in Manchester. I was lucky enough to be in attendance for his London one and much fun was had by all. I really like Jeremy’s approach to the subject – it’s very much bottom-up, giving you a solid base from which to build in your own time. </p>
<p>Predictably, he links Ajax-style coding to DOM scripting, under the catchy name of <a href="http://domscripting.com/blog/display/41">Hijax</a>, which really appeals to my sense of style and best practice. My first shot at Ajax was my <a href="http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps/">Fixing the Back Button</a> article here on CwS and I used a similar approach. Now people are starting to ask about <a href="http://muffinresearch.co.uk/archives/2006/03/13/notes-web-21-making-web-20-accessible/">accessibility in Web 2.0 apps</a> I’m expecting more and more people to start thinking this way.</p>
XSL and DTD2006-04-12T10:05:43+01:00http://www.contentwithstyle.co.uk/content/xsl-and-dtd/xsl-and-dtd <p>A quick code snippet to include the right XHTML DTD in your XSL generated output. This took me a bit of reseach to find out:</p>
<pre>
<xsl:text disable-output-escaping="yes">
<![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">]]>
</xsl:text>
</pre>
<p>Include this into you template and switch the output method to “xsl” and you should be fine.</p>
Mono sounds Sharp2006-04-10T06:58:12+01:00http://www.contentwithstyle.co.uk/content/mono-sounds-sharp/mono-sounds-sharp <p>If platform specifications change sometimes one has to make the descision if the app needs to be ported or if there is another way. My research about porting .Net applications reveals a surprisingly great range of Open Source tools.</p>
<p>This starts with the IDE I am using for coding my C# application, the really well done <a href="http://www.sharpdevelop.com/OpenSource/SD/">SharpDevelop</a> that after a while I find very easy to use.</p>
<p>But for all the .Net newbies: Compiling a binary DLL in .Net doesn’t mean that it’s a windows dead end automatically. Especially for non-GUI applications (i.e. CLI or Web applications) it might work to just execute the DLL with <a href="http://www.mono-project.com/Main_Page">mono</a>, <a href="http://www.apacheworld.org/modmono/">mod_mono</a> or <a href="http://www.go-mono.com/archive/xsp-0.10.html">XSP</a>.</p>
<p>At least it won’t be a big effort to port applications for mono most of the times, and since mono is so close to .Net there obviously is a mono port of SharpDevelop: <a href="http://www.monodevelop.com/Main_Page">MonoDevelop</a>, which should run on most UNIX/Linux platforms (and MacOSX, even though I didn’t get my head around installing it properly), once all dependencies are installed.</p>
<p>This road is not a one way track, either:<br />
For all those people who want to improve the performance of PHP scripts and don’t worry about using .Net or mono, the <a href="http://www.php-compiler.net/">Phalanger Compiler</a> might be the way to go.</p>
Emergent design2006-04-07T06:32:25+01:00http://www.contentwithstyle.co.uk/content/emergent-design/emergent-design <p>Pascal mentioned it in his <a href="http://www.contentwithstyle.co.uk/Blog/89/php-unit-testing-tutorial/">Unit testing tutorial post</a> but emergent design has changed the way I see coding… I work with Marcus (<a href="http://www.lastcraft.com">lastcraft</a>) and he’s had his work cut out for him converting me, but I’m now a born again test driven designer. I’ve also started to apply the same philosophy to loads of other things. I’m slowly moving over the the <a href="http://www.43folders.com/2004/09/08/getting-started-with-getting-things-done/">Getting things Done</a> way of thinking and I let my designs evolve the same way. I’ve even started using a bulldog clip and index cards as a note pad…</p>
Relaunch: DoNotRemove2006-04-03T11:19:52+01:00http://www.contentwithstyle.co.uk/content/relaunch-donotremove/relaunch-donotremove <p>The homepage of Content with Style author Mike Stenhouse, to be found at <a href="http://www.donotremove.co.uk">www.donotremove.co.uk</a>, just got a huge makeover. We must say it looks stunning. Congratulations Mike!</p>
<p>Mike, by the way, is the quy who is responsible for the CwS design and CSS as well as for the biggest numbers of visitors in the articles section. <br />
Pay him a visit on his website to find out more.</p>
PHP Unit testing tutorial2006-04-03T05:45:48+01:00http://www.contentwithstyle.co.uk/content/php-unit-testing-tutorial/php-unit-testing-tutorial <p>During the research for my upcoming article about comments and code I came accross this <a href="http://www.lastcraft.com/first_test_tutorial.php">interesting tutorial on unit testing</a>.<br />
It gives you a quick insight into how test driven design works with <a href="http://simpletest.sourceforge.net/">SimpleTest</a> and PHP.</p>
<p><a href="http://en.wikipedia.org/wiki/Extreme_Programming">Extreme Programming</a> (XP), one of the latest and most radical approaches on software development, is gaining a lot of attention (and causes <a href="http://groups.google.co.uk/group/comp.programming/browse_frm/thread/e9e433ce84a1f290/18e831b2491f8ad3?tvc=1&q=%22leave+out%22+code+comments&hl=en#18e831b2491f8ad3">a couple of interesting discussions</a> as well) in the developers world right now and test driven design is one of the core techniques of XP. For XP newbies like me this might be a good starting point.</p>
Faster website moving with lftp2006-04-01T09:00:36+01:00http://www.contentwithstyle.co.uk/content/faster-website-moving-with-lftp/faster-website-moving-with-lftp <p>Consider <a href="http://lftp.yar.ru/get.html">lftp</a> to be the better alternative to ftp when it comes to moving your website. Unlike the standard ftp command line client it had the function “mirror”. <br />
And that is a dead handy one when you don’t want to ftp your whole website down to your machine and back to the new host …</p>
<p>After grabbing the source from the <a href="ftp://ftp.cs.tu-berlin.de/pub/net/ftp/lftp/">german mirror site</a> I compiled the program with the usual linux commands (./configure, make, make install).</p>
<p>Then move to the HTML folder on the new machine, type <code>lftp -u myuser myhost</code> to log onto the FTP server. There, move to the directory that you wanna grab and then type <code>mirror .</code> and that’s it.</p>
EXSLT2006-03-30T11:01:55+01:00http://www.contentwithstyle.co.uk/content/exslt/exslt <p>EXSLT is an extension kit for XSL. A collection of templates that can be used within an XSL transformation to perform mathematic operation, string manipulations or working with reg-exp.</p>
<p>Quite a powerful toolkit for any skilled XSL coder.<br />
The templates just get included into the XSL, their namespaces get declared and then the functions and templates are available for use like normal XSL functions.</p>
<p>EXSLT also contains a couple of functions that emulate XSL functions only availble for newer parsers, such as string-tokenize.</p>
<p>More information and package downloads available at <a href="http://www.exslt.org">exslt.org</a>.</p>
Clean URLs for a better search engine ranking2006-02-28T04:22:26+00:00http://www.contentwithstyle.co.uk/content/clean-urls-for-a-better-search-engine-ranking/clean-urls-for-a-better-search-engine-ranking<p>
Search engines are often key to the successful promotion and running of your website, with high traffic making or breaking your online
business. To maximise the visibility of your site in the organic listings of the biggest search engines it is important to strategically work
out how <a href="http://www.wordtracker.com/academy-articles-ultimate-primer.html">keywords</a> are used.
<br />
<br />
While link building (placing links to the site or from the site) and, most importantly, writing useful content form the foundation of search engine rankings,
some careful attention to how your site treats <a href="http://en.wikipedia.org/wiki/URL">URLs</a> will influence its ranking massively.
</p>
<h2>URLs</h2>
<h3>The messy ones</h3>
<p>
Most big websites are rendered out of a database and it is very rare to find systems generating the pages statically onto a webserver
to save processing power. Most small to mid-range CMS make use of on-the-fly rendering and the same applies to most of the tailor-made
dynamic sites I've seen so far.
<br />
The most common ways of passing information between these dynamic pages are:
</p>
<ul>
<li>In a cookie</li>
<li>In a session</li>
<li>In the host-header (POST)</li>
<li>In a the URL as a querystring (GET)</li>
</ul>
<p>
The last one mentioned is by far the most common.
It's also the only way that variables passed to an application can be bookmarked and sent by email to other people,
since cookies and sessions are bound to the specific computer and browser.
But let's have a look how a URL works:
</p>
<pre>
protocol://myserver/folder/file.ext?queryvariable=value#anchorname
</pre>
<p>
Historically, search engines were not able to spider links with querystring parameters because of page rendering speeds and so-called
spider traps. Today, most of the big search engine spiders will follow these untidy links, doing their best to strip out the portions that
can cause them trouble. Forcing them to do this, however, makes one of the most common and easy techniques, the GET method and the use
of the GET array in various scripting languages, the worst coding technique when it comes to search page rankings.
<br />
A URL like this is not ideal for most search engines:
</p>
<pre>
http://myserver/folder/file.php?pageid=230
</pre>
<h3>The clean ones</h3>
<p>
Therefore the first step to improve your URLs would be to move information needed to trigger the page rendering into another part of the URL...
Something similar to these:
</p>
<pre>
http://myserver/folder/230/file.php
http://myserver/folder/230.php
http://myserver/GUID_whatever_230.php
</pre>
<h3>The meaningful ones</h3>
<p>
But this still is not the ideal URL. Not for people who have to type it in, nor for search engine rankings, since it doesn't contain
any meaningful keywords. An ideal example would look more like this:
</p>
<pre>
http://myserver/this/url/is/stuffed/with/keywords/index.htm
</pre>
<p>
As you can see, this is more legible than any kind of cryptic ID. It is far more easy to remember for human visitors and
it is keyword rich for search engines as well.
Google pays especially close attention to the keywords within the URL, and they can, if they match what can be found in the content,
drastically improve the ranking. I suggest you try to think of a system that logically makes sense and that represents the path to your page,
similar to a <a href="http://www.webdesignpractices.com/navigation/breadcrumb.html">breacrumb navigation</a> maybe.
<br />
A nice article about dirty and clean URLs can be found on the website of <a href="http://www.port80software.com/support/articles/nextgenerationurls">Port80 Software</a>.
</p>
<h2>Technique</h2>
<h3>How to rewrite URLs</h3>
<p>
Now that we have worked out how a good URL should look we can actually rethink the way our web-application renders pages.
It's obvious that we need to point the URLs that contain the information to the same file that contained the script
dealing with the query string.
<br />
There are a couple of ways to do this: Apache's <a href="http://www.evolt.org/article/Making_clean_URLs_with_Apache_and_PHP/18/22880/">Force Type</a> for example,
with others for <a href="http://www.aspnetworld.com/articles/2004011901.aspx">ASP and .Net</a>, but with PHP and Apache the most comnmon technique to rewrite URLs is the
Apache module <a href="http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html">mod_rewrite</a>.
</p>
<h3>What is mod_rewrite?</h3>
<p>
Basically, mod_rewrite is a module for Apache that provides an engine that is able to rewrite URLs to other locations using regular expressions.
It is not activated in Apache by default though, and if you run your website on a shared hosting server you might have to ask your hosting
provider to get it up and running for you.
<br />
To get yourself right into the sytax for URL rewriting I recommend reading <a href="http://www.sitepoint.com/article/guide-url-rewriting">A Beginner's Guide to URL Rewriting</a>
on <a href="http://www.sitepoint.com">sitepoint.com</a> and the <a href="http://httpd.apache.org/docs/2.0/misc/rewriteguide.html">URL Rewriting Guide</a> written by <a href="http://engelschall.com/">Ralf S. Engelschall</a>,
the guy who wrote the module.
</p>
<h3>Rewrite rules and htaccess files</h3>
<p>
Usually you would define a rewrite rule in an htaccess file put into the roots folder of your site.
I'm just giving a little example here rather than going into too much detail.
Please check the comments to see what each line does.
</p>
<pre>
RewriteEngine On # activate mod_rewrite
RewriteCond %{REQUEST_URI} ^/admin.* [OR] # if in folder admin
RewriteCond %{REQUEST_FILENAME} -f [OR] # or if the request is a real file
RewriteCond %{REQUEST_FILENAME} -d # or if an existing directory
RewriteRule ^(.+) - [PT,L] # then leave the URL as it is
RewriteRule ^(.*) myscript.php # else rewrite is to myscript.php
</pre>
<p>
A more detailed introduction to Rewrite rules can be found on the
<a href="http://httpd.apache.org/docs/1.3/misc/rewriteguide.html">manual pages of mod_rewrite</a>.
Even a quick look will show you that mod_rewrite offers a sophisticated toolkit for rewriting URLs including
the search for files in multiple locations and even time-dependent rewriting. Clean URLs
are only one of many reasons to get amiliar with mod_rewrite.
</p>
<h2>Fancy an example now?</h2>
<p>
Enough of the theory. Now that we've found out how to rewrite URLs to a specific files I want to give a quick and very simple example
of how I tweaked old code quickly and efficiently using mod_rewrite and a bit of code. Afterwards my PHP application
was capable of handling clean URLs instead of GET parameters... and the whole thing took me just half an hour.
</p>
<h3>The old URL</h3>
<p>
In the existing application the rendering output got triggered by the GET parameter "page_id"
</p>
<pre>
http://server/index.php?page_id=100
</pre>
<h3>The new URL</h3>
<p>
The pattern for a quick tweak I worked out uses the set prefix "page", then the page_id (that before was found in the get parameter)
and finally a modified title slug to improve the indexing.
</p>
<pre>
http://server/page/100/here+are+my+keywords
</pre>
<h3>Three lines of code</h3>
<p>
All I needed to do was to read the page_id from the URL and assigning it to the GET variable.
In this case I used a simple regular expression but you could use explode or any other technique.
</p>
<pre>
<?php
preg_match("//page/(d+)/.*+/", $_SERVER['REQUEST_URI'], $match);
if($match[1])
$_GET['page_id'] = $match[1];
?>
</pre>
<h3>Security</h3>
<p>
Always bear in mind that you never should trust the URL.
As with all form inputs and GET parameters you need to escape variables taken out of the REQUEST_URI before you use them in your script,
otherwise you're inviting script kiddies to hack your application. This is particularly important for scripts that use eval() or write values into databases,
store files or do anything else that could cause crucial damage.
</p>
<h2>Conclusion</h2>
<p>
Using clean URLs improves your site and the search engine rankings.
It's more likely that people will be able to remember certain locations within the site.
Your page-rank in Google is likely to go up and you stand a better chance of turning up in search engines.
<br />
With mod_rewrite and a couple of small tweaks existing applications
can usually be coaxed into using clean URLs.
</p>Playing Nice with the Other CSS Kids2005-10-31T12:51:14+00:00http://www.contentwithstyle.co.uk/content/playing-nice-with-the-other-css-kids/playing-nice-with-the-other-css-kids<p><a href="http://simon.incutio.com/">Simon Willison</a> recently posted to <a href="http://css-discuss.incutio.com/?page=MaintainableCss">css-d</a> asking for peoples' thoughts on writing maintanable CSS and that got me thinking... Over the years I've had the privilege of working on some very large web standards projects in small teams of other CSS/XHTML developers, but I've also spent a lot of time building little sites on my own for smaller clients. Maintenance on a small project involves being able to understand your own code when you come back to it months later. On larger projects it means your team mates being able to understand and edit your code as quickly and efficiently as possible at any point in the future. It's a far more complicated objective.</p>
<p>My <a href="http://www.contentwithstyle.co.uk/Articles/17/a-css-framework">CSS Framework</a> and <a href="http://www.contentwithstyle.co.uk/Articles/12/modular-css">Modular CSS</a> articles touched on maintainability but here I am going to expand on those ideas, and attempt to suggest some guidelines to make team CSS coding and maintenance easier.</p>
<h2>Bite sized pieces</h2>
<p>If you have read my previous articles you will already know that I favour breaking my CSS down into <a href="http://www.contentwithstyle.co.uk/Articles/12/modular-css">manageable pieces</a>. I started doing this back in early 2003 when I coded the first few versions of <a href="http://business.gov.uk">business.gov.uk</a> with 3 other developers at <a href="http://www.nykris.com">Nykris (now defunct)</a>. In the first phase of the project I developed a base set of templates and extracted from them the core stylesheets and and an HTML framework. In subsequent phases, each member of the team then took these as their starting point, adding their own section- or page-specific CSS files. This isolated the individual pages and sections but ensured a consistent overall look. The complicated version merging that a single large file would have required was also avoided. The small section/page sheets had a defined purpose and could easily be digested by any other member of the team, requiring less orientation. </p>
<p>The major drawback to this approach comes when the client decides to make changes that effects the base files. These changes cascade through the entire site, which is great, but with so many small files following up on the new developments can get time consuming. The answer is obvious - get a detailed project specification finalised and signed off at the very beginning... But it's never that simple. Web projects are organic and like it or not the goalposts move. I don't really have an answer to this problem - I believe that there has to be some trade off between the team environment and flexibility. </p>
<h2>Self-documenting code</h2>
<p>The base HTML featured meaningful ids and classes, as good web standards practice suggests. This is a concept that bears a distinct similarity to ideas I've come across recently as part of the Extreme Programming methodology: the class and method names should make sense when read as a sentence, resulting in self-documenting code. The rationale is that programmers hate documentation. How many people ever read it? How many people know how to write it well? The number of times I've come to comments in code that bear no relevance to the code itself because a developer (often me!) has forgotten to update these embedded tips in his rush to get the job done... Using sensible, meaningful ids and classes is as close as we can get in HTML and CSS to this self-documentation nirvana.</p>
<p>Take this CSS snippet:</p>
<p><code>div#content div#main p.intro</code></p>
<p>Anyone can look at that, figure out what is going on and find the effected HTML in the source without any trouble. I am restyling the introductory paragraph in my main content div. Simple. In the source that is a <p class="intro"> inside <div id="main"> inside <div id="content">. </p>
<h2>Sandboxing</h2>
<p>The little snippet above also demonstrates something else... I tend to over-specify my rules. Classes and ids aren't orphaned (div#content instead of #content) and as much of the path is specified as possible (div#content div#main p.intro instead of just p.intro). Yes, this increases the size of the resulting stylesheet but when anyone else comes to look at it they will be able to see generate their own HTML from the CSS, if necessary. It makes for heavier code but far better maintainability... </p>
<p>Over-specifying also helps to avoid name clashes later on. <a href="http://css-discuss.incutio.com/?page=MaintainableCss">I've heard</a> this approach called 'sandboxing', which describes it very well. Sandboxing your CSS to apply to a very select set of elements and widening it only when required helps you keep a tight reign on your code as it expands. </p>
<p>If you take a look at my <a href="http://www.contentwithstyle.co.uk/Articles/17/a-css-framework">CSS Framework</a> HTML you will see that every page section is given an ID. Within those sections I tend to use classes (I'm not going to get into when to use classes vs ids here). This clearly separates the rules for each part of the page.</p>
<h2>Controlled use of <code>!important</code></h2>
<p>If you want sandboxing to work for you properly you should make sure that you keep away from <code>!important</code> as much as possible. I use it almost exclusively as a debugging tool - if something isn't behaving as I expect it to, I will add <code>!important</code> to the rules... If <code>!important</code> cures the problem then my rule is under specified, otherwise it's something more complicated. </p>
<p>Controlled use of <code>!important</code> is essential when your CSS is distributed across several files. I ran into this problem on the <a href="http://business.gov.uk">business.gov.uk</a> project while trying to set the width of an input button - for some reason it just wasn't working. After trawling my way up through the cascade I eventually found that someone had added <code>width: 30px !important</code> to the core forms.css file. Poor practice and wasted time.</p>
<h2>Redundant rules</h2>
<p>In addition to overspecifying I also tend to reuse sets of rules. I set margins and padding as a pair even if I don't strictly need them. My most common layout problems come from unintentional inheritance so I use this redundancy to attempt to minimise the chance of this happening.</p>
<h2>Consistent naming conventions</h2>
<p>Over the years I have come up with a consistent vocabular for ids and class names that I reuse across projects. Most of these ids are documented in my <a href="http://www.contentwithstyle.co.uk/Articles/17/a-css-framework">CSS Framework</a> article but there are a few classes that turn up again and again too - things like 'promo' for promotional spots and 'intro' for the unique styling often applied to introductory paragraphs. This is hardly rocket science but I find it helps when it comes to maintenance. Once a member of the team becomes familiar with the naming conventions they can edit the CSS without constantly having to refer to the HTML source code. </p>
<h2>Version control</h2>
<p>Back on the <a href="http://business.gov.uk">business.gov.uk</a> project we used the most lo-fi of all versioning systems: Comments at the top of the code, emailed files to a single developer in charge of the CSS source (that thankless task fell to me as lead developer) and shouts of "You got x open?". This didn't really work, creating no end of accidental overwrites, but the deadlines were so tight that we couldn't afford the time to stop and implement a proper versioning system. I would never work like that again. </p>
<p>Since then I have become a convert to CVS. CVS is a brilliant piece of technology allowing automated versioning, diffing and merging of text files, with a more basic set of tools for binaries. This means that several people can edit the same file all their changes can be merged together automatically.</p>
<p>CVS's successor, SVN, provides very similar functionality but with a few enhancements. On css-d Alan Stevens explained the importance of SVN to his team's workflow:</p>
<blockquote>
<p>We do lots and lots of projects for smaller customers, and it became impossible to try to remember the details of them all. Also, customers would ask for changes, and then after everything was live would ask for roll-backs of some of the changes ("we want this, no we dont, we want this, no we dont..."). We solved the problem with Subversion (<a href="http://subversion.tigris.org/">http://subversion.tigris.org/</a>), an open-source version control system. We now have a history of everything that was done for a client, and it's easy to roll things back. At first it seemed like overkill, but now I can't imagine working without it.</p>
</blockquote>
<p>I've been working with CVS on a large project for about 6 months now and I can't imagine getting by without it either.</p>
<h2>Intelligent commenting for easy scanning</h2>
<p>Last but not least is formatting the stylesheets for easy consumption later. A consistent format for your team's source code will allow anyone to make changes with the minimum of orientation. This is good old common sense and anyone who's ever worked on ANY programming project in a team environment will understand the benefits.</p>
<p>I surround all id blocks with: </p>
<pre><code>
/* THE ID IN CAPS */
....
/* END THE ID IN CAPS */
</code></pre>
<p>And all class blocks with: </p>
<pre><code>
/* the class in lowercase */
....
/* end the class in lowercase */
</code></pre>
<p>I also indent child blocks by one extra tabs each time to give me a visual representation of the cascade and specificity.</p>
<p>The resulting code looks like:</p>
<pre><code>
/* MAIN */
div#main {
}
/* intro */
div#main p.intro {
}
div#main p.intro strong {
}
/* end intro */
/* END MAIN */
</code></pre>
<p>Doug Bowman has suggested using what he calls <a href="http://www.stopdesign.com/log/2005/05/03/css-tip-flags.html">'flags' in your comments</a> to aid the finding of blocks of code in large stylesheets, which are also worth considering when you come to work on your next project.</p>
<h2>The road to maintainable team CSS</h2>
<p>This set of guidelines is by no means the last word in the art of writing maintainable CSS but I hope it can mark the start of a broader discussion. They have worked for me on both large and small projects but I would love to hear how other people tackle the same problems. </p>DOM scripting or how to keep the code clean2005-10-27T04:24:11+01:00http://www.contentwithstyle.co.uk/content/dom-scripting-or-how-to-keep-the-code-clean/dom-scripting-or-how-to-keep-the-code-clean<h2>Required knowledge</h2>
<p>
In this tutorial I want to show up the differences between DOM-Scripting and the “traditional” JavaScript
technique using event-handlers embedded into the HTML-code.
I'll show a way to have accessible popups, and by showing how to do those, I'll explain the proper use of DOM-scripting.
Obviously knowledge of JavaScript is required to get my explanations.
</p>
<h2>Pseudo-format javascript:</h2>
<p>
So, what was the old way? Well, let's start with the very old way:
</p>
<pre>
<a href="javascript:window.open('http://www.google.de',
'popup','width=200,height=400');">open google</a>
</pre>
<p>
This is really annoying! First of all it's not even a real link. The use of this pseudo-format takes away the possibility of having
an action and a location where the link points to. So this is the very old way. And, as you can see, it's code that is not very good
when it comes to maintaining it. So that's why for a long time we used the following stuff.
</p>
<h2>Event handlers as HTML attributes</h2>
<p>
Yeah, you know what I'm talking about! I mean “onclick”, “onmouseover” and so on, embedded directly into the
HTML-tag. So that now our tag would look like this:
</p>
<pre>
<a href="#" onclick="window.open('http://www.google.de',
'popup','width=200,height=400');">open google</a>
</pre>
<p>
And maybe, instead of having the whole thing in there, we'd have a function in javascript where we just pass the URL to?
Because we open popups in the same size throughout the whole site anyway?
</p>
<pre>
<script type="text/javascript">
function pop(url)
{
window.open(url,'popup','width=200,height=400');
}
</script>
<a href="#" onclick="pop('http://www.google.de');">open google</a>
</pre>
<p>
That would be better for maintanance, no doubt, but how about accessible? No, it's not!
No textbased browser or no system where javascript has been turned-off could open that link.
Now how am I going to make that accessible? Well, there's a way!
</p>
<h2>Accessible popups</h2>
<p>
The way to do accessible popups is quite an overhead:
</p>
<pre>
<script type="text/javascript">
function pop(url)
{
window.open(url,'popup','width=200,height=400');
}
</script>
<a href="http://www.google.de" target="_blank"
onclick="pop('http://www.google.de');return false;">open google</a>
</pre>
<p>
How does this work? Well, first of all it's a simple link with <code class="inline">target="_blank"</code>.
But on top of that we have an event handler with an action inside and a return statement, which quits the click-event.
By doing this the browser quits before going to the new location, which would be in another window.
But if javascript is off, none of this would happen and just the new location would be opened in a new window.
</p>
<h2>Shortcut</h2>
<p>
Still one annoying thing, though: It's to much code to write! So my first approach would be to take a shortcut:
</p>
<pre>
<script type="text/javascript">
function pop(url)
{
window.open(url,'popup','width=200,height=400');
return false;
}
</script>
<a href="http://www.google.de" target="_blank"
onclick="return pop('http://www.google.de');">open google</a>
</pre>
<p>
The saved amount of characters is not to big, but still, if the site is big and you can save typing “false” 300
times you'll be happy about it.
<br />
Next point is that I'm annoyed about the possibility to type “www.google.de” in one and a typo like
“www.gogole.de” in the other part of the link. Stuff like that ALWAYS happens, and it sucks so much!
But that's the first opportunity to use propper DOM-scripting!
</p>
<h2>“this” or the link as object</h2>
<p>
Instead of calling the function now with a string as parameter, we'll call it with an object reference:
</p>
<pre>
<script type="text/javascript">
function pop(a)
{
url = a.getAttribute("href");
window.open(url,'popup','width=200,height=400');
return false;
}
</script>
<a href="http://www.google.de" target="_blank"
onclick="return pop(this);">open google</a>
</pre>
<p>
Here it is, the link acts as object, it has a method that gives back an attribute. That is DOM (Document Object Model).
Well, that's fine for the beginning, but still there's two things that really I consider as below the optimum:
We have to writre the handler into each link. And we have changed the markup of the document in the HTML-area, probably many times,
depending on how many popup-links we have.
</p>
<h2>Some of the DOM-toolkit</h2>
<h3>Event-handler just in javascript</h3>
<p>
The way to apply event-handler in javascript looks like this:
</p>
<pre>
window.onload = function()
{
alert("window has been loaded now");
}
</pre>
<p>
It is possible to apply already existing functions as well:
</p>
<pre>
function alertLoaded()
{
alert("window has been loaded now");
}
window.onload = alertLoaded;
</pre>
<p>
The benefit obvoiusly is that we can define handler for HTML-objects without changing their markup. If we can find them, of course!
</p>
<h3>Navigating the DOM tree</h3>
<p>
There are two methods to find elements, <code class="inline">document.getElementById()</code> and <code class="inline">document.getElementsByTagName()</code>.
Once the object has been found we can run through the tree up and down by using the object <code class="inline">node.parentNode</code> and the array <code class="inline">node.childNodes</code>.
If you want to get deeper into DOM-scripting you might have a look at the <a href="http://www.mozilla.org/docs/dom/domref/dom_doc_ref.html">Gecko DOM interface</a>.
</p>
<h2>The tree-parsing solution</h2>
<p>
Let's get back back to our little example. We'll make use of <code class="inline">document.getElementsByTagName()</code> to change all links with <code class="inline">target="_blank"</code>
to popup-links without adding any markup to them.
</p>
<pre>
function applyPopups()
{
a = document.getElementsByTagName("a");
for(i=0; i<a.length; i++)
{
if(a[i].getAttribute("target") && a[i].getAttribute("target") == "_blank")
{
a[i].onclick = function()
{
url = this.getAttribute("href");
window.open(url,'popup','width=200,height=400');
return false;
}
}
}
}
window.onload = applyPopups;
</pre>
<p>
As you can see, you now don't even have the hassle of adding additional markup to your HTML-code, but can apply popup-behaviour by just importing
one js-file in the head of the document. Splendid!
</p>A to Z(ee) with P3P2005-09-22T07:08:50+01:00http://www.contentwithstyle.co.uk/content/a-to-zee-with-p3p/a-to-zee-with-p3p<h2>The Situation</h2>
<p>
The other day I came across a weird security problem at work: While a system to customize a site's appearance worked fine in Firefox, storing the state kept failing in IE. Now, I'm not a big fan of these browser discussions and don't mind using either, but that error made me curious. I quickly found out that it was down to the company's security standard, which defaulted <a href="http://msdn.microsoft.com/workshop/security/privacy/overview/privacyIE6.asp">IE's privacy level</a> to high-medium.
This level doesn't allow third-party cookies for permanent storage, and as the whole site was running in a Frameset for a couple of days (thank you, domain-hogging cheapie-host), the cookies used were rightly considered third party.
</p>
<p>
They are allowed to be stored under this setting, though, when they come with a P3P (Platform for Privacy Preferences) policy. Microsoft offers a <a href="http://msdn.microsoft.com/workshop/security/privacy/overview/createprivacypolicy.asp">nice overview</a> to what this is, but leaves you pretty much in the dark of how you actually get from A (Problem) to B (Solution). P3Ptoolbox.org, a website dedicated to P3P offers an <a href="http://www.p3ptoolbox.org/guide/section3.shtml">all-embracing guide</a>, but the sheer amount is more off-putting than encouraging.
</p>
<h2>What Is P3P?</h2>
<p>
P3P is a protocol designed to offer an "automated way for users to gain more control over the use of personal information on Web sites they visit", states the <a href="http://www.w3.org/P3P/">W3C</a>.
Policies are something that is used quite frequently, in the Flash and Java (J2EE) World, usually describing a set of permissions granted or revoked. P3P is an XML based policy that describes the scope or the way how the data in cookies is used. It consists of four logical parts:
</p>
<ul>
<li>a written, and as they like to state "human readable" policy, as html-file is recommended as the easiest starting point for it all. But, hooray, if you rather track down cookie operations in your code, the policy editor will generate one for you, which you can then amend to your needs.</li>
<li>The policy editor helps you create the full XML privacy policy</li>
<li>The Policy-Reference File, again as XML, which is stored in a "well-known location", links to the privacy policy.</li>
<li>The compact policy(a string of 3-letter-abbrevations), which is sent in the header of the page containing the cookie-related action.</li>
</ul>
<h2>How can I build it?</h2>
<p>
If you look into tools for generating P3P policies, you'll find yourself in a land of darkness and badly designed websites with dollar signs. But there's light at the end of the tunnel: The <a href="http://www.alphaworks.ibm.com/tech/p3peditor">IBM P3P Editor</a> is free, being an executable jar it's crossplatform, and if you know what you're doing, it's really easy. So what do you have to do?
</p>
<ol>
<li>Do your homework: the first step is, and the P3Ptools site isn't wrong with it, a thorough preparation. Does the company running the website already have a privacy policy? Does it cover all cookie-related actions as well? If not, "Where do you store and read out cookies?", and "What's stored in them?" are the first key questions to ask. Ideally you know it beforehand, if not, an extended search over the usual cookie-dealing functions should refresh your memory.</li>
<li>What to do with the data? Who will be dealing with it? Once you have figured out what it is you'll need to ask yourself or, even better, your client, if they are planning to use the requested data, e.g. for stats on returning users or to find out about preferred customizations and the like. This should be (again ideally) honest, or alternatively passed by a legal department that does the juggling of the words.</li>
<li>Fire it up. The IBM P3P policy editor luckily generates XML and P3P strings as well as a good skeleton for the written privacy policy. All you have to do is
<ul>
<li>To categorise your cookies into groups and usage.</li>
<li>Connect them to their purpose</li>
<li>Add information about dispute and contact possibilities</li>
<li>Generate policy</li>
<li>Deploy: A great word which took me a while to understand, back at university. In this case, all it means is to take the generated xml and html and drop it in a folder.</li>
</ul>
</li>
</ol>
<h2>An example</h2>
<p>
Let's say we set and read a cookie on the homepage that stores a unique ID for tracking reasons and retrieves preferences from the db on how the site is displayed (font size and regular/high-contrast layout, for example).
</p>
<p>
Open up the P3P editor, choose "create a blank policy", and you'll find a well categorized list of elements on the left, and an empty tree on the right.
</p>
<p>
<img src="/resources/old_images/17.gif" alt="" />
</p>
<p>
Locate and drag the data elements matching your purpose from the list into the "new group" on the right. In our case that's:</p>
<p>
Broad Categories --> unique identifier and<br />
Dynamic Data --> http cookie
</p>
<p>
You can edit the group name, and you'll probably want to do it in real life situations that are more complex - in order to find grouped entries easier later on.
</p>
<p>
When you do so, you are also asked to put down an explanation to why the data is requested and you have to decide if "there is no reasonable way to link this data to the visitor's real-world identity." (check the help button). As users don't register on my site, they will be unique by identifier, but I won't be able to find out who they really are. The display data is anonymous anyway, so, tick the box here as well. Describe the reasons then on to the next step.
</p>
<p>
Make your way through the tabs, I will check "Site customisation" and "Anonymous user tracking", I offer an opt-out possibility for the site customisation, and uncheck pseudonymous decision-making, as I’m not tracking that. Recipient is just me, and as Retention I choose indefinitely.
</p>
<p>
To the right of the tabs in the lower part of the window is a properties button, click that to add all necessary data for the website, starting with a full contact information of the person/organisation responsible for the site.
</p>
<p>
Click on the next tab to enter a generic name for the policy and add an opt-out url (essentially a page where you can choose to get all cookies from your site deleted).
</p>
<p>
Make sure your language selection is the right one and add the url for your privacy policy on your website.
</p>
<p>
The Access-tab wants you to indicate what personal data the user can view to doublecheck what's stored on your site. In my case: no identified data is collected.
</p>
<p>
Next, let's eliminate that dispute warning: go to "Assurances", and add a dispute.
I call it "all disputes" chose customer service as dispute remedy for "all disputes", with a link to contact-us, to reach the customer service.
</p>
<p>
Lastly, in the global properties, set the expiry date. I chose the same as the cookies lifetime, or up to a date when the cookie situation, and therefore the privacy policy, changes.
</p>
<p>
The easiest way to get rid of the must-haves is to eliminate one error after the next. Click the error tab to see them.
If you have any other errors, fix them accordingly... it's most probably in your data group, or it’s a conflict between your data elements, the group and global properties.
</p>
<p>
All done? I had to add a category to my HTTP cookie. Now it looks like this:
</p>
<p>
<img src="/resources/old_images/18.gif" alt="" />
</p>
<p>
Save it, and go to the final step: deploy your data. For that, you'll need to generate a reference file first. Here's one that fits my example - it directs to the P3P file in all cases (include *).
</p>
<pre>
<META xmlns="http://www.w3.org/2000/12/p3pv1">
<POLICY-REFERENCES>
<POLICY-REF about="testsite_com.p3p">
<INCLUDE>*</INCLUDE>
<COOKIE-INCLUDE name="*" value="*" domain="*" path="*"/>
</POLICY-REF>
</POLICY-REFERENCES>
</META>
</pre>
<p>
All you have to do now, is:
</p>
<ul>
<li>Save the P3P reference file in a folder called w3c in the webroot, as /w3c/p3p.xml. Feel free to <a href="http://www.contentwithstyle.co.uk/w3c/p3p.xml">look at my example reference</a>.</li>
<li>Save the P3P. You can see from how I set up the reference file that I chose to store it in the same folder, which is the easiest and most common thing. Here's my <a href="http://www.contentwithstyle.co.uk/w3c/testsite_com.p3p">example p3p policy for download</a></li>
<li>Every time you interact with the cookie, send the created header first. Below is an overview of how to do it for my example, taken directly from the <a href="http://www.privacycouncil.com/implementation.php">privacy council</a>.</li>
<li>Finally: <a href="http://www.contentwithstyle.co.uk/resources/p3p-example/">Take a look at it</a> (in IE: view-->privacy report).</li>
</ul>
<table cellpadding="5" cellspacing="0" border="0" summary="Implementations for P3P compact policy example">
<caption>P3P compact policy implementations</caption>
<tr>
<th>Method</th>
<th>Code</th>
</tr>
<tr>
<td>HTML</td>
<td><meta http-equiv="P3P" content="CP='NOI DSP NID TAIo PSAa OUR IND UNI OTC TST'"></td>
</tr>
<tr>
<td>PHP</td>
<td>Header("P3P: CP='NOI DSP NID TAIo PSAa OUR IND UNI OTC TST'")</td>
</tr>
<tr>
<td>ASP</td>
<td>Response.AddHeader "P3P","CP='NOI DSP NID TAIo PSAa OUR IND UNI OTC TST'"</td>
</tr>
<tr>
<td>JSP</td>
<td>Response.setHeader("P3P","CP='NOI DSP NID TAIo PSAa OUR IND UNI OTC TST'")</td>
</tr>
<tr>
<td>ColdFusion</td>
<td><cfheader name="P3P" value="CP='NOI DSP NID TAIo PSAa OUR IND UNI OTC TST'" /></td>
</tr>
</table>
<h2>Conclusion</h2>
<p>
Congratulations, you made through this dry and rocky path of an article, or alternatively you found the scrollbar overly useful. I've had my doubts while writing this, about how complex it should be, and decided to go for an overview with a simple A-Z example, as my key problem was piecing it together initially.
</p>
<p>
P3P doesn't end here though - there are many more possibilities, from setting up different policies for different parts of a website, to getting deeper into the personal data aspect (there's quite a difference in cookie handling with P3P when personal data is involved), to taking a closer look into the client side and the possibilities that P3P offers, which mostly aren't quite built into browsers yet.
</p>
<p>
The idea of P3P is a good one, but it's flawed, as it's so far based on honesty. If I used information differently than I stated in my P3P policy, how would the user ever know?
If developers use <a href="http://support.microsoft.com/default.aspx?scid=kb;en-us;323752">half-hearted workarounds</a> by simply adding a non-offensive P3P header, why would the user trust P3P? These are the first things that you'll ask yourself while spending time on this, but there's more elaborated critizism: the <a href="http://www.epic.org/reports/prettypoorprivacy.html">Electronic Privacy Information Center</a> has assessed P3P and is not happy at all with it. So why do it? Given you do use cookies, 3 reasons come to mind, and they seem oddly close to web standards (looking forward to the comments):
</p>
<ul>
<li><strong>Because you have to:</strong> I don't use cookies very much, maybe a couple of projects a year. But if I do and it doesn't work for a group of users or, even worse (*gasp*) the client, heck, I gotta make it work.</li>
<li><strong>Because you would rather solve problems than work around them:</strong> I never like grey areas in projects, identified ones or the ones that are brushed off. The fix-it-now-research-it-later approach usually ends in not researching it at all, and next time round you're none the smarter.</li>
<li><strong>Because it makes a better website:</strong> This is the moral high ground for today, but think about it: the web standards movement is something that had to happen sooner or later. Cookies aren't used as much as in the olden days, but as the basics are set and can be extended, any improvement in their handling should improve users trust in using personal data on the internet, with reasonable resonance in the future.</li>
</ul>Processing the output buffer with XSLT2005-07-24T18:07:12+01:00http://www.contentwithstyle.co.uk/content/processing-the-output-buffer-with-xslt/processing-the-output-buffer-with-xslt<h2>The output buffer</h2>
<p>
Most programmers dealing with PHP will have come across various PHP errors when trying to do a redirect after an echo
or something similar.
</p>
<p>
The error usually looks like this:
</p>
<pre>
Warning: Cannot add header information - headers already sent by
(output started at /directory/to/starting_file.php:XXX)
in /directory/to/calling_file.php on line XX
</pre>
<p>
And for many of you that will have been the only application for the function <a href="http://uk2.php.net/manual/de/function.ob-start.php">ob_start</a>
which immediately fixes exactly these errors.
But most ignore that <em>ob_start</em> is just one function of a whole toolkit of functions that are referred to as <a href="http://uk2.php.net/manual/en/ref.outcontrol.php">“Output Control Functions”</a>,
which provide a sophisticated toolkit for controlling and manipulating the output generated by PHP.
</p>
<h2>The callback function</h2>
<p>
The most powerful bit in this set of functions is definitely <em>ob_start</em> and it's optional parameter, the callback function.
This callback function will be called when the output is finally thrown. Using this it's easy to generate output and, for example, clean it afterwards with
HTML tidy, escape it, replace parts of it or replace all of it.
</p>
<p>
To show what I mean I'll provide a little class-based script as an example:
</p>
<pre>
<?
class examplePage
{
function examplePage()
{
ob_start(array($this,'parseOutput'));
echo $this->getExampleXML();
}
function parseOutput()
{
$str = "<pre>" . htmlentities(ob_get_contents()) .
"</pre> is the XML string we get from getExampleXML()";
return $str;
}
function getExampleXML()
{
$str = "<root><test>Teststring</test></root>";
return $str;
}
}
$example = new examplePage();
?>
</pre>
<p>
As you can see the content thrown by the echo is parsed afterwards by the <em>parseOutput</em> method, and stuff gets added and escaped in one go.
</p>
<h2>Layered applications</h2>
<p>
This alone is a very powerful tool that can be used in pretty much every application that generates output with PHP, but we can push it one step further.
</p>
<p>
We'll use <em>XML as an intermediate application layer</em>.
The callback function will then process the whole output and render it through an XSL transformation.
</p>
<pre>
<?
class examplePage
{
function examplePage()
{
ob_start(array($this,'parseOutput'));
echo $this->getExampleXML();
}
function parseOutput()
{
$this->xslt = xslt_create();
$this->arguments['/_xml'] = ob_get_contents();
$this->xmlDoc = 'arg:/_xml';
$this->arguments['/_xsl'] = $this->getExampleXSL();
$this->xslDoc = 'arg:/_xsl';
return xslt_process($this->xslt, $this->xmlDoc,
$this->xslDoc, NULL, $this->arguments);
}
function getExampleXML()
{
$str = "<root><test>Teststring</test></root>";
return $str;
}
function getExampleXSL()
{
$str = '<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
Test: <xsl:value-of select="//test" />
</xsl:template>
</xsl:stylesheet>
';
return $str;
}
}
$example = new examplePage();
?>
</pre>
<p>
And here we go - dynamic processing of the XML-based application output.
This is obviously a raw example, and it needs integration in whatever framework you use, but hopefully you can see the power and flexibility of this technique.
</p>
<h2>Outlook</h2>
<p>
So what could this be useful for?
</p>
<p>
In my opinion this could give some web applications a whole new twist. One possibility for the techniques described
would be to separate the presentation-related rendering process
into the step after the output. While your application is built to render XML and throw that into the PHP output,
a separate method, maybe even a separate class, could handle this output and transform it into the right format.
</p>
<p>
The advantages are immediately obvious. Output rendering would become a reusable module and without it the application would
still output W3C-compliant XML code (<em>if you did everything right, that is</em>).
</p>
<p>
And again, this is just one possibility to use the callback function. Together with regular expressions or applications like
<a href="http://tidy.sourceforge.net/">Tidy</a> you could ensure that the output of dynamic data is valid. This could be useful for all people
who use variables to pass html-content into XSL templates.
</p>Fixing the Back Button and Enabling Bookmarking for AJAX Apps2005-06-15T09:01:57+01:00http://www.contentwithstyle.co.uk/content/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps<h2>The problem</h2>
<p>Everyone's favourite AJAX technology app is Google Maps. Google have done a stunning job... But when I came to try to bookmark a page I had to hunt around for 'link to this page' over on the right hand side. Why have they broken such a basic function of the web? I use bookmarks A LOT and the extra effort bothered me. I got over it though, and life went on. </p>
<p>Then I came to flick through the drop down on Kottke.org (now removed) and I kept hitting the back button on my mouse by accident, taking me off the site. Really irritating. The most fundamental online behaviour - click then back, is broken.</p>
<p>I've not picked on these sites for any particular reason. They both happen to be great sites that I visit regularly enough to notice these flaws, which will be common to many AJAX-based applications.</p>
<p>After a chat with <a href="http://www.adactio.com">Jeremy Keith</a>, <a href="http://www.clagnut.com">Rich Rutter</a> and <a href="http://www.andybudd.com">Andy Budd</a> about exactly this problem I decided to take a shot at fixing it.</p>
<p>Read on for the explanation or <a href="http://donotremove.co.uk/extra/ajax-nav/index.html">go straight to the demo</a> to see it in action.</p>
<h2>Standing on the shoulders of giants</h2>
<p>I'm not the first person to tackle this type of problem. I've drawn inspiration and know-how from several places to get this up and running:</p>
<dl>
<dt>The original bookmark/back button fix, as used by Flash developers for a little while now:</dt>
<dd><a href="http://www.robertpenner.com/experiments/backbutton/backbutton.html">www.robertpenner.com/experiments/backbutton/flashpage.html</a></dd>
<dt>I've not actually looked at how they implemented their solution but this is where I got the idea for replacing Robert Penner's frames with iframes:</dt>
<dd><a href="http://dojotoolkit.org/intro_to_dojo_io.html#so-about-that-thorny-back-button">dojotoolkit.org/intro_to_dojo_io.html#so-about-that-thorny-back-button</a></dd>
<dt>Rich Rutter's use of the hash for bookmarking:</dt>
<dd><a href="http://www.clagnut.com/sandbox/slideshow.html#5">www.clagnut.com/sandbox/slideshow.html#5</a></dd>
<dt>For this little experiment I've used Harry Fuecks' <a href="http://jpspan.sourceforge.net/wiki/doku.php">JPSpan</a> </dt>
<dd>It's a fantastic framework that makes the methods you define in your server-side PHP classes available to your Javascript via XmlHttpRequest. It's the simplest way I've come across to get started with AJAX. I had the guts of my demo up and running in about 10 minutes!</dd>
<dt>I'm using Algorithm's Timer object:</dt>
<dd><a href="http://www.codingforums.com/archive/index.php/t-10531.html">www.codingforums.com/archive/index.php/t-10531.html</a></dd>
<dt>And Scott Andrew's cross-browser event handler:</dt>
<dd><a href="http://www.scottandrew.com/weblog/articles/cbs-events">www.scottandrew.com/weblog/articles/cbs-events</a></dd>
</dl>
<h2>Setting things up</h2>
<p>I created a PageLocator object to act as an interface to both real querystrings and my hash pseudo-querystrings. It's nothing complicated but it allows me to access both using the same methods...</p>
<pre>
function PageLocator(propertyToUse, dividingCharacter) {
this.propertyToUse = propertyToUse;
this.defaultQS = 1;
this.dividingCharacter = dividingCharacter;
}
PageLocator.prototype.getLocation = function() {
return eval(this.propertyToUse);
}
PageLocator.prototype.getHash = function() {
var url = this.getLocation();
if(url.indexOf(this.dividingCharacter) > -1) {
var url_elements = url.split(this.dividingCharacter);
return url_elements[url_elements.length-1];
} else {
return this.defaultQS;
}
}
PageLocator.prototype.getHref = function() {
var url = this.getLocation();
var url_elements = url.split(this.dividingCharacter);
return url_elements[0];
}
PageLocator.prototype.makeNewLocation = function(new_qs) {
return this.getHref() + this.dividingCharacter + new_qs;
}
</pre>
<p>I also have a setContent function that simply takes whatever you pass it and inserts it into the content container div on the page. I'm using the innerHtml property because it's really not the point of this demo. If I was doing it properly I'd probably go for something a little cleverer.</p>
<pre>
function setContent(new_content) {
if(!document.getElementById || !document.getElementsByTagName) return;
var container = document.getElementById("content");
container.innerHTML = new_content;
}
</pre>
<p>In the spirit of 'proper' scripting my demo will work with javascript turned off. The links on the page point to content.php, which loads the same content as the AJAX app, but server side.</p>
<h2>It's all too easy</h2>
<p>So, I'm trying to store the session state in the address bar to allow bookmarking. What can be changed in the URL that won't trigger a page reload? The hash portion. So what I need to do is add my AJAX application's parameters after a #. </p>
<p>There is an additional benefit to this approach: When you click on page anchors, these points are added to the browser's history object so that when you press the back button you're taken back to these points within the same page. That's important because items are being added to the history without leaving the current page. </p>
<p>To make use of this behaviour I wrote written a simple DOM script to change the links on my page into # anchors, with the # portion containing the argument that would have been passed to content.php (the server-side equivalent of this app). This effectively maps the real querystring to a hash pseudo querystring.</p>
<p>Now when the links are clicked, the address bar is changed but the page itself doesn't change. To sync the page content with the URL I've set a Javascript timer to poll the window.location.href property on a regular basis and use any changes to trigger an AJAX content-load action. This effectively de-couples the default browser functionality, so instead of changing the page when a link is pressed, this now happens whenever something in the address bar changes. This means that if we change the URL manually or, importantly, with a bookmark, the page's content is automatically changed to reflect the new url.</p>
<p>To my surprise, it was very simple to get this all set up and working in Firefox. I was even more surprised to see that it worked reasonably well in IE6 as well. Reasonably, but not completely. For some reason, IE wasn't adding my anchors to the history so when I hit back, I was taken off my page and the AJAX app reset its state. </p>
<pre>
function AjaxUrlFixer() {
this.fixLinks();
this.locator = new PageLocator("window.location.href", "#");
this.timer = new Timer(this);
this.checkWhetherChanged(0);
}
AjaxUrlFixer.prototype.fixLinks = function () {
var links = document.getElementsByTagName("A");
for(var i=0; i<links.length; i++) {
var href = links[i].getAttribute("href");
var hash = href.substr(href.indexOf("hash=")+5);
links[i].setAttribute("href","#"+hash);
}
}
AjaxUrlFixer.prototype.checkWhetherChanged = function(location){
if(this.locator.getHash() != location) {
doGetPage(this.locator.getHash());
}
this.timer.setTimeout("checkWhetherChanged", 200, this.locator.getHash());
}
</pre>
<h2>Now you're just being difficult</h2>
<p>So, IE won't add my modified anchors to the history object. There is a fix that's been in use by Flash developers for a little while that uses frames to trick the browser into thinking that it's loading new pages, and use that to trap and mimic the back button action within the Flash apps. See <a href="http://www.holler.co.uk">www.holler.co.uk</a> for an example of this in action.</p>
<p>After reading through the Flash back button fix and remembering Dojo Toolkit's reference to their own back button fix I decided to give iframes a shot. The catch is that the iframe has to be present on the page before the DOM tree is complete so it has to be document.written out inline. It's not the cleanest solution because it means that I need to have a script block in the BODY, which I like to avoid, but as far as I know there's no way around that.</p>
<p>With the iframe in, the mechanism is roughly the same... </p>
<p>Instead of changing the links on the page to # anchors, they are modified to change the src attribute of the iframe, loading a page called mock-page.php with a querystring that contains the same argument as the hash did before. A timer polls the iframe for it's location and if it detects a change then a content load is triggered in the AJAX app, same as before. </p>
<p>This is complicated by the fact that the iframe's src property doesn't change when the back button is pressed. To get around this I've written a little function to sit within mock-page.php and report it's location when asked.</p>
<pre>
function getLocation() {
return '<?php print "http://" . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . "?" . $_SERVER['QUERY_STRING'] ?>';
}
</pre>
<p>When a change in mock-page.php's url is detected an AJAX content load is triggered and the parameters from it's querystring are duplicated into the url, to make sure that bookmarking will still work. It's sounds a bit convoluted but the code is quite straight forward. Except for one thing... IE wouldn't let me access the window.location immediately. I have no idea why... As a simple workaround, I've added a tiny 100ms delay to the firing of the script, which seems to sort it out.</p>
<pre>
function AjaxIframesFixer(iframeid) {
this.iframeid = iframeid;
if (document.getElementById('ajaxnav')) {
this.fixLinks();
this.locator = new PageLocator("document.frames['"+this.iframeid+"'].getLocation()", "?hash=");
this.windowlocator = new PageLocator("window.location.href", "#");
this.timer = new Timer(this);
this.delayInit(); // required or IE doesn't fire
}
}
AjaxIframesFixer.prototype.fixLinks = function (iframeid) {
var links = document.getElementsByTagName("A");
for(var i=0; i<links.length; i++) {
var href = links[i].getAttribute("href");
var hash = href.substr(href.indexOf("hash=")+5);
links[i].setAttribute("href", "Javascript:document.getElementById('"+this.iframeid+"').setAttribute('src', 'mock-page.php?hash="+hash+"');");
}
}
AjaxIframesFixer.prototype.delayInit = function(){
this.timer.setTimeout("checkBookmark", 100, "");
}
AjaxIframesFixer.prototype.checkBookmark = function(){
window.location = this.windowlocator.makeNewLocation(this.locator.getHash());
this.checkWhetherChanged(0);
}
AjaxIframesFixer.prototype.checkWhetherChanged = function(location){
if(this.locator.getHash() != location) {
doGetPage(this.locator.getHash());
window.location = this.windowlocator.makeNewLocation(this.locator.getHash());
}
this.timer.setTimeout("checkWhetherChanged", 200, this.locator.getHash());
}
</pre>
<h2>It's not what you said it's how you said it</h2>
<p>I hate branching scripts but I couldn't find a method that would work across all browsers. To make things as easy as possible I've made the fixes objects so they can be plugged or removed as easily as possible.</p>
<pre>
function FixBackAndBookmarking() {
if(!document.getElementById || !document.getElementsByTagName) return;
if(document.iframesfix) {
fix = new AjaxIframesFixer('ajaxnav');
} else {
fix = new AjaxUrlFixer();
}
}
</pre>
<h2>Moving on</h2>
<p>What I've produced here is a simple illustration of a method for storing the browser state in the URL bar, to allow bookmarking, replaced the default browser linking mechanism to use that url-stored state and mimic traditional web behaviour. For a real world application the querystring used is likely to be far more complicated... I've separated out my code into distinct objects to try and make customisation easier.</p>
<p>To see it in action, I have put up <a href="http://donotremove.co.uk/extra/ajax-nav/index.html">a demo page with everything hooked together</a>.</p>
<p>This method could be applied to Flash as well. As far as I know most people are still using the full frames method documented by Robert Penner. Using some Javascript we can do away with that extra complexity to make going back and bookmarking a plug-and-play addition.</p>
<h2>Reservations</h2>
<p>As I was writing this it occurred to me that what I've been doing here is remarkably like the old Frames hacks from back in the day. Should we be trying so hard to duplicate traditional browser behaviour? If it's important enough to put in the effort to duplicate it then should we really be breaking it in the first place? It's a question that can only be answered on a project-by-project basis but it seems important enough to be worth asking...</p>
<h2>Components</h2>
<dl>
<dt>index.html</dt>
<dd>The important part, from the point of view of this article. Contains:
<ul>
<li>Timer object</li>
<li>addEvent fix</li>
<li>JPSpan AJAX functions</li>
<li>PageLocator object</li>
<li>AjaxIframesFixer object for IE</li>
<li>AjaxUrlFixer object for Firefox and others</li>
</ul>
</dd>
<dt>test.php</dt>
<dd>Sets up JPSpan server side.</dd>
<dt>mock-page.php</dt>
<dd>Loaded into iframe. Has getLocation function that reports it's current URL.</dd>
<dt>pageholder.class.php</dt>
<dd>Simple class used to serve content. </dd>
</dl>
<table cellpadding="5" cellspacing="0" border="0" summary="Browser support for the back button and bookmarking fix">
<caption>Browser support</caption>
<tr>
<th>Browser</th>
<th>Bookmarking</th>
<th>Back button</th>
</tr>
<tr>
<td>IE6/PC</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>IE5.5/PC</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>IE5/PC</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>IE5/Mac</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Firefox/PC</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Firefox/Mac</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Safari1.2/Mac</td>
<td>Yes</td>
<td>No</td>
</tr>
</table>
<p>If you want to have a play with this yourself, I've zipped up <a href="http://www.contentwithstyle.co.uk/resources/ajax-nav/ajax-nav.zip">the whole lot ready for download</a>.</p>Database-driven tree structures with XML and XSLT2005-06-13T20:56:43+01:00http://www.contentwithstyle.co.uk/content/database-driven-tree-structures-with-xml-and-xslt/database-driven-tree-structures-with-xml-and-xslt<p>This article deals with the display of tree-structures that are driven by a database.
There are actually a few approaches to transform a 2-dimensional structure into
a tree, and it seems odd that most are unknown to many developers.</p>
<p>The most obvious approach is using the parent-ID as a back-reference for recursion <a href="http://www.devx.com/tips/Tip/22127">like in this example</a>. But then what happens if the tree-structure
gets a bit bigger? How about 5 childnodes and a depth of 5 levels? Well, suddenly you end up with 5<sup>2</sup> database requests and your application becomes incredibly slow... That's why we're just about to bin the idea of using the parent-ID!</p>
<h2>Database structure</h2>
<p>To do this we have to get familiar with a thing called
<strong>preordered tree traversal</strong>. If you've not come across it before, take a moment to read <a href="http://www.sitepoint.com/article/hierarchical-data-database/2">Storing Hierarchical Data in a Database</a>.
Essentially, the idea is to store left and right values for each node and then to figure out whether there is an increase or decrease in the depth of the tree from the difference in the left value between consecutive elements.
</p>
<pre>
+--------------------+-----+-----+
| name | lft | rgt |
+--------------------+-----+-----+
| page 1 | 1 | 10 |
| page 1.1 | 2 | 3 |
| page 1.2 | 4 | 9 |
| page 1.2.1 | 5 | 6 |
| page 1.2.2 | 7 | 8 |
+--------------------+-----+-----+
</pre>
<p>This is far more efficient than recursively requesting on the ID of the parent element because now we just need two SELECTS: </p>
<ul>
<li>The first to get the left and right values of the node that is the root node of the tree to describe.</li>
<li>The second one to get all dependencies.</li>
</ul>
<p>Keep in mind that LEFT and RIGHT are SQL keywords, so label your database fields accordingly. I use 'lft' and 'rgt'.</p>
<h2>XML tree structure</h2>
<p>Now we are about to transform the 2-dimensional structured data in the database into an XML format.
The beauty of XML for the purpose of displaying trees is the ability to have nested structures.
Also read my previous article about <a href="http://www.contentwithstyle.co.uk/Articles/29/xml-as-intermediate-application-layer">XML as intermediate application layer</a> to find out what else XML has to offer.</p>
<p>Our structure is going to look something like this:</p>
<pre>
<page id="1" depth="1">
<name><![CDATA[page 1]]></name>
<page id="2" depth="2">
<name><![CDATA[page 1.1]]></name>
</page>
<page id="3" depth="2">
<name><![CDATA[page 1.2]]></name>
<page id="4" depth="3">
<name><![CDATA[page 1.2.1]]></name>
</page>
<page id="5" depth="3">
<name><![CDATA[page 1.2.2]]></name>
</page>
</page>
<page id="6" depth="2">
<name><![CDATA[page 1.3]]></name>
</page>
</page>
</pre>
<p>In case you are wondering why I am not writing the page name into an attribute: An attribute cannot contain
CDATA or entities. Since I want to keep the structure as flexible as possible I keep the name in
a CDATA field that lets us store unencoded markup.
</p>
<h2>Creating the XML with PHP</h2>
<p>In order to create the XML structure we will walk through the result set and trigger the opening and closing tags by comparing the left and right values.</p>
<p>Bear in mind that this function is supposed to be a method of a class that already has the basic toolkits for
database handling available. Again, read <a href="http://www.contentwithstyle.co.uk/Articles/29/xml-as-intermediate-application-layer">my previous article</a> to find out more.</p>
<pre>
function getPageTreeXML($ID = false)
{
// get left and right values of root-node
$q = ($ID) ? 'SELECT lft, rgt FROM pages WHERE ID = '
. $ID : 'SELECT * FROM pages ORDER BY lft ASC LIMIT 1';
$res = $this->DB->query($q);
$depth = 0;
$ol = mysql_result($res, 0, 'lft');
$or = mysql_result($res, 0, 'rgt');
// get tree branch
$q = '
SELECT * FROM pages WHERE
lft >= ' . $ol . '
AND
rgt <= ' . $or . '
ORDER BY lft ASC';
$res = $this->DB->query($q);
// open the pagetree tag
$xmlStr = '';
$xmlStr .= '<pagetree>';
while($arr = mysql_fetch_array($res))
{
// store old depth
$old_depth = $depth;
// trigger new depth values
if($ol == ($arr['lft'] - 1))
$depth++;
if($or < ($arr['rgt'] - 2))
$depth -= (($arr['lft'] - $or) - 1);
// if depth doesn't increase close page tag
if($old_depth != 0 && $old_depth >= $depth)
$xmlStr .= str_repeat('</page>', ($old_depth - $depth) + 1);
// write xml data
$xmlStr .= '<page id="' . $arr['ID'] . '" depth="' .
$depth . '">' . '<name><![CDATA[' .
$this->mysqlDecodeText($arr['name']) . ']]></name>';
// store the left and right values for next iteration
$ol = $arr["lft"];
$or = $arr["rgt"];
}
// close all open tags
if($depth > 0)
$xmlStr .= str_repeat('</page>', $depth + 1);
// close pagetree tag
$xmlStr .= '</pagetree>';
return $xmlStr;
}
</pre>
<h2>Transforming the XML with XSLT</h2>
<p>Now that the XML is created we can easily create any kind of HTML
out of it by using XSLT. The following example is a quickly mocked up
nested structure to create an indented display of the page-names.</p>
<p>You can create nested XHTML elements easily by using a recursive structure in XSLT.
In my example, you can see that the “page” template is calling itself in the for-each loop.</p>
<pre>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/pagetree">
<html>
<head>
<title>pagetree</title>
<style type="text/css">
.tree div { padding: 10px 0 10px 10px;}
</style>
</head>
<body>
<div class="tree">
<xsl:apply-templates />
</div>
</body>
</html>
</xsl:template>
<xsl:template match="page" name="page">
<div>
<xsl:value-of select="name" />
<xsl:for-each select="page">
<xsl:call-template name="page" />
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
</pre>
<h2>Conclusion</h2>
<p>This is a poster child application for seperating your functionality into an XML-rendering layer.
I find this method a handy way to generate menus for the front-end and the back-end of an application using the same XML-rendering
for both sides. Also, by using XSLT, we have plenty of styling possibilities.</p>
<p>But the best advantage for me in this 3-step process is the first one - the pre-ordered tree traversal.
It speeds up your application dramatically and makes it possible to render even large trees with maximum pace.</p>MVC in smaller web applications2005-06-07T15:57:04+01:00http://www.contentwithstyle.co.uk/content/mvc-in-smaller-web-applications/mvc-in-smaller-web-applications<h2>The real world</h2>
<p>Web development is, in many cases, a process where time is a crucial factor. As coding is usually the last step in the process, all things come together and prior mistakes are revealed.
<br /><br />
"Build this so it looks the same in all browsers, works better than the IA was ever planned and can fly." Or so we hear.
<br /><br />
With a limited amount of time, developers are often tempted to fall for quick fixes and, not only due to last-minute changes, find themselves in a huge ball of code at the end of the project.
<br /><br />
"What does that matter, if the site is working nicely, nobody complains and it's nodded off by the client?" I hear you ask.
<br /><br />
This attitude is something I've encountered many times, and it usually results in a second-phase disaster, be it an extension of the site, a new face or a server change.
</p>
<h2>Theory</h2>
<p>This is where the MVC comes to the rescue. It's basically an attempt to structure a web application into three components:</p>
<ul>
<li>Model is generally understood as the data-administration component. In the majority of website projects, this is the data retention in a relational database system, but it could also include other persistent business objects, Enterprise Java Beans for example. Model is passive and does not trigger any actions. Data is requested independently of their representation (view); Model does not know anything about the data presentation. Model can work with one (1:1-relation) or several Views and Controllers (1:n-relation).</li>
<li>View describes the visual representation of the Model. In the case of many dynamic websites, you would imagine one or two Views on the same Model: in the case of this article, one would be the public View - the way you see this article now, and another one would be where I create and edit. Of course, there are many more possibilities: different user levels, each with a distinctive set of permissions; different representations of the same website for regular users; high contrast for easier reading; a View without head and navigation for printing, and so on.</li>
<li>The Controller has the entire application logic implemented. It is an active component. The application receives inquiries, passes it on to the responsible subcomponents in the system and possibly sends answers back to the user. The Controller carries out various manipulations of the Model for the execution of user actions.</li>
</ul>
<p>So far, so good. Now that the theory is clear, we can look at how it's used in the web development process. Do I actually need it? This seems to be a very sensitive subject. While researching, I've found several approaches, most are somewhere in the triangle of determined, angry and ignorant. I'd like to point out the most obvious reasons to use it, hopefully not stepping on any toes:</p>
<ul>
<li>Match a thought pattern: Putting your code into a programming pattern is difficult when you try doing it while coding, but the idea is to structure the code first, in your head, on a piece of paper or, for large scale projects, with CASE-tools. With the structure documented, your code becomes more manageable, and the number of people that want to see you roast in hell (possibly you yourself in 1 or 2 years time) is decreasing. Every developer that understands MVC will find their way through your stuff quickly, you will both work on the same wavelength, because you both follow the same pattern, in code and thought.</li>
<li>Reusability. This goes for any kind of modularisation, but it's an argument not to ignore: Once you have thought through and set up your model, you can reuse it many times with no or only minimal changes.</li>
<li>Extendibility: With a strict modularisation, you have a defined field of work when it comes to changes of face, functionality or data storage. Depending on your web applications needs and real-world situation, you can decide which parts need upgrading.</li>
</ul>
<h2>Practice</h2>
<p>I <a href="http://www.sitepoint.com/article/struts-first-steps">discovered</a> that <a href="http://struts.apache.org/">Jakarta Struts</a> is a really good Java-based solution and, more recently, <a href="http://www.phparch.com/">PHP Architect magazine</a> offered a free download of their may 2003 issue, containing a nice introductory article with a solution based on Smarty templates. A very extensive solution incorporating the three-tier architecture (no, <a href="http://www.tonymarston.net/php-mysql/infrastructure-faq.html#faq26">they're not the same</a>) also PHP-based, can be found on <a href="http://www.tonymarston.net/php-mysql/model-view-controller.html">Tony Marston's website</a> along with tons of information on different design patterns.<br />
I could stop here and leave you with one of the examples, but let me say this: I believe that the size of the project and the probability of exchanging or updating certain components should play as important a role as the reusability of the code.
</p>
<p>
Over time, Pascal and I have come up with a loose framework of PHP classes, which have saved enormous amounts of time when building medium-scale projects, thanks to their reusable subcomponents.
<br />
The Views are set up with CSS and (X)HTML, which is generated with XSL stylesheets (see Pascal's article with his <a href="http://www.contentwithstyle.co.uk/Articles/29/">five-layer structure</a> for more detail).
<br />
In the backend PHP, classes are structured by their functionality. A db-class manages all lower-level database work: replace it or change the configuration for a database change. This class represents the Model. Your server was hacked and you need to refer to a backup db-server? It's done in 10 seconds.
<br /><br />
A base class, meanwhile, carries all of the helper functions that are used across most projects, for things like string and date operations. It is extended by a page class, which is the first class to adapt to the project. Here, the site structure is taken care of (usually dug out of the database in one way or another, as a tree or a flat page model), functions for breadcrumbs and navigation grab the data and return the XML, and global site elements find their data. But not only that: All what's generally called business logic, calculations, workflows and so forth, can be found here as well. The class can then be extended for special purpose pages, to keep it nice and tidy. I group them as Controller. They receive all the requests, they instantiate the Model (the db class) to deliver XML, to be transformed by the View, by XSL.
<br /><br />
But this does not strictly fit into the MVC pattern: In a clean approach, no SQL would be found in the page class. A common technique here is to set up a class for each table that contains all queries. This way, you know where to find your SQL and what classes to touch should your db-server change. Alternatively, you can extend the db-class and top it up with higher level of functions that build the SQL for you. On the upside, you only have one file to change (as opposed to one per table); on the downside, solutions tend to be developed in a complicated way, thereby making it difficult for the next developer to get their head around it. Furthermore, it may be too specific to one site and, therefore, not very portable.
<br /><br />
With these classes set up as a starter package, you'll have incredibly quick results - and working as a frontend/backend team is a joy. Simply set up an abstract XML file with all the data you need, and each of you can work towards it from both sides: one on the xsl/html/css side, the other in the php/db department.
<br /><br />
On several occasions, I left out the table classes as a set-up for medium-sized projects.
As an alternative, I either stuck to relational databases (usually sufficient) and coded in <a href="http://www.opengroup.org/public/tech/datam/sql.htm">pure SQL-92</a> - although this tends to be awkward and I'm not sure if all common databases follow this standard entirely - or I made sure the db-application stayed the same throughout the lifetime of the site.
<br /><br />
As with most things in modern digital life, sites are ever-changing, but the code hardly ever survives three years. Your budget, timeframe and the purpose of the site will determine to what extent you want to follow MVC. But it's always worth considering.</p>Dynamic tables with XSLT2005-06-01T09:08:30+01:00http://www.contentwithstyle.co.uk/content/dynamic-tables-with-xslt/dynamic-tables-with-xslt<h2>Required knowledge</h2>
<p>
To get into what I want to do in this tutorial, you should have a basic understanding of what XSLT is and how you can get it running.
<br />
I explained a couple of ways in my <a href="/Articles/4">basic tutorial</a>. Maybe it's worth reading it.
<br /><br />
You can <a href="/resources/xsl_tables/sample_files.zip">download the example-files</a>, coming with my little development-javascript for MSXML.
</p>
<h2>Processing</h2>
<p>
Again you are forced to make your choice! How are you getting the variables passed to your XSL? I don't know!
It depends on the processor you are using. I'll have an example-file for javascript and MSXML and PHP using sablotron provided in the zip-file.
The links to the output will be transformed by using PHP and sablotron.
I just pass querystrings to the processor, so you could use pretty much the same thing in ASP, PHP, JSP, whatever!
Just to make the level of abstraction completely clear, let me AGAIN summarize what the file must do:
</p>
<ol>
<li>Loading XML</li>
<li>Loading XSL</li>
<li>Passing querystring-variables to the processor</li>
<li>Process the XML with the XSL</li>
</ol>
<p>
Now let's move on to the important bits!
</p>
<h2>Getting started</h2>
<p>
I'll use a <a href="/resources/xsl_tables/simple_table/data.xml">pretty boring piece of data</a> for this tutorial. Basically it should be data that is ready to be displayed as a table.
Here is an excerpt:
</p>
<pre>
<circus>
<show>
<start_day><![CDATA[31.12.2003]]></start_day>
<end_day><![CDATA[26.01.2004]]></end_day>
<postcode><![CDATA[81939]]></postcode>
<city><![CDATA[München]]></city>
<address><![CDATA[Kieferngarten]]></address>
<artist>
<key><![CDATA[KNO 01]]></key>
<name><![CDATA[Esad Jones]]></name>
<program><![CDATA[waghalsige Stunts]]></program>
</artist>
</show>
</circus>
</pre>
<p>
Like you see, we're able to get a nice table out of that. We'll start by creating a basic HTML-table by parsing the whole document.
The XSL for that look somehow like this:
</p>
<pre>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:param name="smode" />
<xsl:template match="circus">
<html>
<head>
<title>mytable</title>
</head>
<body>
<xsl:call-template name="show" />
</body>
</html>
</xsl:template>
<xsl:template name="show">
<h1>show overview</h1>
<table>
<thead>
<tr>
<th>start_day</th>
<th>end_day</th>
<th>postcode</th>
<th>city</th>
<th>address</th>
<th>view artists</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates />
</tbody>
</table>
</xsl:template>
<xsl:template match="show" name="srow">
<tr>
<td><xsl:value-of select="current()/start_day" /></td>
<td><xsl:value-of select="current()/end_day" /></td>
<td><xsl:value-of select="current()/postcode" /></td>
<td><xsl:value-of select="current()/city" /></td>
<td><xsl:value-of select="current()/address" /></td>
<td></td>
</tr>
</xsl:template>
</xsl:stylesheet>
</pre>
<p>
You can watch the output <a href="/resources/xsl_tables/simple_table/transform.php">here</a>, the XSL is <a href="/resources/xsl_tables/simple_table/example.xsl">here</a>.
</p>
<h2>Sorting</h2>
<p>
First thing we're going to do is to add dynamic sorting.
This works using the XSL-Element <code class="inline"> <xsl:sort /> </code> in combination with a variable.
We'll insert a parameter into our XSL on top and reform the th-content in two of our collumns.
</p>
<pre>
<th><a href="?smode=postcode">postcode</a></th>
<th><a href="?smode=city">city</a></th>
</pre>
<p>
So now the variable “smode” will be passed to the processor. Buit we have to introduce it within the XSL as well.
So within the first template we'll declare it with this line of code:
</p>
<pre>
<xsl:param name="smode" />
</pre>
<p>
Now we can check the variable and choose the right <code class="inline"> <xsl:sort /> </code>.
I do this by wrapping it into an additional template, so I can reuse it later.
Let's have a look:
</p>
<pre>
<xsl:template name="sorting">
<xsl:choose>
<xsl:when test="contains($smode, 'postcode')">
<xsl:apply-templates>
<xsl:sort select="postcode" order="ascending" data-type="text" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="contains($smode, 'city')">
<xsl:apply-templates>
<xsl:sort select="city" order="ascending" data-type="text" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</pre>
<p>
Now, instead of just using <code class="inline"><xsl:apply-templates /></code> we'll call the template we created with a simple
<code class="inline"><xsl:call-template /></code>.
Have a look at the <a href="/resources/xsl_tables/sorting/transform.php">transformed output</a> and make sure you understand the <a href="/resources/xsl_tables/sorting/example.xsl">XSL</a>.
</p>
<h2>Details</h2>
<p>
Alright, that's a nice beginning. But what is that link for, called “view artists”?
Well, that's where we get to the interesting point. Obviously it's possible to display the artists for one show
as a table on it's own. But how are we going to do that? We'll AGAIN use variables!
What we need is a way to identify a row and then display the matching content.
I'll use UID. Anyway, let's give it a go.
We just need to use <code class="inline">position()</code>. Like that we can identify the row for which we want to see the sub-nodes.
Because we use the position, we need to keep the sorting-mode as well, otherwise we select the wrong node when we sorted before.
In the empty collumn of the table I insert this:
</p>
<pre>
<a href="?ident={position()}&smode={$smode}">artists</a>
</pre>
<p>
Obviously I need somehow a way to check if the variable “ident” is set.
This is why I change the <code class="inline"><xsl:call-template name="show" /></code> to this:
</p>
<pre>
<xsl:choose>
<xsl:when test="not($ident)">
<xsl:call-template name="show" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="artist" />
</xsl:otherwise>
</xsl:choose>
</pre>
<p>
So now I need an additional template called “artist”, which in this case just changes the table-structure and inserts a back-button.
</p>
<pre>
<xsl:template name="artist">
<h1>artists overview</h1>
<a href="?smode={$smode}">back</a>
<table>
<thead>
<tr>
<th>name</th>
<th>program</th>
</tr>
</thead>
<tbody>
<xsl:call-template name="sorting" />
</tbody>
</table>
</xsl:template>
</pre>
<p>
As you might already have noticed, the sorting-wrapper will work like it did before as well.
That's why we have to check the ident-variable within the applied template, which is the one named “srow”.
I will create a new template called “artistrow”, so the table-structure is right.
</p>
<pre>
<xsl:template match="show" name="srow">
<xsl:choose>
<xsl:when test="not($ident)">
<tr>
<td><xsl:value-of select="current()/start_day" /></td>
<td><xsl:value-of select="current()/end_day" /></td>
<td><xsl:value-of select="current()/postcode" /></td>
<td><xsl:value-of select="current()/city" /></td>
<td><xsl:value-of select="current()/address" /></td>
<td><a href="?ident={position()}&smode={$smode}">artists</a></td>
</tr>
</xsl:when>
<xsl:otherwise>
<xsl:if test="position() = $ident">
<xsl:for-each select="current()/artist">
<xsl:call-template name="artistrow" />
</xsl:for-each>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="artistrow">
<tr>
<td><xsl:value-of select="current()/name" /></td>
<td><xsl:value-of select="current()/program" /></td>
</tr>
</xsl:template>
</pre>
<p>
The transformed output looks like <a href="/resources/xsl_tables/details/transform.php">this</a>, the XSL like <a href="/resources/xsl_tables/details/example.xsl">this</a> .
</p>
<h2>Nearly done</h2>
<p>
Now we have both views, all we need is a bit of styling via <a href="/resources/xsl_tables/final/style.css">CSS</a> and we
have a <a href="/resources/xsl_tables/final/transform.php">nice looking dynamic application</a> where most of the logic happens within the <a href="/resources/xsl_tables/final/example.xsl">XSL</a>.
</p>XML as intermediate application layer2005-05-31T08:37:12+01:00http://www.contentwithstyle.co.uk/content/xml-as-intermediate-application-layer/xml-as-intermediate-application-layer<h2>Why bothering?</h2>
<p>Dealing with any server-side scripting language the things that I find most annyoing are the ones where I have to change
<br />
<code class="inline"> echo '<a href="' . $url . '">' . $text . '</a><br />';</code>
<br />
to something like
<br />
<code class="inline"> echo '<li><a href="' . $url . '">' . $text . '</a></li>';</code>
</p>
<p>It's always a waste of time, and often, due to some typo, errors sneak in and suddenly you're debugging again.</p>
<p>In this article I want to share my thoughts on techniques for keeping our code XML-based - so there's no need to get your hands dirty in your application code to change the markup that is rendered afterwards. Most things will be PHP related though.
</p>
<h2>Other benefits</h2>
<p>Once we get the seperation working properly, we can completely detach the development of front end from the application logic by first agreeing on an XML scheme to exchange data between those two.</p>
<p>By providing dummy XML to the guy doing the XHTML and CSS he can flesh everything out and then put it into an XSLT. Even before the application is built he can have finished everything... Then to get up and running, just the XSLT files, images and CSS need to be dropped in. Good to go.</p>
<p>By keeping the dataflow of the application logic stricly XML compliant we also have no problems using the same code
for outputting a different version, to mobile devices, for example, OR to swap the data-source with an external web-service. </p>
<p>And did I mention that suddenly you can work with UTF-8 throughout the whole application and the XSLT automatically
transforms it into the needed output format? You just need the right parser.</p>
<p>On top of all that, we are using a w3c technique and have the ability to render tree structures and stuff...</p>
<h2>A 5 layer approach</h2>
<p>
The <a href="http://www.graphicpush.com/archives/000070.shtml">3 layer paradigm</a> has been around for a while and most of you probably are familliar with it.</p>
<p>I mostly agree but for our needs, just half of the job is covered. That's why I want to extend it into a 5 layer approach.
</p>
<p><img src="http://www.contentwithstyle.co.uk/resources/old_images/15.gif" alt="5 layer approach" longdesc="This graphic shows 5 layers of a web-application as follows: XML: Application data output, XSL: Transformation for target platform, (X)HTML: Markup - Client side, CSS: Presentation - Client side, Javascript: Behaviour" />
</p>
<h2>Planning your application</h2>
<p>First of all you obviously need to flesh out what the application does etc. and which part does what.</p>
<p>We should also take care of the platform that we'll be developing for, to avoid facing the worst of all situations: having to throw the whole goddam thing in the bin and start from scratch.</p>
<p>Then, like I already mentioned, we can work out the output XML structure and pass it on to the front end developer. As soon as that's done we can move on to plan the code for our application.
</p>
<h2>Structuring the application code</h2>
<p>Now that we know what needs to be done we can assemble a robust set of classes, either the <a href="http://pear.php.net/">PEAR</a> ones or custom ones (I tend to use custom ones) that perform several actions for us:
</p>
<dl>
<dt>General DB class</dt>
<dd>
A class that gives us easy access to our database and returns resultsets or arrays, manages updates and inserts and explains
tables and all that ...
<br />
If you're working with several database-types I'd recommend to <a href="http://www.jakober.ch/asp/asp_ado.php">make use of
ADO in ASP</a>, and there is an <a href="http://adodb.sourceforge.net/">ADODB toolkit for PHP</a> as well.
</dd>
<dt>Database output to XML</dt>
<dd>
It's a very simple thing, but it makes life very comfortable. I use my own one in PHP, but
<a href="http://pear.php.net/package/XML_sql2xml" title="PEAR::XML_sql2xml">there is a PEAR class for this</a>
as well.
</dd>
<dt>XSLT transformation class</dt>
<dd>
This class should manage to process XML input by using XSL files. And again,
<a href="http://pear.php.net/package/XML_XSLT_Wrapper#results" title="PEAR::XML_XSLT_Wrapper">there is a PEAR class for doing
this</a>.
</dd>
</dl>
<p>Obviously it's down to the developer to add more classes for specific problems, but once we have gathered our basic toolkit
we can think about ways to transform things easily.</p>
<h2>Class based application</h2>
<p>I highly recommend taking a big big step back from procedural scripting. Instead you can work out a class structure and
write rendering methods for either the page or parts of the page. Again, classes are robust and reusable. If you are unfamiliar with
<a href="http://en.wikipedia.org/wiki/Object-oriented_programming" title="Object-oriented Programming">OOP</a> in PHP,
<a href="http://www.informit.com/articles/article.asp?p=24607&rl=1" title="A Quick and Dirty Introduction to OOP with PHP">make
yourself familiar with it</a> ASAP.
</p>
<h2>PHP - output buffering</h2>
<p>And here comes one of the nicest ideas ever.</p>
<p>I think that <a href="http://www.zend.com/zend/art/buffering.php">output buffering</a> is a handy thing anyway. And there are <a href="http://www.jinxidoru.com/_php/buffering.html">many ways to benefit from it</a>. But the coolest feature though is the callback function.</p>
<p>Instead of having a method called by something to finally get the XHTML out of our application, we just process the whole output
generated in PHP with this single callback funtion. Some flags can trigger which XSLT to use. </p>
<p>And there we are, <strong>we have a strictly XML based internal dataflow, but rendered HTML in the output</strong>.</p>
<h2>Outlook</h2>
<p>I hope everyone sees the point of my approach and how you can work out a robust application. I also hope everyone understands why I didn't provide much code this time,
since it would be quite a lot of code and still be platform specific.
</p>
<p>Using output buffering may seem a bit like a loss of control, since you cannot pass parameters to the callback function, but it makes sense to just call one XSL and, similar to use @import in CSS, include XSL files for patricular elements. How to do that will be the next topic, so stay tuned.</p>A CSS Framework2005-05-22T16:20:22+01:00http://www.contentwithstyle.co.uk/content/a-css-framework/a-css-framework<p>In my <a href="/Articles/12/modular-css/">Modular CSS</a> article I documented the possibility of breaking down stylesheets into components that could be reused across projects. All well and good. The next logical step is to extend this to become a CSS framework, allowing rapid development of sites with pre-written and tested components. All that's really required to produce this is a set of naming conventions and a flexible base template... </p>
<h2>Who is this for?</h2>
<p>If you've been creating sites with CSS for a while you may be getting frustrated with having to recreate and retest basic layouts on a regular basis. In this article I'm trying to illustrate a simple way of skipping the tedious startup on your average project, letting you get to the interesting stuff as quickly and efficiently as possible. I've not attempted to explain the layouts included here so it may not be suitable if you're a CSS beginner. Sorry about that... Feel free to dissect them yourself if you're interested; I've kept them as simple as possible.</p>
<h2>How many layouts are there? </h2>
<p>Well, loads but the majority of them fall into rough groups. Any framework must account for the most common layouts, otherwise it'll never get used. </p>
<ul>
<li>Vertical navigation with one content column</li>
<li>Vertical navigation with two columns of content</li>
<li>Horizontal navigation with one content column</li>
<li>Horizontal navigation with 2 columns of content</li>
<li>Horizontal navigation with local navigation and one column of content</li>
<li>Horizontal navigation with local navigation and 2 columns of content</li>
</ul>
<p>These 6 cover most of the blogs out there and most of the corporate sites as well. How can we structure a document so that it'll be useful in all 6 of these scenarios?</p>
<h2>What are the common elements?</h2>
<p>Malarkey had a shot at pinning down <a href="http://www.stuffandnonsense.co.uk/archives/whats_in_a_name_pt2.html">websites' common elements and giving them names</a> a while back but I want something a little more general so that the building blocks can be retasked without slipping their labelling.</p>
<p>So... what are the common building blocks in the above 6 layouts?</p>
<ul>
<li>Header</li>
<li>Footer</li>
<li>Main content</li>
<li>Sub content</li>
<li>Main nav</li>
<li>Local nav</li>
</ul>
<p>These elements are used mix and match across most sites you visit so these are the constituent elements that any framework will have to cater for. No designs will use all of them but most will use header, footer, main content and main nav with optional sub content and sub nav.</p>
<h2>The ideal structure</h2>
<p>For me the most important single aspect of accessibility is the source code order: if it makes sense you're well on your way to building an accessible site. </p>
<p>You can't spent much time on the accessibility mailing lists or forums without someone being told that they should structure their HTML so that the content preceeds the navigation. This stops screenreader users from having to sit through the site's navigation every time they travel to a new page. You can get around this with skip links but that's an extra link to follow...</p>
<p>So, in an ideal world, how would we structure our websites?</p>
<ol>
<li>Header</li>
<li>Main content</li>
<li>Sub content</li>
<li>Local nav</li>
<li>Main nav</li>
<li>Footer</li>
</ol>
<p>This source order follows the exact structure people are expected to want: once they have read/listened to the content of your page they are most likely to want to navigate within the current section or to related pages. After they have worked their way through those they'll probably want another section of your site so the main navigation follows.</p>
<h2>The page structure</h2>
<p>This page structure above looks something like this when turned into (X)HTML:</p>
<pre>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
</head>
<body>
<div id="page">
<div id="header" class="clearfix">
<hr />
</div><!-- end header -->
<div id="content" class="clearfix">
<div id="main">
<hr />
</div>
<div id="sub">
<hr />
</div>
<div id="local">
<hr />
</div>
<div id="nav">
<hr />
</div>
</div><!-- end content -->
<div id="footer" class="clearfix">
</div><!-- end footer -->
</div><!-- end page -->
<div id="extra1">&nbsp;</div>
<div id="extra2">&nbsp;</div>
</body>
</html>
</pre>
<h2>The magic of CSS</h2>
<p>We're going to need some farily advanced CSS tricks to turn this source order into useful layouts. They all have to be flexible, to account for text resizing, which with the footer rules out absolute positioning...</p>
<p>The eureka moment for me was when <a href="http://www.1976design.com/blog/archive/2004/11/21/solving-css-problems-mozilla-europe/">Dunstan published his absolutely positioned menu technique</a> as part of his Mozilla Europe work. Suddenly it became possible, using an incredibly sneaky trick, to pull the nav bar from the bottom of the source and use it horizontally in the header and still allow for text resizing. Now that's genius. </p>
<p>Then you've got the negative margin tricks for moving floats around. I first came across these when <a href="http://www.mezzoblue.com/archives/2004/01/23/friday_chall/">Ryan Brill posted about them on Mezzoblue</a>... Dead handy.</p>
<p>If we're going to use floated layouts we're going to need some way of clearing the contents. I could put clearing divs into each of my elements but there is the black magic <a href="http://positioniseverything.net/easyclearing.html">clearfix method by Tony Aslett</a> that will do this for me, keeping the source clean.</p>
<p>A few other things that might come in handy:</p>
<ul>
<li>Body ids to switch styles</li>
<li><a href="http://positioniseverything.net/explorer/floatIndent.html">Display: inline to cure IE's double float bug</a></li>
</ul>
<h2>See where I'm going with this?</h2>
<p>We have a single, flexible source document that will take a variety of content and a load of CSS techniques to manipulate them... To make this framework useful I have to be able to get my single source document to display as any of the common layouts listed above. Once that's done I'll be able to drop any content into the HTML framework, plug in the appropriate layout stylesheet for the design, tweak it and the job will be done. No more rewriting XHTML and CSS for every project; every time I start I will begin work with a solid foundation that I can tweak to fit the specifics of the job. Startup time will be next to nothing.</p>
<h2>Back to our five main layouts</h2>
<p>Okay, so the source code is fixed. What we want is to be able to plug in tried and tested layout stylesheets for reuse on future projects. I'm going to stick with fixed width layouts for this because they're simpler but using negative margins we could get these fluid with a bit of extra work.</p>
<ul>
<li><a href="/resources/css-framework/1.1/?css=layout-navleft-1col.css&demo=true">Vertical navigation with one content column</a></li>
<li><a href="/resources/css-framework/1.1/?css=layout-navleft-2col.css&demo=true">Vertical navigation with two columns of content</a></li>
<li><a href="/resources/css-framework/1.1/?css=layout-navtop-1col.css&demo=true">Horizontal navigation with one content column</a></li>
<li><a href="/resources/css-framework/1.1/?css=layout-navtop-subright.css&demo=true">Horizontal navigation with 2 columns of content</a></li>
<li><a href="/resources/css-framework/1.1/?css=layout-navtop-localleft.css&demo=true">Horizontal navigation with local navigation and one column of content</a></li>
<li><a href="/resources/css-framework/1.1/?css=layout-navtop-3col.css&demo=true">Horizontal navigation with local navigation and 2 columns of content</a></li>
</ul>
<h2>Achieved: Level 2!</h2>
<p>That's it - all the common layouts done with pluggable stylesheets and ready to go. They're set up in a kind of Zen Allotment for you to <a href="/resources/css-framework/1.1/">have a flick through</a>...</p>
<p>If you want to have a play with these yourself, <a href="/resources/css-framework/1.1/css-framework.zip">all the source files</a> are packaged up in quick-grow zip file.</p>
<p><strong>30/11/2007 UPDATE:</strong> I've uploaded a slightly updated version of the framework. Very little has changed but a few people have voiced concerns over the lack of a formal license. Taking advice from <a href="http://paul-m-jones.com">Paul Jones</a> (thanks!) I've added the new BSD terms, which are about as open as they come. I'm happy for people to use the framework for whatever they want. If you do, please drop me a line - I'd love to see what you come up with!</p>
<p><strong>6/09/2010 UPDATE:</strong> This article is <a href="http://www.fatcow.com/edu/a-css-framework-be/">now available in Belorussian</a>, apparently, translated by Amanda Lynn.</p>Find your node: Advanced XPATH commands2005-05-21T11:49:00+01:00http://www.contentwithstyle.co.uk/content/find-your-node-advanced-xpath-commands/find-your-node-advanced-xpath-commands<h2>XSLT and XPATH?</h2>
<p>
All that XSLT does is applying code-templates on XML-nodes. In order to do this you need to find the right node.
XPATH offers you an advanced toolkit to do that within an XSL-file.
</p>
<h2>Go dynamic</h2>
<p>
XPATH and XSLT offer you dynamic features that remind more of a scripting-language rather than a stylesheet language,
but keep in mind that all you can do is transforming the data you've got, so on a more abstract level it is
still just styling up the content, just that this time you can choose what to display triggered with variables and
control-structures.
</p>
<h3>Locations in XPATH</h3>
<p>
Let's have a look into a simple, concatinated XML structure:
</p>
<pre>
<root>
<node>
<subnode />
<subnode />
<subnode />
</node>
<node>
<subnode>
<subsubnode />
</subnode>
</node>
</root>
</pre>
<p>
In order to navigate between the nodes you'll need to use Location Path Expressions.
The basic ones work pretty much the same like in an operating-system enviroment.
<code class="inline">/node/subnode</code> for example would be the absolute path to all subnodes in the example above.
<br />
On top of that XPATH offers you differnet types of axes like <code class="inline">ancestor</code>, <code class="inline">parent</code> or
<code class="inline">child</code>. Even attributes can be selected with <code class="inline">attribute</code> or you can use <code class="inline">*</code> as a wildcard.
<br />
<br />
A full reference can be found at at <a href="http://www.w3schools.com/xpath/xpath_location.asp">w3schools</a>.
</p>
<h3>Node-tests</h3>
<p>
On top of these axes you can test nodes against expressions that most of you might know as operators when
using scripting- or programming-languages.
<br />
There are expressions like <code class="inline">+</code>, <code class="inline">-</code> but also relational ones like <code class="inline">=</code>, <code class="inline">></code> and
<code class="inline">!=</code>.
Keep in mind that within valid XML <code class="inline">></code> and <code class="inline"><</code> need to be escaped as <code class="inline">&gt;</code> and <code class="inline">&lt;</code>.
</p>
<h3>Functions in XPATH</h3>
<p>
For those that already used basic XSLT functionallity the functions won't be new, either.
The function <code class="inline">current()</code> for example gives back the node that you are in now, maybe while within a loop.
<br />
But there is also string-functions like <code class="inline">contains()</code> or <code class="inline">substring()</code>.
Those can be used to manipulate the data that you will use for outputting data or locating nodes.
<br />
<br />
Again, find a full list of XPATH functions at <a href="http://www.w3schools.com/xpath/xpath_functions.asp">w3schools</a>.
</p>
<h3>Combine them!</h3>
<p>
Now you can combine the location path, the axes and the node tests to get the node you really want. The syntax is <code class="inline">axisname::nodetest[predicate]</code>.
<br />
I'll fit everything in one expression and you'll immediatly figure what I mean:
<br />
<br />
<code class="inline">current()/child::*[attribute::type='classic']</code>
<br />
<br />
This expression selects all subnodes of the child-elements within the current node, that have the attribute type with the value 'classic'.
</p>
<h2>An example please!</h2>
<p>
I know, that was pretty abstract, but now we'll move straight on to the practical example.
We will use dynamic XPATH expressions to display a node-set, remember that when using server side techniques
or client-side scripting this also could be triggered by a variable.<br />
Let's have a look at the piece of XML that will be transformed.
</p>
<pre>
<?xml version="1.0" ?>
<company_list>
<company country="uk">
<name>Company 1</name>
<sales>3200900</sales>
<employees>250</employees>
</company>
<company country="usa">
<name>4th capitalist</name>
<sales>102310000</sales>
<employees>3050</employees>
</company>
....
<company country="uk">
<name>UK stores</name>
<sales>12300000</sales>
<employees>3301</employees>
</company>
<company country="uk">
<name>THEUSTOYSTORES</name>
<sales>22200000</sales>
<employees>18639</employees>
</company>
</company_list>
</pre>
<p>
What we're going to do now is to find out the top 3 in the UK
regarding the ration of employees and sales.
<br />
Let's have a look at the XSL file:
</p>
<pre>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/company_list">
<html>
<body style="font-family: Arial">
<h1>Top 3 stores in the uk regarding employees per sales</h1>
<ul>
<xsl:for-each select="company[@country = 'uk']">
<xsl:sort select="sales div employees"/>
<xsl:if test="position() < 4">
<li>
Company: <xsl:value-of select="name" />
<br />
Employees: <xsl:value-of select="employees" />
<br />
Sales: <xsl:value-of select="sales" />
</li>
</xsl:if>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
</pre>
<p>
Download the files here:
<br />
<a href="/resources/advanced_xpath/company_list.xml">company_list.xml</a>
<br />
<a href="/resources/advanced_xpath/uk_top3.xsl">uk_top3.xsl</a>
</p>
<h2>Conclusion</h2>
<p>
That wasn't bad, but now imagine what you could do when you have dynamic variables.
Especially with the MSXML toolkit or Sarissa that have the method selectSingleNode()
these expressions are a piece of cake.
</p>XSL: the other way of styling up content2005-05-21T11:24:14+01:00http://www.contentwithstyle.co.uk/content/xsl-the-other-way-of-styling-up-content/xsl-the-other-way-of-styling-up-content<h2>Buzz-words</h2>
<p>
Two of the best known acronyms around right now are XML and XSL, often being mentioned as “the way to go” or
some abstract technique that stands for a new direction within the whole web.
Rather than dealing with the languages itself in detail I'll try to give a pragmatic approach and to show basic examples
how to transform data into browser-ready HTML.
</p>
<h2>Data?</h2>
<p>
To transform XML we will need XML.
<br />
<br />
To say it very simple, an XML-file is just structured Data.
It works with tags as well, and we'll leave out that validating thing so far and be just working with well-formed XML.
<br />
<br />
This article will deal with a piece of well formed XML code, that stands for data coming straight out of a database.
<a href="/resources/xsl_basic/example_loadxsl.zip">Download the example files</a>
</p>
<h2>Getting started</h2>
<h3>Simple way</h3>
<p>
Now we are going to transform that using XSLT. To do that we first have to know how we apply the XSL to the XML file.<br />
There is several ways to do that.<br />
First possibility would be to just have a reference to the XSL within the XML.
The code for that would look like that:
</p>
<pre>
<?xml-stylesheet type="text/xsl" href="example.xsl"?>
</pre>
<p>
To test if the XSL is applied we will use an XSL-file that is doing nothing but having “Hello world” as output which looks like this:
</p>
<pre>
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" encoding="iso-8859-1"/>
<xsl:template match="/">
<test>Hello world</test>
</xsl:template>
</xsl:stylesheet>
</pre>
<h3>Scripting way</h3>
<p>
Using IE and <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=3144b72b-b4f2-46da-b4b6-c5d7485f2b42&displaylang=en">MSXML</a>
we could use Active Scripting to load the XML and the XSL. For example you could, in any XHMTL-File, embed a javascript contaiming code for that would look simmilar to this:
</p>
<pre>
// Load XML file
xmlDoc = new ActiveXObject("Msxml2.DOMDocument.4.0");
xmlDoc.async = false;
xmlDoc.load("data.xml");
// Load XSL file
xslDoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.4.0");
xslDoc.async = false;
xslDoc.load("example.xsl");
// XSL Transformation
xslTpl = new ActiveXObject("Msxml2.XSLTemplate.4.0");
xslTpl.stylesheet = xslDoc;
xslProc = xslTpl.createProcessor();
xslProc.input = xmlDoc;
xslProc.transform;
// Transform
document.write(xslProc.output);
</pre>
<p>
Like this the XSL and the XML will be pulled in without changing them in any kind of way.
This obviously just works with javascript activated.
</p>
<p>
For Mozilla it would look like this:
</p>
<pre>
function onload() {
processor.importStylesheet(xslDoc);
}
function transform() {
doc = processor.transformToDocument(xmlDoc);
var xmls = new XMLSerializer();
document.write(xmls.serializeToString(doc));
}
var processor = new XSLTProcessor();
var xslDoc =
document.implementation.createDocument("", "xslDoc", null);
// onload handler
xslDoc.addEventListener("load", onload, false);
xslDoc.load("example.xsl");
var xmlDoc = document.
implementation.createDocument("", "xmlDoc", null);
xmlDoc.addEventListener("load", transform, false);
xmlDoc.load("data.xml");
</pre>
<h3>Server-side way</h3>
<p>
Rather than having all the transformation on the client, we also could use server-sided transformation using sablotron or something similar.
The PHP-code for using Sablotron would loke like this:
</p>
<pre>
//create the processor
$my_xslt=xslt_create();
//filepath on UNIX
$xml = getcwd()."data.xml";
$xsl = getcwd()."example.xsl";
//process the fiel and echo the result
echo xslt_process($my_xslt,$xml,$xsl,NULL,$arguments);
//free the processor
xslt_free($my_xslt);
</pre>
<p>
Note that on win32-systems you need to put in a path like this:
</p>
<pre>$xml = "file://".getcwd()."data.xml";</pre>
<p>
See an example of a transformed file <a href="/resources/xsl_basic/example_loadxsl/transform.php">here</a>
</p>
<h3>So what do I do?</h3>
<p>
Depends. Obviously the stylesheet-reference is a nice and easy way, but you have to modify the XML-file and you cannot pass parameters or anything to the XSL,
which would make things like expanding menus or highlighted table-rows possible.<br />
This is possible when using client-side scripting, but you have to provide a sophisticated script for IE and Mozilla.<br />
Anyway, for any client-side solution IE needs to have the MSXML-parser installed.
<br />
So you could let the server do the work, IIS with MSXML or an apache with PHP and Sablotron should do the job.
The output can be any kind of HTML and XHTML you specify. But that obviously requires a server.
</p>
<h2>Ok, let’s transform</h2>
<h3>find your node</h3>
<p>
The first thing to get is that in XSL you can break the structure down to different templates and apply those on nodes of the
DOM-tree found in the XML Document. That means that first you have to navigate through the DOM-tree using XPATH-commands.
We will begin carefully with creating our basic output-document by inserting the title and finding the page element.
The example files are <a href="/resources/xsl_basic/example_transform_1.zip">here</a>.
</p>
<pre>
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" encoding="iso-8859-1"/>
<xsl:template match="/site">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><xsl:value-of select="current()/title" /></title>
</head>
<body>
<xsl:value-of select="current()/page/@label" />
</body>
</html>
</xsl:template>
</xsl:stylesheet>
</pre>
<p>
So according to our XML-Structure we navigate to the node named “site”, the slash in front of it indicates that it is child of the root element.
With the “xsl:value-of” statement we get the data that is held in the selected node, in this case “current()/title” where current() points to the node that the template is applied on, in this case “/site”
<br />
It is also possible to display values of attributes, in this case the “label”-attribute of the “page”-node
<br /><br />
<a href="/resources/xsl_basic/example_transform_1/transform.php">Check the output transformed with sablotron</a>
</p>
<h3>templates</h3>
<p>
Now we'll apply templates to repeating nodes.
Let's have a look on the new code first!
Get the example files <a href="/resources/xsl_basic/example_transform_2.zip">here</a>.
</p>
<pre>
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" encoding="iso-8859-1"/>
<xsl:template match="/site">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><xsl:value-of select="current()/title" /></title>
<style>@import url(css/example.css);</style>
</head>
<body>
<div class="content">
<h1><xsl:value-of select="current()/page/@label" /></h1>
<xsl:for-each select="current()/page/contentblock">
<xsl:call-template name="contentblock"/>
</xsl:for-each>
</div>
<div class="right">
<xsl:for-each select="current()/page/box">
<xsl:call-template name="box"/>
</xsl:for-each>
</div>
<div class="clear"></div>
</body>
</html>
</xsl:template>
<xsl:template name="contentblock">
<xsl:param name="url" select="current()/image/@url" />
<xsl:param name="width" select="current()/image/param/@width" />
<xsl:param name="height" select="current()/image/param/@height" />
<div class="contentblock">
<p><xsl:value-of select="current()/text" /></p>
<div class="img">
<img src="{$url}" width="{$width}" height="{$height}" />
<p><xsl:value-of select="current()/image/subtitle" /></p>
</div>
</div>
</xsl:template>
<xsl:template name="box">
<div class="box">
<h2><xsl:value-of select="current()/headline" /></h2>
<p><xsl:value-of select="current()/boxtext" /></p>
</div>
</xsl:template>
</xsl:stylesheet>
</pre>
<p>
Ok, first thing you should put your eye on is the code, that loops through all nodes with a particular name.
I have used this code for all elements named “contentblock” and “box”.
The xsl-command for looping in this case was
</p>
<pre>
<xsl:for-each select="current()/page/contentblock">
</pre>
<p>
Within this loop I have called different templates. The xsl-command for calling a template is
</p>
<pre>
<xsl:call-template name="contentblock"/>
</pre>
<p>
Annother interesting thing that you will have noticed is the possibility to insert values inline using <code class="inline">{@attribute}</code>.
Like this its becomes easily possible to insert values into attributes of tags,
<br />
which would be not possible with
<code class="inline"><xsl:value-of /></code>
<br /><br />
The other way to do that would be <code class="inline"><xsl:attribute /></code>, wich I hardly ever use, but feel free to search google for it.
<br /><br />
You also may have noticed that I introduced the use of variables by using
</p>
<pre><xsl:param name="width" select="current()/image/param/@width" /></pre>
<p>
The value of this variable can now be called with <code class="inline">{$width}</code> or <code class="inline"><xsl:value-of select="$width" /></code>.
Using javascript or server-side techniques for XSLT, working with these variables becomes more important (and makes more sense as well).
But I am afraid that this would be to much in-depth for just the start.
</p>
<h2>Style it up</h2>
<p>
Now we have our XML-document already transformed into browser-ready HTML. All we need to add now is some CSS, and I guess you allready have noticed the <code class="inline"><style>@import url(css/exaple.css);</style></code> in the document-head.
Have a look at the <a href="/resources/xsl_basic/example_transform_2/transform.php">final output with sablotron</a> or the <a href="/resources/xsl_basic/example_transform_2/data_ref.xml">xml file including a stylesheet-link</a>!
</p>
<h2>Conclusion</h2>
<h3>benefits</h3>
<p>
Even without looking into the really dynamic bits of XSL we can see that XSL is esspecially usefull when it comes to repeating data-patterns which have to be displayed as table-row or something simmilar.
This keeps your data lean and the look seperated from the content. It gets even more interesting when we start using dynamic scripting to call the transformation and variables within the XSL.
In addition it becomes possible to test the output without being sure about how the data will be transformed, either on the server or on the client side.
</p>
<h3>problems</h3>
<p>
XSL on the client is not really implemented yet. To use dynamic processing you have to script solutions for every browser, and many don't even support it.
This problem is solved when you are using XSLT on the server.
</p>Modular CSS2005-05-18T08:47:35+01:00http://www.contentwithstyle.co.uk/content/modular-css/modular-css<p>This isn't a new idea but looking at people's code it doesn't seem to be
a particularly widely used practice: modular CSS. That's a poncy name for
the very simple idea of grouping related styles into separate
stylesheets. The same set of tasks turn up on project after project and a
little careful thought can save hours of foundation work, allowing you to
get on with the serious business of turning a flat design into a web page
far more quickly.</p>
<p>The broad groups that I use are: typography, forms, layout, navigation
and colour. These aren't set in stone and some projects require more or
less categories.</p>
<h2>Common components</h2>
<h3>Typography:</h3>
<p>Basic styles for html elements with font faces, sizes, weights and
neutral colours. Bearing in mind that colour is dealt with elsewhere
you'll probably only find you need to make minor adjustments to these
between projects – a couple of ems here and there, bold to normal
for a heading or two, that sort of thing. I limit the styles in this
sheet to root elements only – no classes or ids. Anything requiring
a class is probably going to be site-sepcific and so belongs in layout.</p>
<h3>Forms:</h3>
<p>Forms are my least favourite things to try and style - the variation
between browsers often forces lots of ugly hacks and filters and I like
to keep these separate from the rest of the CSS.</p>
<h3>Navigation:</h3>
<p>How often have you coded a horizontal nav bar? Do it once, do it well,
and reuse it. You'll need to tweak a few bits and pieces but put it in
it's own CSS file and the bulk of the work will be a copy and paste away.</p>
<h3>Layout:</h3>
<p>This is where the majority of the work lies and most of it will be unique
to the project you're working on. I use naming conventions for my common
structural elements to ease the transition between sites when it come to
maintenance but that's off-topic for this article.</p>
<h3>Colour scheme:</h3>
<p>The only properties you'll need in here are: color, background and
border-color. Nothing else. Be sure to avoid adding any layout info here
– a misplaced margin:0 can ruin your whole afternoon!</p>
<p>An added benefit of the separate colour stylesheet is that you can write
yourself a nice neutral colour scheme and avoiding 10 hours per day of
staring at the eye-searing orange that your client is insisting on.</p>
<p>On a similar note: <a
href="http://www.stopdesign.com/log/2004/08/27/bleached.html">Stop Design | Introducing Bleached</a>.</p>
<p>Possibly of interest here: <a
href="http://www.stuffandnonsense.co.uk/archives/black_and_white_day_five.html">
And All the Malarkey | Black and white: Day five</a>.</p>
<h2>Putting them all together</h2>
<p>I use a file called screen.css but it can be whatever you like. All
you're doing here is @importing your CSS modules to make up a complete
site. The @imports cover N4 degredation so you can just <link> to
screen.css from your HTML.</p>
<pre>
/* typography styles */
@import url("typo.css");
/* form elements */
@import url("forms.css");
/* main layout */
@import url("layout.css");
/* navigation */
@import url("horizontal-nav.css");
/* colour scheme */
@import url("skin.css");
</pre>
<p>For your print stylesheet you might choose to just import your
typography. Rather than having a print sheet that overrides all your
layout just have it import typo.css and nothing else. Job done. What
about handheld media types? Well you might want a flexible layout but the
same typography, form styling and colour scheme. Modular CSS comes to the
rescue and you only need to code a new layout and import it into
handheld.css along with typo.css, form.css and skin.css.</p>
<p>This technique also allows you to unplug stylesheets globally from your
site without having to touch the HTML, adding a layer of abstraction
between content and style.</p>
<p>Another happy side benefit of modular CSS is in the debugging –
just comment out whole stylesheets until you isolate the problem and
focus your efforts there.</p>
<h3>Addons:</h3>
<p>You could plug in a development stylesheet with Andrew Krespanis' Global
Whitespace Reset (<a
href="http://leftjustified.net/journal/2004/10/19/global-ws-reset">Left Justified | Global White Space Reset</a>).
Before you hand over just remove the import from screen.css and you're
good to go.</p>
<p>If you find that your're reusing chunks of code from previous projects
for promo boxes of headers, consider placing them in their own stylesheet
for easier access later.</p>
<h2>A word of caution</h2>
<p>The more stylesheets you have the more you, the developer, have to
remember and keep track of. Where did you set the padding on that
element? It could be in a number of places and if you're not used to your
system, or you're not consistent with it, things can get frustrating. Set
yourself some guidelines and stick to them to give yourself a fighting
chance of taking an educated guess at where you might have placed your
rules. Consider putting these guidelines in comments at the top of your
files – these can help to keep you on topic while you're coding as
well as setting you off running when you come to make maintenance changes
months down the line. Something as simple as sensible naming conventions
for your CSS files can be a great help.</p>