How can I defer or async javascript in OpenCart - javascript

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

Related

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

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;
}

smoothState.js - Different stylesheets on different pages?

I am currently trying to implement smoothState.js on my website. I have, however, run into a problem when transitioning between pages.
The problem is that the new content is 'unstyled' (more accurately put it is styled with the styles of the previous page). Since I have focused quite a lot on performance for this site, I would prefer if there was a way in which I could avoid combining the stylesheets.
I have thought a bit about how to tackle this problem, but cannot decide which of the solutions would have least performance impact. These are the possible solutions which I could think of.
1. Combine the stylesheets
Despite me being reluctant to do this, it might be the best way. After all, it is only a few extra bytes, right? Stylesheets (minified and gzipped) are quite small, after all.
2. Add a javascript function to the body
For smoothState, I am currently using the body as the container. I guess that I could thus, for example, add the following javascript directly after the body tag.
var cb = function() {
var l = document.createElement('link'); l.rel = 'stylesheet';
l.href = 'css/single.css';
var h = document.getElementsByTagName('head')[0]; h.parentNode.insertBefore(l, h);
};
var raf = requestAnimationFrame || mozRequestAnimationFrame ||
webkitRequestAnimationFrame || msRequestAnimationFrame;
if (raf) raf(cb);
else window.addEventListener('load', cb);
(I would have to modify the above since document.load isn't run when changing pages with smoothState. Sadly, I am quite terrible at vanilla JS. But I guess I can figure it out... Not a big problem :) )
3. Do some javascript magic in the onStart.render() of smoothstate()
Thirdly, and lastly, I realise that it would be plausible to run some javascript to get the stylesheet of the target page and append it to the new. My instinct tells me this will be less efficient performance wise than just combining the stylesheets...
In case it helps, I will include my smoothState function below.
'use strict';
var $body = $('html, body'),
content = $('#main').smoothState({
prefetch: true,
pageCacheSize: 4,
development: true,
onStart: {
duration: 250,
render: function (url, $container) {
// #3 would be implemented here.
content.toggleAnimationClass('is-exiting');
$body.animate({
scrollTop: 0
});
}
},
onProgress : {
duration: 0,
render: function (url, $container) {
$body.css('cursor', 'wait');
$body.find('a').css('cursor', 'wait');
}
},
onEnd: {
duration: 250, // Duration of our animation
render: function (url, $container, $content) {
$body.css('cursor', 'auto');
$body.find('a').css('cursor', 'auto');
$container.html($content);
}
}
}).data('smoothState');
})(jQuery);
Can anyone suggest which of the three methods provided would have the least impact on the page-load of my pages, or suggest another method of tackling the problem?
UPDATE 28/04/15
I have chosen to simply go with method number 1 and combined the stylesheets.
Can anyone suggest which of the three methods provided would have the
least impact on the page-load of my pages, or suggest another method
of tackling the problem?
You could also include new stylesheet references inside of the 'div#main' of the target page. When you inject the new html the browser will understand and execute any new <link rel="stylesheet" href="" /> being introduced.
I would personally still prefer to concatenate all of the CSS files, though.

jQuery not loading javascripts

So I'm stuck behind this problem
I'm using AJAX to refresh my website and when I also need to add scripts to my newly loaded div. Foe this I'm using this piece of code
$.getScript('scripts/shBrushJava.js');
$.getScript('scripts/shCore.js');
setTimeout(function(){
$(".left-content").load(document.location.hash.substr(1));
}, 10);
Sometimes this works and sometimes not as shown on pictures below
This is the working 2 pictures (Website and Firebug console)
These are the images from the time it's not working (95%)
As you can see, when the JS is not loaded properly, they are shown in console. How to fix this?
As mentioned, ajax is asynchronous. So you are not guaranteed that the script files is loaded at any given time. You've set a timeout for 10 milliseconds. Some times, the files may have been loaded and parsed, sometimes not.
Here is an example on how you can guarantee that the scripts are loaded, but there is a tons of third party libraries available for this already. For example RequireJS.
Simple example:
var getAllScripts = function (scripts, callback) {
var i, count = scripts.length;
// Start loading all files
for (i = 0; i < scripts.length; i += 1) {
$.getScript(scripts[i], function () {
// When loaded, decrease count
count -= 1;
// If all is loaded, call the callbacl
if (count === 0) {
callback();
}
});
};
};
getAllScripts([
'scripts/shBrushJava.js',
'scripts/shCore.js'
],
function () {
// Here all files is loaded, do your magic here
alert('All is loaded');
}
);
Here is a fiddle to try it out
Or you can just put:
<script src="scripts/shCore.js" type="text/javascript"></script>
<script src="scripts/shBrushJava.js" type="text/javascript"></script>
In your <head> in your html. Then it's guaranteed to be loaded in the whole <body>.

Loading external scripts async in JavaScript

This is my first module i'm writing in JS and I want to make sure i'm doing it right.
It is going to be a simple gallery module but I want it to be able to display youtube and vimeo movies also.
In my module I have function called init(). Before i'm displaying the gallery first I want to add all necessary libs, like youtube iframe api and vimeo js api.
only after they are loaded I want to display the gallery.
so my module looks something like this:
myModule = {};
myModule.isYoutubeApiLoaded = false;
myModule.isVimeoApiLoaded = false;
myModule.controller;
myModule.init = function (){
myModule.loadYoutubeapi();
myModule.loadVimeoApi();
myModule.startController();
}
now startController function looks like this:
myModule.startController = function (){
myModule.controller = setInterval(function(
if (myModule.isYoutubeApiLoaded && myModule.isVimeoApiLoaded ) {
myModule.buildGallery();
clearInterval(myModule.controller);
}
), 5);
}
in loadYoutubeapi() and loadVimeoApi() i'm setting the given flags when scripts are loaded.
is it a good approach?
No, it's not a good approach. It will load CPU and will possibly have unnecessary delay of 5 milliseconds.
Better way would be to add callbacks to loadYoutubeapi() and loadVimeoApi(). Once they finish they must call your new function (e.g. moduleHasLoaded()), which will count loaded modules. When all will be loaded you can call startController().
It will save CPU and will not have a unnecessary delay at all.
Something like this:
myModule = {};
myModule.isYoutubeApiLoaded = false;
myModule.isVimeoApiLoaded = false;
myModule.loaded = false;
myModule.controller;
myModule.loadYoutubeapi = function() {
/* Do your stuff. */
myModule.isYoutubeApiLoaded = true;
myModule.moduleHasLoaded();
}
myModule.loadVimeoApi = function() {
/* Do your stuff. */
myModule.isVimeoApiLoaded = true;
myModule.moduleHasLoaded();
}
myModule.moduleHasLoaded = function() {
if (!loaded && myModule.isYoutubeApiLoaded && myModule.isVimeoApiLoaded ) {
loaded = true;
myModule.buildGallery();
}
}
myModule.init = function (){
myModule.loadYoutubeapi();
myModule.loadVimeoApi();
}
The simple solution is to use a script loader like lazyload. It allows you to specify the scripts you want to load and run a callback when the are actually loaded, i.e.:
LazyLoad.js([
"http://www.youtube.com/apiplayer?enablejsapi=1&version=3",
"...vimeo script...",
"...other scripts..."
],
function() {
myModule.buildGallery();
}
);
The function will be called when all scripts are loaded. The caveat is that you get no error callback, i.e. if a script fails to load. There are also other script loaders besides lazyload.
More complicated solution, but better if you are working on a medium to large size client-side application: Refactor your modules to use require.js.

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