jQuery Font Selector – version 2

A guy called Rob just commented on the jQuery Font picker I posted a while back – and asked whether I could provide an example.

Looking at the code I thought it was in need of a refresh, so have just rewritten it. You can see the jQuery font selector demo here.

The plugin hasn’t been extensively tested, but does seem to work OK in Chrome, Firefox and IE7 and 8. Enjoy!

/**
* Font selector plugin
* turns an ordinary input field into a list of web-safe fonts
* Usage: $('select').fontSelector();
*
* Author     : James Carmichael
* Website    : www.siteclick.co.uk
* License    : MIT
*/
jQuery.fn.fontSelector = function() {

  var fonts = new Array(
'Arial,Arial,Helvetica,sans-serif',
'Arial Black,Arial Black,Gadget,sans-serif',
'Comic Sans MS,Comic Sans MS,cursive',
'Courier New,Courier New,Courier,monospace',
'Georgia,Georgia,serif',
'Impact,Charcoal,sans-serif',
'Lucida Console,Monaco,monospace',
'Lucida Sans Unicode,Lucida Grande,sans-serif',
'Palatino Linotype,Book Antiqua,Palatino,serif',
'Tahoma,Geneva,sans-serif',
'Times New Roman,Times,serif',
'Trebuchet MS,Helvetica,sans-serif',
'Verdana,Geneva,sans-serif' );

  return this.each(function(){

    // Get input field
    var sel = this;

    // Add a ul to hold fonts
    var ul = $('<ul class="fontselector"></ul>');
    $('body').prepend(ul);
    $(ul).hide();

    jQuery.each(fonts, function(i, item) {

      $(ul).append('<li><a href="#" class="font_' + i + '" style="font-family: ' + item + '">' + item.split(',')[0] + '</a></li>');

      // Prevent real select from working
      $(sel).focus(function(ev) {

        ev.preventDefault();

        // Show font list
        $(ul).show();

        // Position font list
        $(ul).css({ top:  $(sel).offset().top + $(sel).height() + 4,
                    left: $(sel).offset().left});

        // Blur field
        $(this).blur();
        return false;
      });

      $(ul).find('a').click(function() {
        var font = fonts[$(this).attr('class').split('_')[1]];
        $(sel).val(font);
        $(ul).hide();
        return false;
      });
    });

  });

}

And here is the required CSS:

ul.fontselector {
  background: white;
  border: 1px solid #ccc;
  border-top: 0;
  font-size: 14px;
  float: left;
  list-style: none;
  margin: 0;
  padding:0;
  line-height: 1.2;
  z-index:    10;
  position:   absolute;
}
ul.fontselector li {
  margin:   0;
  padding:  0;
  list-style: none;
}
ul.fontselector a {
  display:    block;
  padding:    3px;
  color:      black;
  text-decoration: none;
}
ul.fontselector a:hover {
  background: #ddd;
  cursor:     pointer;
}

VAT and SaaS Web Applications

The UK VAT rules don’t exactly make it easy to create a software-as-a-service web application if you’re a UK based supplier who is VAT registered. The rules over whether VAT applies depends both on whether the service is for business use, where the customer is based, and where the service will be ‘enjoyed’.

They do publish this flowchart which makes it slightly easier to tell what’s what.

I set about trying to code up the logic in this PDF – if anyone is setting up a web application in the UK using PHP please feel free to use or adapt the code below.

There are some unit tests at the bottom which should ensure the logic matches that on the HMRC’s flowchart.

<?php

function is_eu($sLocation) {
  $sLocation = strtolower($sLocation);
  switch($sLocation) {
    case 'uk':
      return true;

    case 'eu':
      return true;

    case 'non-eu':
      return false;
  }
  trigger_error('Unknown location "' . $sLocation . '"', E_USER_ERROR);
}

function vat_applies($bBusiness = true, $sLocation = 'UK', $sEnjoyed = 'UK') {
  $sLocation = strtolower($sLocation);
  $sEnjoyed  = strtolower($sEnjoyed);

  // Validate input
  $aOptions  = array('uk', 'eu', 'non-eu');
  if(!in_array($sLocation, $aOptions)) {
    trigger_error('Customer location "' . $sLocation . '" invalid (must be uk, eu, non-eu)',
E_USER_ERROR);
  }
  if(!in_array($sEnjoyed, $aOptions)) {
    trigger_error('Services enjoyed in "' . $sEnjoyed . '" invalid (must be uk, eu, non-eu)',
E_USER_ERROR);
  }
  if(!is_bool($bBusiness)) {
    trigger_error('Business use must be boolean (true or false)', E_USER_ERROR);
  }

  // Customer in EU
  if(is_eu($sLocation)) {

    // Customer in UK
    if($sLocation == 'uk') {

      // Business purposes
      if($bBusiness) {

        // Service enjoyed outside EU
        if(!is_eu($sEnjoyed)) {
          return false;
        }
        // Service enjoyed inside EU
        else {
          return true;
        }

      }
      // Non-business purposes
      else {
        return true;
      }

    }
    // Customer not in UK
    else {
      // Business purposes
      if($bBusiness) {
        return false;
      }
      // Non-business purposes
      else {
        return true;
      }
    }

  }
  // Customer is not in EU
  else {
    // Business use
    if($bBusiness) {

      // Enjoyed in EU
      if(is_eu($sEnjoyed)) {

        // Enjoyed in UK
        if($sEnjoyed == 'uk') {
          return true;
        }
        else {
          return false;
        }
      }
      // Not enjoyed in EU
      else {
        return false;
      }
    }
    else {
      return false;
    }
  }
}

/**
 * Unit testing
 */
 /*
function assert_true($b) {
  echo '<li>' . ($b === true ? 'passed' : 'failed');
}
function assert_false($b) {
  echo '<li>' . ($b === false ? 'passed' : 'failed');
}

// Customer in UK - business
assert_true(vat_applies(true, 'uk', 'uk'));
assert_true(vat_applies(true, 'uk', 'eu'));
assert_false(vat_applies(true, 'uk', 'non-eu'));

// Customer in UK - non-business
assert_true(vat_applies(false, 'uk', 'uk'));
assert_true(vat_applies(false, 'uk', 'eu'));
assert_true(vat_applies(false, 'uk', 'non-eu'));

// Customer in EU
assert_false(vat_applies(true, 'eu', 'non-eu'));
assert_true(vat_applies(false, 'eu', 'non-eu'));

// Customer outside EU
assert_false(vat_applies(false, 'non-eu', 'non-eu'));
assert_false(vat_applies(true, 'non-eu', 'non-eu'));
assert_true(vat_applies(true, 'non-eu', 'uk'));
assert_false(vat_applies(true, 'non-eu', 'eu'));
assert_false(vat_applies(true, 'non-eu', 'non-eu'));
*/

Doctrine PHP Merge Function

If you’ve tried Doctrine PHP, you might get pretty frustrated when using the $Record::synchronizeWithArray() function. Why?


$data = array('name' => 'jimbob');

$User->Doctrine::getTable('User')->find(1);

$User->synchronizeWithArray($data);

$User->save();

If you try this code you will find that the synchronizeWithArray() function deletes everything except the ‘name’ field in the record. I think the name ‘synchronize’ is slightly confusing here, as what it really means is “delete everything except what I’m explicitly telling you right now”.

A bit of digging in the API reference turned up a nice function called merge().


$data = array('name' => 'jimbob');

$User->Doctrine::getTable('User')->find(1);

$User->merge($data);

$User->save();

You will find that all the fields except ‘name’ are untouched. Lovely job.

PHPMailer and HTML email

If you haven’t already tried it, the PHPMailer class is an excellent way of sending email using PHP. It makes all those dreaded email tasks (multiple message parts, encoding, attachments etc.) really nice and easy.

Today however I came across a problem sending HTML formatted email – only occuring when sending via the PHP mail() transport rather than SMTP.

I eventually traced it to a large block of HTML which formed part of the formatted email. This was generated by a WYSIWYG editor and had no newline characters, and so was too long to handle for the mail() function (the SMTP transport was able to resolve the issue, but I didn’t want to use SMTP for this application).

The $mail->WordWrap parameter didn’t seem to help either.

So I rustled up a quick little function to split the HTML at opening tag boundaries, which solved the problem while keeping the code tidy – here it is:

/**
* Function to wrap HTML, but only at opening tag boundaries
* @param    $s            the HTML string to wrap
* @param    $chars  the number of chararacters to allow per line
* @return $s            the modified HTML
*/
function htmlWrap($s, $chars = 40) {
$aChunks = explode('<', $s);

$sLine     = '';
$aLines  = array();

foreach($aChunks as $i => $sChunk) {
$sLine .= ($i == 0 ? '' : '<') . $sChunk;

// Check if line length too long
if(strlen($sLine) + strlen(@$aChunks[$i+1]) + 1 >= $chars) {
$aLines[]    =    $sLine;
$sLine        = '';
}
}

return implode("\n", $aLines);
}

You can use this as so:

$Mail             = new PHPMailer();
$Mail->MsgHTML(htmlWrap('Your HTML email message here...'));
...

Choosing a CMS in 10 Steps

Picture this: you have a great website contract, a stunning design, and everything is ready to roll. Except for one little problem: which content management system (CMS) should be used?

There’s no real right or wrong answer to this question, but it can certainly cause a lot of grief if you’re battling the technology rather than being assisted by it. So here’s a couple of handy pointers to help with the decision:

1. Blog or CMS?

If the website is primarily going to handle ‘posts’ – journal entries spread out in time – then a blog is more appropriate. If on the other hand you need to organise a quantity of more structured articles, such as sales literature or support documents, a CMS is going to be your best bet.

There’s actually a fair amount of crossover between blogs and CMS systems these days, but you may as well start out with a tool that’s specialised for the task at hand.

WordPress is becoming fairly ubiquitous for blogging, with a vast range of third-party themes and plugins available.

2. Self-hosted or cloud-hosted

‘CMS as a service’, where the software is pre-installed and ready to use, is becoming increasingly popular. Available at a whole range of price points, from web-builder type software with pre-designed templates (such as Jimdo) right up to fully fledged business solutions. Our own CreationCentre CMS falls within the cloud-hosted CMS category, and can be implemented very rapidly. If you are using a cloud-hosted solution, what data backup procedures are in place?

For blogging, there are popular pre-installed choices such as WordPress.com and Blogger.com.

If you are planning to write your own modules, in a language such as PHP or ASP.NET, or require bespoke database interaction, you’re probably going to require a self-hosted solution. Popular solutions here include Joomla, Drupal, and Expression Engine.

3. Programming Language

For cloud-hosted solutions this isn’t an issue, but if you’re installing it yourself the language the CMS is written in (as well as the database that persists it) is going to be an important factor – especially if the client already has a server they wish to use.

Popular choices here include PHP, ASP.NET, Java, and Ruby is spreading quickly too.

4. Built-In Features

You might find it useful at this stage to draw a spider diagram with ‘Website’ in the middle, and broad technical requirements on each of the spokes. Keep going out until you’ve exhausted every possible situation.

Which CMS seems to fit the bill most closely? The closer the match, the better.

Here’s a couple of the more obvious features:

  • Allow for easy management of articles, and the publication of new ones
  • Upload and manage images and files
  • Customisation of templates (whether in vanilla HTML or a scripting language such as PHP)
  • Blogging or news-posting
  • RSS feeds
  • Embedding of custom HTML such as YouTube video or Google Maps
  • Contact forms
  • Galleries
  • Website login accounts, with the ability to set user-only content
  • E-commerce facilities
  • Forum
  • SEO tools such as meta and title tag control
  • Permissions – the ability to allow only certain users to control certain articles or features
  • Workflow tools – allowing a logical flow to the creation and approval of content
  • Document lifecycle tools – keeping a record of all revisions made to a particular article

5. Page-centric or content-centric

Some CMSs have the ‘page’ as the unit of content. In others, content exists in units independent of pages, with one or more content items being displayed on a particular page.

The former method is definitely easier to grasp, and works well if the website is relatively small or the end user is not technical. The latter is more powerful, and can support ‘content-reuse’, where blocks of content can be reused on other pages or even websites in multi-site implementations.

6. Usability

Who is going to be using the CMS on a day-to-day basis? If they’re not particularly technical then this is a crucial point, otherwise you will be inundated with support requests.

For example, not all CMS’s allow for WYSIWYG editing, meaning the user is going to have to learn either basic HTML or a similar in order to format their content. Are they capable? How much training will they require, and who’s going to perform that training?

Choose a CMS which is appropriate for the end-user.

7. Support and Documentation

This is a crucial one too – how much support and documentation are available to you as a developer while you build the website? You don’t want to be left high and dry with important deadlines to meet.

What are the ongoing support costs from the vendor, and does this cover software upgrades?

8. Compatibility

Most CMS systems run in the browser – it’s worth checking what operating system the end-user has, and what browser they have installed. Unfortunately some corporate users may not have administrative permission to install the browser you wish they had!

Does the CMS need to interface with any other business software – the most obvious being Customer Relationship Management (CRM) software? It’s definitely a bonus if after filling in an online contact form, the customer’s information is seamlessly imported into their CRM profile.

Thanks to XML and web services, the CMS can work as part of a larger whole rather than being a single silo of information.

9. Accessibility

It’s a legal requirement in many countries that a website is accessible to users with various disabilities such as visual impairment. So be sure that your chosen CMS can publish content meeting relevant accessibility legislation.

Typically this means meeting the WCA guidelines with a double-A rating.

10. Advanced Features

Is your website going to be serving content in different languages, content that is targeted to a particular geographic region, or publishing content to multiple websites? Does the CMS support the character set of all the target languages?

This makes the CMS implementation considerably more complex, and will narrow the choices quite considerably.

“Organica” – a free HTML CSS template

Today I’m launching a new free HTML/CSS template called “Organica”.

It has a fresh blue and green theme inspired by nature, with beautiful nature photography from photos8.com.

It uses transparent PNGs so that it’s easy to customise the images, even though they have curved borders. Transparent PNG support was added for IE6 with the help of the excellent TwinHelix PNG fix which worked nicely.

Feel free to use in your own projects, just please leave the footer link intact – thanks!

Get it now (ZIP archive)

A fresh blue and green theme inspired by nature

A fresh blue and green theme inspired by nature

JQuery Multiple file upload

I’ve been on the hunt for many years for a decent way of uploading multiple files at once via a browser-based upload form. Finally there is one that seems to do a great job, and be fairly easy to install – the JQuery Uploadify plugin.

There’s a couple of ‘gotchas’ that I came across and thought I would share.

Firstly, if you use PHP sessions for your authentication, you’ll find that your PHP script that accepts the upload won’t have access to the current session. This is because Flash gets given a different session ID. One way to get around this problem is to manually send your PHP session id as one of the options, something like:


'scriptData'  : {'sessid' : <php echo session_id(); ?>}

Then your script that accepts the file upload would require:


session_id($_REQUEST['sessid']);
session_start();

Secondly, I found that for the ‘script’ option, the plugin requires a relative path. I don’t want to have to keep setting paths because they’re different in my development and live environment, so I did this:


var rel_path     = location.href.split(/localhost|\.co\.uk|\.com|index\.php/)[1];
$('#uploadify').uploadify({
'script':         rel_path + 'upload_script.php'

// other options here...

})

This is a brilliant plugin – a big thank-you to the authors!

Update 09/09/2009: after lots of hair-pulling/teeth-grinding/tea-drinking, the PHP sessions problem just couldn’t be resolved quickly on the live server so I adopted a different approach in the end:

  • A singe use ‘upload authentication key’ is generated each time you upload the file, and stored in the database along with the current user
  • Uploadify sends this key via the scriptData option
  • The script that accepts the file upload ignores PHP sessions, and instead looks in the database for the authentication key to verify the user that sent the file has access.

Fancy zapping some baddies? Go ahead…

Check out this simple Flash game I developed below… Use cursor keys to get around and space bar to fire.

Click to open in a new window

Future of web apps 2009

Just spotted the “Future of web apps” conference in London on 30th Sep – 2nd Oct 2009 – looks awesome!

FOWA badge

A noob’s guide to PHP Doctrine

This week I am a newbie to PHP Doctrine. So I thought I’d share some of the experiences I’ve had with it, not so much in a coding sense but in a “this is the cool stuff you can do with it” sense.

Doctrine is an ‘object relational mapper’ or ORM for short, letting you code your app in terms of the objects you require, without worrying too much about the underlying database table structure. With SQL queries, a simple 2-dimensional table of rows and columns is returned. Using an ORM, a multi-dimensional object or array can be returned with all the relationships between objects prepared for you.

In general I’m a bit of framework-sceptic, but I think this one really deserves a look as there’s some really powerful tools included.

Also the website is nicely designed and has pretty good documentation on it, which makes all the difference.

So here’s a list of 10 things I’ve learned about it this week, in no particular order:

  1. You can define your data structures using a simple YAML file, which while it sounds like some kind of novel pet food, is in fact dead easy to get to grips with and is very quick to type out.
  2. Doctrine will create PHP model objects all ready to go from the YAML file and install the necessary database tables for you.
  3. If you find yourself needing to change the data structure, you can do so in the YAML file and Doctrine will update the PHP scripts and the database tables aswell, while retaining the data – I think this is a great feature.
  4. Doctrine can guess from the YAML file what the relationships are between objects/tables, and so far seems to do a great job of it.
  5. If you don’t want to hand craft your YAML, you can also do various things like build the models from an existing database structure or vice versa
  6. You can fetch data from one object and it fetches all the related data too, eg if I fetch a blog post and it fetches all the related comments at the same time.
  7. You can use “Doctrine Query Language” or DQL, which is has a similar feel to SQL, but selects objects together with their relationships instead of a flat table of results. Gone are the days of laboriously typing out JOIN statements: because you’ve described the data relationships in your YAML file, Doctrine automatically joins things together for you. Nifty.
  8. You can hook into the models that are automatically generated allowing you to, for example, modify queries on the fly before data is requested, or modify data as it’s returned. This lets you reuse business logic efficiently that is always used on particular fields.
  9. There are built-in behaviours such as ‘timestampable’ or ‘sluggable’ which automatically generate timestamp fields or URL slugs (eg ‘this-is-a-nice-blog’) for particular tables
  10. It also has the usual suspects such as data validation – but I haven’t got to that yet…

Right, that’s it for now – if I find out anything else interesting I’ll post it here!