nlsclientscript

NLSClientScript extends the CClientScript class smart loading javascript files and optionally merging/minifying js+css files
53 followers

NLSClientScript prevents duplicated linking of javascript files when updating a view by ajax, when eg. paging or sorting a gridview, ajax-submitting a form or any custom ajax-updating a part of a view.

The extension does not prevent the multiple loading of CSS files. I simply couldn't find a way to manage it clearly (too long to explain here).

The issue what this extenson fights is for example when you render Jui widgets by CHtml::ajax, the js files used by the widget will be loaded as many times as you render such a widget in a view. The unnecessary bandwidth usage is the smaller problem, the bigger problem is eg. loading jquery.js again may reset some js objects set by previously loaded ui-related js files. That can cause js errors and the view may stop working.

Using NLSClientScript helps to avoid it all.

From 6.0, it optionally merges/caches + minifies the registered js and css files.

History

  • 6.3

    • serious bug fixed: filtering duplications (usually) failed when js-merging applied for the response of an xhr request
    • new params: mergeIfXhr, mergeJsExcludePattern, mergeJsIncludePattern, mergeCssExcludePattern, mergeCssIncludePattern, resMap2Request (see the phpdoc comments in the source for more info)
    • appended an extra ; to the js files
    • some other small improvements
  • 6.21

    • fixed another bug: merged files have been re-generated on every request when the appVersion parameter was used
  • 6.2 (see the updated Usage)

    • fixed a serious bug broke the original functionality when merging happened (duplicates couldn't been recognized)
    • added a new parameter appVersion
  • 6.1 (see the updated Usage)

    • fixed several bugs (serverBaseUrl composing, merging css files by media correctly)
    • added parameters mergeAbove, curlTimeOut, curlConnectionTimeOut
  • 6.0 (see the updated Usage)

    • added optional merge and minify functionalities
    • to keep the simpleness of the single-file extension, embedded JSMin.php from https://github.com/eriknyk/jsmin-php
  • 5.0 (see the updated Usage)

    • in 4.0RC found an issue couldn't worked around: it registered also the script tags being in html/css/js comments, input field, textarea value so i had to drop the native source analysis by regexp. Fortunately found the solution in 5.0 looks like the most perfect till now. Tested successfully in IE7+, latest FF,Chrome,Opera. Reports about testing are welcome as always.
  • 4.0 RC

    • refactored, hopefully all bugs reported about 3.x have been eliminated
  • 3.6

    • fixed a typo
  • 3.5 (see the updated Usage)

    • handling special case when updating a table by tr tag
    • further IE fixes
    • added 2 new parameters: ignoredPattern and processedPattern
    • general refactoring
  • 3.4

    • fixed non-script-rendering bug in IE
  • 3.3

    • removes the occasional ...?_=3767454656434 -like timestamps from the url keys used to store/identify the loaded scripts
    • fixed the accidental naming NLSClientScript to EClientScript
  • 3.2

    • fixed accessing HEAD element for IE
    • compressed js code (full source is still there in the php source)
  • 3.1

    • the extension now prevents the duplicated loading of css files also.
  • 3.0

    • brand new approach simplifying dramatically the extension and the usage of the extension, based the great idea of Eirik Hoem
    • see the Usage below!
  • 2.1

    • dirty fix for a rendering bug of jquery.js v1.6.1 affecting binline=true mode in Yii 1.18
  • 2.0

    • brand new approach: resource hash stored at the server side, in the webuser state. All these info deleted when a non-ajax request comes
    • no $.ajax usage - better performance
    • the extension does not require jquery.js and jquery.yii.js to be linked initially any more
    • js/css files can be linked from other domain
    • new parameter: bInlineJs - if true, the scriptFile method will insert the js file content into the html instead of linking the file what can result even better performance
  • 1.3

    • added cache:true to the ajax js load
    • compressed core js code
  • 1.2

    • fixed js error when app not in YII_DEBUG mode
  • 1.1
    • hash key generated on server side
    • two hash key mode: PATH and CONTENT
    • shortened client-side code
  • 1.0
    • base version

If you interest the details, see the comments in the source.

Requirements

Yii 1.x

Limitations

  • The extension identifies the scripts by its paths so it does not prevent to load the same script content from different paths. So eg. if you published the same js file into different asset directories, NLSClientScript considers those to be different and won't prevent to load those several instances.

  • The extension doesn't watch wether a js/css file has been changed. If you set the merge functionality and some file changed, you need to delete the cached merged file manually, otherwise you'll get the old merged one.

Usage (v6.3)

1 . Set the class for the clientScript component in /protected/config/main.php, like

...
'components'=>array(
  ...
  'clientScript' => array(
    'class' => 'your.path.to.NLSClientScript',
    //'excludePattern' => '/\.tpl/i', //js regexp, files with matching paths won't be filtered is set to other than 'null'
    //'includePattern' => '/\.php/', //js regexp, only files with matching paths will be filtered if set to other than 'null'
 
    'mergeJs' => true, //def:true
    'compressMergedJs' => false, //def:false
 
    'mergeCss' => true, //def:true
    'compressMergedCss' => false, //def:false
 
    'mergeJsExcludePattern' => '/edit_area/', //won't merge js files with matching names
 
    'mergeIfXhr' => true, //def:false, if true->attempts to merge the js files even if the request was xhr (if all other merging conditions are satisfied)
 
    'serverBaseUrl' => 'http://localhost', //can be optionally set here
    'mergeAbove' => 1, //def:1, only "more than this value" files will be merged,
    'curlTimeOut' => 10, //def:10, see curl_setopt() doc
    'curlConnectionTimeOut' => 10, //def:10, see curl_setopt() doc
 
    'appVersion'=>1.0 //if set, it will be appended to the urls of the merged scripts/css
  )
  ...
)
 
For more information about the parameters, see the header comment of NLSClientScript.php.

2 . use Yii::app()->getClientScript() by the standard way to link js and css files/snippets

Example:

$cs = Yii::app()->getClientScript();
$systemJsPath = Yii::app()->getAssetManager()->publish( Yii::getPathOfAlias( 'system.web.js' ), false, -1, false );
$cs->registerScriptFile( $systemJsPath . '/ext/yii_ext.js');
$cs->registerScriptFile( $systemJsPath . '/ext/plugins/jquery.form.js');

3 . DOES NOT WORK FROM v5.0: If you want to do a custom ajax request with "dataType"="json" and there are some fields of the response you want to update your page with, filter that part "by hand" with $.ajaxSettings.dataFilter like eg.:

echo CHtml::ajaxLink('custom update', array('/site/testupdate'), array(
 
  'dataType' => 'json',
 
  'success'=>'js:function(data){ $("#cont").html($.ajaxSettings.dataFilter(data.content)); }',
 
));

Resources

Total 20 comments

#11924 report it
nlac at 2013/02/13 03:43am
@Nafania

Well, i've just have seen that http://code.google.com/p/cssmin/ is able to merge the @import-ed css files into the parent one by its documentation. That's indeed a feature what makes me consider to use it in a later version some way. Thanks for the tip...

#11922 report it
nlac at 2013/02/13 02:53am
@Nafania

Hi, i'm not sure the problem relates to the minification. When the extension merges the files, it publishes the merged one into the /assets dir. That way, if you use relative url in your @import (like "dir/xy.css" or "../dir/xy.css"), that will usually break.

Have you tried to use url like "/dir/xy.css" in the @import rule (relative to the server root) or perhaps full absolute url?

About cssmin, i try to keep my extension lightweight, without merging 3rd parties into being 10x large than the extension itself, unless it is really necessary.

#11913 report it
Nafania at 2013/02/12 06:19pm
import in css not work

Thanks, nice extension, but import function of css not work after minify. May be you should use full css minifier (e.g http://code.google.com/p/cssmin/) not simple css minifier?

#11746 report it
nlac at 2013/01/31 02:10pm
serverBaseUrl issue

yes i'm aware, will fix soon. Till then set that param explicitly in config.php

#11740 report it
marcovtwout at 2013/01/31 04:18am
Fatal error in 6.0

$_SERVER['REQUEST_SCHEME'] is not always available, and will throw undefined index:

if (!$this->serverBaseUrl)
            $this->serverBaseUrl = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];

Omitting it is safe in most browsers, but I haven't checked how it affects other parts of your code:

if (!$this->serverBaseUrl)
            $this->serverBaseUrl = '//' . $_SERVER['HTTP_HOST'];
#11075 report it
IslamAmeen at 2012/12/14 10:42am
A problem & a suggested fix

Hello nlac, Thanks so much for such work, was totally a savior for me.

I'm reporting that there's a conflict between nlsclientscript & Minscript extension http://www.yiiframework.com/extension/minscript/

The js file is loaded again & duplicated after the first ajax request.. thats because the normUrl function of nlsclientscript takes the minscript url in the form of:

Http: //localhost/soedu/min/serve?g=7668aa4b832f39e552dd77ac5e156c5c&lm=1353462851&_=1355498134802

& returns

Http: //localhost/soedu/min/serve?g=7668aa4b832f39e552dd77ac5e156c5c&lm=1353462851&

Notice the '&' at the end

The suggested fix would be to modify normUrl function, replacing

return url.replace(/\?*(_=\d+)?$/g,"");

With

return url.replace(/(\?|&)*(_=\d+)?$/g,"");
#9606 report it
Lothor at 2012/08/28 01:58pm
=/

Even with the extension when using AJAX, the Bootstrap CSS files are reloaded the page.

#8235 report it
le_top at 2012/05/20 04:41pm
5.0 not ok with 'dataFilter'

Hi

I just tried version 5.0 after previously using v3.6.

I use it as in the example: $("#cont").html($.ajaxSettings.dataFilter(data.content)); . Apparently this does not work anymore ad 'dataFilter' is unknown in Chrome (for example) when using 5.0. It appears that 'dataFilter' is defined only when using 'IE'.

#8008 report it
marcovtwout at 2012/05/03 08:50am
Version 5

Using version 5 seems to work as expected in IE7/8 and Chrome. I upgraded after IE7/8-issues in version 3.3.

Only thing I would like to see is proper DocBlock commenting on variables and functions in your extension.

And actually, maybe a better name because "NLSClientScript" doesn't say anthing about what it does, but something like "AjaxCompatibleClientScript" or maybe just "EClientScript" or "XClientScript" is better.

#7821 report it
klaus66 at 2012/04/20 05:22am
Not really static page

You are right. My 'static' pages register a small js script. But how you said they don't need jquery.

I register now jquery for all my sites in my main layout view.

I use the google lib and I think it is no problem for performance.

#7812 report it
nlac at 2012/04/19 02:22pm
for klaus66

I guess that is not a static page where the issue occurs. On the pages where no script file registered, the component renders nothing. On your "static page" some script is registered requires no jquery, that's when the issue occurs.

So yes, for now all pages need jquery registered since the component uses it. I'll fix in the next release to register jquery automatically in every case.

#7743 report it
klaus66 at 2012/04/13 03:57pm
Script error jquery undefined

I have install version 5.0.

It works on all my pages where jquery is included.

But on my static pages (e.g. impressum page) where I need no jquery there I get the error 'jquery undefined'.

What can I do. Must I include on all my pages jquery or can I do this with exclude pattern ?

#7205 report it
nlac at 2012/03/03 04:08pm
guys please test 5.0

test results about usages of the latest version are welcome

#7167 report it
Athos at 2012/02/29 12:19pm
IE7 bug

IE7 has a problem with the "src" attribute of the script tag, as can be seen in http://bugs.jquery.com/ticket/11404

So the update of the grid returns the alert "Parser error".

The solution to the problem would be to change the script to:

(...)
selector : "script:not([src=\"\"]),link[rel=\"stylesheet\"]",
(...)
#7114 report it
vids at 2012/02/24 06:09am
Duplicate html content rendering

is #7098 fixed in 3.6 version? I am still facing the problem in filtering in the cgridview

#7098 report it
Athos at 2012/02/23 02:56pm
HTML content is being duplicated.

I have problems. When the extension is enabled the HTML content is being duplicated in ajax requests.

Detail: the problem does not occur in IE, only in Firefox and Chrome. And only occurs when the return of Ajax is the complete page, including the HTML tag.

The javascript code behaves completely different in IE vs Firefox and Chrome.

The developer needs to fix the default behavior on all browsers.

A workaround for Firefox and Chrome (but certainly not the ultimate solution):

(...)
firstTagRg : /<([a-z1-6]+)[^>]*>/i,
(...)
holders : {
    "default" : $(document.createElement("div")),
    "tr" : $(document.createElement("tbody")),
    "html" : $(document.createElement("html"))
},
#6919 report it
nlac at 2012/02/13 02:06pm
thx

hoplayann thanks for the report, i'll check and will release a new version in a few days, there's also an other issue needs to be fixed.

#6851 report it
hoplayann at 2012/02/09 06:27am
Issue with the parser in Internet Explorer 7

Hello,

I am using version 3.6 of your extension in my Yii project.

It seems that the selection of script tags in the HTML source fails for some reason in Internet Explorer 7. The looping through the SCRIPT or LINK elements is the place where we have now added an extra test case, to be sure that there is at least a not empty SRC or HREF attribute defined. (see added code below)

//fetching scripts from the DOM
                    for(i=0,res=$(document).find($._nlsc.selector); i<res.length; i++) {
                        tmp = res[i];
 
                        // CODE ADDED:
                        // ie7 sometimes gets accidental script tags and selector returns undefined objects
                        if (tmp.src == "" && typeof(tmp.href) === "undefined") {
                            continue;
                        }
 
                        ind = tmp.src ? tmp.src : tmp.href;
                        ind = ind.replace($._nlsc.urlGarbageRg,"");
                        if (tmp.href && tmp.parentNode!=$._nlsc.head)
                            $._nlsc.head.appendChild(tmp);
                        $._nlsc.resMap[ind] = 1;
                    }

And I had to add the code again in the other loop :

// iterate over new scripts and remove if source is already in DOM:
                for(i=0,res=holder.find($._nlsc.selector); i<res.length; i++) {
                    tmp = res[i];
 
                    // CODE ADDED:
                    // ie7 sometimes gets accidental script tags and selector returns undefined objects
                    if (tmp.src == "" && typeof(tmp.href) === "undefined") {
                        continue;
                    }
 
                    ind = tmp.src ? tmp.src : tmp.href;
                    ind = ind.replace($._nlsc.urlGarbageRg,"");
                    if (resMap[ind])
                        $(tmp).remove();
                    else {
                        if (tmp.href)
                            $._nlsc.head.appendChild(tmp);
                        else
                            tmp.removeAttribute("defer");
                        resMap[ind] = 1;
                    }
                }

Is it possible that the jquery selector is not precise enough ?

selector : "script[src],link[rel=\"stylesheet\"]",

Please let me know if there would be a fix for that issue in your next revision.

Yann

#6357 report it
nlac at 2012/01/02 03:26pm
thx thimt8-yii

fixed in 3.6

#6355 report it
Athos at 2012/01/02 02:49pm
Warning: "head" is not defined.

Here is returning an error warning that "head" is not defined.

This piece of code:

// iterate over new scripts and remove if source is already in DOM:
for(i=0,res=holder.find($._nlsc.selector); i<res.length; i++) {
    tmp = res[i];
    ind = tmp.src ? tmp.src : tmp.href;
    ind = ind.replace($._nlsc.urlGarbageRg,"");
    if (resMap[ind])
        $(tmp).remove();
    else {
        if (tmp.href)
            head.appendChild(tmp);
        else
            tmp.removeAttribute("defer");
        resMap[ind] = 1;
    }
}

should be?

// iterate over new scripts and remove if source is already in DOM:
for(i=0,res=holder.find($._nlsc.selector); i<res.length; i++) {
    tmp = res[i];
    ind = tmp.src ? tmp.src : tmp.href;
    ind = ind.replace($._nlsc.urlGarbageRg,"");
    if (resMap[ind])
        $(tmp).remove();
    else {
        if (tmp.href)
            $._nlsc.head.appendChild(tmp);
        else
            tmp.removeAttribute("defer");
        resMap[ind] = 1;
    }
}

Leave a comment

Please to leave your comment.

Create extension