Tag javascript

[Cake on Steroids] PHP + JS

16 July, 18:29, by zehzinho Tags: , ,

Hi, today I’m starting a series of posts with tips on how to improve your CakePHP productivity even more. By productivity I mean: doing things faster and/or in a more organized way. I’m calling it “Cake on Steroids”. As I searched the web and could not find anything like this, I think it’s ok to use this name, right? Enough said, let’s start.

Well, one thing that really annoyed me since the first time I used CakePHP was the way Javascript is handled per default by the framework. It’s not hard to find views with stuff like this:

$javascript->codeBlock(
  'window.addEvent("domready", function(){alert("hi there);});',
  array("inline" => true) );

The two main problems of writing JS code this way are:

  1. No editor can provide javascript syntax highlighting in PHP strings, right? This is a nightmare.
  2. Javascript mixed with PHP and HTML code.

We all know that the second reason can easily be solved in CakePHP, since the framework, obviously, allows us to use JS files. But there are some situations in which it would be nice to be able to use some PHP code within the Javascript code. For example, sometimes we use URLs in the javascript code (in an autocomplete function, for example) and the url() function from the Html helper is nice.

Another strong reason to use PHP + JS is to maintain consistency between elements in the view and the Javascript. What kind of elements? Images and, even more importantly for those who need it, translations. We just don’t want to have to translate things twice (one using cake and another using JS), right? Even if we could use the same translation file for PHP and JS, it would be nice to have elements attached to only one keyword, so we reduce the chances of making mistakes (eg. changing the keyword only in the PHP or only in the JS).

Well, as I said, all this annoyed me. And I think most people can figure out more scenarios in which it would be nice to have PHP + JS in their environments. So, the good news are that it is perfectly possible to do it in CakePHP.

I have found some articles which teach how to do it. Oh, I forgot to say. I’m using this solution for quite some time and I’ve been postponing this post for months and now I just couldn’t find my original sources. If I find them, I’ll update the post, ok?

So, how do we do it?

The first step is to tell cake to parse the js extension. You can do it by editing app/config/routes.php and adding the following statement:

Router::parseExtensions('js');

The second step is to add a default layout for JS. You can do it by creating and editing the file app/views/layouts/js/gen.ctp:

<?php
 
header("content-type: application/x-javascript");
 
echo $content_for_layout;
 
$this->data = null;
Configure::write('debug', 0);
?>

The third and last step is to create an action in a controller which will be responsible for loading our JS file. I like to add this action to my AppController, so I don’t have to write it in all my controllers. Edit you AppController and add the following function:

 
 public function formjs($id) {
    if ($this->params['url']['ext'] != 'js') {
        exit;
    }
 
    $this->layout = 'gen';
    $this->ext = '.js';
 
    $this->set("cacheDuration", '1 hour');
  }

Adding this function to my AppController allows me to add the following lines to my default layout (usually app/views/layouts/default.ctp):

$autoloadFormJs = APP . 'views' . DS . this->params["controller"] . DS . "js" . DS . "formjs.js";
 
$urlAction = $this -> params['action'];
 
if (($urlAction == 'add' || $urlAction == 'edit') && is_file($autoloadFormJs)) {
      $jsPath = '/' . $this->params["controller"] . '/formjs/1';
      echo $javascript->link(array($jsPath));
}

and voilá. All you have to do is create a js/formjs.js file inside the view folder of your component and it will be inserted automatically. For example, if you have a component called bodies, and you want some javascript to be automatically loaded for the add and edit actions, all you have to do is create the file views/bodies/js/formjs.js and the code above will insert the file automatically in the page.

Of course, I’m just trying to speed up your development process ;) But you can be more conservative and place the formjs function only in the bodies controller. Or you can load the javascript only in the add.ctp view of the bodies component. To do it, you would have to place following line in your views/bodies/add.ctp file:

echo $javascript -> link(array('/bodies/formjs/1'));

A positive side-effect of this approach is that you can pass the id argument in the javascript URL. In the examples above, all links to javascript files use the id = 1. But you can use different values for the id and the javascript file will be loaded the same way. This is actually a feature of this approach, since you can use aggressive caching policies for your javascript files and change the id whenever the file is modified, assuring consistency.

Now our javascript files are being loaded by the CakePHP framework and we can use PHP inside of them in anyway we want.

I usually write the PHP code inside javascript comments, so the editor does not give me syntax errors. For example:

/*<?php
$usersAutocompleteUrl = $html->url(array('controller' => 'users', 'action' => 'autocomplete'));
*/
 
$(function() {
  // using the previously defined PHP variable
  $('#userSearchField').autocomplete('<?= $usersAutocompleteUrl ?>', {
    // lots of JS code
  });
});

Finally, I created a helper called PhpInJS to help me define variables in the PHP view which can be used in a JS file. Place the following code in the views/helpers/php_in_js.php file:

class PhpInJsHelper extends AppHelper {
  function set($name, $value) {
    if (!empty($this->params['controller'])) {
      $controller = $this->params['controller'];
      $_SESSION['PhpInJs'][$controller][$name] = $value;
      return $value;
    }
 
    return null;
  }
 
  function get($name) {
    if (isset($_SESSION['PhpInJs'][$this->params['controller']][$name])) {
      return $_SESSION['PhpInJs'][$this->params['controller']][$name];
    }
  }
}

You can use this helper in the view file (eg. add.ctp) to set a variable:

$eraseImage = $phpInJs -> set('eraseImage', $html -> image('erase.png'));
 
// use the $eraseImage var in the PHP file

and use the the same value in the JS file:

/*<?php
$eraseImage = $phpInJs -> get('eraseImage');
?>*/

Well, I think it’s enough. You can/should always use the comments to help me improve/fix the solution, ok? See ya.