CSS3 Snippets in TextMate – Linear Gradients

August 31st, 2010

Gradients are great. Linear gradients are super-great, because (as long as you don’t want funky angles) they work on pretty much all current browsers if you can remember the syntax. I can’t remember the syntax, but with a little home-schooling TextMate can…

Create these 2 snippets within TextMate’s CSS scope:

Name:
background: vertical linear gradient
Tab trigger:
background
Scope Selector:
source.css
Snippet:
background: ${1:top-color};
background: -webkit-gradient(linear, left top, left bottom, from(${1:top-color}), to(${2:bottom-color}));
background: -moz-linear-gradient(top, ${1:top-color}, ${2:bottom-color});
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=${1:top-color}, endColorstr=${2:bottom-color});
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=${1:top-color}, endColorstr=${2:bottom-color})";
$3

Name:
background: horizontal linear gradient
Tab trigger:
background
Scope Selector:
source.css
Snippet:
background: ${1:left-color};
background: -webkit-gradient(linear, left top, right top, from(${1:left-color}), to(${2:right-color}));
background: -moz-linear-gradient(left top, ${1:left-color}, ${2:right-color});
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=${1:left-color}, endColorstr=${2:right-color}, GradientType=1);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=${1:left-color}, endColorstr=${2:right-color}, GradientType=1)";
$3

Now, typing background within a CSS context and pressing tab should show something like this:

background.jpg

Choosing one of the two new options at the bottom will give you all the code you need for most browsers to show a horizontal or vertical linear gradient. The first colour you enter (either the top-most or left-most colour) will become the default background for browsers that don’t support gradients, which is hopefully usually what you want. In case you were wondering, -ms-filter is specifically for IE8 (in IE8-mode) which requires quotes around the contents.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka CSS, TextMate

The Growling Doorbell

August 18th, 2010

As promised in my previous post, I now have my doorbell sending growl notifications. Sending just one to my mac mini in the living room would be a bit pointless, since at that point I am within three metres of the front door – but, I have the notifications forwarding to every other mac in the house, and even with push notifications to my iPhone. This means that I now get three notifications on my iPhone when the doorbell is rung… One for the Twitter direct message, one for the email for the Twitter direct message, and one for the Growl notification, but they are non-sticky notifications so I can live with that.

Firstly, I installed Prowl – the excellent forwarding mechanism to get growl notifications on your iphone. I had to install on the iPhone as well, of course. I set it up to still show regular notifications, and to only forward items with a priority of at least “High” to my phone so I don’t get inundated with DropBox and Hardware Growler updates vibrating my phone all the time. It doesn’t support the icon images sadly that Growl does, but you can’t have everything (and I imagine this is an iPhone shortcoming rather than a Prowl restriction).

The next step is to install growlnotify (thanks to ZeissS for pointing this out). I had seen growlnotify in the Extras folder for Growl many times, but never really messed with it before. It’s a command line interface for sending local (or networked) growl notifications. It turns out the syntax is pretty simple.

For my purposes I was interested in showing a title saying the doorbell had been rung, a picture of the person ringing the bell, and the date and time that it occurred. According to the documentation the switch -n or --name should identify the application that is sending the notification, as well as showing the title for the notification. It states also:

To be compatible with gNotify the following switch is accepted:
    -t,--title      Does nothing. Any text following will be treated as the
                    title because that's the default argument behaviour

I found that -n did not work as I expected, and I actually had to use -t as well. Perhaps the documentation is outdated? YMMV.

So, to the AppleScript. The only thing I needed to pass was the image which we have already located in the script in the previous post.

 on growlNotify(img)
	set date_ to (current date) as string
	set scriptpath to "/usr/local/bin/growlnotify -p High -n Doorbell -t \"Doorbell Rung!\"  --image " & img & " -m \"" & date_ & "\""
	do shell script (scriptpath)
end growlNotify

Nice and easy. This displays a notification something like this:

GrowlBell.jpg

Then, I just modified the original script to include a call to this immediately after this line:

set the item_path to quoted form of the POSIX path of itemadded

Amended:

set the item_path to quoted form of the POSIX path of itemadded
growlNotify(item_path)

Finished? Almost. Now, I needed to forward the growl notifications to any other Macs I have. Unfortunately, there is no “global announce to subnet method” I could figure out easily, but Growl does have this functionality built-in. In your Growl preference pane, under “Network”, enable “Listen for incoming notifications on each machine”. You can add passwords if you share your network with other people. Then, on the machine that will be forwarding the notifications, in this pane check “Forward notifications to other computers”. The computers should automagically show up in the list (via Bonjour) then you can just check “Use” for each one.

Job done! Your doorbell is now probably better connected than some politicians.

The next step is to implement proper authentication with Twitter, and try to find a service rather than TwitPic so everyone ringing my doorbell isn’t presented to anyone who wants to spend the (minimal) time trying to figure out how to see them.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka Hardware

The Tweeting Doorbell

August 16th, 2010

If you’re anything like me (which you are probably thankfully not) you have a dream. A dream that you make all of your friends listen to you endlessly discussing in the pub. A dream that has long ago ceased being based in logic or sense, but is now just something you have to achieve if only to make up for never shutting up about it.

My dream, was to have my doorbell tweet me when someone rang it. Not much of a dream, I’ll admit, but still something I wanted. Before I got my iPhone I used BluePhone Elite with every phone I had (the iPhone doesn’t support the best bits, though). Aside from the main benefits such as texting and answering the phone from my mac, it also alerted me when my phone was ringing and who was calling. This was very unexpectedly useful – often my phone would be on silent and in the pocket of my jacket somewhere, and I would know firstly that it was ringing at all, and secondly whether or not it was worth answering. It solved a problem I didn’t realise I had.

I decided at this point, that everything in my house must be able to send me notifications. Preferably Growl notifications. If you don’t know what Growl is, follow that link and then come back. We’ll wait.

Recently I got a new front door put in. Part of the deal was a Yale “Electronic Door Viewer” which I got cheap since I get on well with the builder that fitted it. It consists of a small box, and a camera that looks like a regular spyhole – except it also has a doorbell button underneath. This little box and camera, both play doorbell chimes and display a picture of the person ringing the bell on a small (poor quality, but good enough) screen. What pleased me most though, was that the pictures are stored with a date/time stamp on an SD card inside the box, so that I can see who rang the bell at what time when I return home. There are a few issues with the setup… people seem to have difficulty locating the bell even with a sticker underneath it saying “this is the doorbell”, the LED lights that light up when the bell are rung blind people, very short people appear to be invisible and I never remember to check the box for pictures when I get home (much like when my phone answering machine became a phone-based service instead of hardware – with no blinking light, I don’t realise I have a message and forget to check). I can’t do much about some of these issues. People can’t find the bell – I might make another, bigger sticker. I will stick some kind of filter over the LED lights to save me from lawsuits. Short people will just have to remain invisible. But forgetting to check the box for new pictures, this seemed impossible to overcome.

Then I came across the Eye-Fi range of SD cards. These have an 802.11n wireless network adapter – built-in. I have no idea how they do that. But they do. The smallest was 4GB, which is excessive for my plans, but that was the smallest. I bought one from Amazon for £50 with free delivery.

Setting up the Eye-Fi card is really easy. It comes with a USB adapter and the software worked fine and was reasonably intuitive. Whenever the card has power and it can find one of the wifi networks you have setup on it with USB it automatically transfers any images (or videos) stored upon it to your mac/PC. I was worried that the doorbell wouldn’t keep power to the card for long enough to make the transfer, but the default setting of 20 seconds or so is apparently enough time to set up a network connection and copy the file across. You can set it up to automatically post them to Facebook or Flickr (or various other services), but that wasn’t what I wanted to achieve. To get the pictures to Twitter was going to take a few extra steps.

As a quick note, I am not 100% sure of the legal position of posting pictures of people on the Internet without their permission. Since it says “CCTV Recording In Operation” above the camera, and the people are outside in plain view, I am pretty sure I’m okay – but I wouldn’t want you to cite me as the person that gave you the go ahead to do this. So if you’re not sure, find out.

So, now the Eye-Fi was automatically putting pictures in a folder (I think ~/Pictures/EyeFi/). It was also adding date information to the folder structure, so in the Eye-Fi software settings I changed this to just stick them all in the root of the directory that was set. OS X has something built-in called “Folder Actions” which allows it to react to changes to a folder. Normally you just locate the folder, right-click it (or control-click for you really old school people) and choose “Folder Actions Setup…”. For some reason, that wasn’t working on my machine so I had to locate the app manually at /System/Library/CoreServices/Folder Actions Setup.app. Until you have a script written though, there’s not yet much you can do except use the provided scripts which are no good to me. So, I started writing the AppleScript. First I needed to be able to send direct messages. Using curl, this is pretty easy:

on dm(user, pwd, recipient, msg)
	do shell script ("curl -k -u " & user & ":" & pwd & " -d 'text=" & msg & "&user=" & recipient & "' https://twitter.com/direct_messages/new.xml")
end dm

Posting a picture online was a bit trickier. For the time being I have used TwitPic but this seems a bit open for my liking. I may revisit that in the future. So, I created another Twitter account (set to private) and made that and my normal account follow each other so they were able to send direct messages to each other. Then I created this script to post images to TwitPic:

on twitpic(filepath, user, pwd)
	set scriptpath to "curl -k -F media=@" & filepath & " -F username=" & user & " -F password=" & pwd & " -F message= https://twitpic.com/api/upload"
	set xmlValue to (do shell script scriptpath)
	--Parse the result for whether the upload was successful
	if xmlValue contains "<rsp stat=\"ok\">" then
		tell application "System Events"
			set xmlData to make new XML data with data xmlValue
			set URI to value of (XML element "mediaurl" of XML element "rsp" of xmlData)
			quit
		end tell
		return URI
	else
		display dialog "TWITPIC ERROR " & xmlResponse as string
	end if
end twitpic

It’s not the most bug resistant code in the world, but it will at least show an error message on the machine if it fails, saying (hopefully) why it failed.

Finally, I needed to hook these altogether. I added to my fast growing script:

on adding folder items to this_folder after receiving added_items
	set twitteruser to "twitteruser" --amend this to be the user name of your doorbell
	set twitterpwd to "password" --amend this to be the password of your doorbell
	repeat with itemadded in added_items
		set the item_path to quoted form of the POSIX path of itemadded
		set mediauri to postPicture(item_path, twitteruser, twitterpwd)
		sendDirectMessage(twitteruser, twitterpwd, "username", mediauri as string) --amend this to be the username you want to be alerted. Duplicate the line if you want to alert more users.
	end repeat
end adding folder items to

I saved this as eyefi.scpt in Macintosh HD/Library/Scripts/Folder Action Scripts/. Now I was ready to open up the Folder Actions Setup… again. I located the folder Eye-Fi was downloading into (uploading to? Whatever), right clicked it, and chose Folder Actions Setup…. I selected eyefi.scpt from the list, and made sure that the right folder was in the left-hand column, and that Enable Folder Actions was ticked. Finally, I pressed the doorbell (while ducking out of the way like a coward). Success! I received a direct message with this attached:

doorbell.jpg

I also received an email about it, since it was a direct message and I have Twitter configured that way. I was living the dream! Perhaps a shallow, superficial, superfluous and ridiculous dream, but the dream nonetheless.

My next job is to make it send Growl notifications.

I hope you love this as much as I do, and if not, keep it to yourself. What do you think? How could I make this more epic? Let me know in the comments.

Update: I now have growl notifications working too! Read about that here.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka Hardware

Video of SEERS presentation at Agile Evangelists

July 5th, 2010

Here is a video of me presenting at Agile Evangelists, on the 29th of April.

Antony Kennedy: Standardized Bug Reporting – 29/04/2010 from Skills Matter on Vimeo.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka Agile

CSS3 Snippets in TextMate – box-shadow

June 30th, 2010

As promised, the next in the series – box-shadow.

Create this snippets within CSS:

Name:
box-shadow
Tab trigger:
box-shadow
Snippet:
-moz-box-shadow: ${1:hOffset} ${2:vOffset} ${3:blurRadius} ${4:color};
-webkit-box-shadow: ${1:hOffset} ${2:vOffset} ${3:blurRadius} ${4:color};
box-shadow: ${1:hOffset} ${2:vOffset} ${3:blurRadius} ${4:color};
$0

More to come. Any requests?

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka CSS, TextMate

CSS3 Snippets in TextMate – border-radius

June 29th, 2010

I don’t have the best memory in the world. I am incapable of remembering the exact syntax for most CSS properties, let alone the vendor specific versions of them. To that end I have started creating TextMate snippets to help me out.

text-shadow comes by default, but border-radius does not.

Create these 5 snippets within CSS:

Name:
border-radius
Tab trigger:
border-radius
Snippet:
border-radius: ${1:radius};
-moz-border-radius: ${1:radius};
-webkit-border-radius: ${1:radius};
$0

Name:
border-top-left-radius
Tab trigger:
border-radius
Snippet:
border-top-left-radius: ${1:radius};
-moz-border-radius-topleft: ${1:radius};
-webkit-border-top-left-radius: ${1:radius};
$0

Name:
border-top-right-radius
Tab trigger:
border-radius
Snippet:
border-top-right-radius: ${1:radius};
-moz-border-radius-topright: ${1:radius};
-webkit-border-top-right-radius: ${1:radius};
$0

Name:
border-bottom-left-radius
Tab trigger:
border-radius
Snippet:
${1:radius};
-moz-border-radius-bottomleft: ${1:radius};
-webkit-border-bottom-left-radius: ${1:radius};
$0

Name:
border-bottom-right-radius
Tab trigger:
border-radius
Snippet:
${1:radius};
-moz-border-radius-bottomright: ${1:radius};
-webkit-border-bottom-right-radius: ${1:radius};
$0

Now, anywhere that is a CSS context (inside a CSS file or inside style tags in an HTML file) type border-radius and press tab. TextMate asks which of our five snippets you want. Choose the appropriate one by typing the number alongside it, and the code appears. Now type the radius you want (don’t forget, you can enter two values if you want the equivalent of an elliptical radius) and it will automatically appear for all vendor-specific code. Finally, press tab again and your cursor jumps outside of the code we just typed.

I’ll provide more of these as I make them.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka TextMate

How social networks should take advantage of sheep mentality in targeted advertising

March 11th, 2010

Facebook now has more than 400 million active users. 50% of these log on to the site at least once a day. 3 billion photos are uploaded a month. These figures are staggering. Facebook now drives more traffic to news sites than Google, according to GigaOm. Farmville – a small Facebook-based game which depends upon social interaction – actually has three times as many active users as Twitter.

In fact, some people may be starting to think that Facebook is the Internet. A few weeks ago, a post on www.readwriteweb.com ranked very highly for the search term “facebook”. As the comments on that page demonstrate, users did not understand that this was not Facebook. They complained about the new format and the missing login box, whereas most of my readers (I imagine) would certainly have realised the difference, and would have typed facebook.com into the location bar of their browser initially in any case. Some of us may take a cheap laugh at this, but the sheer volume of comments there (not taking into account those that didn’t comment) demonstrates that this is not unusual. Many people search for what they want, and dangerously assume the first result is what they are looking for. A friend of mine’s wife never browses to youtube.com – she searches for youtube and clicks the first link.

Facebook likely knows more about you than Google does. There is widespread concern that Google could be relating our searches and browsing history together, and that there are privacy implications for this. But, for the huge amount of people that use Facebook as their portal to the Internet – not only do they know what we are sharing, clicking on, and interacting with – they also know our personal details that we give to them willingly, and even who our friends are and how often we interact with them. At a recent event I attended, an employee of Facebook told me they have enough data now to be able to fairly accurately predict when a couple who are interacting will change their relationship status to show that they have got together, or broken up.

Surely this amount of data gives Facebook the ability to start the really targeted marketing and affiliate network advertising that I have been hoping for? I do not think it is hard to conceive that at some point in the near future, advertising will no longer be an irritation. If the advertiser knows enough about me to present me with advertising that I genuinely am interested in, it becomes a service. For advertisers too, presenting an ad to a group that are more likely to be interested in their product enhances their brand rather than tarnishing it, saves costs on unnecessary blanket marketing, and ultimately increases ROI.

This has been discussed many times, and Facebook and Google (and others) are already trying to achieve it. Google’s effort is based on keywords within the page and is not yet as intelligent as we would like it to be. At best this can have funny results, and at worse it can be very offensive. Some examples are here.

Facebook makes a better effort. Since it knows the bands I enjoy, it tells me about tickets and upcoming concerts, with affiliate links. It also gives me the ability to effectively vote ads down that do not interest me, for whatever reason. But, Facebook have the potential to take advantage of sheep mentality (also known as group think, or group solidarity) – the mindless following of peers that the vast majority of us subscribe to. There have been many studies on this, with the outcome being always that we are more likely to subscribe to the same opinions, use the same services, and buy the same products that our peers and (to a lesser degree) the general populous do.

Facebook knows my interests. It knows who my friends are, and it knows of them who I hold in greater esteem based on the status updates and other shared items that I “like” or respond to. If they were to surface ads based on how many times they have been interacted with by my friends, or products that they know my friends and peers have purchased or are interested in, there would be a clearly stronger chance that I would also be interested in these things. If my friends that I hold in highest regard are treated as more important in these algorithms, the effect would be stronger still. The events application within Facebook is the best example of what I am getting at, but seems to miss out on the affiliate and advertising potential.

It is one thing to present me with artists that they know I am likely to like based on artists that I have listed as my favourites and other people’s listening preferences who also like the same artists; the value of my circle of friends strikes me as being more valuable than this. Some examples.

  • “10 of your friends including John, Joe and Mary all bought this item.”
  • “6 of your friends that listen to Nirvana also like Pearl Jam.”
  • “12 of your friends will be attending Reading Festival 2010, click here to buy tickets.”

Each of these examples would of course be accompanied by a link to buy the product or service in question, or useful services that relate (for example hotels and restaurants in Reading). This kind of advertising is not only useful, since it gives me information on people I am likely to care about, but it also offers a clear and strong revenue stream based on affiliate links and purchasing opportunities. I believe that using a user’s social network as data to base targeted advertising on is a logical next step.

What do you think?

Update: Andy Beeching pointed me to this article, which describes your circle of peers and the value of individuals within in as the “Social Graph”. It makes for interesting reading.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka UI

My Presentation on SEERS

October 25th, 2009

I recently gave a talk about SEERS at Bar Camp London 7. The slides are available here.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka Agile

Standardised Bug Reporting with SEERS

October 7th, 2009

SEERS

When raising bugs and defects, it can be easy to miss out vital pieces of information without a strict policy to adhere to. Often the developers have to refer back to the tester that raised the bug and ask exactly what went wrong, what they expected to happen or in which environments the problem occurred. SEERS is a term I have coined to use as a standardised series of criteria for bugs and defects. SEERS stands for:

Screenshot

There should always be a screenshot of the problem (unless it is a non-visual bug). If possible some kind of obvious visual outline around the problem should be shown. As noted by Alun Coppack in the comments, if the error is behavioural we should annotate the screenshot to describe the problem – and if this is not practicable a screencast of the problem leaves no margin for miscommunication.

Environment

There should always be details of the environment(s) (browser, operating system, screen resolution, etc) that the problem occurs in.

Expected/Actual Behaviour

There should always be details on the expected behaviour and the actual behaviour.

Reproduction

There should always be accurate details on how to consistently reproduce the bug. As noted by Alun (again!), some errors can be temporal and the testers should be aware of this and take note of the time/date before they try to recreate the error.

Severity

There should always be details on the severity of the bug.

Severity

It is a common complaint that the recording of the severity of a bug or defect is inaccurate. An element being two pixels to the left in IE6 is not a huge problem, but is often rated as Critical. To that end, I list the severities we use below.

Blocker

A blocker means that our entire story or functionality is not working or unusable, even if following The Happy Path. The Happy Path is that where the user does exactly what we expect them to and does not stray from the norm at all – e.g. entering the correct login details and clicking “login”. If we do not fix this bug we cannot release. An example is a login box where clicking the login button does nothing.

Critical

A critical bug indicates that functionality is severely impaired. If we do not fix this bug we cannot release. An example is a login box where entering invalid credentials takes the user to an empty page. The Happy Path (entering correct credentials) works fine, but a very likely situation exists which could cause functionality to break.

Major

A major bug indicates that some functionality does not work as expected. Copy may be obscured, or specific situations may cause errors to occur. We should certainly fix this bug, but it would not be impossible to release without. An example is a login box where entering over 100 characters in the password field causes an error.

Minor

A minor bug indicates some kind of cosmetic issue or unlikely situation that causes an error. We should fix this bug if it is cost effective to do so and other more important tasks do not exist. An example is the login button being aligned incorrectly, or clicking the button 30 times causing an error.

Trivial

A trivial bug indicates a very minor cosmetic issue or very unlikely situation that causes an error. We may decide not to fix this bug. An example is IE6 and Firefox not looking exactly the same, although IE6 looks acceptable and functionality is not impaired.

Terminology

Let’s discuss what actually constitutes a bug or defect, although this is terminology that I use and your mileage may vary.

Bug

When a task is in the current iteration or sprint and we raise an issue against it, this is called a bug.

Defect

When an issue is found that does not relate to any current tasks, this is called a defect.

Graded Browsers

Finally, here is a list of the browsers we support and their grades. We expect grade A browsers to have complete functionality, and look close to perfect (as much is reasonable or practicable). Grade B browsers should look acceptable and work as expected (although potentially with less of the flashy but essentially unnecessary functionality). Grade C browsers should allow the user to read the content, and accomplish their tasks. It is important to mention that we do expect content to be accessible in absolutely any environment; it is the aesthetics and nice-to-have functionality that we are less concerned with.

Grade A

These browsers should work perfectly, look good and perform well.

  • Windows XP/Vista
    • IE8
    • IE7
    • Firefox 3.5
    • Opera (latest version)
    • Safari (latest version)
    • Chrome (latest version)
  • Mac OSX 10.5/10.6
    • Safari (latest version)
    • Firefox 3.5
    • Opera (latest version)

Grade B

These browsers should work acceptably (any unsupported functionality should be dealt with gracefully e.g. JS animations or Flash), look good (any unsupported rendering functionality should be dealt with gracefully e.g. rounded corners, alpha transparency or drop shadows) and perform reasonably well.

  • • Windows XP/Vista
    • IE6
    • Firefox 3
  • Mac OSX 10.5/10.6
    • Safari (previous version)
    • Firefox 3

Grade C

All other browsers should work acceptably (degrading gracefully where appropriate), have no obscured or illegible content and perform acceptably.

Notes

It is important to note that in my list IE6 has been relegated to Grade B (which is at odds with Yahoo!’s graded browser support, which I otherwise use as a base for my list). This is simply due to the development effort to class it as Grade A. Where it is easy to do, I still recommend striving for good aesthetics for IE6 – but (for any version of IE) rounded corners and drop-shadows are often simply not cost-effective. I would implement these with CSS3, and argue that the time saved (and performance increase) by doing this The Right Way gives us enough time to develop new features. I obviously do not expect testers to test every single Grade C browser in existence, but simply to be aware of what does and what does not class as a bug or defect.

Remember, your users do not compare browsers side by side like we do. They have no idea there are inconsistencies, and more often than not simply wouldn’t care even if they did. It is unfortunately impossible for a website to look the same in every browser or environment (without just using a huge image map, and even then a text-only browser like Lynx would not be able to render it) and there is nothing to gain in trying needlessly to overcome this.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka Agile

Defensive AJAX and AJAX retries in jQuery

August 18th, 2009

A lot of code that I have seen over the years always assumes success, particularly with AJAX calls. This creates code that is fragile, and entirely dependant on the result of external (to the client) code.

There are a few ways we can attempt to protect against this. The first, is to always depend on the result of the call to make any changes to the DOM or UI. For example, let’s say we have a quantity field in a shopping basket on an e-commerce site. When the user clicks a plus next to the field, we make an AJAX call that increments a value in a basket stored server-side and updates the UI to match. It is very common, and tempting, to increment the value in the UI immediately, and then make the AJAX call. There are a few obvious problems here. If the AJAX call fails, now we have a UI that is inaccurate. We can decrement the value in the UI to make up for it, but now we are starting to confuse the user with the numbers jumping up and down, and if they have clicked the button six times in quick succession (very common use of this kind of control) our chance of error for accurate UI representation of data increases dramatically.

A much safer way to do this is to return the current stored quantity value from the server with each AJAX call, and only update the UI when the AJAX call completes. This results in a less “snappy” feeling UI, and it will be necessary to display some kind of visual cue that something is going on the background, but the user learns quickly how the site works and this is a much more robust process.

Without having any kind of genuine statistics to call upon, I would suggest that nine out of ten AJAX calls that fail are due to an issue that is temporary and would be resolved by a retry. Anything due to network issues, a lost packet somewhere, a brief server glitch, load balancing problems and so on can cause a timeout or 404 without the target resource actually being missing or consistently failing. Often when a website fails to load, I click refresh and there it is. An AJAX call is just the same. (The same applies to database connections, or anything dependant on a network resource.)

Therefore, rather than display an error on an AJAX timeout (“we could not connect to the server” or “there was a problem, please try later”) or worse, doing nothing at all, there are some things we can try to resolve the issue ourselves without bothering the user about it until we’re certain that it is broken.

Let’s look at a typical jQuery AJAX call.

$.ajax({
	url : 'ajaxurl.json',
	type : 'get',
	data : 	{name : 'value'},
	dataType : 'json',
	timeout : 20000,
	success : function(json) {
		//do something
	}
});

The immediate problem with this is it completely assumes (and depends upon) success. There is not even a basic error handler. Something like this is better:

$.ajax({
	url : 'ajaxurl.json',
	type : 'get',
	data : 	{name : 'value'},
	dataType : 'json',
	timeout : 20000,
	success : function(json) {
		//do something
	},
	error : function() {
		alert('Oops! There was a problem, sorry.');
	}
});

The error callback function is actually passed three arguments.

  • The XMLHTTPRequest object in use
  • A textual equivalent of the status
  • The actual exception thrown

These allow us to react in a more sophisticated manner:

$.ajax({
	url : 'ajaxurl.json',
	type : 'get',
	data : 	{name : 'value'},
	dataType : 'json',
	timeout : 20000,
	success : function(json) {
		//do something
	},
	error : function(xhr, textStatus, errorThrown ) {
		if (xhr.status == 500) {
			alert('Oops! There seems to be a server problem, please try again later.');
		} else {
			alert('Oops! There was a problem, sorry.');
		}
	}
});

Since the error function lives inside the AJAX object itself, and is called in that context, the this keyword very usefully points to the jQuery AJAX instance itself. Using this, and the arguments we are being passed we can very easily set the UI to retry the AJAX call on our behalf. Let’s attach some extra properties to the AJAX object:

$.ajax({
	url : 'ajaxurl.json',
	type : 'get',
	data : 	{name : 'value'},
	dataType : 'json',
	timeout : 20000,
	tryCount : 0,
	retryLimit : 3,
	success : function(json) {
		//do something
	},
	error : function(xhr, textStatus, errorThrown ) {
		if (xhr.status == 500) {
			alert('Oops! There seems to be a server problem, please try again later.');
		} else {
			alert('Oops! There was a problem, sorry.');
		}
	}
});

We have added tryCount and retryLimit. These are going to store how many attempts we have made, and how many attempts we will make respectively. Making use of these:

$.ajax({
	url : 'ajaxurl.json',
	type : 'get',
	data : 	{name : 'value'},
	dataType : 'json',
	timeout : 20000,
	tryCount : 0,
	retryLimit : 3,
	success : function(json) {
		//do something
	},
	error : function(xhr, textStatus, errorThrown ) {
		if (textStatus == 'timeout') {
			this.tryCount++;
			if (this.tryCount <= this.retryLimit) {
				//try again
				$.ajax(this);
				return;
			}
			alert('We have tried ' + this.retryLimit + ' times and it is still not working. We give in. Sorry.');
			return;
		}
		if (xhr.status == 500) {
			alert('Oops! There seems to be a server problem, please try again later.');
		} else {
			alert('Oops! There was a problem, sorry.');
		}
	}
});

So, now we are trying three times before giving in. Where I have actually used this, some modal dialogue boxes are used instead of window.alert() and I save a copy of the AJAX object when we reach our retry limit. At that point, although I tell the user we have given in, I still provide them with a button to try again themselves.

I am convinced that implementing techniques like this will rid us of many unnecessary bad user experiences, and many support calls. Keep the user informed (in simple language!), and assume things will fail. I hope this is helpful.

Share this Article:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • email
  • FriendFeed
  • LinkedIn
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Tumblr
  • Twitter

booshtukka JavaScript, jQuery