📜 ⬆️ ⬇️

Prevent reloading libraries or remake jQuery.html ()

You probably have come across a situation where, when loading content from an ajax request, jquery automatically loads and runs nested libraries and scripts. Undoubtedly, this is convenient, for example, when a self-sufficient plugin or widget is present in the loaded content. Everything will automatically load, and visually looks all good.

But if you look closely, everything is not so beautiful. jQuery loads each nested library in whether it is loaded by the browser or not, and even adds a unique parameter to the query to prevent explorer cache usage. I did not go into details why the jQuery developers chose such a scheme and made an wrapper around the html () function that fixes this problem.

So. What problems we need to solve:

1. Do not load libraries and styles again.
2. When loading new libraries or styles use browser cache
3. Since nested scripts can use functions from connected libraries, they need to be run after loading all libraries.
')
In principle, nothing complicated. The problem is solved using regular expressions.

1. Cut out all the <script> and <link> tags from the content, compare with the already connected tags and load them, depending on the situation.
2. Put the onload event handler for the link libraries in which we run all the nested scripts.

I attach the code of the plugin itself:

(function($){ var originalHtmlMethod = $.fn.html; /** *      */ var sanitize = { scriptsCountLoading:0, inlineScripts:[], /** *     onload. *          */ loadingCompleted:function(){ if (this.scriptsCountLoading==0){ if ($.isArray(this.inlineScripts)){ for (var i=0; i<this.inlineScripts.length; i++){ eval.call(window,this.inlineScripts[i]); } this.inlineScripts = []; }else{ if (this.inlineScripts){ eval.call(window,this.inlineScripts); this.inlineScripts = []; } } } }, /** *   javascript       . *   scriptsCountLoading */ sanitizeScripts:function(data){ var scripts = $('script'); var scriptSrc = []; for (var i=0; i<scripts.length; i++){ scriptSrc[scriptSrc.length] = scripts[i].src; } var patternScripts = /<script[^<>]*?src=\"?([^><\\\"\\']*)\"?[^<>]*?>[\s\S]*?<\/script>/igm; var absolutePath = /https?:\/\//; var matches = null; var dataScriptSrc = {}; while (matches = patternScripts.exec(data)){ var matchedString = matches[0]; var src = matches[1]; if (absolutePath.test(src)){ // absolute path }else{ // relative path if (src[0]!='/'){ src = window.location.href.replace(/#|\?.*$/)+'/'+src; var m = null; while (m = /[^\/]*\/\.\.\//ig.exec(src)){ src = src.replace(m[0],''); } }else{ src = window.location.protocol+'//'+window.location.hostname+src; } } data = data.replace(matchedString,''); patternScripts.lastIndex -= matchedString.length; if ($.inArray(src,scriptSrc)==-1){ this.scriptsCountLoading++; //   var self = this; this.loadScript(src,function(){ self.scriptsCountLoading--; self.loadingCompleted(); }); } } return data; }, /** *   .        */ sanitizeInlineScripts:function(data){ var scripts = $('script'); var scriptSrc = []; for (var i=0; i<scripts.length; i++){ scriptSrc[scriptSrc.length] = scripts[i].src; } var patternScripts = /<script[^<>]*?>([\s\S]*?)<\/script>/igm; var absolutePath = /https?:\/\//; var matches = null; var dataScriptSrc = {}; while (matches = patternScripts.exec(data)){ var matchedString = matches[0]; var script = matches[1]; data = data.replace(matchedString,''); patternScripts.lastIndex -= matchedString.length; this.inlineScripts[this.inlineScripts.length] = script } return data; }, /** *   css ,       */ sanitizeHeadLinks:function(data){ var links = $('link'); var linkSrc = []; for (var i=0; i<links.length; i++){ linkSrc[linkSrc.length] = links[i].href; } var patternHeadLinks = /<link[^<>]*?href=\"?([^><\\\"\\']*)\"?[^<>]*?\/>/igm; var absolutePath = /https?:\/\//; var matches = null; var dataScriptSrc = {}; while (matches = patternHeadLinks.exec(data)){ var matchedString = matches[0]; var src = matches[1]; if (absolutePath.test(src)){ // absolute path }else{ // relative path if (src[0]!='/'){ src = window.location.href.replace(/#|\?.*$/)+'/'+src; var m = null; while (m = /[^\/]*\/\.\.\//ig.exec(src)){ src = src.replace(m[0],''); } }else{ src = window.location.protocol+'//'+window.location.hostname+src; } } data = data.replace(matchedString,''); patternHeadLinks.lastIndex -= matchedString.length; if ($.inArray(src,linkSrc)==-1){ this.loadCSS(src); } } return data; }, /** *    */ sanitizeData:function(data){ data = this.sanitizeInlineScripts(this.sanitizeScripts(this.sanitizeHeadLinks(data))); return data; }, /** *   */ loadScript:function(url,onload){ var e = document.createElement("script"); e.src = url; e.type="text/javascript"; if (onload instanceof Function){ e.onreadystatechange= function () { if (this.readyState == 'complete') onload(); } e.onload= onload; } document.getElementsByTagName("head")[0].appendChild(e); }, /** *  css  */ loadCSS:function(url){ var oLink = document.createElement("link") oLink.href = url; oLink.rel = "stylesheet"; oLink.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(oLink); } } /** *    html() */ $.fn.html = function(data){ data = sanitize.sanitizeData(data); var res = originalHtmlMethod.apply( this, [data] ); sanitize.loadingCompleted(); return res; } })(jQuery); 


Upd: Considering the reaction of habrovchan, I consider it necessary to add some explanations.

This post was created as an idea or option for a quick solution to the problem (without changing the architecture) arising from the use of this or that php framework. In this case, guided by the architecture of Yii.

Since plugins are a self-contained package of scripts, it’s enough just to “plug in” them, as everything miraculously starts working. In this case, the plugin itself will add all the necessary libraries, scripts, styles, and the framework will send this whole thing to the output.

Many plugins load content dynamically (ajax), this is where the problem occurs. In this dynamic content can be self-contained plug-in connected by means of Yii (with all libraries, scripts and styles). When loading such content, jQuery will behave as described above. Since these plugins use jQuery, it was logical to suggest overloading the html () function for global impact.

In general, the change of the html () function itself is not critical. You can also use the pre-installed dataFilter as stated in the first comment.

Source: https://habr.com/ru/post/134741/


All Articles