I've built a JavaScript widget that must be embeddable on any third-party site, in any environment. The widget relies on jQuery and jQuery UI. I followed the steps in How to embed Javascript widget that depends on jQuery into an unknown environment to add jQuery in a responsible manner -- works great for embedding jQuery. But when I try to add jQuery UI, it fails. Here's the code:
(function(window, document, version, callback) {
var j, d;
var loaded = false;
if (!(j = window.jQuery) || version > j.fn.jquery || callback(j, loaded)) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js";
script.onload = script.onreadystatechange = function() {
if (!loaded && (!(d = this.readyState) || d == "loaded" || d == "complete")) {
callback((j = window.jQuery).noConflict(1), loaded = true);
j(script).remove();
}
};
document.documentElement.childNodes[0].appendChild(script)
}
})(window, document, "1.3.2", function($, jquery_loaded) {
$.getScript('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.js', function(){
console.log('loaded');
});
});
When I run this, I get the 'loaded' mesage, followed by an error saying that "$ is undefined" on line 15 of jquery-ui.js. But how can $ be undefined when I'm using $.getScript() to load jQuery UI? And why do I see the message 'loaded' before I get the error? According to the jQuery documentation, getScript shouldn't execute the callback until the script has been loaded and executed.
Is there any way I can use this framework to include jQuery UI, or do I need to use a script loader like RequireJS in order to load everything, enforce dependencies, etc.?
By calling .noConflict(1), the same as .noConflict(true), you're deleting jQuery, just remove the 1. The true argument to .noConflict() tells jQuery to remove not only $, but window.jQuery, which jQuery UI is trying to use afterwards, when it loads.
You can test it here, see there are no errors in the console.
Related
I am in the middle of creating a small script to 'help' me with my homework. It uses jQuery. The script (so far) is below:
var s = document.createElement('script');
document.body.appendChild(s);
s.src = "http://code.jquery.com/jquery-latest.js"; // Include jQuery
var tmp_1 = document.embeds[0].GetVariable("q1answers"); // Read raw values
var answers_1 = tmp_1.split(","); // Explode into array
answers_1.splice(0,1); // Remove first element (always 0)
var tmp_2 = document.embeds[0].GetVariable("q2answers");
var answers_2 = tmp_2.split(",");
answers_2.splice(0,1);
answers_1.push("LINE_BREAK");
var answers = answers_1.concat(answers_2);
$("body").append("<div id='answers-wrap'></div>");
$("#answers-wrap").css("position", "fixed");
$("#answers-wrap").css("background", "none");
The problem arises when it gets to the 3rd-to-last line. Chrome console claims that Object #<HTMLBodyElement> has no method 'append', however if I extract that line and put it into the console on its own, it works fine. I can use a different method to insert HTML, but I would like to know what isn't working with this one.
Thanks in advance!
Since you're adding the jQuery script dynamically, it's loaded asynchronously, so it's probably not loaded yet when you're trying to use it. Use an onload handler for the dynamic script block:
var s = document.createElement('script');
document.body.appendChild(s);
s.src = "http://code.jquery.com/jquery-latest.js"; // Include jQuery
s.onload = function() {
$("body").append("<div id='answers-wrap'></div>");
$("#answers-wrap").css("position", "fixed");
$("#answers-wrap").css("background", "none");
}
The error message you're getting also indicates that $ exists (another library, maybe?) and is not returning a jQuery object, so you'll probably have to use jQuery in "noConflit" mode, and use jQuery or a user-defined alias instead of $.
Just a guess, but may you are running the script before the browser has finished rendering the DOM?
Try wrapping the code in
window.onload = function(){
// ... your code here
};
in order to execute it onload.
EDIT: changed code to reflect the feedback below, of course one cannot use jQuery's $ before jQuery is loaded, my fault.
I'm working with an external script (from http://segment.io) and I'm writting an AngularJS module to interact with it.
I am wondering how can I effectively test that their script is well loaded (except than running the real app).
Should I write an end2end test?
Thanks for your help!
// Service is a factory
service.load = function(apiKey) {
// Create an async script element for analytics.js.
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = ('https:' === document.location.protocol ? 'https://' : 'http://') +
'd2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/' + apiKey + '/analytics.js';
// Find the first script element on the page and insert our script next to it.
var firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
};
if the file leaves behind a global, you can simply look for window.whatever to see if it's loaded.
one very flexible cross-browser pattern i use is what i call a sentinal. You use a wrapper function to wait for the dependencies to arrive before executing the custom code.
for example, if i was dynamically injecting jQuery into the page, and i knew it was needed for something else dynamic:
(function waiter(){
if(!window.jQuery){ return setTimeout(waiter, 37); }
$("#myDiv").fadeOut();
}())
this pattern works independently of any script loader or browser-specific event, and doesn't require the dependancy file to be modified, great for waiting on CDN copies of libraries.
you can easily extent the notion to await several dependencies using modern Array methods:
(function waiter(){
if(![
window.jQuery, // core
window.jQuery.fn.effect, // jq ui
window.jQuery.fn.whizBang // jq ui plugin
].every(Boolean)){ return setTimeout(waiter, 37); }
$("#myDiv").whizBang();
}())
Someone on the AngularJS IRC channel points me to a working solution with Jasmine waitsFor block: github.com/pivotal/jasmine/wiki/Asynchronous-specs
Below a spec following spec:
it('should load the API when called with api key', inject(function ($window, segmentio) {
segmentio.load(apiKey);
waitsFor(function() {
return $window.analytics.initialized == true;
}, "Segmentio never loaded", 10000);
runs(function () {
expect($window.analytics).toBeDefined();
expect($window.analytics.initialized).toBeTruthy();
// Unload
$window.analytics = null;
});
}));
I'm building a widget that depends on jquery being loaded - I'm using the following to load it and my code:
(function () {
var jqueryVersion = (window.jQuery !== undefined) ? window.jQuery.fn.jquery.charAt(0) + window.jQuery.fn.jquery.charAt(2) : 0;
var jQuery;
/******** Called once jQuery has loaded ******/
function scriptLoadHandler() {
jQuery = window.jQuery.noConflict(true);
main();
}
/******** Load jQuery if not present *********/
if (parseInt(jqueryVersion) < 17) {
var script_tag = document.createElement('script');
script_tag.setAttribute("type", "text/javascript");
script_tag.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js");
script_tag.onload = scriptLoadHandler;
script_tag.onreadystatechange = function () { // Same thing but for IE
if (this.readyState === 'complete' || this.readyState === 'loaded') { scriptLoadHandler(); }
};
document.getElementsByTagName("head")[0].appendChild(script_tag);
} else {
jQuery = window.jQuery;
main();
}
function main() {
//my code goes in here
//leaving blank for now because I still get error
}
})();
I got most of that from here: http://alexmarandon.com/articles/web_widget_jquery/
I need jquery 1.7 or higher because I use the .on() method. When I run this on a page using jquery that is older than 1.7 I sometimes get errors in IE that lead me to believe there is a conflict with either the old version of jquery or some other js on the page. Some errors:
SCRIPT5007: Unable to get property 'forceInt' of undefined or null reference
SCRIPT5007: Object expected
SCRIPT5007: Unable to get property 'easing' of undefined or null reference
SCRIPT438: Object doesn't support property or method 'on'
These errors go away if I change
jQuery = window.jQuery.noConflict(true);
to
jQuery = window.jQuery.noConflict();
Am I doing something wrong?
Your problem might be caused by the fact that the 'handler' is called twice.
http://msdn.microsoft.com/en-us/library/ie/hh180173(v=vs.85).aspx
Also, taken from http://api.jquery.com/jQuery.noConflict/
"If necessary, you can free up the jQuery name as well by passing true
as an argument to the method. This is rarely necessary, and if you
must do this (for example, if you need to use multiple versions of the
jQuery library on the same page), you need to consider that most
plug-ins rely on the presence of the jQuery variable and may not
operate correctly in this situation."
I'm making a cdn for a bunch of scripts I've written for work. I used to have to distribute them and each person get the file and install it on their computer, but I'm moving them to an amazon cloudfront/s3 instance.
I'll be using this to inject the bookmarklet: http://allben.net/post/2010/01/30/CSS-JavaScript-Injection-Bookmarklets
However, the old way I was doing this I used the jquery bookmarklet generator. This is different than that and I am unaware how to include jquery should I want to use it.
Here is an example script:
javascript:(function(){var%20s=document.createElement('script');s.setAttribute('src','cdn.domain.com/scripts/somescript.js');document.getElementsByTagName('body')[0].appendChild(s);})();
That goes in a bookmark.
The script:
alert($(somecontainer).size());
Obviously this doesn't work because the bookmarklet is no longer injecting jquery. So, what is the best way to go about this?
The problem you are facing, I guess, is that the jQuery bookmarklet generator does not make $ available within the page. It keeps the jQuery variable isolated within a function and then removes jQuery entirely from the page after running.
Below is a modified version of code from here: http://benalman.com/projects/run-jquery-code-bookmarklet/ which should work.
function (e, a, g, h, f, c, b, d) {
if (!(f = e.jQuery) || g > f.fn.jquery || h(f)) {
c = a.createElement("script");
c.type = "text/javascript";
c.src = "http://ajax.googleapis.com/ajax/libs/jquery/" + g + "/jquery.min.js";
c.onload = c.onreadystatechange = function () {
if (!b && (!(d = this.readyState) || d == "loaded" || d == "complete")) {
var s = document.createElement('script');
s.setAttribute('src', 'cdn.domain.com/scripts/somescript.js');
document.getElementsByTagName('body')[0].appendChild(s)
}
};
a.documentElement.childNodes[0].appendChild(c)
}
})(window, document, "1.3.2")
Keep in mind that this will replace any jQuery and $ variable on the page. If you need to run the bookmarklet on pages that use those variables already, use jQuery.noConflict(1). For example _jq = e.jQuery.noConflict(1) will let you use _jq instead of $ and will return the original $ and jQuery to their original values. Example:
alert(_jq(somecontainer).size());
If you want to use noConflict but also use $ in your .js code, wrap your code in a function and create a locally scoped $. Example:
(function(){
var $ = _jq; // this $ will not affect any $ that exists outside this function.
alert($(somecontainer).size());
})();
My problem is that I need to dynamically include a javascript file from another external javascript file. I'm trying to do it by using this function:
function addCustomScriptTag(url) {
var scriptTag=document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src=url;
var myElement = document.getElementsByTagName("head")[0];
myElement.appendChild(scriptTag);
}
The problem happens only in IE6 where trying to append to the head element causes an 'operation aborted' error.
Any help would be appreciated
It depends when you add it to the head DOM element. Operation aborted occurs in all versions of IE because you're trying to modify a DOM element via JavaScript before that DOM element has finished loading, http://support.microsoft.com/default.aspx/kb/927917.
If you need this script loaded right away, you could do an old school document.write to add the script tag, e.g.
<head>
<script>document.write('<script src='yourUrl.js'><\/scr'+'ipt>');</script>
</head>
Otherwise call your function in the body onload via plain old JavaScript or via a framework like jQuery a la document.ready.
Consider using a library like jQuery and then just use the equivalent (if not using jQuery) of getScript. This will handle cross-browser quirks and inconsistencies for the most part.
Append it to the body then. Javascript doesn't have to go exclusively in the <head> of your document.
I steal from the jQuery source:
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function(){
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true;
success();
complete();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
head.removeChild( script );
}
};
head.appendChild(script);
I think it's because IE6 doesn't support getElementsByTagName(), try replacing it with document.body.