23 August 2011

Listing event handlers tied to DOM elements with jQuery and Firebug

I'm busy hacking a template from an existing source to fit into a new Cake backend.  The template is pretty well written but I need to update the way in which content is being loaded and displayed.  Currently the site loads the *entire* site into one page and then flipping the visibility of elements in order to simulate dynamic loading.  Of course this works fine for the demo site the template loads, but is not going to fly for a production site.

I ran into the problem of having to work out where the event handlers are being declared for various elements on the page.  I was faced with the task of trekking through 15 files of Javascript.  Okay some are obviously not candidates (like jQuery.js itself) but that's still a mission.

Luckily jQuery has an extra bit of goodness that made the task trivial.  It stores event handlers in the data attribute (read up about that here http://api.jquery.com/jQuery.data/ ).

Using Firebug you can open your console tab and bash in the magic command:

$('#sneakybatmachine').data('events');

replacing the element selector with something more likely to match in your document.

That will give you a nice interactive way to explore the element and all of it's handlers, letting you click through right to the point in script where the handler is defined.

This saves lots of time having to RTFS.


Tip

17 August 2011

Caching your Twitter feed on your website in PHP

Instead of reinventing the wheel I'm going to copy the script found at css-tricks.com (click here)
function getTwitterStatus($userid){
$url = "http://twitter.com/statuses/user_timeline/$userid.xml?count=1";

$xml = simplexml_load_file($url) or die("could not connect");

       foreach($xml->status as $status){
       $text = $status->text;
       }
       echo $text;
 }

//my user id kenrick1991
getTwitterStatus("kenrick1991");

I'm leaving the original author's Twitter ID in there, but obviously you'll change this to yours.

This function will retrieve the most recent Tweet from your feed (see the "count=1" variable in the URL). But it will request this every time the page loads.

The most simple caching strategy is to write your Twitter status to disk, and when your page loads check the timestamp of the file against the value returned by time(). If it exceeds whatever threshold you deem appropriate you can reload the feed from the web.

Rather than boring you with code that demonstrates the use of file_exists(), filemtime(), and file_get_contents() I'll save you a Google search by highlighting the way to serialize and deserialize a simplexml object so that it is suitable for writing to disk (or database).

Here's a routine to dump the Twitter simplexml object to disk:

private function updateCache() {
        $xml = $this->xml->asXML();
        $handle = fopen($this->cacheFile,'w');
        fwrite($handle, $xml);
        fclose($handle);
    }

And here is how to retrieve it:

$xmlCache = file_get_contents($this->cacheFile);
$this->xml = simplexml_load_string($xmlCache);
Tip

20 July 2011

Microsoft Internet Explorer - a "special needs" browser

When developing a website I usually consider how long it will take to code the site, template it, and test it.  Then I add a whole lot of extra time to make it work in Internet Explorer.

A Microsoft User - ready to use the interweb
In a way Microsoft's refusal to believe that there is a world of computing outside of Redmond is a good thing - it means that I get more billable hours per project.  Of course those billable hours are spent in frustrating searches through Google to try and work out all of the various quirks and bugs in Internet Explorer.

I tend to think of Microsoft Internet Explorer users as "special needs" kids who are being set loose in the playground that is the Internet.  Barely able to prevent themselves from drooling on their keyboards and eating their mouses they demand that websites conform to their special needs.  Standards such as Javascript DOM level 2, HTML5, CSS, CSS3 are pretty much ignored because their parent doesn't believe in modern technology.  Microsoft and the Amish are pretty much similar in that respect.  They're both American, highly insulated, and refuse to believe in an outside world that is advancing without them.

BUT Microsoft insists on installing its browser into its Windows operating system.  And not only does it ship as a default browser, but it's so deeply entrenched into the OS that you can't uninstall it.  I'm not sure why Microsoft thinks that a web browser should be integrated into an operating system, but I'm sure there is a reason for this (like preventing users from choosing other browsers).

So the average Internet Explorer user is a Windows user (typically less savvy than Mac/Linux users) who is unaware that there are better ways to explore the Internet (less savvy than Firefox or Chrome users).  Who would choose a browser that is slower and less featured than a competing browser (like Chrome)?

So, if your target market consists of special needs people who "choose" inferior products then by all means target the gullible Internet Explorer users.  It's like taking candy from special needs kids.

Is there hope for the future?

An Internet Explorer is pulled over on the Internet Highway
Of course Microsoft is promising to change in IE9.  They are promising "native" support for HTML5.  Apart from the fact that HTML5 is an external standard and doesn't belong to Microsoft why would they want to  build this into their operating system?

Ah well, that's Microsoft for you.  But Microsoft also promised that IE8 would be standards compliant and would solve problems like world hunger.  I'm still hungry and margin:auto doesn't work, and neither do phase down events in Javascript, neither does HTML5 and CSS3 is also ignored.  So when Microsoft aims to be standards compliant they actually mean "Microsoft standards compliant" and not the standards that the rest of us use.

IE7 promised to offer a cure for cancer and be an improvement on IE6.  That's pretty much the only promise that Microsoft actually managed to keep, but having Satan poke my eyes out while Steve Ballmer (crazy monkey man) sodomizes me would be better than IE6.  So by aiming for the low hanging fruit Microsoft actually delivered on a promise.  Sodomy from the world's sweatiest billionaire would only be made worse by his screams "DEVELOPERS DEVELOPERS DEVELOPERS" which I imagine would be  his replacement for "oh God I'm coming".  I'm not suggesting Mr Ballmer enjoys anal sex (I know he's married with tadpoles) but he does appear to have a somewhat sadistic approach to web developers so the analogy is used.  I'm actually quite fond of the monkey man - he gives me about 5 hours extra on every project I do.
Tip

08 July 2011

CakePHP : Changing .htaccess to prevent one (or more) directory from being handled by Cake

This technique may be useful if for example you have decided to use Wordpress to handle your blog, rather than trying to code your own competing blog system.

This .htaccess file is based on the CakePHP default but simply adds a rewrite rule to exclude the "blog" directory from Cake's control.

<ifmodule mod_rewrite.c=""%gt;
   RewriteEngine on
   
   RewriteRule   ^(blog).* - [NC,L]
   
   RewriteRule    ^blog /blog    [L]
   RewriteRule    ^$ app/webroot/    [L]
   RewriteRule    (.*) app/webroot/$1 [L]
</ifmodule%gt;

If you want to prevent Cake from controlling multiple directories you can simply modify the rewrite rule like such:

RewriteRule   ^(blog|my|other|directories|go|here).* - [NC,L]

In order to accomplish this on an IIS server you can email sballmer@microsoft.com and ask for support in getting his expensive software to do what it says it does on the box.
Tip

04 July 2011

CakePHP : Adding a file upload and adding a select list of URLs for users in a CMS

CakePHP automagically generates textboxes for users, but it's usually a project requirement that these boxes are "user friendly".

Adding CK editor to CakePHP is easy, but lets go a few steps further and give it the ability to allow users to upload images directly into their content and to select a list of pages when creating a link.

This article is based heavily on two articles (Adding file upload in CK editor and Adding a ‘Link to local page from site’ field) which I have simply modified to be CakePHP specific. So all credits to Ben Roberts and Zac.

Step 1 - Adding CK editor to CakePHP with the FileManager plugin
1) Download CK editor from the official site and unzip it into your /app/webroot/js directory. To make things easy I put it in /app/webroot/ckeditor directory.
2) Download FCK editor from the same site.  It was at the bottom of the CK downloads page (because it is deprecated).  Unzip it to a temporary directory and copy the filemanager directory (fckeditor/editor/) to /app/webroot/ckeditor/filemanager directory.  We will be using the FCK filemanager in CK editor.
3) Edit filemanager/connectors/php/config.php and enable the plugin and do whatever other configuration you may require. Take note of the document root option and remember that this is going through Javascript and not CakePHP, which means that you need to set it /app/webroot/userfiles and not the default of /userfiles
4) Open up filemanager/connectors/php/config.php and make the following changes:

Add this function:

function GetUrlParam( paramName )
{
 var oRegex = new RegExp( '[\?&]' + paramName + '=([^&]+)', 'i' ) ;
 var oMatch = oRegex.exec( window.top.location.search ) ;
 
 if ( oMatch && oMatch.length > 1 )
  return decodeURIComponent( oMatch[1] ) ;
 else
  return '' ;
}
Replace the OpenFile function with the below function:

function OpenFile( fileUrl )
{
 
 //PATCH: Using CKEditors API we set the file in preview window. 
 
 funcNum = GetUrlParam('CKEditorFuncNum') ;
 //fixed the issue: images are not displayed in preview window when filename contain spaces due encodeURI encoding already encoded fileUrl 
 window.top.opener.CKEDITOR.tools.callFunction( funcNum, fileUrl);
 
 ///////////////////////////////////
 window.top.close() ;
 window.top.opener.focus() ;
}
Create a new helper in your helpers directory called fck.php and include the code below. Remember to use this helper in the controller you want to be able to use CK editor. This helper is based on the one available in the bakery but I have modified the code so that it uses the file manager we copied in from FCK.

// file /app/views/helpers/fck.php
class FckHelper extends Helper { 

    var $helpers = Array('Html', 'Javascript'); 

    function load($id) { 
        $did = ''; 
        foreach (explode('.', $id) as $v) { 
            $did .= ucfirst($v); 
        }  
        // filebrowser patch   
        $here = 'http://' .  $_SERVER['HTTP_HOST'] . $this->webroot; 

        $fileBrowser = "{\n
            filebrowserBrowseUrl :'".$here."js/ckeditor/filemanager/browser/default/browser.html?Connector=".$here."js/ckeditor/filemanager/connectors/php/connector.php',\n
            filebrowserImageBrowseUrl : '".$here."js/ckeditor/filemanager/browser/default/browser.html?Type=Image&Connector=".$here."js/ckeditor/filemanager/connectors/php/connector.php',\n
            filebrowserFlashBrowseUrl :'".$here."js/ckeditor/filemanager/browser/default/browser.html?Type=Flash&Connector=".$here."js/ckeditor/filemanager/connectors/php/connector.php'}\n";

        $code = "CKEDITOR.replace( '".$did."', $fileBrowser );"; 
        return $this->Javascript->codeBlock($code);  
    } 
} 

When you want to include CK editor in a view you can now use the code provided in the bakery. I'm just quoting a snippet, but you can view the full source here

echo $javascript->link('ckeditor/ckeditor', NULL, false);  
 echo $form->input('body', array('cols' => '60', 'rows' => '3')); 
 echo $fck->load('News.body'); 

We should now be able to include CK editor and make use of the File Manager.

Step 2 - Allowing users to select a list of URL's on the site

This section is a CakePHP specific implementation of the guide published online here.

I'm using a menu table in my database to list a menu name and url. It doesn't really matter how you want to handle your pages just so long as you can get them into JSON formatted string of description/url pairs.

Here is an example of the desired output in your controller:

[['Home Page','home'],['About Us','about'],['Contact','contact']]

I used code like the following snippet, because for this project I am using a "slug" field to be the same as the page title (project specification). Slugs are stored in my database with a hyphen which is simply stripped to get the page title.

// get pages for ckEditor
$this->ContentItem->recursive = -1;
$contentItems = $this->ContentItem->find('all',array('fields'=>array('slug')));
$json_links = array();
    foreach ($contentItems as $contentItem) {
        $slug = $contentItem['ContentItem']['slug'];                                      
        $description = str_replace('-',' ',$slug);
        $json_links[] .= "['" . $description . "','". $slug ."']";
    }     
    $json_links = '[' . implode(',',$json_links) . ']';               
    $this->set(compact('json_links'));

You will no doubt need to modify this to suit your needs, either in your model or controller depending on your coding style.

Open up the view and add a dummy input box like the tutorial suggests:

<input type='hidden' id='pageListJSON' value="<?php echo $json_links; ?>" >

Personally I feel that there is probably a better way to do this, but since this method works I'm not going to bother trying to improve on it. The only reason the author of the tutorial uses the input is to use a Javascript query on it later. So I wonder if setting a variable there and replacing this look-up later would be a better approach? Comments anybody? Anyway, back to work...

You can now follow the rest of the tutorial that this section is based on from step 3 as this all deals with the Javascript and so can be directly applied to Cake.

When following the tutorial make sure that you take note of the comment made by amosmos on 01.01.11 regarding the code addition in step 5... if you are using a database to pull your pages you may need to add a prefix to your URL (or you can do it in your Model/Controller when you encode in JSON):

attributes[ 'data-cke-saved-href' ] = 'pages/' + data.localPage; 

You should now have a workable CK Editor for your CakePHP CMS.

Tip

15 April 2011

Hassles with Uniforum and co.za registration

Dreams of Technology foiled by pokey companies
I've previously found that the Uniforum email form does not allow for providing more than a certain, fixed, number of nameservers.  Apparently nobody would ever require more than that number.  Well unless they're using a high availability DNS service (like www.dnsmadeeasy.com) that is.  If you happen to be using more nameservers than Uniforum has determined to be the maximum that South Africans need then they won't register your domain.

More recently I've found that they don't like nameservers where the FQDN lookup doesn't match the reverse lookup.  This is pretty much understandable, but what I don't understand is why it was working for a particular host I use until a month ago and is now no longer working.

I raised a support ticket with Uniforum and it took them a good couple of days to come back to me.  I wonder how a company that charges R 50 for every single co.za domain offers such poor service and such an antiquated API (email forms?  I mean really).

To put things in perspective.  To register a domain in .com costs about R 67 (without shopping around).  This is R 17 more than a co.za domain. 

There are many benefits to using a .com domain:
  1. foreign customers will recognize .com better, 
  2. it's easier to say on the radio, 
  3. domains can be locked against transfers, 
  4. you can register them automatically, 
  5. you can register them for multiple years at a time, and 
  6. you don't have to hassle with Uniforum.
Is it time for another company to replace Uniforum?
Tip