Regarding this SO article and this SO article. I looked at these after noticing my web app does not fire in IE8...I don't care about backward compatibility at the moment but if it's one line of code why not? Anyways the other issue I was having is the onload event waits for all the content to load...so the user has no controls if he/she is waiting for images to download. This led me to believe that I should just use
<script type='text/javascript'>my_initialize_function()</script>
placed in the html where I want the page to initialize.
and say to bye to
window.onload = initializePage;
or
window.addEventListener('load',initialize_page);
or any similar.
My question is: Is this a valid approach to initializing one's page?
PS: I'm not using any libraries including JQuery...and obviously I would not try to initialize elements that have not been loaded yet.
No.
jQuery and similar libraries has an interesting approach. They simply capture different events in a crossbrowser manner while making it easier for the developer to use.
Let's consider the following code:
<script> document.getElementById('a').innerHTML='b'; </script>
<div id="a"></div>
It may or may not work depending on whether the browser runs javascript when it finds it or only after the whole document has been built.
On the other hand, if you used the proper event mechanism but the document has already been built, your code will not be called.
jQuery unites both paradigms to get a seamless and better system. Something like so:
var foo = {
// holds list of callbacks to call when document starts
stack: [],
// document not ready yet
ready: false,
// add callback to be called when document is ready
add: function(callback){
foo.stack.push(callback);
if(foo.ready)foo.fire(); // document is ready, call fire
},
// fire callbacks (document IS ready)
fire: function(){
for(var i=0; i<foo.stack.length; i++)
foo.stack[i]();
foo.stack = [];
foo.ready = true; // document IS ready
}
};
// bind to browser events
window.onload = foo.fire; // TODO: you should use actual events
// example of use
foo.add(function(){
alert('Document is ready!');
});
You could use jQuery's $(document).ready() event. It fires after the DOM is complete, but before all images are loaded.
Related
Suppose that get a DOM as reference Node, then create and insert a DOM as new Node.
If I create and insert the new Node as soon as possible, is DOMContentLoaded the best event option in the above situation?
The reference Node is not image, it's HTMLnknowElement such as a HTMLDivElement, HTMLSpanElement.
Here is an example code flow.
window.addEventListener("DOMContentLoaded", (event) => {
// Below is an example code as pseudo code.
// Get a reference DOM.
const referenceEl = document.querySelector(".foo .reference-dom");
// Create a new DOM.
const newEl = document.createElement(htmlTag);
// Insert the new DOM before reference DOM.
referenceEl.parentElement.insertBefore(newEl, referenceEl.nextElementSibling)
})
If I execute the code as soon as possible, is DOMContentLoaded the best event option in the above situation?
If you want the code to run as soon as possible, given what elements it depends on already existing in the DOM, the easiest way to do it would be to put your <script> tag just below the final element required for the script to run. For example, for the .reference-dom, you might do
<div class="reference-dom">
...
</div>
<script>
const referenceEl = document.querySelector(".foo .reference-dom");
// or
const referenceEl = document.currentScript.previousElementSibling;
// ... etc
If that's not an option, another avenue available to you would be to add a subtree MutationObserver to the document ahead of time (such as at the beginning of the <body>, and to watch the addedNodes to see if the node you're looking for appears - but that's expensive, overkill, and much more difficult to manage.
For very large pages (example), waiting for DOMContentLoaded could indeed take too long, in which case what you're doing would be a good idea - to add essential functionality to the app before that event fires, to ensure a good user experience. (Though, if what you're doing is just adding another tag to the HTML, a better approach would be to use a template engine or something server-side so that it serves the full HTML itself, rather than having to alter it client-side through JavaScript later)
I have a simple SVG loaded inside a object tag like the code below. On Safari, the load event is fired just once, when I load the first time the page after opening the browser. All the other times it doesn't. I'm using the load event to initialize some animations with GSAP, so I need to know when the SVG is fully loaded before being able to select the DOM nodes. A quick workaround that seems to work is by using setTimeout instead of attaching to the event, but it seems a bit akward as slower networks could not have load the object in the specified amount of time. I know this event is not really standardized, but I don't think I'm the first person that faced this problem. How would you solve it?
var myElement = document.getElementById('my-element').getElementsByTagName('object')[0];
myElement.addEventListener('load', function () {
var svgDocument = this.contentDocument;
var myNode = svgDocument.getElementById('my-node');
...
}
It sounds more like the problem is that, when the data is cached, the load event fires before you attached the handler.
What you can try is to reset the data attribute once you attached the event :
object.addEventListener('load', onload_handler);
// reset the data attribte so the load event fires again if it was cached
object.data = object.data;
I also ran into this problem while developing an Electron application. In my workflow I edit index.html and renderer.js in VSCode, and hit <Ctrl>+R to see the changes. I only restart the debugger to capture changes made to the main.js file.
I want to load an SVG that I can then manipulate from my application. Because the SVG is large I prefer to keep it in an external file that gets loaded from disk. To accomplish this, the HTML file index.html contains this declaration:
<object id="svgObj" type="image/svg+xml" data="images/file.svg"></object>
The application logic in renderer.js contains:
let svgDOM // global to access SVG DOM from other functions
const svgObj = document.getElementById('svgObj')
svgObj.onload = function () {
svgDOM = this.contentDocument
mySvgReady(this)
}
The problem is non-obvious because it appears intermittent: When the debugger/application first starts this works fine. But when reloading the application via <Ctrl>+R, the .contentDocument property is null.
After much investigation and hair-pulling, a few long-form notes about this include:
Using svgObj.addEventListener ('load', function() {...}) instead of
svgObj.onload makes no difference. Using addEventListener
is better because attempting to set another handler via 'onload'
will replace the current handler. Contrary to other Node.js
applications, you do not need to removeEventListener when the element
is removed from the DOM. Old versions of IE (pre-11) had problems but
this should now be considered safe (and doesn't apply to Electron anyway).
Usage of this.contentDocument is preferred. There is a nicer-looking
getSVGDocument() method that works, but this appears to be for backwards
compatibility with old Adobe tools, perhaps Flash. The DOM returned is the same.
The SVG DOM appears to be permanently cached once loaded as described by #Kaiido, except that I believe the event never fires. What's more, in Node.js, the SVG DOM remains cached in the same svgDOM variable it was loaded into. I don't understand this at all. My intuition suggests that the require('renderer.js') code in index.html has cached this in the module system somewhere, but changes to renderer.js do take effect so this can't be the whole answer.
Regardless, here is an alternate approach to capturing the SVG DOM in Electron's render process that is working for me:
let svgDOM // global to access from other functions
const svgObj = document.getElementById('svgObj')
svgObj.onload = function () {
if (svgDOM) return mySvgReady(this) // Done: it already loaded, somehow
if (!this.contentDocument) { // Event fired before DOM loaded
const oldDataUri = svgObj.data // Save the original "data" attribute
svgObj.data = '' // Force it to a different "data" value
// setImmediate() is too quick and this handler can get called many
// times as the data value bounces between '' and the actual SVG data.
// 50ms was chosen and seemed to work, and no other values were tested.
setTimeout (x => svgObj.data = oldDataUri, 50)
return;
}
svgDOM = this.contentDocument
mySvgReady(this)
}
Next, I was very disappointed to learn that the CSS rules loaded by index.html can't access the elements within the SVG DOM. There are a number of ways to inject the stylesheet into the SVG DOM programmatically, but I ended up changing my index.html to this format:
<svg id="svgObj" class="svgLoader" src="images/file.svg"></svg>
I then added this code to my DOM setup code in renderer.js to load the SVG directly into the document. If you are using a compressed SVG format I expect you will need to do the decompression yourself.
const fs = require ('fs') // This is Electron/Node. Browsers need XHR, etc.
document.addEventListener('DOMContentLoaded', function() {
...
document.querySelectorAll ('svg.svgLoader').forEach (el => {
const src = el.getAttribute ('src')
if (!src) throw "SVGLoader Element missing src"
const svgSrc = fs.readFileSync (src)
el.innerHTML = svgSrc
})
...
})
I don't necessarily love it, but this is the solution I'm going with because I can now change classes on the SVG object and my CSS rules apply to the elements within the SVG. For example, these rules from index.css can now be used to declaritively alter which parts of the SVG are displayed:
...
#svgObj.cssClassBad #groupBad,
#svgObj.cssClassGood #groupGood {
visibility: visible;
}
...
I want to attach a jQuery plugin function to an element on my site that exists only on one page. Currently, I'm using this conditional to prevent triggering the limiter function and throwing an error when there is no #advertiser_co_desc in view.
jQuery(document).ready(function($) {
var elem = $('#charNum');
if ($('#advertiser_co_desc').length) {
$('#advertiser_co_desc').limiter(180, elem);
}
});
On my website #advertiser_co_desc is present only on one page.
My solution does the job, but my qualm stems from the fact that the jQuery plugin code as well as the plugin function call presented above (they are both in the same file) get fetched by the browser and the condition is continuously evaluated regardless of whether a user ever gets to a page where #advertiser_co_desc exists.
Is the method I'm using optimal, or is there a better way to attach this particular JS only to the page where '#advertiser_co_desc` exists? Naturally, I wan to avoid adding my scripts in the same file with the PHP code.
Or you can wrap the plugin method as,
var _limiter = $.fn.limiter;
$.fn.limiter = function(limit, element) { // provide complete argmuments
if(this.length) {
_limiter.call(limit, element);
}
};
Make sure that plugin is loaded before this statements.
The best and optimal way to check existence of an element by jquery, is $('#advertiser_co_desc').length that you have used already. So no need to change your code.
I provide a JavaScript widget to several web sites, which they load asynchronously. My widget in turn needs to load a script provided by another party, outside my control.
There are several ways to check whether that script has successfully loaded. However, I also need to run different code if that script load has failed.
The obvious tools that don't work include:
I'm not willing to use JavaScript libraries, such as jQuery. I need a very small script to minimize my impact on the sites that use my widget.
I want to detect the failure as soon as possible, so using a timer to poll it is undesirable. I wouldn't mind using a timer as a last resort on old browsers, though.
I've found the <script> tag's onerror event to be unreliable in some major browsers. (It seemed to depend on which add-ons were installed.)
Anything involving document.write is right out. (Besides that method being intrinsically evil, my code is loaded asynchronously so document.write may do bad things to the page.)
I had a previous solution that involved loading the <script> in a new <iframe>. In that iframe, I set a <body onload=...> event handler that checked whether the <script onload=...> event had already fired. Because the <script> was part of the initial document, not injected asynchronously later, onload only fired after the network layer was done with the <script> tag.
However, now I need the script to load in the parent document; it can't be in an iframe any more. So I need a different way to trigger code as soon as the network layer has given up trying to fetch the script.
I read "Deep dive into the murky waters of script loading" in an attempt to work out what ordering guarantees I can count on across browsers.
If I understand the techniques documented there:
I need to place my failure-handling code in a separate .js file.
Then, on certain browsers I can ensure that my code runs only after the third-party script either has run or has failed. This requires browsers that support either:
Setting the <script async> attribute to false via the DOM,
or using <script onreadystatechange=...> on IE 6+.
Despite looking at the async support table, I can't tell whether I can rely on script ordering in enough browsers for this to be feasible.
So how can I reliably handle failure during loading of a script I don't control?
I believe I've solved the question I asked, though it turns out this doesn't solve the problem I actually had. Oh well. Here's my solution:
We want to run some code after the browser finishes attempting to load a third-party script, so we can check whether it loaded successfully. We accomplish that by constraining the load of a fallback script to happen only after the third-party script has either run or failed. The fallback script can then check whether the third-party script created the globals it was supposed to.
Cross-browser in-order script loading inspired by http://www.html5rocks.com/en/tutorials/speed/script-loading/.
var fallbackLoader = doc.createElement(script),
thirdPartyLoader = doc.createElement(script),
thirdPartySrc = '<URL to third party script>',
firstScript = doc.getElementsByTagName(script)[0];
// Doesn't matter when we fetch the fallback script, as long as
// it doesn't run early, so just set src once.
fallbackLoader.src = '<URL to fallback script>';
// IE starts fetching the fallback script here.
if('async' in firstScript) {
// Browser support for script.async:
// http://caniuse.com/#search=async
//
// By declaring both script tags non-async, we assert
// that they need to run in the order that they're added
// to the DOM.
fallbackLoader.async = thirdPartyLoader.async = false;
thirdPartyLoader.src = thirdPartySrc;
doc.head.appendChild(thirdPartyLoader);
doc.head.appendChild(fallbackLoader);
} else if(firstScript.readyState) {
// Use readyState for IE 6-9. (IE 10+ supports async.)
// This lets us fetch both scripts but refrain from
// running them until we know that the fetch attempt has
// finished for the first one.
thirdPartyLoader.onreadystatechange = function() {
if(thirdPartyLoader.readyState == 'loaded') {
thirdPartyLoader.onreadystatechange = null;
// The script-loading tutorial comments:
// "can't just appendChild, old IE bug
// if element isn't closed"
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
firstScript.parentNode.insertBefore(fallbackLoader, firstScript);
}
};
// Don't set src until we've attached the
// readystatechange handler, or we could miss the event.
thirdPartyLoader.src = thirdPartySrc;
} else {
// If the browser doesn't support async or readyState, we
// just won't worry about the case where script loading
// fails. This is <14% of browsers worldwide according to
// caniuse.com, and hopefully script loading will succeed
// often enough for them that this isn't a problem.
//
// If that isn't good enough, you might try setting an
// onerror listener in this case. That still may not work,
// but might get another small percentage of old browsers.
// See
// http://blog.lexspoon.org/2009/12/detecting-download-failures-with-script.html
thirdPartyLoader.src = thirdPartySrc;
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
}
Have you considered using the window's onerror handler? That will let you detect when most errors occur and you can take appropriate action then. As a fallback for any issues not caught this way you can also protect your own code with try/catch.
You should also check that the third-party script actually loaded:
<script type="text/javascript" onload="loaded=1" src="thirdparty.js"></script>
Then check if it loaded:
window.onload = function myLoadHandler() {
if (loaded == null) {
// The script doesn't exist or couldn't be loaded!
}
}
You can check which script caused the error using the url parameter.
window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
if (url == third_party_script_url) {
// Do alternate code
} else {
return false; // Do default error handling
}
}
Disclaimer: I have no experience with SharePoint2013.
I have problem - I must include/fire some javascript functions after the whole page has been loaded. I tried listening to DOMDocumentReady and window.load events, but sharepoint render the rest of page after those events.
My question is: what I should do to be able to run script after whole page with ajax is rendered.
Also I noticed that page navigation is based on hash (#) part. Obviously I must detect that moment also.
Any help or even link to right page in documentation would be great!
You are right, MANY things happen on page after $(document).ready(). 2013 does provide a few options.
1) Script on Demand: (load a js file then execute my code.)
function stuffThatRequiresSP_JS(){
//your code
}
SP.SOD.executeFunc("sp.js")
2) Delay until loaded (wait for a js file, then run)
function stuffToRunAfterSP_JS(){
//your code
}
ExecuteOrDelayUntilScriptLoaded(stuffToRunAfterSP_JS, "sp.js")
3) load after other stuff finishes
function runAfterEverythingElse(){
// your code
}
_spBodyOnLoadFunctionNames.push("runAfterEverythingElse");
Sources:
executeFunc: http://msdn.microsoft.com/en-us/library/ff409592(v=office.14).aspx
ExecuteOrDelayUntilScriptLoaded: http://msdn.microsoft.com/en-us/library/ff411788(v=office.14).aspx
Cant find a source on _spBodyOnLoadFunctionNames but I am using it in a few places.
good luck.
A documented event registration function that is roughly equivalent of _spBodyOnLoadFunctionNames is SP.SOD.executeOrDelayUntilEventNotified. Call this to receive notification of the "sp.bodyloaded" event:
SP.SOD.executeOrDelayUntilEventNotified(function () {
// executed when SP load completes
}, "sp.bodyloaded");
This handler actually fires slightly before the _spBodyOnLoadFunctionNames functions.
This reference is for SharePoint 2010, but the SOD utility is present in SharePoint 2013: http://msdn.microsoft.com/en-us/library/office/ff410354(v=office.14).aspx
There are different techniques used to provided our custom JavaScript code loaded before / in the middle / after OnLoad events in SharePoint:
ExecuteOrDelayUntilBodyLoaded.
Sys.Application.pageLoad.
document.ready Jquery.
_spBodyOnLoadFunctionNames.
_spBodyOnLoadFunction.
ExecuteOrDelayUntilScriptLoaded:sp.core.js.
SP.SOD.executeFunc: sp.js.
Sys.WebForms.PageRequestManager.PageLoaded
– ExecuteOrDelayUntilBodyLoaded function is always executed the first (but at this stage we can not access to SP methods). This could be usefull to execute our custom code at really earlier stage in the OnLoad process.
– There are two SharePoint onLoad functions _spBodyOnLoadFunctionNames and _spBodyOnLoadFunction. Always executed in the order. SO, if we want to execute some code after all functions included by us (or other devs) in _spBodyOnLoadFunctionNames, then is useful to use this one _spBodyOnLoadFunction, because is executed the last.
– ExecuteOrDelayUntilScriptLoaded:sp.core.js and SP.SOD.executeFunc: sp.js. are swapping the order of execution in a random way.
– If we want to execute some functions after all functions (SP, after load functions, Yammer, etc.) we can use this function to attach the OnLoad event –> Sys.WebForms.PageRequestManager.PageLoaded.
You can see the whole article explaining each type, pros and cons here: https://blog.josequinto.com/2015/06/16/custom-javascript-function-loaded-after-the-ui-has-loaded-in-sharepoint-2013/
Regards!
https://mhusseini.wordpress.com/2012/05/18/handle-clicks-on-calendar-items-in-sharepoint-2010-with-javascript/
The above link worked for me. He basically uses the ExecuteOrDelayUntilScriptLoaded to launch his calendar hook code after the SP.UI.ApplicationPages.Calendar.js loads.
Then, in the calendar hook he attaches his own function to the:
SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed function.
_spBodyOnLoadFunctionNames.push("LaunchColorCodeCalendarScriptOnReady");
function LaunchColorCodeCalendarScriptOnReady() {
ExecuteOrDelayUntilScriptLoaded(
MyCalendarHook,
"SP.UI.ApplicationPages.Calendar.js");
}
function MyCalendarHook() {
var _patchCalendar = function () {
//Do something to the items in the calendar here
//ColorCodeCalendar();
};
var _onItemsSucceed = SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed;
SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed = function ($p0, $p1) {
_onItemsSucceed.call(this, $p0, $p1);
_patchCalendar();
};
}
Here is nice article how to use the built-in SharePoint SOD (Script On Demand) functionality: http://www.ilovesharepoint.com/search/label/SOD
It works ok both for SP 2010 and 2013.
The idea of on demand script loading make really sense. SharePoint 2010 loads really a lot of JavaScripts - this takes time! So the idea is: first load the HTML and let it render by the browser so that the user is able to read the requested information as fast as possible. And in the second step load the behavior (the JavaScripts).