Adding page-specific Javascript to each view in CakePHP - javascript
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() {
// ???
}
}
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>
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!
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);
}
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');
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.
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.
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!
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).
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) ?>
Related
Wordpress - enqueue script only if specific class is on page
What would be the most appropriate solution to enqueue a script in WordPress only when a class of .google-map is detected on that page? In my main.js I can detect the item on the page and do something, but I am not so sure you can use the enqueue function in a JS file. $(document).ready(function(){ if (document.getElementsByClassName('.google-map')) { alert(true); } }); The above is just attempt #1. Please feel free to provide any other solutions, using functions or anything else. I am simply not too sure what is possible that's why I don't have more examples.
Ordinarily add the file google.js (or whatever name you choose) to WP footer by adding the code below into your functions.php file. This will add the javascript file into WP footer the right way. https://developer.wordpress.org/reference/functions/wp_enqueue_script/ function my_scriptings_scripts() { wp_enqueue_script( 'my_scriptings', get_template_directory_uri() . '/js/google.js', array('jquery'), '20171212', true ); } add_action( 'wp_enqueue_scripts', 'my_scriptings_scripts' ); Inside your js/google.js Using Vanilla javascript, check for the element with class name. If it exists then call the function for the action. var element = document.getElementByClassName('google-map'); if (typeof(element) != 'undefined' && element != null) { //call function for google actions google_acts_like_this(); } function google_acts_like_this(){ console.log('Google will take over the world'); alert('Google will take over the world'); } OR Try wienter code hereth Jquery - if ($(document).find(.google-map).length > 0) { //If the element exist, then do something. google_acts_like_this(); } function google_acts_like_this(){ console.log('Google will take over the world'); alert('Google will take over the world'); }
Where to Include Sugarcrm
I have been working on some custom code for a sugar module and am fairly unclear where to put my javascript code to be called in module. Currently I have put my custom JS in include/javascript/popup_parent_helper.js This works fine in developer mode but does not work when that is turned off and unfortunately dveloper mode runs SUPER slow I have done a lot of research and I am getting some conflicting results. Some tell me that I should include it in: /modules/[ModuleName]/ Others say that it should to in: /custom/modules/[ModuleName/ and some further in adding js as a directory Please help me clarify proper structure for this and where I need to make my proper include statement Clarifications: We are using SugarCrm 6.5x In this case the JS is only being used for one module. It is being used in the Quick Create View and the Edit View
If the javascript should be accessible by any module, you can create a new JSGrouping and pull in your custom js file using the following technique: http://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_7.6/Extension_Framework/JSGroupings/#Creating_New_JSGroupings It sounds like you want it to be isolated to your custom module, so you should probably extend the view desired. If you are extending the record view, create a new file called record.js at custom/modules/-your_module-/clients/base/views/record/ ({ extendsFrom: 'RecordView', initialize: function(options) { this._super('initialize', [options]); this.doSomething(); }, doSomething: function(){ console.log("Help you I will"); }, ... }) https://developer.sugarcrm.com/2014/02/10/extending-sugar-7-record-view/
I had also faced similar issue (JS should be work for edit and quickcreate form) but after making some RnD I achieved it as per following way: \custom\modules\<modulename>\views\view.edit.php <?php if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point'); require_once('include/MVC/View/views/view.edit.php'); class {moduleName}ViewEdit extends ViewEdit { public function __construct() { parent::ViewEdit(); $this->useForSubpanel = true; // this variable specifies that these changes should work for subpanel // / $this->useModuleQuickCreateTemplate = true; // quick create template too } function display(){ ?> <?php $jsscript = <<<EOQ <script> Your JS code </script> EOQ; parent::display(); echo $jsscript; //echo the script } } ?>
How to add external <script> to <head> section for all mediawiki pages?
I want add external script to head section for all pages in mediawiki. Function onBeforePageDisplay callback from BeforePageDisplay hook: //LocalSettings.php ... # Assign my functions to hook $wgHooks['BeforePageDisplay'][] ='onBeforePageDisplay'; function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) { mw.loader.load('http://static.wowhead.com/widgets/power.js', 'text/javascript'); $out->addModules( 'mw.loader' ); return true; }; In this function i want to add <script type="text/javascript" src="http://static.wowhead.com/widgets/power.js"></script> <script>var wowhead_tooltips = { "colorlinks": true, "iconizelinks": true, "renamelinks": true }</script> to <head> section for all pages in wiki. For old versions of mediawiki used addScript method of OutputPage object: $out->addScript( $html ) // Add a JS file. $html is a full script tag: '<script type="text/javascript" src="..."></script>' but now For MediaWiki 1.17 and above, use ResourceLoader modules. $out->addModules( array( /modules/ ) ); I could not make it work and don't find any examples of this. ResourceLoader description Default_modules description Maybe I have to use mw.loader.load module, but I have no idea how to do it. Help me, please, and sorry for my english. P.s. this solution work, but is not right. Need solution with used ResourseLoader. (c)IMHO
Solution was simple (it looks like 2nd solution): //LocalSettings.php ... # Assign my functions to hook $wgHooks['BeforePageDisplay'][] ='onBeforePageDisplay'; function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) { $script = '<script type="text/javascript" src="http://static.wowhead.com/widgets/power.js"></script><script>var wowhead_tooltips = { "colorlinks": true, "iconizelinks": true, "renamelinks": true }</script>'; $out->addHeadItem("wowhead script", $script); return true; }; This way look better then this, because it work with OutputPage directly (after parsing).
Pass parameters to javascript using wp_localize_script
I have read a half-dozen examples of how to pass parameters to javascript and they all describe the process in a very similar manner but don't speak to one event: how does the javascript code actually get called? I have seen several examples where the code is tested in $document.ready, but can't get them to work. Here is my php code: $base_id = 'id_'.wp_create_nonce("postlister"); $url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; $parms = array( 'url' => $url, 'action' => $action, 'base_id' => base_id ); wp_localize_script(self::slug.'-postlister', 'postlister_parms', $parms); And my jQuery code: jQuery(document).ready(function (postlister_parms) { if (postlister_parms !== undefined) { var parms = postlister_parms; $.diagnostic('postlister loaded'); } }); When the page loads, there is a div generated were my code writes some additional html: echo '<div id="'.$base_id.'"></div>'; produces: <div id="id_c8aca14643"></div> The footer contains: <script type="text/javascript" src="http://localhost/mkpusa.org/nw/wp-content/plugins/rhj4-postlister/js/postlister.js?ver=3.9.2"></script> When the jQuery code executes, postlister_parms is a function: postlister_parms: function (a,b){return new n.fn.init(a,b)} But postlister_parms.url, .action and .base_id are all undefined. Please help me spot what I am missing.
The resulting code should look something like this: var postlister_parms = {"url":"the_url","action":"the_action","base_id":"the_baseid"}; Make sure that the script with the handle self::slug.'-postlister' is enqueued before you call wp_localize_script
Have you read the Notes section of the documentation The call to wp_enqueue_script and wp_localize_script should be in a wp_enqueue_scripts action and the script should be enqueued before it is localized. That being true, it should work
updating javascript in the body tag using zend framework
Hi not sure if this is possible or not but I want to programaticaly update the <body> tags to change the onload function in my zend framework application. The App is using layouts so the body tag currently looks like this <body class="trandra"> However in one of my views I have a map from google being loaded and it needs the following in the body tag this particular view <body onload="initialize()" onunload="GUnload()"> As you can understand I don't want this to be hardcoded in my layout as this will cause all matter of nightmares with the different views. How can this be done programaticaly, if at all it is possible? Im using the headScript functions to add the javascript so is there an equivalant for the body tag? Thanks in advance...
Approach one - Use a layout variable One idea would be the following: <body class="trandra" <?php echo $this->layout()->bodyScripts ?>> And in your view: <?php $this->layout->bodyScripts = 'onload="initialize()" onunload="GUnload()"'; Approach two - Additional JS-file that adds event handlers Another approach, which is less obtrusive and doesn't affect the HTML whatsoever is to add an additional JS-file in the view that requires the onload- and onunload-handlers. It could look something like this: <?php $this->headScript()->appendScript( '/path/to/javascripts/loadGMaps.js'); In your loadGMaps.js (using prototype) Event.observe(window, 'load', function onLoadHandler() { // Code for initializing Google maps here }); Event.observe(window, 'unload', function onUnloadHandler() { // Code for unloading Google maps here });
Instead of putting your Javascript directly in the code, you could also use an non-obstrusive approch : plugging in the javascript when the page is fully loaded. Have a look, for instance, at a function called addOnLoadEvent (can be found on many websites ^^ ) If you are using a JS Framework, it certainly has that kind of feature : for jQuery : http://docs.jquery.com/Events/ready#fn for prototype : http://www.prototypejs.org/api/event/observe If you register the "plugging-in" with headScript, there should be no need to modify the tag directly.
Developed something like this recently, I've blogged about it here: http://www.evilprofessor.co.uk/311-zend-framework-body-tag-view-helper/ Demo on site and code is available via github.
I'm no expert on the Zend framework, so I don't know if there is any build in functions for this, but you could do something like this: In layout-file: body_params?>> And then in your controller, you set or add to the body_params: $this->view->body_params='onload="initialize()" onunload="GUnload()"';
I know this is an old thread, but I was looking through some of the suggested solutions and came up with one of my own playing off of some of the ideas I had seen. What I did was I extended Zend_View in my own library files (I'm using a vanilla MVC layout but similar things can be done using a bootstrap.php rather than the Bootstrap class described below) class Custom_View extends Zend_View { protected $bodyAttrs = array(); public function _setBodyAttr($attrName,$attrValue=null) { $attrName = strtolower(strval($attrName)); if(!(in_array($attrName, HTML::getValidBodyAttrs()))) { throw new Zend_Exception(__METHOD__." attrName '$attrName' is not a valid BODY attribute!"); } $this->bodyAttrs[$attrName] = strval($attrValue); } public function _getBodyAttrsAsString() { $bodyAttrs = ""; if(count($this->bodyAttrs) > 0) { $attrs = array(); foreach($this->bodyAttrs as $_k => $_v) { array_push($attrs,sprintf("%s=\"%s\"", $_k, $_v)); } $bodyAttrs = " " . implode(" ", $tags); } return $bodyAttrs; } } // some useful tag definitions for HTML class HTML { // HTML attributes as described by W3C public static $BODY_ATTRIBUTES = array('alink','background','bgcolor','link','text','vlink'); public static $GLOBAL_ATTRIBUTES = array('accesskey','class','contenteditable','contextmenu','dir','draggable','dropzone','hidden','id','lang','spellcheck','style','tabindex','title'); public static $WINDOW_EVENT_ATTRIBUTES = array('onafterprint','onbeforeprint','onbeforeunload','onerror','onhaschange','onload','onmessage','onoffline','ononline','onpagehide','onpageshow','onpopstate','onredo','onresize','onstorage','onundo','onunload'); public static $MOUSE_EVENT_ATTRIBUTES = array('onclick','ondblclick','ondrag','ondragend','ondragenter','ondragleave','ondragover','ondragstart','ondrop','onmousedown','onmousemove','onmouseout','onmouseover','onmouseup','onmousewheel','onscroll'); public static $KEYBOARD_EVENT_ATTRIBUTES = array('onkeydown','onkeypress','onkeyup'); public static $FORM_EVENT_ATTRIBUTES = array('onblur','onchange','oncontextmenu','onfocus','onformchange','onforminput','oninput','oninvalid','onreset','onselect','onsubmit'); public static $MEDIA_EVENT_ATTRIBUTES = array('onabort','oncanplay','oncanplaythrough','ondurationchange','onemptied','onended','onerror','onloadeddata','onloadedmetadata','onloadstart','onpause','onplay','onplaying','onprogress','onratechange','onreadystatechange','onseeked','onseeking','onstalled','onsuspend','ontimeupdate','onvolumechange','onwaiting'); public static function getValidBodyAttrs() { return array_merge(self::$BODY_ATTRIBUTES,self::$GLOBAL_ATTRIBUTES,self::$WINDOW_EVENT_ATTRIBUTES,self::$MOUSE_EVENT_ATTRIBUTES,self::$KEYBOARD_EVENT_ATTRIBUTES); } } after creating this file I added a method _initView to the Bootstrap.php file pointed to by the index.php and application.ini at the root of the application directory: protected function _initView() { // Custom_View extends Zend_View $view = new Custom_View(); // Add it to the ViewRenderer $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; } The new, extended Zend_View now allows adding your body tags along with some simple checking for validity. Modify your layout's body tag to get the attributes: <body<?= $this->_getBodyAttrs(); ?>> Once you have this set up you can add your body tags to any given view in the controller with $this->view->_setBodyAttr('key','val'); or in the view with $this->_setBodyAttr('key','val');