Avoid a dynamically injected deferred script to be injected two times? - javascript

You are developing two JavaScript widget, say first_widget.js and second_widget.js, both relaying on shared_library.js, which is creating the global window.MyLibrary.
Normally, you would use the two widgets like this, where shared_library.js is imported only once:
<!-- The first widget -->
<div id="first_widget_root"></div>
<script src="https://example.com/first_widget.js"></script>
<!-- The second widget -->
<div id="second_widget_root"></div>
<script src="https://example.com/second_widget.js"></script>
<!-- The shared library -->
<script src="https://example.com/shared_library.js"></script>
Then you decide to simplify things, embedding the loading of shared_library.js into each script:
// first_widget and second_widget skeleton
var loadCallback = function () {
// Use window.MyLibrary
};
(function(document, tag) {
var script = document.createElement(tag),
el = document.getElementsByTagName(tag)[0];
script.src = 'https://example.com/shared_library.js';
script.defer = true; // Execute after HTML parsing ends
script.onload = function () {
loadCallback();
};
el.parentNode.insertBefore(script, el);
}(document, 'script'));
Nice, now one can use the widget more easily:
<!-- The first widget -->
<div id="first_widget_root"></div>
<script src="https://example.com/first_widget.js"></script>
When using both widgets, the problem is shared_library.js imported twice: how would you check and inject the script only one?
I was thinking about adding a check for window.MyLibrary but I think isn't reliable: shared_library.js is imported twice depending on the loading speed anyways.

In your case:
if(! document.querySelector('script[src*=shared_library]'){
// insert
}
!
That is a common practice to check of a script containing smth in its attribute (need of some browser support probably to write such comfortable code) and, if not existent, it's to be quietly inserted there into the document.
Alongside with real site examples, SO is often talking about this topic of finding a script to whether it's to be inserted or the script is already loading like in this, for example:
//given url argument, in a call, e.g url='http://example.com/shared_library.js'
var scripts = document.getElementsByTagName('script');
for (var i = scripts.length; i--;) {
if (scripts[i].src == url) return true;
}
return false;
}

Related

How to dynamic load Jquery inside a JavaScript Class constructor and proceed the execution only after jquery has been fully loaded

This is actually a merge of three differents questions already all well answered here on stack overflow !
1 - How to Dynamic Load a JavaScript file from inside a Js Script :
2 - How to Dynamic Load Jquery
3 - SetTimeout inside a JS Class using this
Basically, I am building a class that will inject some pages inside my clients's website.
To do so, the client just need to add my script src on the page.
<script src="<my_pub_http_address>/MyClass.js">
Once the script is invoked, I will need jquery to continue the execution !
But, I cannot know if the website that invoked the scripts has jquery already loaded.
So, I need to check if jquery is loaded, if not, I will have to load it, append to head and only then when jquery is loaded and working, I will proceed with the script's execution .
PS: this is a kind of legacy answer ! I already had the solution beforehand !
So, any improvement will be appreciated !
That's the solution I've found:
// MyClass.js
var counterLoopLoad = 0;
class MyClass {
constructor(){
// do the code that does not need jQuery
return this.Init()
}
JqueryLoader() {
// Loop Breaker
counterLoopLoad ++;
if (counterLoopLoad == 100) {
throw 'I need jQuery in order to do what I am suppose to do!';
}
var __jquery = document.createElement('script');
__jquery.src = "http://code.jquery.com/jquery-latest.min.js";
__jquery.type = 'text/javascript';
// if needed ....
// __jquery.onload = function() {
// some code here
//
// };
// must be prepend !!! append won't work !!!!
document.head.prepend(__jquery);
// here is the point that makes all work !!!!
// without setTimeOut, the script will get in a loop !
var that = this;
setTimeout(function () {
that.Init();
}, 500);
}
Init() {
if (typeof jQuery == 'undefined') {
return this.JqueryLoader();
}
jQuery.ajax(...);
}
}

How can I defer or async javascript in OpenCart

I have OpenCart application. Javascripts are loaded in settings.php inside path '/catalog/controller//settings.php with similar codes as:
$this->document->addScript('catalog/view/theme/<theme>/lib/lazy/jquery.lazy.1.6.min.js');
$this->journal2->minifier->addScript('catalog/view/theme/<theme>/lib/actual/jquery.actual.min.js', 'header');
$this->journal2->minifier->addScript('catalog/view/theme/<theme>/lib/hover-intent/jquery.hoverIntent.min.js', 'footer');
Here, 'theme' means theme name that is installed. I want to defer or async these javascript loading in OpenCart, how can I do it?
I know that addScript syntax has 1s parameter as file, second location, 3rd defer and 4th async where defer and async can be boolean.
I have tried statement as below to see defer false and async true:
$this->journal2->minifier->addScript('catalog/view/theme/<theme>/lib/hover-intent/jquery.hoverIntent.min.js', 'footer', false, true);
but I am not sure if this will work or not. Please suggest
Here is a script I've been using for quite some time, in the head element.
With this you get good control of your loading of files, and can start loading anything after all the DOM is loaded, just make sure the files is not required anywhere in the DOM upon load.
<head>
<title></title>
<link rel='stylesheet' type='text/css' href='css.css' />
<script type='text/javascript'>
var DomLoaded = {
done: false, onload: [],
loaded: function () {
if (DomLoaded.done) return;
DomLoaded.done = true;
if (document.removeEventListener) { document.removeEventListener('DOMContentLoaded', DomLoaded.loaded, false); }
for (i = 0; i < DomLoaded.onload.length; i++) DomLoaded.onload[i]();
},
load: function (fireThis) {
this.onload.push(fireThis);
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', DomLoaded.loaded, false);
} else {
/*IE<=8*/
if (/MSIE/i.test(navigator.userAgent) && !window.opera) {
(function () {
try { document.body.doScroll('up'); return DomLoaded.loaded(); } catch (e) { }
if (/loaded|complete/.test(document.readyState)) return DomLoaded.loaded();
if (!DomLoaded.done) setTimeout(arguments.callee, 10);
})();
}
}
/* fallback */
window.onload = DomLoaded.loaded;
}
};
DomLoaded.load(function () {
var d = document;
var h = d.getElementsByTagName('head')[0];
var s = d.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('async', true);
s.setAttribute('defer', true);
s.setAttribute('src', '/path/to/scripts.js');
h.appendChild(s);
});
</script>
</head>
And here is a good article which describes a few more ways to speed things up, and one of them is combining your js files into 1, or 2-3-4, based on which one needs which. The benefit is less http request.
http://exisweb.net/web-site-optimization-making-javascript-load-faster
And google is filled with articles
https://www.google.com/search?q=speed%20up%20loading%20javascript%20files&rct=j
For reducing page load times, you must do two mainly things
Reduce number of requests
Reduce package sizes
For reducing number of requests, you may merge all javascripts & css to one file. You should also applying lazy load images (This helps reduce package size too)
If you are running a VPS, you may try to install mod_pagespeed (developed by google) - It will help decrease a lot page load time.
I am not sure if you have used gtmetrix.com or http://www.webpagetest.org/
for reviewing your site speed yet.
For my experience, lazy load JavaScript will not help you much

Best way to execute js only on specific page

I was wondering what would be the best way to execute a java-script code only on specific pages.
Let's imagine we have a template-based web-site, rewrite rule for the content ist set, jquery available and it basically looks like this:
<!DOCTYPE html>
<html>
<head>
<script src="script.js"></script>
</head>
<body>
...
include $content;
..
</body>
</html>
content 'info' contains a button, we want something to happen on click, content 'alert' should give us a message when you hover a text field.
What is the best way to trigger these actions, without running into an error, because the object is not found?
Option one: using window.location.pathname
$(document).ready(function() {
if (window.location.pathname == '/info.php') {
$("#button1").click(function(){
//do something
})
}else if(window.location.pathname == '/alert.php'){
$("#mytextfield").hover(){
alert('message');
}
}
Option two: checking if elements exist
$(document).ready(function() {
if ($("#button1").length > 0) {
$("#button1").click(function(){
//do something
})
}else if ($("#mytextfield").length > 0){
$("#mytextfield").hover(){
alert('message');
}
}
Option three: include the script in the loaded template
//stands for itself
Is there a better solution? Or do I have to get along with one of these solutions?
Your experience, usage, or any links related to this topic are appreciated.
//EDIT:
I might have choosen a bad example, the actual code would be somethin like:
mCanvas = $("#jsonCanvas");
mMyPicture = new myPicture (mCanvas);
where the myPicture constructor get's the context of the canvas element, and throws an error, if mCanvas is undefined.
Set a class attribute to your body tag.
<body class="PageType">
And then in your script..
$(function(){
if($('body').is('.PageType')){
//add dynamic script tag using createElement()
OR
//call specific functions
}
});
I would use the switch statement and a variable. (I'm using jQuery!)
var windowLoc = $(location).attr('pathname'); //jquery format to get window.location.pathname
switch(windowLoc){
case "/info.php":
//code here
break;
case "/alert.php":
//code here
break;
}
//use windowLoc as necessary elsewhere
This will allow you to change what "button" does based on the page that you're on. If I understood your question correctly; this is what I would do. Also, if I had were serving large amounts of javascript, I would simply add a new JS file completely.
var windowLoc = $(location).attr('pathname'); //jquery format to get window.location.pathname
switch(windowLoc){
case "/info.php":
var infoJS = document.createElement('script');
infoJS.type = 'text/javascript';
infoJS.src = 'location/to/my/info_file.js';
$('body').append(infoJs);
break;
case "/alert.php":
var alertJS = document.createElement('script');
alertJS.type = 'text/javascript';
alertJS.src = 'location/to/my/alert_file.js';
$('body').append(alertJs);
break;
}
Hope this helps -
Cheers.
A little different approach than checking the URL path : You can group page specific event handlers in a single function and then in each include, have a domready which will call these functions.
Eg: in script.js you have two functions (outside domready) viz. onPage1Load() and onPage2Load().
While in your page1.php you have a $(document).ready(onPage1Load)
and so on for other pages. This will make sure that unintended event handlers are not registered.
You can also use vanilla javascript to do the same
console.log(window.location.href);
const host = "http://127.0.0.1:5500/";
// JAVASCRIPT FOR INDEX PAGE
if (window.location.href == host + 'index.html') {
console.log("this is index page");
}
// JAVASCRIPT FOR ORDER PAGE
if (window.location.href == host + 'order.html') {
console.log("this is order page");
}
You can use Require js (RequireJS is a JavaScript file and module loader) and load script if they only needed. Link is http://requirejs.org/, I know using require js not so much easy.

Ordered JavaScript File Loading

I need to dynamically load several JavaScript file assets in a very specific order after a page has loaded. I'm trying to use onload, but this seems to fire before the asset has fully loaded. How should I adjust the below script to fire a proper callback to load the next script?
Note: Only needs to work in the latest Chrome, Firefox, Safari, and IE9.
function loadAssets() {
// Setup script
var scriptJS = document.createElement('script');
scriptJS.type = 'text/javascript';
scriptJS.src = objectUrl;
scriptJS.onload = loadAssetsNext();
// Begin insertion
var headerJS = document.getElementsByTagName('HEAD');
headerJS[0].appendChild(scriptJS);
},
function loadAssetsNext() {
// Increment object counter
objectsCount++;
// Test to see if you should call another item
if ((objectsCount) < objects.length) {
// Setup script
var scriptJS = document.createElement('script');
scriptJS.type = 'text/javascript';
scriptJS.src = nextObjectUrl;
// Declare callback to fire after script has fully loaded
scriptJS.onload = loadAssetsNext();
// Begin insertion
var headerJS = document.getElementsByTagName('HEAD');
headerJS[0].appendChild(scriptJS);
}
}
What I need is something like scriptJS.fullyLoaded = doStuff. Have no clue where to go from here though.
PS: jQuery is not an option or another library. You should be able to do this by slightly modifying the above script.
The reason your onload event is firing immediately is that you are calling it, not assigning it.
scriptJS.onload = loadAssetsNext();
This just assigns the returned value from the call to loadAssetsNext to the property onload of the scriptJS object. What you are intending to do is:
scriptJS.onload = loadAssetsNext;
That sets the onload handler to be the loadAssests function. This should take care of your issues.
I think the problem is that your scriptJS.onload = loadAssetsNext(); is placed before headerJS[0].appendChild(scriptJS);
That means that your scripts would load and get appended to the page like this:
load script 1
load script 2
load script 3
...
append script 3
append script 2
append script 1
So I think you should just reorder your script a little bit.

Loading external Javascript Sequentially

I am working on a javascript that sequentially loads a list of other external javascript.
The code I have so far:
function loadJavascript(url){
var js = document.createElement("script");
js.setAttribute("type", "text/javascript");
js.setAttribute("src", url);
if(typeof js!="undefined"){
document.getElementsByTagName("head")[0].appendChild(js)
}
}
loadJavascript("Jquery.js");
loadJavascript("second.js");
loadJavascript("third.js");
The problem I ran into is that sometimes the other js files loads before the Jquery file completes its loading. This gives me some errors.
Is it possible to make it so that the next JS file is only initiated when the previous file is finished loading.
Thanks in advance
Sure there is, but there's entire libraries written around doing this. Stop reinventing the wheel and use something that already works. Try out yepnope.js or if you're using Modernizr it's already available as Modernizr.load
loadJavascript("Jquery.js");
$(function(){
$.getScript('second.js', function(data, textStatus){
$.getScript('third.js', function(data, textStatus){
console.log("loaded");
});
});
}
Also, consider using the Google or Microsoft CDN for the jQuery, it will save you bandwidth and hopefully your visitors will already have it cached.
Actually, it's not necessary to load jquery within a js function. But if you insist, you can callback to make sure other js loaded after jquery.
Still, I recommend you load jquery just before </body> then use $.getScript to load other .js
You could do a check to see if jQuery is loaded, not the best way to do it, but if you really have to wait until jQuery is loaded before loading the other scripts, this is how I would do it, by checking for $ :
loadJavascript("Jquery.js");
T=0;
CheckIfLoaded();
function CheckIfLoaded() {
if (typeof $ == 'undefined') {
if (T <= 3000) {
alert("jQuery not loaded within 3 sec");
} else {
T=T+200;
setTimeout(CheckIfLoaded, 200);
} else {
loadJavascript("second.js");
loadJavascript("third.js");
}
}
In technical terms: Browsers have a funny way of deciding I which order to execute/eval dynamically loaded JS, so after suffering the same pain and checking a lot of posts, libraries, plugins, etc. I came up with this solution, self contained, small, no jquery needed, IE friendly, etc. The code is extensively commented:
lazyLoader = {
load: function (scripts) {
// The queue for the scripts to be loaded
lazyLoader.queue = scripts;
lazyLoader.pendingScripts = [];
// There will always be a script in the document, at least this very same script...
// ...this script will be used to identify available properties, thus assess correct way to proceed
var firstScript = document.scripts[0];
// We will loop thru the scripts on the queue
for (i = 0; i < lazyLoader.queue.length; ++i) {
// Evaluates if the async property is used by the browser
if ('async' in firstScript ) {
// Since src has to be defined after onreadystate change for IE, we organize all "element" steps together...
var element = document.createElement("script");
element.type = "text/javascript"
//... two more line of code than necessary but we add order and clarity
// Define async as false, thus the scripts order will be respected
element.async = false;
element.src = lazyLoader.queue[i];
document.head.appendChild(element);
}
// Somebody who hates developers invented IE, so we deal with it as follows:
// ... In IE<11 script objects (and other objects) have a property called readyState...
// ... check the script object has said property (readyState) ...
// ... if true, Bingo! We have and IE!
else if (firstScript.readyState) {
// How it works: IE will load the script even if not injected to the DOM...
// ... we create an event listener, we then inject the scripts in sequential order
// Create an script element
var element = document.createElement("script");
element.type = "text/javascript"
// Add the scripts from the queue to the pending list in order
lazyLoader.pendingScripts.push(element)
// Set an event listener for the script element
element.onreadystatechange = function() {
var pending;
// When the next script on the pending list has loaded proceed
if (lazyLoader.pendingScripts[0].readyState == "loaded" || lazyLoader.pendingScripts[0].readyState == "complete" ) {
// Remove the script we just loaded from the pending list
pending = lazyLoader.pendingScripts.shift()
// Clear the listener
element.onreadystatechange = null;
// Inject the script to the DOM, we don't use appendChild as it might break on IE
firstScript.parentNode.insertBefore(pending, firstScript);
}
}
// Once we have set the listener we set the script object's src
element.src = lazyLoader.queue[i];
}
}
}
}
Of course you can also use the minified version:
smallLoader={load:function(d){smallLoader.b=d;smallLoader.a=[];var b=document.scripts[0];for(i=0;i<smallLoader.b.length;++i)if("async"in b){var a=document.createElement("script");a.type="text/javascript";a.async=!1;a.src=smallLoader.b[i];document.head.appendChild(a)}else b.readyState&&(a=document.createElement("script"),a.type="text/javascript",smallLoader.a.push(a),a.onreadystatechange=function(){var c;if("loaded"==smallLoader.a[0].readyState||"complete"==smallLoader.a[0].readyState)c=smallLoader.a.shift(),
a.onreadystatechange=null,b.parentNode.insertBefore(c,b)},a.src=smallLoader.b[i])}};

Categories