Yii renderpartial (proccessoutput = true) Avoid Duplicate js request

17,860

Solution 1

To avoid including core scripts twice

If your scripts have already been included through an earlier request, use this to avoid including them again:

// For jQuery core, Yii switches between the human-readable and minified
// versions based on DEBUG status; so make sure to catch both of them
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery.min.js'] = false;

If you have views that are being rendered both independently and as HTML fragments to be included with AJAX, you can wrap this inside if (Yii::app()->request->isAjaxRequest) to cover all bases.

To avoid including jQuery scripts twice (JS solution)

There's also the possibility of preventing scripts from being included twice on the client side. This is not directly supported and slightly more cumbersome, but in practice it works fine and it does not require you to know on the server side what's going on at the client side (i.e. which scripts have been already included).

The idea is to get the HTML from the server and simply strip out the <script> tags with regular expression replace. The important point is you can detect if jQuery core scripts and plugins have already been loaded (because they create $ or properties on it) and do this conditionally:

function stripExistingScripts(html) {
    var map = {
        "jquery.js": "$",
        "jquery.min.js": "$",
        "jquery-ui.min.js": "$.ui",
        "jquery.yiiactiveform.js": "$.fn.yiiactiveform",
        "jquery.yiigridview.js": "$.fn.yiiGridView",
        "jquery.ba-bbq.js": "$.bbq"
    };

    for (var scriptName in map) {
        var target = map[scriptName];
        if (isDefined(target)) {
            var regexp = new RegExp('<script.*src=".*' +
                                    scriptName.replace('.', '\\.') +
                                    '".*</script>', 'i');
            html = html.replace(regexp, '');
        }
    }

    return html;
}

There's a map of filenames and objects that will have been defined if the corresponding script has already been included; pass your incoming HTML through this function and it will check for and remove <script> tags that correspond to previously loaded scripts.

The helper function isDefined is this:

function isDefined(path) {
    var target = window;
    var parts = path.split('.');

    while(parts.length) {
        var branch = parts.shift();
        if (typeof target[branch] === 'undefined') {
            return false;
        }

        target = target[branch];
    }

    return true;
}

To avoid attaching event handlers twice

You can simply use a Javascript object to remember if you have already attached the handler; if yes, do not attach it again. For example (view code):

Yii::app()->clientScript->registerScript("view-script","
  window.myCustomState = window.myCustomState || {}; // initialize if not exists
  if (!window.myCustomState.liveClickHandlerAttached) {
    window.myCustomState.liveClickHandlerAttached = true;
    $('.link').live('click',function(){
       alert('test');
    })
  }
");

Solution 2

The cleanest way is to override beforeAction(), to avoid any duplicated core script:

class Controller extends CController {

  protected function beforeAction($action) {
        if( Yii::app()->request->isAjaxRequest ) {
            Yii::app()->clientScript->scriptMap['jquery.js'] = false;
            Yii::app()->clientScript->scriptMap['jquery-2.0.0.js'] = false;
            Yii::app()->clientScript->scriptMap['anything.js'] = false;
        }

        return parent::beforeAction($action);
  }

}

Note that you have to put the exact js file name, without the path.

Solution 3

To avoid including script files twice, try this extension: http://www.yiiframework.com/extension/nlsclientscript/

To avoid attaching event handlers twice, see Jons answer: https://stackoverflow.com/a/10188538/729324

Share:
17,860
butching
Author by

butching

Updated on June 16, 2022

Comments

  • butching
    butching almost 2 years

    Im creating a site who works with ajaxRequest, when I click a link, it will load using ajaxRequest. When I load for example user/login UserController actionLogin, I renderPartial the view with processOUtput to true so the js needed inside that view will be generated, however if I have clientScriptRegister inside that view with events, how can I avoid to generate the scriptRegistered twice or multiple depending on the ajaxRequest? I have tried Yii::app()->clientScript->isSCriptRegistered('scriptId') to check if the script is already registered but it seems that if you used ajaxRequest, the result is always false because it will only be true after the render is finished.

    Controller code

    if (Yii::app()->request->isAjaxRequest)
    {
       $this->renderPartial('view',array('model'=>$model),false,true);
    }
    

    View Code

    if (!Yii::app()->clientScript->isScriptregistered("view-script"))
       Yii::app()->clientScript->registerScript("view-script","
          $('.link').live('click',function(){
             alert('test');
         })
    ");
    

    If I request for the controller for the first time, it works perfectly (alert 1 time) but if I request again for that same controller without refreshing my page and just using ajaxRequest, the alert will output twice if you click it (because it keeps on generating eventhough you already registered it once)

    This is the same if you have CActiveForm inside the view with jquery functionality.. the corescript yiiactiveform will be called everytime you renderPartial.

  • butching
    butching about 12 years
    how about for corescripts like for example yiiactiveform? jquery? thanks by the way.
  • butching
    butching about 12 years
    and is this window.myCustomState = window.myCustomState || {}; a shorthand for creating object in js?
  • Waihon Yew
    Waihon Yew about 12 years
    @butching: For corescripts, see update. For the object-creation syntax, it's a shorter/cute way to write if (!window.myCustomState) window.myCustomState = {};
  • butching
    butching about 12 years
    To avoid including core scripts twice --> Yes Im using that script to remove the corescript but my question is how can you know that the script is already included in earlier request if both request are ajax? Because as I said earlier Yii::app()->clientScript->isSCriptRegistered('scriptId') the result is always false in ajaxRequest For the object-creation, yah its cute and I get it.. thx
  • Waihon Yew
    Waihon Yew about 12 years
    @butching: Simply include those corescripts in your "initial" page and then all your AJAX can assume that they are already loaded; problem solved. You can also use other tricks, but it shouldn't be necessary.
  • butching
    butching about 12 years
    yeah it would be easy to declare them all at initial page but Im trying to lessen the request if not necessary needed. Like for ex. Homepage doesnt need yiiactiveform but the contact page does, so Im only gonna call the yiiactiveform when they request for the contact page. Where can I learn or see the other tricks? thanks again
  • Waihon Yew
    Waihon Yew about 12 years
    @butching: I 'll edit to give that code in 7-8 hours or so, don't have access to it right now.
  • Waihon Yew
    Waihon Yew about 12 years
    @butching: Hi, I updated the answer with an additional option.
  • chapskev
    chapskev almost 9 years
    Will this have impact on registered scripts that have been initialize on the main layout or do i have to register the scripts in the config main
  • taseenb
    taseenb almost 9 years
    Actually you should not hardcode the scripts in the main layout (unless you are sure that you really want that).
  • chapskev
    chapskev almost 9 years
    Thanks a lot for the feedback 👍🏾