THE BLOG

05
Mar

Soapbox: Native vs Web-Based Applications

As you’re probably aware, I develop mainly for the web. I love developing Web Applications – the technologies are great, it’s easy to use a blend of technologies, deployment is simple and they can provide a fantastic user experience. However, I also do quite a bit of native development in Objective-C and Cocoa, which I also love – the applications are quick, they fit in to your OS environment, and you can take advantage of really low-level APIs for threading etc. There are hundreds of debates on the web where web developers and native developers try and convince each other that their technique is best: this isn’t one of those debates.

This post was inspired by a talk at devnest this past Tuesday, where someone gave a presentation on developing mobile applications with jQuery Mobile. It was very interesting – I think jQuery Mobile is a fantastic framework, and it really simplifies the process of turning your web application into something that people can use on-the-go with their mobile devices. However, this person then introduced a piece of software called PhoneGap, which allows you to take an application you’ve developed in HTML/CSS/JS, offers you access to the low-level APIs, and lets you deploy it across multiple platforms as a native application.

Don’t get me wrong, jQuery Mobile looks great and works really well – but put it alongside a native application on an iPhone, for instance, and suddenly you see that the jQuery Mobile app actually is slow as a pig and looks out-of-place. That’s the sort of user experience I’d expect from a web app, but I’d expect a load more from a native application, especially if I’d paid money for it.

At work, I’ve been doing a load of research into different technologies and how we could utilise them in our roadmap. One thing I’m continually pushing for is using the best tool for the job. If it turns out that PHP is the best solution, great, but if it turns out that Python is the best solution, and you don’t know Python, you better get hold of some manuals and start learning. If you want to develop a mobile application, the device’s native language is the best solution. If you want to deploy across multiple platforms, and don’t need access to the device’s APIs, then develop a web app. If you do need access to the APIs, you’ve got a lot of work to do.

25
Feb

Commuter Sunrise

Pretty soon I’ll be giving up my daily Leamington Spa to London commute (more on that in a later blog post), so I thought I’d share this picture I took at the station this morning to show that it does have some nice moments.

09
Feb

Clock of Clocks

Clock of Clocks

On a recent trip to London my girlfriend and I visited the Saatchi Gallery (superb gallery, you must go if you’re in the area), where I was mesmerised by a piece in their store called “The Clock Clock” by humans since 1982. Check out this video of it, it’s amazing. I thought that it would make a really cool little HTML5 experiment, so I gave it a go.You can view it here, but you’d do well to fire up the latest version of Chrome, Safari or Firefox first.

I’ve reached out to humans since 1982 to see what they think of it, but haven’t heard anything back yet.

Enjoy!

http://clock.imaginaryroots.co.uk/

05
Feb

Joining the idio team

idioIt was announced last week that the company I work for, thrudigital, had been acquired by idio, a company we’d had a close relationship with for some time. As a result, I, along with the rest of thrudigital, will be joining the idio development team as their “London office”. Primarily, my work for idio will focus on their platform, which deals with content aggregation, analytics, personalisation and distribution, and making improvements to help it scale. Very exciting stuff, so look out for some scalability related topics on here in the near future!

20
Jan

CakePHP routing with slugs and actions

Here’s a quick link to a post I put up on the thrudigital blog the other day, regarding a little technique I came up with to allow CakePHP to disambiguate between slugs and controller actions in routes. You can check the post out here. Enjoy!

07
Jan

Getting the most of PHP’s date() and strftime() functions

I posted on our company blog today about a CakePHP helper I wrote which allows you to make the most of the PHP date() and strftime() methods in one function. You can check out the blog post here, or grab the source code on Github.

06
Dec

Total Adsense Mastery

James CaanFor a period of over a month during October and November 2010, I was stalked across the internet by none other than James Caan, the multi-millionaire businessman and Dragon. He was advertising a one-day seminar entitled “James Caan’s Total Business Mastery” through a company called EBA which took place on November 27th. During the stalking period, almost any site I browsed which served up Google Ads (of which there are one or two) would display James’ smiling face, inviting me to purchase a ticket so I could learn to be more entrepreneurial. Sometimes there were as many as 3 of these adverts on a single page.

I noticed the trend sometime in early October, and made many comments about it in the office. It became something of an internal meme, and I assumed it would pass soon. In late October, it was apparent that the ad was sticking with me for a while, so I began to keep a tally of how many I saw in a day. Obviously, this tally is dependent on a number of factors, mostly how much I browsed the net that day (which isn’t necessarily a direct correlation to the amount of work I got done that day, of course…), and days I wasn’t in the office since I only saw them on my work computer, but it still makes for (vaguely) interesting statistics. A more interesting statistic would have been the ratio of pages which served Google Ads to the number of those which served James Caan adverts, but hey, I’m not made of tally charts, am I?

Stop, number time!

Between October 21st and November 26th 2010, I recorded 127 individual sightings of adverts for James Caan’s Total Business Mastery, averaging 4 a day, or 5 per work day. The most I saw in a single day was 16, and the minimum (not counting the rare days that I saw none) was 1. The mode of the dataset was 3, and the median was 3.5.

Enough stats already, give me a freakin’ graph! OK.

All this has made me wonder – is this normal? I can’t help but think that it is. Perhaps normally I simply ignore it, and that it was only James Caan’s happy smiling face that made me notice this particular ad campaign. I haven’t noticed any more Adsense stalkings since this one, but, as I say, I may be immune to them unless they have pictures of James Caan on them. Which leads me to wonder – is it only James Caan’s face I’ll respond to? Google, in the interests of science, can you serve me up some ads with Tina Fey on them? Thanks muchly.

As an aside, did anyone actually go to the seminar? Was it any good? I can’t help but think that it undersold, since the ads proclaimed ‘limited space available!’ yet they served me ads up until the day before the event.

Update: Turns out he’s doing another one in January, and I’m seeing adverts again. I’m taking tallies, so expect another update soon.

21
Nov

Words are funny, right?

Today I’ve hacked together and launched my own Twitter-bot, @chinesetwispers. The basic premise is that it finds tweets with certain words in them, replaces those words for other words which either sound like the word or are generally hilarious replacements, and then retweets it at the original author. I’m looking forward to seeing what it comes out with – hopefully no-one will get too offended!

As well as that, I’ve done some work on Twipestry, so it’s now a load more stable and produces more relevant results. As well as that it now uses the search API, meaning instead of just searching for users you can also do keyword-based searches as well. Lovely, no?

So that’s what I’ve been up to today. Other than that, I’ve been doing some work on DoodleDesk, trying to get it ready for release sometime soon, and also been working on Fretless on and off. Hopefully I’ll find a weekend or two in the near future to get both of these ready for a proper release. We’ll see…

31
Oct

Using CakePHP’s finderQuery Attribute For Complex Associations

I had some fun (“fun”) on a project the other day when I had to write a rather complex has-and-belongs-to-many association for a model in CakePHP. The basic scenario was this:

  • There are ‘Meetings’, which are associated with many People (and many People can be associated with many Meetings, but that’s not important here)
  • Each meeting has many ‘Conversations’, which currently are assumed to involve everyone in the Meeting

My task was to alter this model so as to cater for Conversations that only involve a subset of the People involved in the Meeting. For backwards compatibility, and to save on space (although admittedly the join table wouldn’t have exactly been huge), we decided to go with the following model:

  • We would create a ‘ConversationPerson’ model, which would obviously store the people involved in a conversation, but…
  • If a Conversation has no records in the join table, then the People involved in the Conversation is assumed to be all the people in the Meeting.

(Does that make sense?)

This is a (relatively) easy query to construct, so we decided to use a single query to define the association and then use the finderQuery parameter to tell Cake how to get the data. Our Conversation model then looked a bit like this:

<?php
class Conversation extends AppModel {
	...
	$hasAndBelongsToMany = array(
		'Person' => array(
			'finderQuery' =>
				'SELECT `ConversationPerson`.`id` AS `conversation_id`, 
						`Person`.* 
				 FROM 	`people` AS `Person`, 
						`meeting_people` AS `MeetingPerson`, 
						// Left join conversations with conversation_person
						// so we don't lose any conversations if there are 
						// no rows in conversation_people, and so we don't
						// get extra rows if there are
						`conversations` AS `Conversation` 
						LEFT JOIN 
						`conversation_people` AS `ConversationPerson` 
						ON 
						`Conversation`.`id` = `ConversationPerson`.`conversation_id`,
						// Count the number of associations in the
						// conversation_people table
						(SELECT COUNT(*) AS count 
						FROM `conversation_people` AS `CountConversationPeople` 
						WHERE `CountConversationPeople`.`conversation_id` = {$__cakeID__$}) 
						AS `Count`
						// This mimics the operation of an
						// if(count == 0){...}else{...}
						// If there are no associations in the conversation_person
						// table, grab the people from the meeting_person table
				 WHERE (`Count`.`count` = 0 AND 
						`Conversation`.`id` = {$__cakeID__$} AND 
						`Person`.`id` = `MeetingPerson`.`person_id` AND 
						`Conversation`.`meeting_id` = `MeetingPerson`.`meeting_id`)
						// If there are associations in the conversation_person
						// table, use those to define the people in the conversation
					OR (`Count`.`count` > 0 AND 
						`Conversation`.`id` = {$__cakeID__$} AND 
						`MeetingPerson`.`meeting_id` = `Conversation`.`meeting_id` AND 
						`MeetingPerson`.`person_id` = `ConversationPerson`.`person_id` AND 
						`ConversationPerson`.`person_id` = `Person`.`id`)'
		)
	);	
	...
}
?>	

This seemed alright, but Cake was throwing me error after error, and it took me a while to figure out what was going on. It turns out that when you perform a HABTM find in Cake, it expects to always see a join table. This is especially apparent when you select a group of rows, since it then uses the values in the join table to determine which row is associated with which model.

In the example above, Cake was expecting to always see two fields, one called ConversationPerson.conversation_id and another called ConversationPerson.person_id. Obviously, in the cases where the association exists in the join table, this is fine. However, no associations exist and we grab the data from the MeetingPerson table, Cake is unhappy. Unfortunately, Cake isn’t clever enough to look for those ids (conversation_id and person_id) in the other fields it returns, it explicitly wants them to come from a table called ConversationPeople.

Obviously, the solution is to use the query we created above to construct the join table, and then select from that. It is a bit frustrating that there isn’t a neater solution to this, but there we go. So, our Conversation model looked like this:

<?php
class Conversation extends AppModel {
	...
	$hasAndBelongsToMany = array(
		'Person' => array(
			'finderQuery' =>
				'SELECT `Person`.*, `ConversationPerson`.*
				FROM `people` AS `Person`,
					(SELECT `ConversationPerson`.`id` AS `conversation_id`, 
							`Person`.* 
					 FROM 	`people` AS `Person`, 
							`meeting_people` AS `MeetingPerson`, 
							`conversations` AS `Conversation` 
							LEFT JOIN 
							`conversation_people` AS `ConversationPerson` 
							ON 
							`Conversation`.`id` = `ConversationPerson`.`conversation_id`,
							(SELECT COUNT(*) AS count 
							FROM `conversation_people` AS `CountConversationPeople` 
							WHERE 
							`CountConversationPeople`.`conversation_id` = {$__cakeID__$}) 
							AS `Count`
					 WHERE (`Count`.`count` = 0 AND 
							`Conversation`.`id` = {$__cakeID__$} AND 
							`Person`.`id` = `MeetingPerson`.`person_id` AND 
							`Conversation`.`meeting_id` = `MeetingPerson`.`meeting_id`)
						OR (`Count`.`count` > 0 AND 
							`Conversation`.`id` = {$__cakeID__$} AND 
							`MeetingPerson`.`meeting_id` = `Conversation`.`meeting_id` AND 
							`MeetingPerson`.`person_id` = `ConversationPerson`.`person_id` 
							AND `ConversationPerson`.`person_id` = `Person`.`id`))
					AS `ConversationUser`
				WHERE `Person`.`id` = `ConversationUser`.`person_id`'
		)
	);	
	...
}
?>

In retrospect, I should have just bitten the bullet and populated the ConversationPerson table with the values from MeetingPerson (or perhaps gone one step further and inferred the people in the meeting from the people involved in Conversations in that meeting, but whatever), but in this case I think this was probably the nicest solution.

The example in this post is so incredibly edge-case that it will probably help absolutely no-one, but I thought I’d put it up anyway, since the Cake docs don’t mention requiring those particular fields when specifying a finderQuery.

24
Oct

An Interesting IE6 Bug (bleurgh)

I came across an interesting bug in IE6 the other day, and couldn’t find any documentation of it on the internet, so thought I’d share it with you all here (aren’t you lucky?).

I was working on a distributable widget, which has the functionality to grab some embed code so you can place it with pride on your own website. This embed code was generated dynamically by JavaScript based on some parameters the user entered, and was then placed in a textfield using the jQuery $().val(value) function. This worked fine in most browsers, but IE6 would throw a wobbly at this point in the form of an “Unspecified Error”. Incredibly helpful.

I found that the bug didn’t occur if I escape()‘d the value before I passed it into the textfield. It seems, therefore, that IE6 has an issue with strings that contain HTML content – presumably it was trying to parse the HTML when it was put in the textfield. The same behaviour was observed when using document.getElementById(…).innerHTML = value; as well.

The solution was to use the jQuery function $().text() – and this works cross browser. Presumably this only works for textfields – if you are setting the contents of normal text inputs then I’m not quite sure what you will observe. But then, you never are with IE6…