Disclaimer: This is sort of a hack and not recommended. I implemented this solution before since I have no other available option. Oracle might already released an OCC widget that supports asynchronous loading of Maxymiser script on a page. Please research on it first.
On Oracle Commerce Cloud application, you build custom features on your e-commerce store by creating a widget. With this article, I assumed you have experience working with Oracle Commerce Cloud store front and implementing Maxymiser.
Here are some detailed documentation about OCC widgets.
During the time when I implemented this solution, Maxymiser does not support asynchronous loading of their script. The script tag has to be inserted into the global template of your site before the tag, and before any other scripts. Here’s more info about it.
Also, one limitation working with Oracle Commerce Cloud as a developer is you don’t have direct access like copy/pasting a script tag directly on the head tag of a page. One solution is to create a widget and write a script that will dynamically inject the Maxymiser script on page load. Also OCC is using require.js library in loading javascript files in asynchronous pattern.
Creating an OCC widget will start like this on the widget’s main script:
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
onLoad: function (widget) {
//run custom logic when a widget is instantiated
}
};
}
);
The main issue here is we cannot include Maxymiser‘s script as a dependency inside the widget’s module since by default, it will be loaded asynchronously. The solution here is to write a script that will paste Maxymiser’s library on the page but ensure that it’ll be synchronous.
I start by writing a cross-browser function to append the Maxymiser script. I called it appendMaxymiserScript
function:
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
/**
* Appends the maxymiser script on the page
* @param string url the src value of the script
* @param object mount an existing DOM element on the page to mount the Maxymiser script
* @param function callback function to call once script is completely loaded
*/
appendMaxymiserScript: function (url, mount, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.async = false; //make sure this is synchronously loaded
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" ||
script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
mount.appendChild(script);
},
//more code here.........
onLoad: function (widget) {
//run custom logic when a widget is instantiated
}
};
}
);
The significant thing to take note on appendMaxymiserScript
function is setting script.async = false;
line.
Also if you noticed, I need that mount
element to append the Maxymiser script.
Next is to create a function to set the mount
element by calling it setMountDom
.
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
/**
* Appends the maxymiser script on the page
* @param string url the src value of the script
* @param object mount an existing DOM element on the page to mount the Maxymiser script
* @param function callback function to call once script is completely loaded
*/
appendMaxymiserScript: function (url, mount, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.async = false; //make sure this is synchronously loaded
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" ||
script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
mount.appendChild(script);
},
/**
* Sets and returns the mount element object where the Maxymiser script will be injected
* @param string id the id of this DOM element to ensure we have only one instance of this element
*/
setMountDom: function (id) {
var mount = document.getElementById(id),
body;
if (mount) {
return mount;
}
mount = document.createElement("div");
mount.id = id;
body = document.getElementsByTagName('body')[0];
body.insertBefore(mount, body.firstChild);
return mount;
}
//more code here.........
onLoad: function (widget) {
//run custom logic when a widget is instantiated
}
};
}
);
Calling setMountDom
will first check if an element with given id exist on the page. If not then it’ll create an element with that given id and
insert on the page. Logically, I need a function that will always generate a unique id. I’ll call it, generateUID
.
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
// more code before
/**
* Sets and returns the mount element object where the Maxymiser script will be injected
* @param string id the id of this DOM element to ensure we have only one instance of this element
*/
setMountDom: function (id) {
var mount = document.getElementById(id),
body;
if (mount) {
return mount;
}
mount = document.createElement("div");
mount.id = id;
body = document.getElementsByTagName('body')[0];
body.insertBefore(mount, body.firstChild);
return mount;
}
//generate a unique string to be used as an ID for the DOM element
generateUID: function () {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
},
//more code here.........
onLoad: function (widget) {
//run custom logic when a widget is instantiated
}
};
}
);
At this point, looks like I have the necessary pieces together. Using all the preceding functions, this is how the onLoad
function looks like.
onLoad: function (widget) {
//run custom logic when a widget is instantiated
var maxyMiserLib = (document.location.protocol == "https:" ? "https://" : "http://") + 'service.maxymiser.net/cdn/mycompanyname/js/mmcore.js',
uniqId = 'mmCoreScript_' + this.generateUID(), //randomize id of script tag container
mount = this.setMountDom(uniqId);
this.appendMaxymiserScript(maxyMiserLib, mount, function () {
console.log('Maxymiser script loaded....');
});
}
However, running the widget will throw an error about document.write
not being able to write on the stream inside Maxymiser’s script.
It turns out on my situation, document.write()
is unstable since it’s called after the document has finished being parsed and is closed. The behaviour is unpredictable cross-browser. Check-out Mozilla’s documentation about it.
At this point, I can conclude that the solution is to override document.write
inside Maxymiser script. This is where the interesting and hacky part occurs.
If you wonder, why I included jQuery
but never used it. I’m going to be using it to replace document.write
inside Maxymiser’s script on the fly. I’m going to call this function overRideScript
.
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
// more code before
//generate a unique string to be used as an ID for the DOM element
generateUID: function () {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
},
overRideScript: function () {
var script = "document.write = function(str){";
script += "var head = document.head||document.getElementsByTagName(\"head\")[0];";
script += "$(head).append(str);";
script += "};";
return script;
}
//more code here.........
onLoad: //........
};
}
);
As you see, I’m using jQuery’s append
function to replace document.write
’s job.
Next step is to create a function to write the returned string from overRideScript
on the page. I named it appendOverRideScript
.
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
// more code before
overRideScript: function () {
var script = "document.write = function(str){";
script += "var head = document.head||document.getElementsByTagName(\"head\")[0];";
script += "$(head).append(str);";
script += "};";
return script;
},
appendOverRideScript: function (text, mount, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.text = text;
mount.appendChild(script); //append just after the opening of body
callback();
},
//more code here.........
onLoad: //more code ........
};
}
);
If you notice, I have a callback
function inside appendOverRideScript
. The callback
function will execute the appendMaxymiserScript
function to ensure that overriding of the function occurs correctly by placing the scripts in the right sequence.
Now rewriting the onLoad
function:
onLoad: function (widget) {
//run custom logic when a widget is instantiated
var maxyMiserLib = (document.location.protocol == "https:" ? "https://" : "http://") + 'service.maxymiser.net/cdn/mycompanyname/js/mmcore.js',
uniqId = 'mmCoreScript_' + this.generateUID(), //randomize id of script tag container
mount = this.setMountDom(uniqId),
oThis = this;
oThis.appendOverRideScript(oThis.overRideScript(), mount, function () {
oThis.appendMaxymiserScript(maxyMiserLib, mount, function () {
console.log('Maxymiser script loaded....');
});
});
}
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//-------------------------------------------------------------------
['jquery'],
//-------------------------------------------------------------------
// MODULE DEFINITION
//-------------------------------------------------------------------
function ($) {
'use strict';
return {
/**
* Appends the maxymiser script on the page
* @param string url the src value of the script
* @param object mount an existing DOM element on the page to mount the Maxymiser script
* @param function callback function to call once script is completely loaded
*/
appendMaxymiserScript: function (url, mount, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.async = false; //make sure this is synchronously loaded
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" ||
script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
mount.appendChild(script);
},
/**
* Sets and returns the mount element object where the Maxymiser script will be injected
* @param string id the id of this DOM element to ensure we have only one instance of this element
*/
setMountDom: function (id) {
var mount = document.getElementById(id),
body;
if (mount) {
return mount;
}
mount = document.createElement("div");
mount.id = id;
body = document.getElementsByTagName('body')[0];
body.insertBefore(mount, body.firstChild);
return mount;
},
//generate a unique string to be used as an ID for the DOM element
generateUID: function () {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
},
overRideScript: function () {
var script = "document.write = function(str){";
script += "var head = document.head||document.getElementsByTagName(\"head\")[0];";
script += "$(head).append(str);";
script += "};";
return script;
},
appendOverRideScript: function (text, mount, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.text = text;
mount.appendChild(script); //append just after the opening of body
callback();
},
onLoad: function (widget) {
//run custom logic when a widget is instantiated
var maxyMiserLib = (document.location.protocol == "https:" ? "https://" : "http://") + 'service.maxymiser.net/cdn/mycompanyname/js/mmcore.js',
uniqId = 'mmCoreScript_' + this.generateUID(), //randomize id of script tag container
mount = this.setMountDom(uniqId),
oThis = this;
oThis.appendOverRideScript(oThis.overRideScript(), mount, function () {
oThis.appendMaxymiserScript(maxyMiserLib, mount, function () {
console.log('Maxymiser script loaded....');
});
});
}
};
}
);
Thanks for reading! :)