I've got a script started that I will need to offer my customers. The idea is that they drop a line of code in their web page and my script does what it needs for them. (in this case, all it is doing is loading content into a div; an image with a link)
so..the customer drops in a <div id="name_of_div"></div> and a script tag pointing to this:
if (!window.jQuery) {
var jq = document.createElement('script'); jq.type = 'text/javascript';
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
}
if ($('#name_of_div').length) {
$('#name_of_div').load('[content url]');
};
}
I'm pretty sure this roughly works how I'd like, however, I've been reading some things about "never do things globally" and something about putting this stuff inside a function so it doesn't encroach on the page's DOM.. do I have that right?
I'm thinking I can enclose in a function to guarantee my own scope...but what other steps should I take to make sure my script, when included into someone else's page, doesn't block or mess with anything already existing?
Secondly, is it better to use jquery.load() or a simple iframe?
thanks
Putting the code into a function is one step toward isolating it. Putting it into an anonymous and immediately-invoked function expression is the next step. Consider this:
(function () {
if (!window.jQuery) {
var jq = document.createElement('script'); jq.type = 'text/javascript';
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
}
if ($('#name_of_div').length) {
$('#name_of_div').load('[content url]');
}
})();
Nothing outside of that function can see its internals, so it doesn't pollute global scope. (Just be sure not to, for example, create values on the window object within that function.) The function is created and immediately executed (note the parentheses at the end), allowing it to just do its thing and be done.
If there are further interactions that would need to take place, such as exposing an API for your users, then you can return the API object from that function. Something like this:
var yourAPI = (function () {
var someAPIFunction = function () {
// some piece of functionality a user may call later
};
return {
someAPIFunction = someAPIFunction
};
})();
Now the user can create that yourAPI object and put it in whatever scope he would like. It exposes only what you want it to expose, and the rest of your internal work is simply performed and completed that one time when the page loads.
Related
first i want to check if jquery exists or not then move on to next function. But next function abc() is not being executed. Maybe somebody knows why!
window.onload = function () {
if (window.jQuery) {
localStorage.setItem("key", "abc");
} else {
script = document.createElement("script");
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js";
document.head.appendChild(script);
}
};
function abc() {
$(document).keypress(function (e) {
alert(e.key);
});
}
There are a few issues with that code. It sounds like you want to call abc when jQuery has been loaded, and for some reason you're allowing for the possibility it may or may not have been loaded prior to this code running. The issues I see are:
Setting a value in localStorage won't cause the function to be run; in fact, nothing in that code will cause abc to be run.
You're not declaring your script variable, which means the code is falling prey to what I call The Horror of Implicit Globals.
The load event on window happens really, really late in the page load process, after all images and other ancillary items are loaded. It's generally best to do your initialization sooner than that, by either using type="module" on your script element (if you're happy to use modules, which are handy) or by setting the defer attribute, or by putting the script at the very end of the body, just before the closing </body> tag (which is compatible with older browsers).
So here's what I might do instead, if I had to allow for this jQuery loaded/not loaded thing and I wasn't able to use modules (see comments):
// Use a wrapper if not using modules, to avoid creating unnecessary globals
(function() {
// Define your initialization function
function init() {
$(document).keypress(function (e) {
alert(e.key);
});
}
// If jQuery is loaded...
if (window.jQuery) {
// ...init
init();
} else {
// Otherwise, load it and init when it's loaded
var script = document.createElement("script");
// ^^^−−−−−−−−− prevents the horror of implicit globals
script.onload = init; // <=== Calls `init` when the script successfully loads
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js";
document.head.appendChild(script);
}
})();
I'm loading a HTML partial through ajax. The partial is attached to the DOM by using innerHTML on an existing node.
The partial contains a few script tags at the bottom, something like:
<script src="/Scripts/Griffin.Editor.js" type="text/javascript"></script>
<script type="text/javascript">
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var textParser = {
parse: function (text) {
return marked(text);
}
}
var prismHighlighter = {
highlight: function (blockElements, inlineElements) {
blockElements.forEach(function(item) {
Prism.highlightElement(item);
});
}
};
var editor = new Griffin.Editor('editor', textParser);
editor.syntaxHighlighter = prismHighlighter;
editor.preview();
</script>
However, as the script tags are not executed, I traverse the loaded partial to identify all script tags. I then create new script nodes in the DOM and attach them to the HEAD.
Something like:
var scripts = viewElem.getElementsByTagName('script');
for (let i = 0; i < len; i++) {
var scriptTag = scripts[0];
let node = document.createElement('script');
if (scriptTag.src && scriptTag.src.length > 0) {
node.src = scriptTag.src;
node.type = scriptTag.type;
} else {
node.text = scriptTag.text;
node.type = scriptTag.type;
//had eval here before (instead of attaching the embedded script to the HEAD).
}
document.head.appendChild(node);
scriptTag.parentNode.remove(scriptTag);
}
From what I understand the browser should load the referenced scripts before invoking the embedded script. That is however not the case for me, because the JS console complains about not finding the object defined in the dependency script.
If I use a timer and eval the embedded script in it everything works. But that seems as a ugly workaround and I really want to understand the mechanics behind the load behavior (i.e. why the scripts are not executed when the partial is attached to the DOM and why the referenced scripts are not loaded directly when I add the nodes to the HEAD tag).
From what I've encountered, you can't have immediately executing JavaScript inside an Ajax response. The reason being you are trying to execute JavaScript inside another JavaScript function. So the browser has no idea which executing context to use with this scenario.
I would recommend using deferred execution like you mentioned. Except, you need to let the browser interpret the Ajax response first. For example:
$.get('url', function (html) {
// html = "<script>function myTest () { console.log('here'); }</script>"
$('#result').html(html);
// Now that the DOM has had time to parse the response we can do:
myTest();
});
Notice it is the Ajax callback invoking the response function not the response immediately executing itself. Hope this helps.
I found a really great article explaining in depth how scripts are loaded into the browser.
In essence you can't be sure of execution order per default when you include scripts dynamically. To be sure of the order you need to do one of the following
a. Use async=false if supported
b. Use readyState (for ie<10)
c. Use defer attribute.
Try to use the mentioned features in that order to be sure.
However, even if you do all that you will still get screwed if you mix embedded scripts (code in in the script tag) with referenced scripts (using src attribute).
The problem is that the embedded scripts will run directly, even if the references script tags are added before. To solve that you need to push the embedded scripts into a queue and hook the load event for all referenced scripts.
Once all referenced scripts have toggled the load even you are free to invoke the embedded scripts (either by added the script tags to an element or by eval their text property).
Source: http://blog.gauffin.org/2015/07/embedded-script-tags-in-content-loaded-through-ajax-and-execute-the-script-tags-dynamically/
Let's say I've got my-file.js or some CDN file in different server that has
for (var i = 0; i < 1000; i ++) {
//something really long and hard to execute
}
//after this long execution
window.myObj = {
//initialization of some global object that I need
}
(I cannot change my-file.js...)
I want to add my-file.js asynchronously to page, and then, after it is loaded and EXECUTED I want to call some event like:
//when my my-file.js is loaded I finally use window.myObj
window.myObj.somefunc(); //yaaay
Is it possible? (cross browser for IE9+ is important for me, but any not critical)
Note:
In case file I need is on CDN or somewhere on different server - I need to remember about cross-domain policy, so I think loading content of file in ajax is not possible in such case.
Note 2:
http://www.chromestatus.com/features/5711871906676736 there is exacly what I need, but I bet it'll be few years before you can easly use it globally.
var script = document.createElement('script');
script.onload = function(){
// loaded
}
document.head.appendChild(script);
script.src = "path/to/script";
That's about the simplest example. And yes, the entire thing is async, hence it needed the onload handler.
You could also wrap it up in a function:
function getScript(src,callback){
var script = document.createElement('script');
script.onload = callback;
document.head.appendChild(script);
script.src = src;
}
getScript('path/to/js',function(){
//loaded
});
However, thinking out of the box, it sounds like you need dependency management. Use RequireJS. Should fit your needs.
jQuery getScript has a callback you can use to execute events after a file is loaded, or you can use script.onload as in Joseph's example:
http://api.jquery.com/jquery.getscript/
If you want to wait until a certain property is available (ie after some work has finished), you can create an interval and then clear it as soon as the property is found in window, ie:
var waitForObj = setInterval(function(){
if(window.myObj && window.myObj.somefunc){
clearInterval(waitForObj);
//Do some stuff you need this object for
}
}, 100);
I made a little script that makes my JS initialization in Partial Pages a bit easier.
It simply searches for all data-onload attributes, and executes the function defined there on-load.
There is also some other functionality. So is the data-onload called automatically when that specific partial view is loaded through an AJAX call.
Anyway, the syntax looks like this:
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-onload="partialInit">
function partialInit()
{
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
function otherFunctions()
{
// [...]
}
</script>
The only thing that I still would love to tackle is that right now I need to have a unique functionName for every partial page (otherwise the names will clash when they are both loaded).
So I have manageProfileInit(), editImageInit() etc.
Now is the OCD-devil in me wondering if there is some way to clean this up even further (without too many negative consequences). I would love to have the situation where I can have a simple clean functon init() in any scriptblocks, and have the same funcionality described above.
Of course in the current situation all the functions will override each other. But does anyone know a nice trick or workaround how this could work?
To summarize, I want to make a script that makes sure this will work on every Partial Page, without any clashes.
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-autoinit>
function init()
{
// this method is automatically called if the 'data-autoinit' is defined
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
</script>
When I do stuff like this, I call them features. Tags look like this:
<div data-feature="featureName"></div>
Then we get all of the tags that have the data-feature tag and loop over them, creating an array of features the page is going to use:
var featureObjects = $('[data-feature]');
var features = [];
if ( !featureObjects.length ) return false;
for ( var i = 0, j=featureObjects.length; i<j; i++ ) {
var feature = $(featureObjects[i]).data('features');
if ($.inArray(feature, features) == -1){
if (feature !== ""){
features.push(feature);
}
}
};
Now you'll want to load the JS file asychronously and call it's init function once it's loaded:
for (var i=0, j=features.length; i<j; i++){
var feature = features[i];
$.ajax({
url: "path/to/js/" + feature + ".js",
dataType: "script",
async: false,
success: function () {
App.features[feature].init();
},
error: function () {
throw new Error("Could not load script " + script);
}
});
}
The actual modules look like this and attach themselves to App.features for later use:
App.features.featureName = (function(feature){
// INIT FUNCTION
feature.init = function(){
};
return feature;
}(App.features.featureName || {}));
Just remember to make sure App.features is an array before doing all of this, hopefully somewhere towards the top of your main.js file. I keep other functionality such as helpers and utilities in the app, so I usually kick it off with something like:
var App = {
utilities: {},
features: {},
helpers: {},
constants: {}
};
Now you can just tag DOM objects with a data-feature tag and functionality will be added automatically and as-needed, keeping a nice tie between specific JavaScript and specific DOM, but without the need of having to keep the JS inline next to the actual DOM. It also makes those "blurbs" re-usable should they need to be used elsewhere, which lowers maintenance overhead when working on your application.
I'm trying to write a function which will append a javascript file to the DOM, but I am looking to have the rest of the code wait until the newly added JS file is completely loaded. Here is an example of what I am trying to accomplish, although this code doesn't work properly:
$(document).ready(function () {
var newScript = document.createElement("script");
newScript.setAttribute("type", "text/javascript");
newScript.src = "http://www.domain.com/script.js";
document.getElementsByTagName("body")[0].appendChild(newScript);
$(newScript).ready(function () { // This is the idea of what I'm trying to do, but this doesn't seem to actually wait until the new file is completely loaded.
foo.bar(); // foo is a new global variable which is declared in the newScript. This causes an error "foo is not defined".
// Here is where more code I wish to execute should continue.
});
});
As Musa mentioned in the comments above. Use jQuery's getScript and use the success callback function to trigger your other functions.
If you want more robust module loading functionality, then require.js works great in this capacity. Check out: http://requirejs.org/docs/why.html for an overview. I use require.js specifically for lazy-loading script modules.
Using jQuery (as you've tagged), it's extremely easy:
$.getScript('/script.js', function() {
foo.bar();
});
There's a few different ways to do this... via libraries or "by hand," so to speak, using only the browser APIs and straight JavaScript. For an answer on how to do this in JS only, look here for Stoyan's post to give you guidance. Basically, the gist of it is setting an event handler to both the script's unload and onreadystatechange properties and then check to see if the readyState is "loaded" or "complete" (if it exists at all). It would look something like this:
var done = false;
newScript.onload = newScript.onreadystatechange = function () {
if (!done && (!newScript.readyState || newScript.readyState === "loaded" || newScript.readyState === "complete)) {
done = true;
// run your actual code here
}
};