Clean JavaScript code in your views

24 followers

This tutorial presents a way of separating JS code from views and passing to it values from PHP.

Yii provides two helpful ways of keeping JavaScript code close to the widgets and other elements they interact with:

  • strings prefixed with 'js:'
  • CClientScript.registerScript method

Quickly, small snippets of JavaScript code turn into big ugly strings filled with PHP variables and without proper syntax highlighting.

This tutorial shows a method of organizing JavaScript code and integrating it with a PHP backend.

jQuery plugin template

This template for building JavaScript plugins is proposed by jQuery:

(function( yourPluginName, $, undefined ) {
   // public method
   yourPluginName.someCallback = function() {
   };
}( window.yourPluginName = window.yourPluginName || {}, jQuery ));

This defines a function that is immediately called and passed two arguments:

  • a reference to window.yourPluginName
  • the jQuery object

This allows you to extend the global yourPluginName object by adding methods and properties to it, keeping them all in one scope.

Now you can place your bulky JS code from your views inside this plugin and use them as:

yourPluginName.someCallback();

Put that template in a .js file, for example protected/components/assets/yourPluginName.js.

Then register it in your action or view:

$path = Yii::app()->assetManager->publish(Yii::getPathOfAlias('application.components.assets'));
Yii::app()->clientScript->registerScriptFile($path.'/yourPluginName.js');

Namespaced code

By putting all your code in this plugin you keep them in a limited scope and thus create a namespace for it. That helps avoiding name conflicts and keeps your code cleaner.

Another important feature is that this plugin can be registered many times. Sometimes this can happen when you load an action through AJAX on a page that has already registered the script.

This also allows extending if further, adding more functions.

Also, since there actually is a limited scope, strict mode can be enabled:

(function( yourPluginName, $, undefined ) {
    "use strict";
    // ... more code
}( window.yourPluginName = window.yourPluginName || {}, jQuery ));

This helps to detect browser-specific issues early in the development that is done using the developer's favourite browser, not the ones that clients are using.

Passing variables from PHP

Very often JavaScript code needs data from PHP, like URLs, ids, translated labels and other configuration.

They can be stored inside the plugin by adding a private variable and an init() function:

(function( yourPluginName, $, undefined ) {
    // guard to detect browser-specific issues early in development
    "use strict";
    // private var
    var _settings;
    // public var
    yourPluginName.someProperty = 'default value';
 
    // public method
    yourPluginName.init = function(settings) {
        _settings = $.extend({}, settings);
    }
}( window.yourPluginName = window.yourPluginName || {}, jQuery ));

Now after registering the script file add a call to the init() function on document load:

$options = CJavaScript::encode(array(
   'someUrl' => $this->createUrl('someUrl'),
   'someLabel' => Yii::t('app', 'someLabel'),
));
Yii::app()->clientScript->registerScript(__CLASS__.'#yourPluginName', "yourPluginName.init($options);", CClientScript::POS_READY);

The '_settings' var is private and can be only referenced inside functions defined in yourPluginName;

Now your PHP and JS code is:

  • separated and cleaner
  • safer
  • easier to read
  • more extensible (no more hardcoded labels and urls in JS!)

Total 6 comments

#17020 report it
Daniel Galvan at 2014/04/24 05:01pm
Excellent work!, I would like to share another example of a plugin.
<script>
!function(module, $, window, document, undefined) {
    'use strict';
 
    var NamePlugin = function() {
        console.log('NamePlugin');
        /*!
         * Private variables, properties and methods
         */
 
        // variables
        var pluginName = 'NamePlugin';
        var publicApi = {};     // public api to expose
 
        // properties
        var properties = {defOpt1: 'defOpt1', defOpt2: 'defOpt2'};
 
        // methods
        var constructor = (function() {
            console.log(pluginName + ':constructor');
        })();
 
        var privateMethod1 = function(message) {
            console.log(pluginName + ':privateMethod1' + ' ' + (message ? message : ''));
        };
 
        var privateMethod2 = function() {
            console.log(pluginName + ':privateMethod2');
        };
 
        /*!
         * Public properties and methods
         */
 
        // properties
        publicApi.property1 = 'Publi Property 1';
        publicApi.property2 = 'Publi Property 2';
        publicApi.defaults = {defProp1: 'defProp1'};
        publicApi.settings = {};
 
        // methods
        publicApi.method1 = function() {
            console.log(pluginName + ':publicApi.method1');
        };
 
        publicApi.method2 = function() {
            console.log(pluginName + ':publicApi.method2');
        };
 
        publicApi.method3 = function() {
            console.log(pluginName + ':publicApi.method3:expose:privateMethod1');
            privateMethod1();
        };
 
        publicApi.method4 = function() {
            console.log(pluginName + ':publicApi.method4:expose:privateMethod2');
            privateMethod2();
        };
 
        //publicApi.method5 = privateMethod1('from publicApi.method5');
 
        publicApi.init = function(options) {
            console.log(pluginName + ':publicApi.init');
            publicApi.settings = $.extend({}, publicApi.defaults, options);
            privateMethod1('defaults' + JSON.stringify(publicApi.defaults));
            privateMethod1('options' + JSON.stringify(options));
            privateMethod1('settings' + JSON.stringify(publicApi.settings));
            return publicApi;
        };
 
        // return the public API (Plugin) to expose
        return publicApi;
    };
 
    module.NamePlugin = NamePlugin;
}(this, jQuery, window, document);
</script>
 
// Passing variables #1 (from view)
<script>
var myRoute = "<?php echo $this->createUrl('someUrl'); ?>";
var myNamePlugin = new NamePlugin;
myNamePlugin.init({name: 'my name', lastName: 'my last name', route: myRoute});
myNamePlugin.method1();
myNamePlugin.method2();
myNamePlugin.method3();
myNamePlugin.method4();
console.log('Get Public Prop1: ' + myNamePlugin.property1);
console.log('Get Public Prop2: ' + myNamePlugin.property2);
console.log('Get Private Prop: ' + myNamePlugin.pluginName);        // undefined because of pluginName is private
<script>
 
// Passing variables #2
<?php 
$options = CJavaScript::encode(array(
   'someUrl' => $this->createUrl('someUrl'),
   'someLabel' => Yii::t('app', 'someLabel'),
));
Yii::app()->clientScript->registerScript(__CLASS__.'#yourPluginName', "var myNamePlugin = new NamePlugin; myNamePlugin.init($options);", CClientScript::POS_READY);
?>
#15007 report it
pravi at 2013/09/27 09:52pm
Nice. Very helpful for JS plugin development. Here is a simple example of a plugin
<script>
 
(function( animatemenu, $, undefined ) {
   // public method
   animatemenu.animate = function(id,options) {
    var defaults = {
                animatePadding: 60,
                   defaultPadding: 10,
                   evenColor: '#ccc',
                   oddColor: '#eee',
            };
    $(id).each(function() {
                  var o =options;
                  var obj = $(this);                
                  var items = $("li", obj);
 
                  $("li:even", obj).css('background-color', o.evenColor);                
                  $("li:odd", obj).css('background-color', o.oddColor);                      
 
                  items.mouseover(function() {
                      $(this).animate({paddingLeft: o.animatePadding}, 300);
 
                  }).mouseout(function() {
                      $(this).animate({paddingLeft: o.defaultPadding}, 300);
 
                  });
            });
};
}( window.animatemenu = window.animatemenu || {}, jQuery ));
 
 
    </script>
 
    <script type="text/javascript">
    $(document).ready(function() {
    animatemenu.animate('#menu',{animatePadding: 30, defaultPadding:10})
 
    });    
    </script>
    <style>
      body {font-family:arial;font-style:bold}
      a {color:#666; text-decoration:none}
        #menu {list-style:none;width:160px;padding-left:10px;}
        #menu li {margin:0;padding:5px;cursor:hand;cursor:pointer}
    </style>
</head>   
<body>
<ul id="menu">
    <li>Home</li>
    <li>Posts</li>
    <li>About</li>
    <li>Contact</li>
</ul>
#14672 report it
robregonm at 2013/09/02 01:00pm
Very good practice

It's a very good practica to keep everything separated. Recommended.

As @blaces says: A complete example will help beginners to misunderstand what you mean, including the simple example of a view and a JS file. Or even easier. You can create a github repo with a simple application using an example.

#14650 report it
nineinchnick at 2013/09/01 08:17am
examples

What do you mean you miss examples? There are examples.

#14649 report it
blaces at 2013/09/01 04:02am
JS code examples pls

Hi, good work. But I miss js examples. I think the beginners will make wrong codes from this schema at the first times; and we know that debugging a js code is not easy :)

#14637 report it
Rajith R at 2013/08/31 04:14am
@nineinchnick

good work. we will try it :-)

Leave a comment

Please to leave your comment.

Write new article