Reputation: 99
I'm jusing an external JS plugin, which I initialised using this script:
(function(g,s,t,p,l,n){
g["_gstpln"]={};
(l=s.createElement(t)),(n=s.getElementsByTagName(t)[0]);
l.async=1;l.src=p;n.parentNode.insertBefore(l,n);
})(window,document,"script","https://cdn.guestplan.com/widget.js");
_gstpln.accessKey = "cebd85865b61ba61961cc749c8ca02e43cad6535";
_gstpln.color = "#000000";
_gstpln.showFab = false;
When listening to its event handlers, I can see it uses the function _gstpln.openWidget()
& _gstpln.closeWidget()
on specific buttons. I want to tweak this function a little bit, so I tried to override it in the following way:
$(document).ready(function() {
window._gstpln.closeWidget = function() {...}
/* also tried it without `window.` */
});
But that doesn't work, I also tried to add _gstpln.closeWidget = function() {...}
at the end of the initialization script. Didn't work either.
Any ideas?
Upvotes: 0
Views: 842
Reputation: 1074335
Since your code is run as soon as the DOM is done loading, but the plugin's code doesn't assign to closeWidget
until the https://cdn.guestplan.com/widget.js
file is downloaded and executed, you have a race condition going on. It's a race I think your code will very likely win, but "winning" means you assign to window._gstpln.closeWidget
before the plugin does, so your assignment is overwritten by theirs.
If you an possibly achieve your purpose another way, I strongly recommend doing so. Monkey-patching a plugin like this is asking for trouble.
So how do we handle the possibility that your code gets there first? I can think of a couple of ways:
window._gstpln.closeWidget
until we see that it has a value, then overwriting the value.The accessor property looks something like this:
$(document).ready(function() {
let original = window._gstpln.closeWidget;
const replacement = function() {
// ...you can use `original` here if you need to call the
// original implementation; presumably if `closeWidget`
// is being called, `openWidget` has been called and so
// the `widget.js` script has loaded...
};
Object.defineProperty(window._gstpln, "closeWidget", {
get() {
return replacement;
},
set(fn) {
original = fn;
},
});
});
How that works:
closeWidget
when your code runs, in case the other script has already run and filled it in.get
function is called and returns your replacement function.widget.js
script finished loading, and so this is that script assigning to window._gstpln.closeWidget
. So that's the "original" version of the function, which we tuck away into the original
variable we set up earlier.So in theory, at that point, your code will always be run when closeWidget
is run, and because there's a setter, we'll get the original function (if you need it) even if your code runs first.
Even if you don't need the original function, you probably don't want to make closeWidget
read-only, because parts (at least) of widget.js
are in strict mode, and assigning to read-only properties in strict mode throws an error, which would interrupt the widget.js
script and probably make things not work.
Alternatively, we could make that a bit smarter by only making it an accessor if the other code didn't get there first, and redefining the property on the first set
if it did:
$(document).ready(function() {
let original = window._gstpln.closeWidget;
const replacement = function() {
// ...you can use `original` here if you need to call the
// original implementation; presumably if `closeWidget`
// is being called, `openWidget` has been called and so
// the `widget.js` script has loaded...
};
if (original) {
// The `widget.js` code got there first, the simple way should work
// Note: This creates a read-only property, so if `widget.js` assigns
// to it a second time, it'll (possibly) throw an error
Object.defineProperty(window._gstpln, "closeWidget", {
value: replacement,
});
} else {
// We got there first, use an accessor that redefines on `set`
Object.defineProperty(window._gstpln, "closeWidget", {
get() {
return replacement;
},
set(fn) {
original = fn;
Object.defineProperty(window._gstpln, "closeWidget", {
value: replacement,
});
},
configurable: true, // <== So that we can change it above
});
}
});
The polling version looks something like this:
$(document).ready(function() {
let timeout = Date.now() + 60000; // 60 second timeout
check();
function check() {
let original = window._gstpln.closeWidget;
if (!original) {
if (Date.now() > timeout) {
// Give up!
} else {
setTimeout(check, 50); // 50ms or whatever
}
return;
}
window._gstpln.closeWidget = function() {
// ...you can use `original` here if you need it...
};
}
});
Neither of these is a good idea. But if you can't avoid it, one or the other (possibly with some tweaking) should work.
Upvotes: 1