Check if JS plugin is loaded before executing code - javascript

I'd like some insight into a little problem I'm encountering with a custom JS plugin that I've made. It's reasonably simple, I'm using a plugin JS template to write out plugins, which I'll attach. I'm then initialising the plugin, and ideally need to figure out if there's a way I can check if the plugin JS file is loaded before doing anything:
plugin template
(function() {
this.MyPlugin = function() {
// default settings
const INBOUND_CONFIG = {
isEnabled: false
}
// Create options by extending defaults with the passed in arugments
if (arguments[0] && typeof arguments[0] === "object") {
this.options = extendDefaults(INBOUND_CONFIG, arguments[0]);
}
// custom public method
HoneycombInbound.prototype.getSettings = function() {
}
// Utility method to extend defaults with user options
function extendDefaults(source, properties) {
var property;
for (property in properties) {
if (properties.hasOwnProperty(property)) {
source[property] = properties[property];
}
}
return source;
}
}
}());
With the above, I'd initialise the plugin in HTML as follows:
<script src="./my-plugin.js"></script>
<script>
const plugin = new MyPlugin()
// this doesn't work if the script file isn't linked:
if (plugin) {
// do something
}
</script>
Unfortunately, a simple check of plugin doesn't actually work if the JS file isn't loaded for instance, I've even tried something like: if (new MyPlugin()) {} with little hope.
I essentially need a way of not throwing an error in the browser. Any solution I can use to check correctly if it's loaded before executing?

A solution that has worked for me is to work with the WebAPI postMessage
I've used to communicate with the client, that the plugin is ready.
So on the plugin you will need something like this:
window.postMessage("The plugin is ready to go" );
And on your client you will need to add a new listener that can catch the message:
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
EDIT
So to achieve this behavio, you will need to enable on the client sidethe initialization:
const myPlugin = {
init: () => {
// Initilize the plugin if you are using the DOM you can add a parameter with an id to mount it whenever you need it
//Otherwise just configure or initilize the variables that you will use
// And here you need to pass the messsage to tell the client that everyting is ready
// Here you need to configure the origin correctly depending on your needs check the documentation for more details
window.addEventListener("message", myMessage, false);
},
someFunctionality: () => {
//This pattern is to expose the funtionalities to the client so you can achieve any
}
};
So now on your client you just need to add your script and tell the plugin to initialize:
<script type='text/javascript' src='/myPlugin.js'></script>
<script>
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
myPlugin.init();
</script>

Related

Loading javascript files in js files. Which is best way to check whether all files are loaded or not?

I have a array where i have specified the files i need to load in javascript before calling specific script. Lets call those particular lines of code as myscript.
I did as follows
var fileNamesArray = new Array();
fileNamesArray.push("abc.js");
fileNamesArray.push("pqr.js");
fileNamesArray.push("xyz.js");
fileNamesArray.push("klm.js");
var totalFiles = jQuery(fileNamesArray).length;
var tempCount = 0;
jQuery.each(fileNamesArray, function(key, value) {
jQuery.getScript(value, function() {
tempCount++;
});
});
to check whether all files are being loaded or not, i done following thing but doesn't seems to be effective
var refreshIntervalId = setInterval(function() {
if (tempCount == totalFiles) {
clearInterval(refreshIntervalId);
return;
}
}, 10);
i have implemented these in object oriented javascript as follows
function Loader() {
this.jQuery = null;
// check for specifically jQuery 1.8.2+, if not, load it
if (jQuery == undefined) {
jQuery.getScript(
"/Common/javascript/jquery/map/javascript/jquery-1.8.2.js",
function() {
this.jQuery = jQuery.noConflict();
});
} else {
var jQueryVersion = $.fn.jquery;
jQueryVersion = parseInt(jQueryVersion.split('.').join(""));
if (182 > jQueryVersion) {
jQuery.getScript(
"/Common/javascript/jquery/map/javascript/jquery-1.8.2.js",
function() {
this.jQuery = jQuery.noConflict();
});
}
}
}
Loader.prototype.LoadAllFile = function() {
//here i am loading all files
}
Loader.prototype.bindMap = function(options) {
this.LoadAllFile();
//execute the script after loading the files... which we called as myscript
}
i am loading more than 12-14 js files via ajax.
if you observe Loader.prototype.bindMap, i am loading all the files first and then executing the script.
But it seems that myscript the script start executing before all files being loaded.
what are the better ways to execute the script only after all js files are loaded.
Take a look at jQuery's .load() http://api.jquery.com/load-event/
$('script').load(function () { });
Based on the documentation on Jquery.getScript , it is a shorthand for Jquery.ajax. By default this in async call. You might want to change it to do a synchronous call.
To set this property, you can refer to this
So instead of doing a setInterval, you can just loop in your array and do a Jquery.getScript.

Unable to use jQuery with server-side Rails widget

I'm creating a widget, for which I have initialized jQuery like so:
# widget.js
(function() {
var jQuery;
var root_url = "<%= root_url%>";
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.9.1') {
var jquery_script = document.createElement('script');
jquery_script.type = "text/javascript";
jquery_script.asyc = true;
jquery_script.src = "http://code.jquery.com/jquery-1.9.1.js";
if (jquery_script.readyState) {
script_tag.onreadystatechange = function () { // For old versions of IE
if (this.readyState == 'complete' || this.readyState == 'loaded') {
scriptLoadHandler();
}
};
} else { // Other browsers
jquery_script.onload = scriptLoadHandler;
}
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(jquery_script, node);
} else {
jQuery = window.jQuery;
main();
}
function scriptLoadHandler() {
jQuery = window.jQuery.noConflict(true);
main();
}
function main() {
jQuery(document).ready(function($) {
// jQuery works great here!
// But not so much in rendered partials or in server-side JS actions
}
}
})();
If I make an ajax request to my server and respond with a JS action, I can't use jQuery in my action.js.erb file. It spits out the error $ is not a function.
Similarly, even rendering partials with jQuery scripts in them yields the same results.
I think I haven't set a global instance of jQuery, and if this is the case, I'm not sure how to do that. Perhaps I'm not supposed to?
How can I get around this problem?
This solution isn't what I was looking for but I was able to combine elements of the native JS widget with an iframe. Once I manipulate whatever elements I need to on the 3rd party site (as per my widget's functionality) I render a partial housing an iframe that lets me completely control the environment.

Having trouble with JS object literal setup

So I've setup my first JS design pattern - but I've run into an issue.
Here is my code on fiddle:
http://jsfiddle.net/jrutter/CtMNX/
var emailSignup = {
'config': {
// Configurable Options
'container': $('#email-signup'),
'emailButton': $('#email-submit'),
'emailInput': $('#email-input'),
'brontoDirectAddURL': 'URL',
'brontoListID': '0bbc03ec000000000000000000000003287b',
},
'init': function (config) {
// stays the same
// provide for custom configuration via init()
if (config && typeof (config) == 'object') {
$.extend(emailSignup.config, config);
}
// Create and/or cache some DOM elements
emailSignup.$container = $(emailSignup.config.container);
emailSignup.$button = $(emailSignup.config.emailButton);
emailSignup.$input = $(emailSignup.config.emailInput);
emailSignup.$brontoURL = emailSignup.config.brontoDirectAddURL;
emailSignup.$brontoList = emailSignup.config.brontoListID;
// Add email track to drop image pixel into for submission
emailSignup.$container.append('<div class="email-error"></div>');
emailSignup.$container.append('<div class="email-track"></div>');
// Call getEmaile
emailSignup.getEmail(emailSignup.$button, emailSignup.$input);
// make a note that the initialization is complete
emailSignup.initialized = true;
},
'getEmail': function ($button, $input) {
// click event
emailSignup.$button.click(function () {
// get the val
var $emailVal = $(emailSignup.$input).val();
// Call validateEmail
console.log($emailVal);
emailSignup.validateEmail($emailVal);
return false;
});
},
'validateEmail': function ($emailVal) {
var $emailRegEx = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
//console.log($emailVal);
if ($emailVal == '') {
$(".email-error").html('<p>You forgot to enter an email address.</p>');
} else if (!$emailRegEx.test($emailVal)) {
$(".email-error").html('<p>Please enter a valid email address.</p>');
} else {
$(".email-error").hide();
emailSignup.submitEmail($emailVal);
}
},
'submitEmail': function ($emailVal) {
$(".email-track").html('<img src=' + emailSignup.$brontoURL+'&email='+$emailVal+'&list1=' + emailSignup.$brontoList + '" width="0" height="0" border="0" alt=""/>');
},
};
Its a function to add a subscriber to an email list via bronto - it works perfectly when the script is included on the page and the init function is setup on the page too. But when I include the script in a shared header and try to fire the function from the document-ready, it doesnt seem to be working.
Also, if I try to pass in a 'container' - that also is breaking the script. Not sure what Im doing wrong? But if I pass in the URL - that does work!
$(function () {
emailSignup.init({
'brontoDirectAddURL':'URL','container':'#email-signup'
});
});
Any advice would be greatly appreciated!
Change the following code...
emailSignup.$container = emailSignup.config.container;
emailSignup.$button = emailSignup.config.emailButton;
emailSignup.$input = emailSignup.config.emailInput;
emailSignup.$brontoURL = emailSignup.config.brontoDirectAddURL;
emailSignup.$brontoList = emailSignup.config.brontoListID;
into the following...
// Create and/or cache some DOM elements
emailSignup.$container = $(emailSignup.config.container);
emailSignup.$button = $(emailSignup.config.emailButton);
emailSignup.$input = $(emailSignup.config.emailInput);
emailSignup.$brontoURL = $(emailSignup.config.brontoDirectAddURL);
emailSignup.$brontoList = $(emailSignup.config.brontoListID);
// Add email track to drop image pixel into for submission
emailSignup.$container.append('<div class="email-error"></div>');
emailSignup.$container.append('<div class="email-track"></div>');
You can not call append on a string. I've update your JSFiddle.
Your default config object contains jQuery collections. However, you are simply passing the string "#email-signup" as your container instead of $("#email-signup"). That's where the error is coming from. Your initial call should thusly be:
$(function () {
emailSignup.init({
'brontoDirectAddURL':'URL','container': $('#email-signup')
});
});
Note that as your initial module includes some jQuery calls, you will need to wrap the whole emailSignup mess into a $(document).ready() as well.
You may consider reconfiguring this whole thing as a jQuery plugin.

Using Prototype to load a JavaScript file from another domain

Using Prototype, anyone know how to load a javascript file using Ajax.Request from another domain? Or if this is possible?
I believe this is possible with jquery, digg do it to load the Facebook API:
jQuery.ajax({type:"GET",
url:"http://static.ak.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php",
cache:true, dataType:"script"});
Source: http://cotnet.diggstatic.com/js/loader/370/digg_facebook
Without looking at the code, I'm guessing jquery has the smarts to use a proxy when url violates the same origin policy and dataType is script.
Check this out.. It seems that there is a specific plugin which enables the functionality in Prototype library, the author mentions that e.g. jQuery supports it already for a long time, but it seems it's not supported by default it Prototype.
Thx to Thomas's answer, I created a FacebookApiLoader class to do this. Here's the source, only tested in Firefox 3 at the moment. Hope this helps someone. What this does is looks to see if there are any facebook api dependent elements on the page, and if there are then it will load the facebook api script by inserting it before the body closing tag. This relies on the PrototypeJS library. Call facebookApiLoader.observe() in a page that might require the facebook api.
var FacebookApiLoader = Class.create({
initialize: function() {
this.observer = null
this.observedElement = null
},
apiDependentsVisible: function() {
if (null == this.observedElement) {
// $('facebook-login') is a div in my site that
// is display:none initially. Once it is made
// visible then the facebook api is needed.
// Replace 'facebook-login' with id relevant for your site
this.observedElement = $('facebook-login')
}
return this.observedElement.visible()
},
apiLoadCompleted: function() {
try {
return !Object.isUndefined(FB) && !Object.isUndefined(FB_RequireFeatures)
} catch (e) {
}
return false
},
initAndRequireFeatures: function() {
FB_RequireFeatures(["XFBML"],
function() {
FB.init('secret-put-your-app-value-here','/xd_receiver.html', {})
}
);
},
initFacebookConnect: function() {
new PeriodicalExecuter(function(pe) {
if (this.apiLoadCompleted()) {
this.initAndRequireFeatures()
pe.stop()
}
}.bind(this), 0.2);
},
loadApi: function() {
// Use body for facebook script as recommended in Facebook
// docs not to insert in head as some browsers have
// trouble with it
body = $$('body')[0]
// TODO use https protocol if page is secure
script = new Element('script', { 'type': 'text/javascript',
'src': 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php' })
body.appendChild(script)
this.initFacebookConnect()
},
loadApiIfRequired: function() {
if (this.apiDependentsVisible()) {
this.observer.stop()
this.loadApi()
}
},
observe: function() {
if (null == this.observer) {
this.observer = new PeriodicalExecuter(this.loadApiIfRequired.bind(this), 0.2)
}
}
});
// The FacebookApiLoader attributes are lazily loaded
// so creating a new facebookApiLoader
// is as low resource usage as possible
var facebookApiLoader = new FacebookApiLoader();
Then on any page that might need the Facebook api on demand, call
facebookApiLoader.observe();

How to use jQuery in Firefox Extension

I want to use jQuery inside a firefox extension,
I imported the library in the xul file like this:
<script type="application/x-javascript" src="chrome://myExtension/content/jquery.js"> </script>
but the $() function is not recognized in the xul file neither do the jQuery().
I googled about the problem and found some solutions but no one did work with me:
http://gluei.com/blog/view/using-jquery-inside-your-firefox-extension
http://forums.mozillazine.org/viewtopic.php?f=19&t=989465
I've also tried to pass the 'content.document' object(which refrences the 'document' object) as the context parameter to the jQuery function like this:
$('img',content.document);
but still not working,
does any one came across this problem before?
I use the following example.xul:
<?xml version="1.0"?>
<overlay id="example" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head></head>
<script type="application/x-javascript" src="jquery.js"></script>
<script type="application/x-javascript" src="example.js"></script>
</overlay>
And here is an example.js
(function() {
jQuery.noConflict();
$ = function(selector,context) {
return new jQuery.fn.init(selector,context||example.doc);
};
$.fn = $.prototype = jQuery.fn;
example = new function(){};
example.log = function() {
Firebug.Console.logFormatted(arguments,null,"log");
};
example.run = function(doc,aEvent) {
// Check for website
if (!doc.location.href.match(/^http:\/\/(.*\.)?stackoverflow\.com(\/.*)?$/i))
return;
// Check if already loaded
if (doc.getElementById("plugin-example")) return;
// Setup
this.win = aEvent.target.defaultView.wrappedJSObject;
this.doc = doc;
// Hello World
this.main = main = $('<div id="plugin-example">').appendTo(doc.body).html('Example Loaded!');
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
main.html(main.html() + ' - jQuery <b>' + $.fn.jquery + '</b>');
};
// Bind Plugin
var delay = function(aEvent) {
var doc = aEvent.originalTarget; setTimeout(function() {
example.run(doc,aEvent);
}, 1);
};
var load = function() {
gBrowser.addEventListener("DOMContentLoaded", delay, true);
};
window.addEventListener("pageshow", load, false);
})();
The following solution makes it possibile to use jQuery in contentScriptFile
(Targetting 1.5 Addon-sdk)
In your main.js:
exports.main = function() {
var pageMod = require("page-mod");
pageMod.PageMod({
include: "*",
contentScriptWhen: 'end',
contentScriptFile: [data.url("jquery-1.7.1-min.js") , data.url("notifier.js") , data.url("message.js")],
onAttach: function onAttach(worker) {
//show the message
worker.postMessage("Hello World");
}
});
};
In your message.js :
self.on("message", function(message){
if(message !== "undefined"){
Notifier.info(message);
}
});
Some pitfalls you need to watchs out for:
The order of the contentScriptFile array. if message.js would be placed first: jQuery won't be reconized.
Do not place a http:// url in the data.url (this does not work)!
All your javascript files should be in the data folder. (only main.js should be in lib folder)
There is an excellent article in the mozillaZine forums that describes this step-by-step: http://forums.mozillazine.org/viewtopic.php?f=19&t=2105087
I haven't tried it yet, though so I hesitate to duplicate the info here.
Turns out the current top-answer by #sunsean does not work as expected when it comes to handling multiple loads. The function should properly close over the document and avoid global state.
Also, you have to call jQuery.noConflict(true) to really avoid conflicts with other add-ons!
This is who I would write it (then again, I would avoid jquery (in add-ons) like the plague...).
First the overlay XUL
<?xml version="1.0"?>
<overlay id="test-addon-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="text/javascript" src="jquery.js"/>
<script type="text/javascript" src="overlay.js"/>
</overlay>
And then the overlay script:
// Use strict mode in particular to avoid implicitly var declarations
(function() {
"use strict";
// Main runner function for each content window.
// Similar to SDK page-mod, but without the security boundaries.
function run(window, document) {
// jquery setup. per https://stackoverflow.com/a/496970/484441
$ = function(selector,context) {
return new jq.fn.init(selector,context || document);
};
$.fn = $.prototype = jq.fn;
if (document.getElementById("my-example-addon-container")) {
return;
}
let main = $('<div id="my-example-addon-container">');
main.appendTo(document.body).text('Example Loaded!');
main.click(function() { //<--- added this function
main.text(document.location.href);
});
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
};
const log = Components.utils.reportError.bind(Components.utils);
// Do not conflict with other add-ons using jquery.
const jq = jQuery.noConflict(true);
gBrowser.addEventListener("DOMContentLoaded", function load(evt) {
try {
// Call run with this == window ;)
let doc = evt.target.ownerDocument || evt.target;
if (!doc.location.href.startsWith("http")) {
// Do not even attempt to interact with non-http(s)? sites.
return;
}
run.call(doc.defaultView, doc.defaultView, doc);
}
catch (ex) {
log(ex);
}
}, true);
})();
Here is a complete add-on as a gist. Just drop in a copy of jquery and it should be good to go.
I think this is what Eric was saying, but you can load Javascript from the URL directly.
javascript:var%20s=document.createElement('script');s.setAttribute('src','http://YOURJAVASCRIPTFILE.js');document.getElementsByTagName('body')[0].appendChild(s);void(s);
Im assuming you want your extension to load JQuery so you can manipulate the page elements easily? My company's labs has something that does this using Javascript directly here: http://parkerfox.co.uk/labs/pixelperfect
It may be bad practice, but have you considered including it inline?
Instead of
$('img',content.document);
you can try
$('img',window.content.document);
In my case it works.

Categories

Resources