nickf
nickf

Reputation: 546503

Adding page-specific Javascript to each view in CakePHP

In an attempt to keep my scripts maintainable, I'm going to move each into their own file, organised by controller and action:

// scripts which only apply to /views/posts/add.ctp
/app/webroot/js/page/posts/add.js

// scripts which only apply to /view/users/index.ctp
/app/webroot/js/page/users/index.js

That's all cool, however I'd like for these to be automatically added by the Controller, since it obviously knows the name of both the controller and action.

I figure the best place for this is in AppController::beforeRender(). (yes?)

The only problem is that I don't know how to actually add this into the $scripts_for_layout variable. I thought that getting a reference to the javascript helper object would work, but I can't find it from the controller!

class AppController extends Controller {
    var $helpers = array("javascript", "html", "form");

    function beforeRender() {
        // ???
    }
}

Upvotes: 12

Views: 15964

Answers (9)

dewwwald
dewwwald

Reputation: 297

With Cake v3 you can do it like this. Add the scripts you want in the specific controller.

//YourController.php
public function beforeRender (Event $event)
{
    array_push($this->scripts, 'Admin/gallery');
    parent::beforeRender($event);
}

Set the default in intialize and render once //AppController.php public function initialize() { parent::initialize();

    $this->scripts = [];
}

public function beforeRender (Event $event)
{
    /* Define scripts in beforeRender of Child */
    $this->set('scripts', $this->scripts);
    /*-----------------------------------------*/
}

Now you can run this function once.

<?= $this->Html->script($scripts) ?>

Upvotes: 0

Fernando Rybka
Fernando Rybka

Reputation: 258

LI worked a little (very little) bit over W1ckd snippet and made it easier for sharing the same js for different actions:

if ( is_dir(WWW_ROOT . 'js' . DS . $this->params['controller'] ) && ( $handle = opendir( WWW_ROOT . 'js' . DS . $this->params['controller'] ) ) )
{
    while ( false !== ( $entry = readdir( $handle ) ) )
    {
        if ( in_array( $this->params['action'], explode( '.', $entry ) ) ) {
            $entry = str_replace( ".js", "", $entry );
            echo $this->Html->script( $this->params['controller'].DS.$entry );
        }

    }
    closedir( $handle );
}

This way you can have something like:

webroot/js/controller/view.edit.add.js

And this js will be included in those three actions (view, edit, add).

Upvotes: 0

Vanja D.
Vanja D.

Reputation: 854

I have authored a Plugin for this exact issue.

If you have two files:

  • /app/View/Post/add.ctp
  • /app/View/Post/add.js

it will include the add.js into the script block of the page.

OR

  • app/View/Post/add.js
  • app/webroot/js/post/add.js

it will include /js/post/add.js into the script block of the page.

A few cute features are in there and it's simple as beans. You can even use PHP inside your .js files and use viewVars which you set in the action. Most of all, you don't need to hack the view or layout files to make it work, simply fetch the 'script' block in the layout. You can also simply write the .js to another view block.

You can check it out here: https://github.com/dizyart/cakephp-viewautoload

It's in the early stages, so make sure to comment and wishlist!

Upvotes: 0

uKolka
uKolka

Reputation: 38750

I just had to do page specific inclusion, but I've found a neater way to do it in the documentation. You can just load it into some script block in your default.ctp. And in corresponding view just use HTML helper to push a script:

You can append the script tag to a specific block using the block option:

echo $this->Html->script('wysiwyg', array('block' => 'scriptBottom'));

Which appends <script type="text/javascript" href="/js/wysiwyg.js"></script> to a block.

In your layout you can output all the script tags added to ‘scriptBottom’:

echo $this->fetch('scriptBottom');

Upvotes: 3

icc97
icc97

Reputation: 12863

There's a nuts and bolts of CakePHP blog post on Convention over Configuration – what's the big deal? - which uses a helper for specifying Javascript files:

<?php

class JsManagerHelper extends AppHelper {

    var $helpers = array('Javascript');

    //where's our jQuery path relevant to CakePHP's JS dir?
    var $_jqueryPath = 'jquery';

    //where do we keep our page/view relevant scripts?
    var $_pageScriptPath = 'page_specific';

    function myJs() {
    return $this->Javascript->link($this->_jqueryPath . '/' .
                                    $this->_pageScriptPath .'/' .
                                    $this->params['controller'] . '_' .
                                    $this->params['action'], false);
    }

}

?>

And then you just have $jsManager->myJs(); in your view.

Upvotes: 0

W1ckd
W1ckd

Reputation: 21

Kinda new to this, but after reading this added the following to my layout:

if ($handle = opendir(WWW_ROOT . 'js' . DS . $this->params['controller'] . DS . $this->params['action'])) 
{
    while (false !== ($entry = readdir($handle)))
        {
           $entry = str_replace(".js", "", $entry);
           echo $this->Html->script($entry);

    }
    closedir($handle);
}

Upvotes: 2

deceze
deceze

Reputation: 522636

Very easy to do in your default.ctp layout file:

An example to automatically include .css files per controller and/or controller/action (because I had this lying around, easily adaptable to .js files):

<head>
...
<?php
    if (is_file(WWW_ROOT . 'css' . DS . $this->params['controller'] . '.css')) {
        echo $html->css($this->params['controller']);
    }
    if (is_file(WWW_ROOT . 'css' . DS . $this->params['controller'] . DS . $this->params['action'] . '.css')) {
        echo $html->css($this->params['controller'] . '/' . $this->params['action']);
    }
?>
...
</head>

Upvotes: 28

brettkelly
brettkelly

Reputation: 28245

Like deceze is saying, we do it using the layout, although I find our solution a bit more elegant :)

In default.ctp:

if(isset($cssIncludes)){
    foreach($cssIncludes as $css){
        echo $html->css($css);
    }
}

if(isset($jsIncludes)){
    foreach($jsIncludes as $js){
        echo $javascript->link($js);
    }
}

Then, in our controller actions, we define these arrays:

$this->set('cssIncludes',array('special')); // this will link to /css/special.css
$this->set('jsIncludes',array('jquery'));   // this will link to /js/jquery.js

For files that need to be loaded in each view, we simply add the same type of link "statically" to the top of the layout, like:

echo $javascript->link('global');
echo $html->css('global');

This works really well for us. Good luck!

Upvotes: 15

timdev
timdev

Reputation: 62924

The best way I can think of is to create your own custom AppView and have all your controllers use that:

class myController extends AppController {
  var view = 'AppView';
  ...
}

Then, somewhere in your AppView, you'd want to do something like:

function __construct(&$controller, $register){
  parent::__construct($controller,$register);
  $this->addScript('<script type="text/javascript" src="/path/to/js/' . $this->controller . '/' . $this->action . '.js"></script>');
}

But I'd take a step back and think about a few things, first.

How big are your scripts, on average? Is the overhead of an external script call (before the script is cached by the client) better than adding a few hundred bytes to your main output stream (by just sticking the script into the page, inline)?

Perhaps you'd be better of somewhere in the middle -- split your scripts up by controller, but not action. That way, after the first visit to any action, the client has all scripts for all actions. This way, you avoid a big initial download for all the application's script, but you avoid adding N http round-trips (where N is the number of actions a brand new user visits).

Another way to approach the problem is to do it all in javascript. Just figure out a lazy-loading scheme. So your app just loads a very small loader.js, and that script figures out which other javascript sources to pull in.

Note: I've never tested my extend-the-view hack, but I bet it'll work if you really want to do this.

Upvotes: 1

Related Questions