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

<channel>
	<title>Daniel Skinner: News and Articles on Web Development</title>
	<atom:link href="http://www.daniel-skinner.co.uk/feed" rel="self" type="application/rss+xml" />
	<link>http://www.daniel-skinner.co.uk</link>
	<description>Daniel Skinner&#039;s Blog: Web Development news and articles</description>
	<lastBuildDate>Wed, 18 Mar 2009 15:11:23 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Custom sorting order in LINQ (ORDER BY WEIGHTING)</title>
		<link>http://www.daniel-skinner.co.uk/custom-sorting-order-in-linq-order-by-weighting/18/03/2009</link>
		<comments>http://www.daniel-skinner.co.uk/custom-sorting-order-in-linq-order-by-weighting/18/03/2009#comments</comments>
		<pubDate>Wed, 18 Mar 2009 15:10:18 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[LINQ]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/?p=32</guid>
		<description><![CDATA[I have developed a C# LINQ extension method to allow very flexible ordering by assigning weightings to the sort keys of the elements to be ordered. I was inspired by the functionality offered by SQL Server&#8217;s ORDER BY CASE WHEN:

ORDER BY
CASE SEASON
    WHEN 'WINTER' THEN 1
    WHEN 'SPRING' THEN [...]]]></description>
			<content:encoded><![CDATA[<p>I have developed a C# LINQ extension method to allow very flexible ordering by assigning weightings to the sort keys of the elements to be ordered. I was inspired by the functionality offered by SQL Server&#8217;s ORDER BY CASE WHEN:</p>
<pre>
ORDER BY
CASE SEASON
    WHEN 'WINTER' THEN 1
    WHEN 'SPRING' THEN 2
    WHEN 'SUMMER' THEN 3
    WHEN 'AUTUMN' THEN 4
END
</pre>
<p>The extension method I have created lets you pass a lambda function which allows the use of logic to apply custom weightings to the sort keys.</p>
<pre>
var data = dt.Select(g =&gt; new
{
    Season = g.season,
    AverageTemp = g.temp
}).OrderByWeight(a =&gt; a.Season, x =&gt;
{
    if (x == "WINTER") return 1;
    if (x == "SPRING") return 2;
    if (x == "SUMMER") return 3;
    if (x == "AUTUMN") return 4;
    return 99;
});
</pre>
<p>The above will return an IOrderedEnumerable sorted by Season in the order WINTER, SPRING, SUMMER, AUTUMN. However, the OrderByWeight extension method provides even more powerful functionality as the lambda allows us to perform string comparisons and more:</p>
<pre>
var data = dt.Select(g =&gt; new
{
    Year = g.year,
}).OrderByWeight(a =&gt; a.Year, x =&gt;
{
    if (x.Contains("2008")) return 1;
    if (x.Contains("2009")) return 2;
    if (x.Contains("2010") return 3;
    return 99;
});
</pre>
<p>Using the above, a non-trivial list such as {&#8216;London 2010&#8242;, &#8216;Leeds 2008&#8242;, &#8216;Cardiff 2009&#8242;, &#8216;Glasgow 2011&#8242;} can be easily sorted to {&#8216;Leeds 2008&#8242;, &#8216;Cardiff 2009&#8242;, &#8216;London 2010&#8242;, &#8216;Glasgow 2011&#8242;}</p>
<p>The extension method used to implement this functionality is:</p>
<pre>
public static IOrderedEnumerable&lt;TSource&gt; OrderByWeight&lt;TSource, TKey&gt;(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, TKey&gt; keySelector, Func&lt;TKey, int&gt; weighting) where TKey : IComparable
{
    Dictionary&lt;TSource, int&gt; order = new Dictionary&lt;TSource, int&gt;();
    foreach (TSource item in source)
    {
        if (!order.ContainsKey(item)) order.Add(item, weighting(keySelector(item)));
    }
    return source.OrderBy(s => order[s]);
}
</pre>
<p>It has not been throughly unit tested yet but it should have the same behaviour as GroupBy. Whilst I have only provided the C# implementation I&#8217;m sure it can be converted to other languages using the CLR.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/custom-sorting-order-in-linq-order-by-weighting/18/03/2009/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>String.Capitalize in C# (PHP&#8217;s ucwords in C#)</title>
		<link>http://www.daniel-skinner.co.uk/php-ucwords-in-c/25/02/2009</link>
		<comments>http://www.daniel-skinner.co.uk/php-ucwords-in-c/25/02/2009#comments</comments>
		<pubDate>Wed, 25 Feb 2009 15:10:50 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Aggeregate]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[Extension Method]]></category>
		<category><![CDATA[LINQ]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[ucwords]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/?p=31</guid>
		<description><![CDATA[I have been playing around with C# recently and found the need for functionality similar to that provided by PHP&#8217;s ucwords() function. I dont think this exists in C# as standard so here is a simple extension method to achieve ucwords in C# using a little bit of LINQ (the extremely useful Aggregate extension method) [...]]]></description>
			<content:encoded><![CDATA[<p>I have been playing around with C# recently and found the need for functionality similar to that provided by PHP&#8217;s ucwords() function. I dont think this exists in C# as standard so here is a simple extension method to achieve ucwords in C# using a little bit of LINQ (the extremely useful Aggregate extension method) to achieve it:</p>
<pre>public static string Capitalize(this String s)
{
  return s.ToCharArray().Aggregate(String.Empty,
    (working, next) =>
      working.Length == 0 &#038;&#038; next != ' ' ? next.ToString().ToUpper() : (
        working.EndsWith(" ") ? working + next.ToString().ToUpper() :
          working + next.ToString()
    )
  );
}</pre>
<p>With this included in your name space, the extension method can be used on any String instance like:</p>
<pre>string myString = "this sentence needs capitalization!";
Console.WriteLine(myString.Capitalize());
//This Sentence Needs Capitalization!</pre>
<p>Short but sweet!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/php-ucwords-in-c/25/02/2009/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Cloning Zend_Config without side-effects</title>
		<link>http://www.daniel-skinner.co.uk/cloning-zend_config-without-side-effects/26/08/2008</link>
		<comments>http://www.daniel-skinner.co.uk/cloning-zend_config-without-side-effects/26/08/2008#comments</comments>
		<pubDate>Tue, 26 Aug 2008 19:09:57 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[General]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/?p=29</guid>
		<description><![CDATA[Cloning in PHP5
PHP5 with its much improved object-oriented style passes objects by reference by default. To create a copy of an object, a new construct &#8216;clone&#8217; is introduced. Cloning an object in PHP5 creates a shallow copy &#8211; any properties that are references to other variables, will remain references. Additionally, PHP5 brings the __clone() magic [...]]]></description>
			<content:encoded><![CDATA[<h2>Cloning in PHP5</h2>
<p>PHP5 with its much improved object-oriented style passes objects by reference by default. To create a copy of an object, a new construct &#8216;clone&#8217; is introduced. Cloning an object in PHP5 creates a shallow copy &#8211; any properties that are references to other variables, will remain references. Additionally, PHP5 brings the __clone() magic method to allow you to customise the cloning behaviour of objects, making those referenced objects full copies if that is the desired behaviour.</p>
<h2>Zend_Config</h2>
<p>Zend_Config is a component of the Zend Framework. Zend_Config makes it trivial to load configuration data into PHP from a range of file types.</p>
<p>Using Zend_Config is easy:</p>
<pre lang="php">$config = new Zend_Config_Xml('/path/to/config');
echo $config->database->host;
</pre>
<p>In a particular scenario of mine I have a heirachy of config files, each inheriting data from it&#8217;s parent and overwriting keys if necessary. Merging two config files to get the resultant is also trivial:</p>
<pre lang="php">(void) $parent->merge($child);
</pre>
<p>The Zend_Config instance $parent has now been augmented with the values of the Zend_Config instance $child and keys may have been overwritten in the parent if a value for that key existed in the child.</p>
<p>Here lies my problem: What I really wanted to do was keep $parent as it was whilst creating a new instance that was the product of $parent merged with $child.</p>
<p>Easy:</p>
<pre lang="php">

$newConfig = clone $parent;
$newConfig->merge($child);
</pre>
<p>Or not&#8230; Currently Zend_Config only implements a shallow clone which effectively means my $newConfig is still tied up with $parent. In fact if I make changes to $newConfig, those changes may well be reflected in $parent. This is not the side-effect-free behaviour I expected when using clone. </p>
<p>It is true that Zend_Config is typically meant to be used in read-only situations but the power of merging has been realised and implemented, making Zend_Config often used in read-write situations aswell (here writing refers to changing the values in the instance, not the source configuration file).</p>
<p>Here is an example of the problem:</p>
<pre lang="php">

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = clone $parent;
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override'  - as expected
echo $parent->key->nested; // 'override' - I was expecting this to be 'parent'
</pre>
<p>This is actually perfectly reasonable behaviour for a shallow clone &#8211; The nested data value was cloned only by reference so (Zend_Config) $newConfig-&gt;key is actually a reference to the original (Zend_Config) $parent-&gt;key object and so changing any one will affect the other. However, this is not what I would expect to happen when cloning a Zend_Config object.</p>
<h2>Solution 1: toArray</h2>
<p>The toArray solution is simple: Flatten the Zend_Config object to a simple array and create a brand new Zend_Config object with the data. All objects are copied into the array and we get a deep copy of the object.</p>
<pre lang="php">$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = new Zend_Config($parent->toArray(), true); //cast the parent object to an array and create a new Zend_Config
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override'  - as expected
echo $parent->key->nested; // 'parent' - as expected
</pre>
<h2>Solution 2: Performing a Deep Clone</h2>
<p>OK &#8211; That&#8217;s how deep cloning is achieved with the current release but what about the problem of the misleading and unexpected operation of the clone construct? The simple and intuitive solution is to create a __clone() method that generates a full deep clone which is completely independent:</p>
<pre lang="php">

    /**
     * Perform a deep clone of this instance to allow side-effect free cloning.
     * @return void
     */
    public function __clone()
    {
        $data = array();
        foreach ($this->_data as $key => $value)
        {
            if ($value instanceof Zend_Config)
            {
                $data[$key] = clone $value;
            } else {
                $data[$key] = $value;
            }
        }
        $this->_data = $data;
    }
</pre>
<p>Now we can do a full clone intuitively without having to cast to an array first:</p>
<pre lang="php">

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = clone $parent;
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override'  - as expected
echo $parent->key->nested; // 'parent' - as expected
</pre>
<h2>Summary</h2>
<p>To summarise, cloning a Zend_Config object doesn&#8217;t have intuitive results due to shallow cloning. To get a full clone we must cast the original object to an array before creating a brand new instance. Alternatively, the Zend_Config class should implement deep cloning to get the desired and expected behaviour.</p>
<p>Test cases, the extended class, and a patch can be downloaded <a title="Zend Config Clone Test Cases" href="http://www.destiny-denied.co.uk/files/ZendConfigClone.zip">here</a> (tests work on R9566 as provided in the file).</p>
<h3>Supporting Test Cases</h3>
<pre lang="php">

/**
 * This file is part of ZendConfigClone
 *
 * @package   ZendConfigClone
 * @author    Daniel Skinner
 */
require_once dirname(__FILE__) . '/../AbstractTest.php';
/**
 */
class ZendConfigCloneTest extends AbstractTest
{
    /**
     * 0 - Use original Zend_Config object R9566
     * 1 - Use new ZendConfigClone object
     *
     */
	const VERSION = 1;
    public function setUp ()
    {
        $this->baseConfig = self::factory(array(
            'value1' => 'base1',
            'value2' => array(
                'nestedkey' => 'nestedvalue'
            )
        ), true);
    }
    /**
     * As expected, flattening the object into an array prevents any
     * references from being maintained and allows for a complete clone
     * of the Zend_Config object.
     *
     */
    public function testToArrayDoesNotKeepReferences()
    {
    	$newConfig = self::factory($this->baseConfig->toArray(), true);
    	$newConfig->value1 = 'newvalue1';

    	$this->assertNotEquals($newConfig->value1, $this->baseConfig->value1);
    	$this->assertEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * As expected, flattening the object into an array prevents any
     * references from being maintained and allows for a complete clone
     * of the Zend_Config object.
     *
     */
    public function testNestedToArrayDoesNotKeepReferences()
    {
        $newConfig = self::factory($this->baseConfig->toArray(), true);
        $newConfig->value2->nestedkey = 'newvalue1';

        $this->assertNotEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * With the current Zend_Config, a shallow clone will copy
     * first level values correctly.
     *
     */
    public function testShallowCloneDoesNotKeepReferences()
    {
        $newConfig = clone $this->baseConfig;
        $newConfig->value1 = 'newvalue1';

        $this->assertNotEquals($newConfig->value1, $this->baseConfig->value1);
    }
    /**
     * Currently cloning Zend_Config isn't side-effect free.
     *
     * A shallow clone means deep nested Zend_Config objects are
     * not cloned and changing values in the cloned object still
     * affects the original.
     *
     * This test should pass with ZendConfigClone
     *
     */
    public function testDeepCloneDoesNotKeepReferences()
    {
        $newConfig = clone $this->baseConfig;
        $newConfig->value2->nestedkey = 'newvalue1';

        $this->assertNotEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * Factory method for generating a concrete Zend_Config instance
     *
     * @param mixed $arg,... Arguments to pass to the constructor
     * @return Zend_Config
     */
    private static function factory()
    {
    	$args = func_get_args();
    	switch (self::VERSION)
    	{
    		//Zend_Config
    		case 0:
    			$refObj = new ReflectionClass('Zend_Config');
    			return $refObj->newInstanceArgs($args);
    			break;
    		//ZendConfigClone
    		case 1:
                $refObj = new ReflectionClass('ZendConfigClone');
                return $refObj->newInstanceArgs($args);
    			break;
    	}
    	throw new Exception('VERSION should be enum{0, 1}');
    }
}
</pre>
<h3>A new class with deep clone functionality for testing:</h3>
<pre lang="php">

/**
 * This file is part of ZendConfigClone
 *
 * @package   ZendConfigClone
 * @author    Daniel Skinner
 */

class ZendConfigClone extends Zend_Config
{
    /**
     * Perform a deep clone of this instance to allow side-effect free cloning.
     * @return void
     */
    public function __clone()
    {
        $data = array();
        foreach ($this->_data as $key => $value)
        {
            if ($value instanceof Zend_Config)
            {
                $data[$key] = clone $value;
            } else {
                $data[$key] = $value;
            }
        }
        $this->_data = $data;
    }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/cloning-zend_config-without-side-effects/26/08/2008/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Install phpUnderControl on CentOS 5</title>
		<link>http://www.daniel-skinner.co.uk/php-under-control-svn-centos-5/12/06/2008</link>
		<comments>http://www.daniel-skinner.co.uk/php-under-control-svn-centos-5/12/06/2008#comments</comments>
		<pubDate>Thu, 12 Jun 2008 15:19:56 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[Guides]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[phpundercontrol]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[svn]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/?p=26</guid>
		<description><![CDATA[If you follow this guide you should start with a basic CentOS 5 installation with Subversion setup correctly and end up with a working installation of phpUnderControl.]]></description>
			<content:encoded><![CDATA[<p>A while back now I started setting up a server to use as a development machine. From a standard CentOS 5 installation I have installed <a title="CentOS SVN" href="http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008">SubVersion and Trac</a>. Now I have finally got round to installing <a title="phpUnderControl" href="http://www.phpundercontrol.org/">phpUnderControl</a> for continuous integration and build management.</p>
<p>As usual, there are many great guides already on the net but getting everything to work usually requires a few of these references and a few additional changes. I will point out where I got the information from where applicable but the aim of this guide is to be complete and comprehensive.</p>
<p>If you follow this guide you should start with a basic <a title="CentOS 5 SVN and Trac" href="http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008">CentOS 5 installation with Subversion</a> setup correctly and end up with a working installation of phpUnderControl.</p>
<p><span id="more-26"></span></p>
<h2>The Environment</h2>
<p>I am aiming for the following:</p>
<ul>
<li>CentOS 5 with SubVersion installed</li>
<li>PHP 5.2.X installed</li>
<li>CruiseControl binary installed, running on Sun JDK 6</li>
<li>phpUnderControl installed</li>
<li>A working build process for the example project</li>
</ul>
<h2>Here it is&#8230;</h2>
<h3>Upgrade CentOS 5 with PHP5.2</h3>
<p>To get PHP5.2 installed on CentOS 5 (required for phpUC) we need to add an additional repository that has it. I am going to use the <a href="http://www.jasonlitka.com/yum-repository/">Yum Repository</a> provided by <a href="http://www.jasonlitka.com/">Jason Litka</a>. Detailed instructions can be found at his site. In summary, for CentOS 5 this consists of:</p>
<pre>rpm --import http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka
vi /etc/yum.repos.d/utterramblings.repo</pre>
<p>Add the following lines:</p>
<pre>[utterramblings]
name=Jason's Utter Ramblings Repo
baseurl=http://www.jasonlitka.com/media/EL$releasever/$basearch/
enabled=1
gpgcheck=1
gpgkey=http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka</pre>
<p>Now Yum can update PHP to 5.2. We also need a few other dependencies for phpUC:</p>
<pre>yum install -y php php-pear php-xml</pre>
<p>And the latest version of pear:</p>
<pre>pear channel-update pear.php.net
pear upgrade pear</pre>
<h3>Get phpUnderControl</h3>
<p>Using pear to install phpUC:</p>
<pre>pear config-set preferred_state beta
pear channel-discover components.ez.no
pear channel-discover pear.phpunit.de
pear install --alldeps phpunit/phpUnderControl</pre>
<p>This will install phpUC and all required dependencies. It&#8217;s a good idea to set pear back to only installing stable (i.e. non beta) packages:</p>
<pre>pear config-set preferred_state stable</pre>
<h3>Sun Java JDK 6</h3>
<p>phpUC needs CruiseControl and CruiseControl needs Sun JDK. Again, a full guide for getting this done can be found on the <a title="Java on CentOS" href="http://www.jasonlitka.com/">CentOS wiki</a>. The steps I took are:</p>
<p>Set up rpmbuild:</p>
<pre>vi ~/.rpmmacros</pre>
<p>Make sure the file contents is:</p>
<pre>%_topdir /home/&lt;username&gt;/rpmbuild
%_tmppath %{_topdir}/tmp</pre>
<p>with &lt;username&gt; replaced with your logon name (if you are using root the path would be /root/rpmbuild).</p>
<pre>mkdir -p ~/rpmbuild/{SOURCES,SRPMS,SPECS,RPMS,tmp,BUILD}
yum install -y rpm-build gcc gcc-c++ redhat-rpm-config jpackage-utils</pre>
<p>Download JDK6 Update 6 (or the latest version) from <a href="http://java.sun.com">java.sun.com</a>. Get the .rpm.bin version. The file I got was called jdk-6u6-linux-i586-rpm.bin.</p>
<pre>chmod +x jdk-6u6-linux-i586-rpm.bin
./jdk-6u6-linux-i586-rpm.bin
wget http://mirrors.dotsrc.org/jpackage/1.7/generic/non-free/RPMS/java-1.6.0-sun-compat-1.6.0.06-1jpp.i586.rpm
rpm -Uvh jdk-6u1-linux-i586.rpm java-1.6.0-sun-compat-1.6.0.01-1jpp.i586.rpm</pre>
<p>With the JDK successfully installed (hopefully) we use Alternatives to select the Sun JDK:</p>
<p>/usr/sbin/alternatives &#8211;config java</p>
<p>Make sure the selected version is along the lines of /usr/lib/jvm/jre-1.6.0-sun/bin/java.</p>
<p>At this point java -version should give something like:</p>
<pre>java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) Client VM (build 10.0-b22, mixed mode, sharing)</pre>
<p>Now to add some environment variables:</p>
<pre>vi /etc/profile.d/java.sh</pre>
<p>Add the following:</p>
<pre>export JAVA_HOME="/usr/java/jdk1.6.0_06"
export JAVA_PATH="$JAVA_HOME"
export PATH="$PATH:$JAVA_HOME"</pre>
<p>Note that JAVA_HOME is not set to &#8220;/usr/java/jdk1.6.0_06/bin&#8221;. I don&#8217;t think this is the standard but it&#8217;s an easy way to get CruiseControl to work (<a href="http://www.centos.org/modules/newbb/viewtopic.php?viewmode=flat&amp;topic_id=5717&amp;forum=28">see here</a>).</p>
<p>You will also need to restart your session for these changes to take effect or just export the variables directly to the prompt.</p>
<h3>Get CruiseControl</h3>
<p>Download the latest <a title="CruiseControl" href="http://sourceforge.net/project/showfiles.php?group_id=23523&amp;package_id=16338">2.7.X CruiseControl binary</a>. My version is cruisecontrol-bin-2.7.2.zip.</p>
<pre>unzip cruisecontrol-bin-2.7.2.zip -d /opt
ln -s /opt/cruisecontrol-bin-2.7.2/ /opt/cruisecontrol</pre>
<p>CruiseControl is bow installed in /opt/cruisecontrol-bin-2.7.2/ which is symlinked by /opt/cruisecontrol/</p>
<h3>Setup phpUnderControl and CruiseControl</h3>
<p>A more thorough guide can be found in the <a title="phpUnderControl" href="http://www.phpundercontrol.org/documentation.html">phpUnderControl Documentation</a>.</p>
<p>To patch the CruiseControl installation you should be able to simply to:</p>
<pre>phpuc install /opt/cruisecontrol</pre>
<p>If not, try this:</p>
<pre>cd /opt/
<code>svn co svn://www.phpunit.de/browser/phpUnderControl/tags/0.4.2 phpundercontrol
phpundercontrol/bin/phpuc.php install /opt/cruisecontrol</code></pre>
<p>And that&#8217;s pretty much it! I&#8217;ll just set up the example project so you can see it all in action:</p>
<pre>phpuc example /opt/cruisecontrol</pre>
<p>And to start cruisecontrol:</p>
<pre>cd /opt/cruisecontrol/
./cruisecontrol.sh</pre>
<p>Give it a while to setup. Once it&#8217;s all done you should be able to access the interface at:</p>
<p>http://localhost:8080/</p>
<p>By now you should have a working phpUC setup on your CentOS 5 machine and the example project failing the build process. Setting up your own projects is a bit more involved &#8211; follow the guide in the documentation for more details. As for the best way to make use of phpUC and optimal project layouts etc. I will have to get back to you as I haven&#8217;t yet played around much.</p>
<p>Please let me know if this helped you. If you come across any problems I will be happy to try and help.</p>
<h2>Resources</h2>
<ul>
<li>Setting up <a title="CentOS 5 SVN and Trac" href="http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008">S</a><a title="CentOS 5 SVN and Trac" href="http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008">VN (and optionally Trac) on CentOS 5</a></li>
<li>Alternative <a title="Install PHPUnderControl on CentOS 5.1" href="http://f13o.blogspot.com/2008/06/install-phpundercontrol-on-centos-51.html">PHP5.2 and phpUC Setup</a></li>
<li><a title="Yum Repository" href="http://www.jasonlitka.com/yum-repository/">Jason Litka&#8217;s Yum Repository</a></li>
<li>Detailed guide to installing <a title="Java on CentOS" href="http://wiki.centos.org/HowTos/JavaOnCentOS">Java on CentOS</a></li>
<li><a href="http://www.phpundercontrol.org/documentation.html">phpUnderControl documentation</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/php-under-control-svn-centos-5/12/06/2008/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>MooTools and Swiff: Slideshow / Thumbnail Gallery</title>
		<link>http://www.daniel-skinner.co.uk/mootools-and-swiff-slideshow-thumbnail-gallery/17/05/2008</link>
		<comments>http://www.daniel-skinner.co.uk/mootools-and-swiff-slideshow-thumbnail-gallery/17/05/2008#comments</comments>
		<pubDate>Sat, 17 May 2008 20:05:23 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mootools]]></category>
		<category><![CDATA[slideshow]]></category>
		<category><![CDATA[swf]]></category>
		<category><![CDATA[swiff]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/mootools-and-swiff-slideshow-thumbnail-gallery/17/05/2008</guid>
		<description><![CDATA[I have recently launched a website for the Bramham Lodge Residential Development in Bramham Village.
The site features a neat Slideshow / Thumbnail Gallery implementation using MooTools and Flash (utilising the Swiff utility of MooTools 1.2).
A demo can be seen on the Bramham Lodge Apartment page.
The SlideShow makes use of Flash to create easily customisable image [...]]]></description>
			<content:encoded><![CDATA[<p>I have recently launched a website for the <a href="http://www.wbaileybramham.co.uk" title="Bramham Lodge Luxury Gated Housing">Bramham Lodge Residential Development</a> in <a href="http://www.wbaileybramham.co.uk/bramham-village/" title="Bramham Village, West Yorkshire">Bramham Village</a>.</p>
<p>The site features a neat Slideshow / Thumbnail Gallery implementation using MooTools and Flash (utilising the Swiff utility of MooTools 1.2).</p>
<p>A demo can be seen on the <a href="http://www.wbaileybramham.co.uk/bramham-lodge-plots/plot1/" title="Bramham Lodge Historical Listed Building Renovation">Bramham Lodge Apartment page</a>.</p>
<p>The SlideShow makes use of Flash to create easily customisable image transition effects that are not possible with JavaScript alone. A collection of MooTools classes are employed to mange the Swiff interface and control the slideshow.</p>
<p><strong>Key Features</strong></p>
<ul>
<li>Images are not hard-coded into flash: no need to recompile the SWF when  adding/removing or re-ordering the gallery as all data is presented in the <acronym title="eXtensible HyperText Markup Language">XHTML</acronym> markup.</li>
<li>Extensible and customisable: The slideshow class makes use of MooTools Events so that add-on classes can be created for bolt-on features whilst being kept separate from the core functionality.</li>
<li>Accessible: No content is hard-coded into flash, search engines are able to index all the information.</li>
</ul>
<p>The modular nature of the code and the ability to change the implementation using bolt-on functionality has allowed me to re-use the same set of classes on an upcoming project in a completely different fashion, making use of the great features of Flash without sacrificing indexable content and accessibility.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/mootools-and-swiff-slideshow-thumbnail-gallery/17/05/2008/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Hair by Caroline</title>
		<link>http://www.daniel-skinner.co.uk/hair-by-caroline/14/04/2008</link>
		<comments>http://www.daniel-skinner.co.uk/hair-by-caroline/14/04/2008#comments</comments>
		<pubDate>Mon, 14 Apr 2008 23:05:39 +0000</pubDate>
		<dc:creator>Emma Place</dc:creator>
				<category><![CDATA[General]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/hair-by-caroline/14/04/2008</guid>
		<description><![CDATA[Hair by Caroline first launched back in 2005&#8230;three years down the line and we decided it was in great need of a re-design. It was also important to update the content, adding more material and creating a new gallery was among the important factors to consider.
15thApril 2008 has been re-launched, with a new and improved [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.hairbycaroline.co.uk" title="Hair Salon Castleford">Hair by Caroline</a> first launched back in 2005&#8230;three years down the line and we decided it was in great need of a re-design. It was also important to update the content, adding more material and creating a new gallery was among the important factors to consider.</p>
<p>15thApril 2008 has been re-launched, with a new and improved navigation, modern images and a whole lot more content to help improve its ranking in the search engines.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/hair-by-caroline/14/04/2008/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>External CSS won&#8217;t load in IE7</title>
		<link>http://www.daniel-skinner.co.uk/external-css-wont-load-in-ie7/05/04/2008</link>
		<comments>http://www.daniel-skinner.co.uk/external-css-wont-load-in-ie7/05/04/2008#comments</comments>
		<pubDate>Sat, 05 Apr 2008 02:04:29 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[charset]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[encoding]]></category>
		<category><![CDATA[ie7]]></category>
		<category><![CDATA[utf8]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/external-css-wont-load-in-ie7/05/04/2008</guid>
		<description><![CDATA[I just spent a while wondering why IE7 wouldn&#8217;t make use of an external stylesheet when it worked in Firefox no problems.
The page is in UTF-8 and the .css file was also encoded in UTF-8 but IE7 simply refused to render the page using the CSS rules.
I have no idea  what the problem is but [...]]]></description>
			<content:encoded><![CDATA[<p>I just spent a while wondering why IE7 wouldn&#8217;t make use of an external stylesheet when it worked in Firefox no problems.</p>
<p>The page is in UTF-8 and the .css file was also encoded in UTF-8 but IE7 simply refused to render the page using the CSS rules.</p>
<p>I have no idea  what the problem is but if you are having similar issues try putting <strong>@charset “UTF-8″;</strong> at the top of your external stylesheet &#8211; it worked for me.</p>
<p>Does anyone know what the underlying issue is?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/external-css-wont-load-in-ie7/05/04/2008/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Link Targets in XHTML</title>
		<link>http://www.daniel-skinner.co.uk/link-targets-in-xhtml/15/03/2008</link>
		<comments>http://www.daniel-skinner.co.uk/link-targets-in-xhtml/15/03/2008#comments</comments>
		<pubDate>Sat, 15 Mar 2008 13:33:56 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[Guides]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mootools]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/link-targets-in-xhtml/15/03/2008</guid>
		<description><![CDATA[I always prefer to build websites in valid XHTML as it allows for a stricter, more maintainable envorinment to build websites than traditional HTML. However, there are a few features of HTML I like to make use of.
For example, making links open in new windows using the target attribute is unsupported by XHTML.

There are two [...]]]></description>
			<content:encoded><![CDATA[<p>I always prefer to build websites in <strong>valid <acronym title="eXtensible HyperText Markup Language">XHTML</acronym></strong> as it allows for a stricter, more maintainable envorinment to build websites than traditional <acronym title="HyperText Markup Language">HTML</acronym>. However, there are a few features of HTML I like to make use of.</p>
<p>For example, making links open in new windows using the <code>target</code> attribute is unsupported by XHTML.</p>
<p><span id="more-14"></span></p>
<p>There are two solutions I know of to get around this:</p>
<h4>1. JavaScript</h4>
<p>The DOM specification allows the use of the <code>target</code> attrbriute. So, mark each link you want to open in a new window using <code>rel="external"</code>. When the page loads use JavaScript to find all these links and add a <code>target="_blank"</code> to each using DOM.</p>
<p>In <strong>MooTools</strong> the code would look like:</p>
<pre type="javascript">
window.addEvent('domready', function() {
    // rel="external" links open in new window
    $$('a[href][rel="external"]').each(function(anchor) {
        anchor.setProperty('target', '_blank');
    });
});</pre>
<p>For a raw JavaScript solution and more information on this approach see this <a href="http://www.sitepoint.com/article/standards-compliant-world" title="Sitepoint Article: New Windows for links in XHTML" rel="nofollow external">Sitepoint article</a>.</p>
<h4>2. Extending XHTML</h4>
<p>Simply extend XHTML to support the <code>target</code> attribute and continue using <code>target="_blank"</code> whilst still producing perfectly valid XHTML!</p>
<p>Essentially you extend the default XHTML <acronym title="Document Type Definition">DTD</acronym> to enable the XHTML Target Module (which is not enabled by default) and then modify the page DOCTYPE definition to point to the extended <acronym title="Document Type Definition">DTD</acronym>.</p>
<p>The full details and how to implement it can be found here at <a href="http://www.texastar.com/tips/2004/target_blank.shtml" title="TexaStar: XHTML Modularization" rel="nofollow external">TexaStar</a>.</p>
<p>Currently I am using the JavaScript workaround. However, modifying the XHTML definition to make it work for me is a very elegant solution that I would look to implement in the future.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/link-targets-in-xhtml/15/03/2008/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Yicrosoft Directory Competition</title>
		<link>http://www.daniel-skinner.co.uk/yicrosoft-directory-competition/09/02/2008</link>
		<comments>http://www.daniel-skinner.co.uk/yicrosoft-directory-competition/09/02/2008#comments</comments>
		<pubDate>Sat, 09 Feb 2008 00:20:20 +0000</pubDate>
		<dc:creator>Emma Place</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[yicrosoft]]></category>
		<category><![CDATA[yicrosoft directory]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/yicrosoft-directory-competition/09/02/2008</guid>
		<description><![CDATA[Ryan at SEO Noobs have hosted a competition, which many of you are probably aware of by now. The key word is “Yicrosoft Directory” which is a word which had zero results in the Google search when the competition started; it’s also a word that has been put together in the light of Microsoft’s $44.6 [...]]]></description>
			<content:encoded><![CDATA[<p>Ryan at <a href="http://seonoobs.com/" rel="external nofollow">SEO Noobs</a> have hosted a competition, which many of you are probably aware of by now. The key word is “<strong><a href="http://yicrosoftdirectory.com" rel="external">Yicrosoft Directory</a></strong>” which is a word which had zero results in the Google search when the competition started; it’s also a word that has been put together in the light of <a href="http://yicrosoftdirectory.com/2008/microsoft-and-yahoo-merge/" rel="external">Microsoft’s $44.6 billion bid to take over Yahoo</a>. All the competitors have until 4<sup>th</sup> March 2008 to produce a website to appear at number one in Google.com search engine rankings. I decided to take part in this competition, not only because it is interesting but to also give myself the opportunity to create and optimise the content of a website the best I can. I guess this is really going to test my skills as a writer! If I can aim to post something remotely interesting daily, perhaps I could get the site to page one! Who knows, but as time goes on more and more people are entering the competition, the Google search alone now holds nearly 2,000 results for “<strong><a href="http://yicrosoftdirectory.com" rel="external">Yicrosoft Directory</a></strong>”. Only time will tell! Of course everyone is in it to win but in all honesty if our website makes it to number one in the Google search I will be more than happy. I’ll keep you all posted on the progression.</p>
<p>I decided to create a Yahoo Directory parody website: <strong><a href="http://yicrosoftdirectory.com" rel="external">http://yicrosoftdirectory.com/</a></strong>. At this point I would like to invite everyone and anyone to submit any type of original, written content to help broaden the horizons! Your help would be much appreciated. If you don’t have anything to say why not come along and vote on the poll anyway.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/yicrosoft-directory-competition/09/02/2008/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Chaining with MooTools 1.2 &#8211; Tutorial</title>
		<link>http://www.daniel-skinner.co.uk/chaining-with-mootools-12-tutorial/31/01/2008</link>
		<comments>http://www.daniel-skinner.co.uk/chaining-with-mootools-12-tutorial/31/01/2008#comments</comments>
		<pubDate>Thu, 31 Jan 2008 22:30:11 +0000</pubDate>
		<dc:creator>Daniel Skinner</dc:creator>
				<category><![CDATA[Guides]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[chain]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mootools]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://www.daniel-skinner.co.uk/chaining-with-mootools-12-tutorial/31/01/2008</guid>
		<description><![CDATA[A short tutorial on how to use Chaining in MooTools.]]></description>
			<content:encoded><![CDATA[<p>This guide will show how powerful the <strong>MooTools Chain class</strong> is. In MooTools chaining facilitates the execution of a stack of functions sequentially and is extremely powerful. I have only tested this in <strong>MooTools v1.2 beta 2</strong>.</p>
<p>I will be posting more of these short guides on using MooTools in the upcoming weeks. Each will focus on a small problem and solve it with MooTools with the aim of being a useful way to learn the framework.</p>
<p><span id="more-15"></span></p>
<h3>Chaining you say!</h3>
<p><strong>Chaining</strong> is very powerful and whilst it is similar to <code>Effect.Queue</code> in Script.aculo.us, it can do much more! The aim of this brief tutorial is to show you how to use <strong>MooTools Chaining</strong> in the context of queueing effects coupled with other arbitrary actions. However, you will see that you can easily apply Chaining to do much more than that.</p>
<p>From the MooTools 1.2 Docs:</p>
<blockquote><p>A Utility Class which executes functions one after another, with each function firing after completion of the previous. Its methods can be implemented with <code><a href="http://docs12b.mootools.net/Class/Class/#Class:implement" title="MooTools Documentation: Class:implement" rel="external nofollow">Class:implement</a></code> into any <code><a href="http://docs12b.mootools.net/Class/Class" title="MooTools Documentation: Class" rel="external nofollow">Class</a></code>, and it is currently implemented in <code>Fx</code> and <code>Request</code>. In <code>Fx</code>, for example, it is used to create custom, complex animations.</p></blockquote>
<p><code>Chain</code> can be used as a stand-alone class but becomes much more powerful if you implement it into classes of your own.</p>
<p>In this tutorial I will be creating a class which implements <code>Chain</code>. The <code>Chain</code> class belongs to the <code>Class.Extras</code> component so you will need to make sure that you select <code>Class.Extras</code> and all its dependencies when downloading MooTools.</p>
<h3>Implementing Chain</h3>
<p>First of all we need to implement the methods and properties of the <code>Chain</code> class into our own class:</p>
<pre lang="javascript">var ChainExample = new Class({

  Implements: [Chain]

});</pre>
<p>Our class now has the following methods available to it:</p>
<ul>
<li><strong><code>Chain::chain</code></strong> &#8211; Pass any number of functions to add them to the bottom of the call stack.</li>
<li><strong><code>Chain::callChain</code></strong> &#8211; Pops a function off the top of the call stack and executes it.</li>
<li><strong><code>Chain::clearChain</code></strong> &#8211; Removes all functions from the call stack without executing any.</li>
</ul>
<h3>Using Chain to Execute Actions in Order</h3>
<p>Now we have implemented <code>Chain</code> into our <code>ChainExample</code> class we are ready to make use of the functionality it provides.</p>
<p>The <code>ChainExample</code> class will add events to three button elements. When a button element is clicked a corresponding panel will appear. Before this happens all other panels will be faded out. Chaining is used to make sure that these events happen in the order we want.</p>
<p>The full code for <code>ChainExample</code> is:</p>
<pre lang="javascript">
/**

 * Add events to buttons. Clicking a button will hide all panels before showing the panel corresponding to that button

 */

var ChainExample = new Class({

    Implements: [Chain],

    /**

     * Define the element ID of the button and the element ID of the corresponding panel

     */

    actions: new Hash({

        'button-one': 'panel-one',

        'button-two': 'panel-two',

        'button-three': 'panel-three'

    }),

    /**

     * An Array to store an effect instance for each panel

     */

    effects: [],

    initialize: function()

    {

        /**

         * Add an onclick event to each button. Clicking a button calls the showPanel method

         */

        this.actions.getKeys().each(function(buttonId) {

            $(buttonId).addEvent('click', this.showPanel.bindWithEvent(this));

        },this);

        /**

         * Create an Fx object for each panel

         */

         this.actions.getValues().each(function(panelId) {

            this.effects[panelId] = new Fx.Tween($(panelId), 'opacity', { duration: 'short', onComplete: function() { this.callChain();}.bind(this)});

         }, this);         /**

          * Initialize by hiding all panels, note the call to callChain to cause stuff to happen

          */

        this.hideAll();

        this.callChain();

    },

    /**

     * Add the a actions required to hide all panels to the Chain call stack

     */

    hideAll: function()

    {

        /**

         * loop each panel and Chain: 1. fade the panel, 2. set the display property to "none" after the effect has finished.

         *

         * Note that this function does not actually cause anything to happen, it simply adds actions to the Chain

         */

        this.actions.getValues().each(function(panelId) {

            this.chain(

                function() { this.effects[panelId].start(0); },

                function() { $(panelId).setStyles({'display': 'none'}); this.callChain(); }

            );

        },this);

    },

    /**

     * Handle a button click by fading and hiding all open panels and then appearing the corresponding panel

     */

    showPanel: function(event)

    {

        this.hideAll();

        var panel = this.actions.get(event.target.get('id'));

        this.chain(

            function() { $(panel).setStyles({'display': 'block', 'opacity': '0'}); this.callChain(); },

            function() { this.effects[panel].start(1); }

        );

        this.callChain(); //this call starts the chain. Since each function in the call also makes a call to callChain the entire stack will be executed

    }

});

window.addEvent('domready',

    function()

    {

        var myChain = new ChainExample();

    }

);</pre>
<p>I will only go through the code relevant to chaining in detail. Lets have a look at each important part:</p>
<h4><code>ChainExample::initialize</code></h4>
<p>In the constructor we bind behaviours to the buttons and create <code>Fx</code> objects for the panels. Notice the custom <code>onComplete</code> callback provided for each <code>Fx</code> instance. This tells the internal chain stack to pop the next function and run it right after the effect has finished. We are now able to execute any function as soon as the effect completes.</p>
<h4><code>ChainExample::hideAll</code></h4>
<p>Look at this method carefully. A call to <code>ChainExample::hideAll()</code> does not actually hide the panels. The method adds to the chain stack a set of functions that will fade and hide each panel in order. To get the panels to actually hide we must execute the chain stack using <code>this.callChain()</code>.</p>
<p>Notice how the second function passed to <code>this.chain</code> invokes <code>this.callChain()</code>. The function is telling the chain stack to continue onto the next function by itself.</p>
<p>Another important thing to note is that there is no need to bind the function passed to <code>this.chain</code> as this is dealt with internally.</p>
<h4><code>ChainExample::showPanel</code></h4>
<p>This is the event handling function that will show a panel.</p>
<p>First off a call to <code>ChainExample::hideAll()</code> is made. Remember that this method doesn&#8217;t cause anything to happen immediately. At this point we have added all the steps needed to hide all the panels to the chain stack. We then proceed to add steps which will show the correct panel.</p>
<p>Once the entire chain stack is set up <code>this.callChain()</code> is executed and the entire stack will be called because each step makes its own call to <code>this.callChain()</code> once it has finished.</p>
<p>That&#8217;s the basics of <code>Chain</code>. We have created a class which allows us to combine any number of effects and arbitrary functions and ensure that the are executed in the order we want. Hence the basic idea of <code>Chain</code> is to create a stack of functions and execute them in the order you please whenever you please.</p>
<h3>Downloadable Demo</h3>
<p>I have put together a quick demo so that you can see the code in action. Download the demo <a href="http://www.destiny-denied.co.uk/files/moo-chain.zip">here</a>.</p>
<h3>Further Considerations</h3>
<p>In my example, every function in the chain stack makes a call to <code>this.callChain()</code> once is has completed.  This means that as soon as the first function is executed (by manually invoking <code>this.callChain()</code>) the entire stack will run from start to finish automatically. <code>Chain</code> can be used in a different fashion where <code>Chain::callChain()</code> is always invoked externally. As a crude modification to my example, every click on a button could invoke <code>this.callChain()</code>. Thus each button click takes one further step to completion of the actions.</p>
<p>If you need to add timings and delays consider using <code><a href="http://docs12b.mootools.net/Native/Function#Function:delay" title="MooTools Documentation: Function:Delay" rel="external nofollow">Function:delay</a></code> to delay the invocation of <code>Chain::callChain()</code>.</p>
<p>It is also possible to use multiple chains in the same class for even more complex behaviour. This would involve using separate instances of the Chain class directly.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.daniel-skinner.co.uk/chaining-with-mootools-12-tutorial/31/01/2008/feed</wfw:commentRss>
		<slash:comments>31</slash:comments>
		</item>
	</channel>
</rss>
