Wednesday, August 11, 2010

Use jQuery to create Twitter and Growl-like notifications in CakePHP

This post describes how to create a twitter-like notification bar in a CakePHP application, using the default setFlash() method.

We will go from these messages:


To these messages:


It is only tested on CakePHP 1.3, but I guess it should work on 1.2.x too :)

In CakePHP a controller can send a message to the user via the session, using the command: $this->Session->setFlash('Hello User!'); . One of the options of setFlash() is that you can override the template that it uses to display the message, by passing a second parameter that specifies the element. So if you use $this->Session->setFlash('Hello User!','flash_success'); it will use the template app/views/layouts/flash_success.ctp , and thus you can influence it's behavior.

The downsite to the approach is that you need change every setFlash() command in your application to reflect the change. In my current app that are more then 150 changes, not a nice idea. Another bad thing about it is that the setFlash() message really needs the templates there. If you copy over your controller (or plugin) to another app that doesn't use these templates you will get a 'white page of death' if the template is not found and setFlash calles it.

The default template that is used by CakePHP is hardcoded in the Session controller so there is no way to really override the default without extending the Session helper (or hacking the core files). Both no-goes for me again.

The solution I came up with is quite simple: Just display the message as you would normally, hide it using CSS and let jQuery grab the text and display it in a fancy way.

There are a couple of twitter-like notification plugins out there, I picked the solution mentioned here because of it's simplicity.

(Note, i will use inline CSS and Javascript code to demonstrate it more easily. In a production environment you will want to move the CSS and Javascript code to separate files)

1. Start using a custom layout (default.ctp)

If you don't already use your own default.ctp template start by copying the file cake/libs/view/layouts/default.ctp to app/views/layouts/default.ctp . If you do already use it you can edit your existing template.

2. Download and install jQuery 

This method needs the great jQuery library to do some work, so we need to download it in our app, and mention it in our template so the app will use it.

Download http://code.jquery.com/jquery-1.4.2.min.js and put it in app/webroot/js/

Now copy the following line into app/views/layouts/default.ctp , in the section, right below the $this->Html->css() line:

        echo $this->Html->script('jquery-1.4.2.min');

CakePHP now will take care of loading the jQuery plugin on your page. This is a good moment to check if it works so far, load up your page in a browser and check the page-source to see if jQuery is linked.

3. Show us some messages

To easily test this behavior just create a file called app/controllers/notify_controller.php and enter the following content:

<?php
class NotifyController extends AppController {
    var $uses = null;
    function test() {
        $this->Session->setFlash('This is a Twitter-like notification :D');
        $this->redirect('/');
    }
}
?>

When you now visit the url  /notify/test  in your application you will see the default notification message on your screen.

4. Hide the message

Next step is to hide the message, which is done by CSS.

Enter the following code to the <head> section of your app/views/layouts/default.ctp template, just before the </head> tag:

<style type='text/css'>
#flashMessage.message { display: none; }
</style>

This will tell your browser to hide all elements with the class 'message' in the element with id #flashMessage .

5. Intercept the message

After we hide the message we will run some Javascript code to get it. Place the following code in the app/views/layouts/default.ctp template , just before the </body> tag :

<script type="text/javascript">
    jQuery(document).ready(function(){
        if(jQuery('#flashMessage..message').length) {
            alert($('#flashMessage..message').html());
        }
    });
</script>
 <noscript>
    <style type="text/css">
        #flashMessage.message {
            display: block;
        }
    </style>
</noscript>

There are actually two snippets here. The one in the <script> tag uses jQuery to wait until the document is finished loading, then fires the if-statement. The if-statement tests if te .message element exists by checking its' length. It the element is found (the test has not returned false) it will alert the content of this element.

The <noscript> part here facilitates the users that are browsing without Javascript, and will unhide the message for them.

If you visit your page now and test a setFlash message, it will show a Javascript alert to display it. Time to make this a little more fancy.

6. Show the pretty notification

To get this notification to work we create Javascript function that shows the message instead of the alert() function, and we will specify some CSS to style the message.

First put the following CSS code in your  < style > tag you used to hide the message in step 3:

    #notify_message {
        font-family:Arial,Helvetica,sans-serif;
        font-size:135%;
        font-weight:bold;
        overflow: hidden;
        width: 100%;
        text-align: center;
        position: absolute;
        top: 0;
        left: 0;
        background-color: #efefef;
        padding: 8px 0px;
        color: #333;
        opacity: .9;
        display:none;
    }

And put the following function in your < script > tag that you used to intercept the message in step 4:

    function showAlert(msg, timeout){
        if(!timeout){
            var timeout = 3000;
        }
        var $alertdiv = $('<div id="notify_message"/> ');
        $alertdiv.text(msg);
        $alertdiv.bind('click', function() { $(this).fadeOut(200); });
        $(document.body).append($alertdiv);
        $("#notify_message").fadeIn(500);
        setTimeout(function() { $alertdiv.fadeOut(500) }, timeout);
    }

The final bit is to remove the alert message and use the function above, you do this by changing the line:
    alert($('#flashMessage.message').html());
to
    notify($('#flashMessage.message').html());

7. That's it, that's all. 

Now generate a setFlash message and watch the notification bar enter your screen :)

Bonus: if you want the notifications to look more like Growl you can use the following CSS code:

    #notify_message {
        font-family:Arial,Helvetica,sans-serif;
        font-size:135%;
        font-weight:bold;
        overflow: hidden;
        width: 400px;
        text-align: center;
        position: absolute;
        top: 0;
        right: 0;
        background-color: #222;
        padding: 18px 0px;
        margin: 10px;
        color: #eee;
        opacity: .9;
        display:none;
        -moz-border-radius:8px;
        -webkit-border-radius:8px;
    }

7 comments:

Ceeram said...

of flash.js in webroot/js plaatsen met:

$(document).ready(function(){
setTimeout(function(){
$("#flashMessage").fadeOut("slow", function () {
$("#flashMessage").remove();
});
$("#authMessage").fadeOut("slow", function () {
$("#authMessage").remove();
});
}, 3000);
});

en in je layout $this->Html->script('flash');

nu kun je met wat css dit ook bereiken

Unknown said...

@Ceeram thanks for your comment :)

Anonymous said...

firebug said :

notify is not defined

[Break On This Error] notify($('#flashMessage.message').html());
any explanation for this error?thanks

Unknown said...

@Anonymous, that was an error on my side.

Change "function showAlert(msg, timeout)" to "function notify(msg, timeout)" and it will work :)

Victor Bruna said...

Nice work there. Is there a way i can use both Jquery and prototype? I have tried your example and it works fine, however my application is depending on prototype for some other functionalities and i realy want to implement this Growl-like notification - any help on how to work with both prototype and jquery frameworks??

Unknown said...

@Vic thanks for you comment, check this link on how to use both libraries together: http://docs.jquery.com/Using_jQuery_with_Other_Libraries

php web development said...

Nice creation I have seen here which is more informative and also more helpful to many people.