zen of coding

An intro look at jQuery Tools, enhancement for setFlash() and CakePHP 1.3

Actually the main focus of this post will be how to creatively use JavaScript and Session::setFlash() to make your app a little more sexy.
(Slightly tweaked to use CakePHP 1.3, although there is nothing special here that you couldn’t do with other versions of cake).

First, I wanted to point out an awesome little library, called jQuery Tools, which comes with a set of really nice widgets, a whopping 5KB in size and its own CDN for easy loading.

To proceed further let’s consider an Articles Controller, where we are adding a new article.

This is something that you should’ve seen at least a few hundred times…

<?php
class ArticlesController extends AppController {
 public function add() {
    if (!empty($this->data)) {
        if($this->Article->save($this->data)) {
          $this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));
        } else {
          $this->Session->setFlash('Article was not saved!', 'modal', array('class' => 'modal problem'));
        }
     }
  }
}
?>

Here we are using a pretty standard approach to write a message to the session by using $this->Session->setFlash().
What usually happens then, is a redirect to some other place where the message is displayed.
In our case the message will be displayed in the same view, using a nice-looking modal/dialog.
Another point to note, is that once the message is displayed to the user, we’ll automatically hide it (let’s say after three seconds).
To further extend the app you might consider disabling (or converting to plain-text) the form fields, and possibly displaying an “Edit” link… though these features are brewing in my app, I thought it would be too much to cover in one example.

Alright, now that we are looking at our familiar add() action, let’s see what tweaks we should do achieve the desired goals listed above.

In our layout, we’ll need to load both the jQuery and jQuery Tools libraries.

This is a snippet to do so using the existing CDN’s (and in CakePHP 1.3 style):

<?php echo $this->Html->script(array('https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js',
                                         'http://cdn.jquerytools.org/1.1.2/jquery.tools.min.js')); ?>

$this->Html->script() replaced CakePHP 1.2 approach of using $javascript->link(), the rest should be pretty obvious.

Next, let’s consider the view for our add() action:

<?php $this->Session->flash(); ?>

<?php
    echo $this->Form->create();

    echo $this->Form->input('Article.title');
    echo $this->Form->input('Article.body');

    echo $this->Form->end('Add Aricle');

?>

One thing you’ll notice is the way helpers are referenced in CakePHP 1.3… while $form->input() still works, the approach above is “safer” and more consistent. (Take a look here for more details).

Let’s get back to this line of code for a second…

$this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));

The main difference in CakePHP 1.3, is that modal, the second param, represents an element (unlike a layout in previous versions).
Therefore for this setup to work properly you’ll need to create app/views/elements/modal.ctp

Let’s see what it looks like:

<div id="flash-message" class="<?php echo $class; ?>"> <?php echo $message; ?> </div>

$message is replaced by whatever we place in the first argument to the setFlash() method (which happens to be “Article saved!”, in our case).
As you’ve probably guessed $class gets replaced with the appropriate value from the array of passed params.

Now, if we have successfully saved the article the add.ctp view gets re-displayed with our message showing up and the element rendered. (This all happens automagically by using $this->Session->flash()).
This could be already fine in itself, but it’s just not sexy enough… so we add a little jQuery and jQuery Tools magic to make things a little more fun.

[sourcecode language=”javascript”]
$(document).ready(function() {

//with this approach you can assign the jQuery Tools API to be used later
//otherwise you could use a standard one-liner:
//$(‘#flash-message’).expose().load();
var flashMessage = $(‘#flash-message’).expose({api: true});

//now that we have a direct access to the API through the “flashMessage” object
//we can call the jQuery Tools methods
//such as load(), which is required to show/expose our modal
flashMessage.load();

//now that the modal is displayed we can create a simple way to automatically
//remove it after 3 seconds
flashMessage.onLoad(function() {
setTimeout(function() {
flashMessage.close();
$(‘#flash-message’).slideUp(500);
}, 3000);
});

});
[/cc]

By including this script we will achieve exactly what our goals are. Display the message in a nice modal/overlay fashion, wait 3 seconds and then auto-hide the message from the user and make our page act as before again.
If you care, please take a look at the comments in the code, to see some nice features of jQuery Tools, i.e. the ability to access any tool’s API. (Please refer to jQuery Tools site for more details).
Overall, we are just relying on the jQuery goodness and overriding our boring flash() behavior, with something quite a bit more interesting.

Well, there you have it, aimed with these tools you are ready to bake sexy CakePHP 1.3 apps ;)

P.S.

Just a little CSS for the modal:

[sourcecode language=”css”]
.modal {
width: 350px;
padding: 60px;
margin: 50px 50px;
}

.success {
border: 1px solid #004000;
}

.problem {
border: 1px solid #800000;
}
[/cc]

  • This is done before by James Fairhurst right? See: http://www.jamesfairhurst.co.uk/posts/view/custom_cakephp_flash_messages_updated/

    Or do I oversee something?

  • @Akif

    Very similar, I haven’t seen this article before.

    Well, while the idea is the same this approach shows how everything is done in CakepPHP 1.3, and using jQuery Tools… plus a few minor differences in the code.

  • me

    No need to call Session->check() before Session->flash(), this check is done inside of flash() already.

  • ADmad

    There’s no need for $this->Session->check(‘Message’). SessionHelper::flash already does a check internally.

  • @me, ADmad

    Well, then … consider that fixed.

  • To use the $this->Form syntax in the views you have to use the CakePHP code from the repository, the current 1.3dev release doesn’t support this syntax.

  • @Daniel Hofstetter

    Thanks for clarifying that, I was not aware.
    Pretty much pull the latest from git whenever possible.

    Hopefully those trying 1.3 and running into this issue will see this :)

  • Adam

    @teknoid In what file do you usually place your jquery functions?

  • @Adam

    Despite my previous convention, I usually follow the following nowadays:

    app/webroot/js/views/user/action_name.js

    Then a lot of stuff can be automated, relatively easily.
    So, in the the example above the JS code would be in:
    app/webroot/js/views/articles/add.js

  • @ teknoid/placement of public css/js files:
    I do the same for both js and css files teknoid. Its a great way to autoload and builds on convention over configuration. Should be a core functionality anyway.

    @ the new setFlash: its awesome, afaik the only way to change the html markup of the flash message was to hackup the core (or override it).

    About the general new apporach to make things more consistant and less error-prone ($this->Form->…) there should be aliases (like with models) for anything you want but primarily for Components and Helpers.

    $components => array(‘MyAuth’ => ‘Auth’);
    $helpers => array(‘MyForm’ => ‘Form’);

    I forgot that suggestion but this here reminded me of it. That way you can easily “override” core components helpers with your own methods without being required to change all $this->Auth and $form-> or $this->Form-> call statements throughout your application code.

  • @ionas82

    Thanks for sharing. All good suggestions.

    There are some ways to extend the helpers, but AFAIK, most are “hackish” way of doing so.
    For example, I would love to be able to do something like
    $this->Form->input(‘Model.something’, array(‘type’=>’autocomplete’));

    And while it can be done, I just haven’t found a very “clean” way.

  • Add aliases for those singletons would solve that. It would be done one time and you could specify as suggested. It would even downgrade nicely.

    My mother language is German, and I am no expert on programming/framework design, I fear that it would be better if someone else opened a well expressed ticket with the right wording on a.) singleton aliases for components/helpers and b.) autoloading of css/js files in webroot/css/layouts/layoutname.css webroot/css/views/users.css webroot/css/views/users/edit.css and so on – same for javascript.

  • chronon

    Thanks for the article. Using the jQuery tools overlay effect with expose makes for a great flash message as well. Something like:

    var flashMessage = $(‘#flash-message’).overlay({
    api: true,
    top: 20,
    expose: {
    color: ‘#333’,
    loadSpeed: 200,
    closeSpeed: 200
    }
    });

  • @chronon

    Nice, thanks for sharing.
    Just a simple tweak of CSS makes it so different.

    And I am speaking from the 80’s…. where we didn’t have Internets and such… :)

  • mike

    $this->Html->script() did not work for me in 1.3.

    had to do:

    $html->script() in my layout.

  • @mike

    Be sure you are using 1.3

    $html->script() is definitely an old way of accessing the helper methods. It does still work in 1.3, but it is not recommended.

  • Great tutorial. Thanks!

  • Pingback: Yet another blog about PHP, HTML and CSS » Blog Archive » Yet another way to customize your flash messages – with a helper()

  • Hi there, I have a problem using $this->Html->script(…) in a view loaded via ajax call. It seems not work properly because there is no way to output the script as inline as in the header. I got the same problem with $this->Html->scriptBlock(“some code here”, array(‘inline’=>true, ‘evalScripts’=>true)) in every view loaded through ajax.
    Any suggestion?
    This is frustrating me because is a week that I’m trying to find any kind of solution but nothing. Even if I hardcode the tag in the view it will not be write in the view.

    Thanks in advance to everyone.

  • I forgot… I’m using cakephp 1.3.6

  • teknoid

    @morghot

    Why are you trying to load an entire view by AJAX?
    Usually you’d load small components of the view using AJAX.

    In other case if your view is truly just a small subset of data (or whatever) that is being loaded by AJAX, you shouldn’t put your script in there, but rather in he containing view.

%d bloggers like this: