I'm implementing (through pure JavaScript) something like facebook's message loading. Meaning that if you scroll to the end of the page, new (HTML) content is loaded and added to the end of the page. I get this extra content through Ajax. But when I add this (HTML) to the page, it could mean the page has to load extra images. Is there a way I can figure out when the page has finished loading everything, so including all the new images?
You can add the load event (https://developer.mozilla.org/en-US/docs/Web/Events/load) to the appended elements:
function LOADER_FUNCTION(e) {
console.log("LOADED", e.target);
}
var element = document.querySelector('#parent');
for(i=0; i<element.childNodes.length; i++) {
element.childNodes[i].addEventListener('load', LOADER_FUNCTION);
}
This will call the LOADER_FUNCTION function when the content within that element is ready. Oddly this doesn't seem to work when attached to the parent.
Edit:
Here is a working example. Although this example is not using Ajax (mainly due to CORS issues) it should work under the same conditions. I've used innerHTML to set my DOM in order to demonstrate that this event is not limited to createElement -> appendChild:
(function(){
// This Code Runs after DOMContentLoaded
var element = document.getElementById('parent');
function LOADER_FUNCTION(e) {
document.querySelector('.status').innerText = ("LOADED " + e.target.tagName);
console.log("LOADED", e.target);
}
element.innerHTML = "<img src='http://i.imgur.com/OfSN9oH.jpg'>";
for(i=0; i<element.childNodes.length; i++){
// Attach new event handler
element.childNodes[i].addEventListener('load', LOADER_FUNCTION);
}
})()
<div class='status'></div>
<div id="parent"></div>
Edit 2:
For nested children in append/innerHTML use element.querySelectorAll('*');
https://jsfiddle.net/bckpL9k6/1/
Related
I'm looking for a way to read the source code of a page after it finished loading and inspect the code to see if it contains a specific text.
I found this reference but this only returns the text visible in the page and not the whole HTML code.
For instance, if the html source code is:
<html>
<header>
<header>
<body>
<p> This is a paragraph</a>
<body>
</html>
I want the script to print exactly the same thing.
Your help is appreciated.
I think you are over-complicating this problem. You don't need to "print" the page's HTML or "inspect the code".
In a comment, you said:
Check if page contains an iframe [and] Display a message if the iframe is found
You can just use DOM traversal functions to examine the DOM.
Try something like this:
window.addEventListener('load', function() {
if(document.getElementsByTagName('iframe').length){
console.log('Found an iframe');
}
});
Or with jQuery:
$(function() {
if($('iframe').length){
console.log('Found an iframe');
}
});
That's so simple, you can use this method to run a script after a page is fully loaded window.onload
function load(){
console.log(document.getElementsByTagName('html')[0].innerHTML);
}
window.onload = load;
For further explanations, check this post
Do like this, call this function on load
Fiddle Demo
function printBody() {
// store oiginal content
var originalContents = document.body.innerHTML;
// get the outer html of the document element
document.body.innerText = document.documentElement.outerHTML;
// call window.print if you want it on paper
window.print();
// or put it into an iframe
// var ifr = document.createElement('iframe');
// ifr.src = 'data:text/plain;charset=utf-8,' + encodeURI(document.documentElement.outerHTML);
// document.body.appendChild(iframe);
// a small delay is needed so window.print does not get the original
setTimeout(function(){
document.body.innerHTML = originalContents;
}, 2000);
}
Src: Print <div id=printarea></div> only?
Assuming that by 'print' you don't actually mean to transfer it to a paper copy, you can add some script like:
window.addEventListener('load', function() {
var content = document.documentElement.innerHTML,
pre = document.createElement('pre'),
body = document.body;
pre.innerText = content;
body.insertBefore(pre, body.firstChild);
});
What this does, step by step is:
window.addEventListener('load', function() > Wait for the page to be fully loaded and then execute the function
content = document.documentElement.innerHTML > store the actual page source in the content variable (document.documentElement refers to the 'root'-node, usually <html> in html documents
pre = document.createElement('pre') > create a new <pre>-element
body = document.body > create a reference to the <body> element
pre.innerText = content > assign the HTML-structure we've stored earlier as text to the <pre>-element
body.insertBefore(pre, body.firstChild) > put the <pre>-element (now with contents) before any other element in the body (usually on top of the page).
This leaves you with the entire source (as it was before creating the <pre>-element containing the source) on top of you page.
Edit: Added <iframe> workflow
It was not clear to me you actually wanted to target an <iframe>, so here's how to do that (using a naive approach, more on that further on):
window.addEventListener('load', function() {
var iframeList = document.getElementsByTagName('iframe'),
body = document.body,
content, pre, i;
for (i = 0; i < iframeList.length; ++i) {
content = iframeList[i].documentElement.innerHTML;
pre = document.createElement('pre');
pre.innerText = content;
body.insertBefore(pre, body.firstChild);
}
});
why is this approach naive?
There is a thing called Same-Origin-Policy in javascript, which prevents you from accessing <iframe>-content which if the contents do not originate from the same domain as the page containing the <iframe>.
There are several ways to take this into consideration, you could wrap the inside of the for-loop in try/catch-blocks, though I prefer to use a more subtle approach by not even considering <iframes> which do not match the Same-Origin-Policy.
In order to do this, you can swap the getElementsByTagName method with the querySelectorAll method (please note the compatibility table at the bottom of that page, see if it matches your requirements).
The querySelectorAll accepts a valid CSS selector and will return a NodeList containing all matching elements.
A simple selector to use would be
'iframe[src]:not([src^="//"]):not(src^="http")' which selects all iframe with a src attribute which does not start with either // or http
Disclaimer: I never use a <base>-tag (which changes all relative paths within the HTML) or refer to the current website using a path containing the domain, so the example CSS-selector does not consider these aberrations.
Can you use :not()
IE9 or better
Can you use document.querySelector(All)
IE8 or better (in order to use with :not(), IE9 or better)
hover/click the boxes above to show the spoiler
I need the same source i can find in the Elements window of DevTool console in my extension. I tried using the content script
var text = document.documentElement.innerHTML;
injected after catched the "complete" status from chrome.tabs.onUpdated.addListener, but i recived only the html code without the content dynamically created.
In particular i want my extension to find all "div" added dynamically.
Any help will be appreciated!
The complete event fires once the initial page content has been loaded. It has no relation to dynamically generated content, otherwise it would have to wait indefinitely, since more content may always be added later.
If you are interested in a specific element, you can use setTimeout to periodically poll for the element. Like so:
function getElement() {
return new Promise(function(res, rej) {
var interval = setInterval(function() {
var elm = document.getElementById('the-element-you-want');
if(elm){
clearInterval(interval);
res(elm);
}
}, 10);
});
}
Another option would be to use a MutationObserver to detect when the desired element(s) have been created.
I know the .load() function in jQuery, and how to use the callback function with that... but is there a way to check if an element has been loaded using an if statement?
My reasoning... I want to set an interval and check if an element and all it's children have been fully loaded.
var load-interval = setInterval(function(){
if($('#content').hasBeenLoaded){
//do stuff
}
}, 1000);
var $content = $('#content');
var intervalId = setInterval(function(){
if(!$content.is(':empty')){
//do stuff
clearInterval(intervalId);
}
}, 1000);
Just note that elements that contains only text nodes considered to be non empty by the spec:
During initial page load, your DOM elements are progressively created as the file is parsed, but image assets are typically loaded asynchronously as they're referenced.
So, as your page loads, the parser will come across the element <div id="mycontent">.
This element will be created immediately, and then its children, and then eventually the closing tag for that div will be found. At that point you could consider that the DOM itself is "loaded" for that element.
The simplest way to execute something then is to put it in a <script> tag immediately after that closing tag.
If you also want to wait for the image assets to load, then this is still the place to put it. You can look for all <img> tags within the previously loaded div, and register onload callbacks, e.g.
<div id="mycontent">
lots of DOM, including image tags
</div>
<script>
(function() {
var div = document.getElementById('mycontent'); // guaranteed to exist
var imgs = div.getElementsByTagName('img');
// put image load detection code here - exercise for the reader
})();
</script>
I'm using window.open to create an empty window and then populating it using jquery DOM manipulation methods. One thing I'd like to do is make sure the new window has all the same scripts available in it that are in the parent window. I'm also duplicating all the style sheets, plus any other data that's in the parent window HEAD section, so what I decided to do is this:
$(floatingMap.window.document.head).append(
$("<base>", {"href": location.href})).append(
$("head").children().clone()));
This first creates a <base> tag that ensures the relative URLs in the source document are interpreted correctly, then injects a copy of all the tags from the head section of the source document. I can inspect the injected objects in the new window using Chrome's DOM inspector, and everything looks OK, but the problem I'm having is that the scripts aren't loading. The stylesheets, on the other hand, are loading fine. Any ideas what I can do to make the scripts load correctly?
Update:
In a potentially related problem, I've found that the following code has unexpected results:
$(floatingMap.window.document.head).append(
$("<script>").text("window.opener.childWindowReady()"));
This causes the specified code to execute in the context of the parent window, not the child window. Any ideas why this would be the case?
This appears to be a jquery bug. Excluding the script tags from the jquery operation and then adding those using pure javascript works as expected:
var scripts = document.getElementsByTagName("script");
function loadScript (index)
{
if (index == scripts.length)
onChildWindowReady ();
else if (scripts[index].src)
{
console.log ("injecting: " + scripts[index].src);
var inject = document.createElement("script");
inject.src = scripts[index].src;
floatingMap.window.document.head.appendChild(inject);
inject.onload = function () { loadScript (index + 1); };
}
else
loadScript (index + 1);
}
loadScript (0);
In addition with document.writeln it is possible to add all contents dynamically and also execute them.
For example,
jQuery(document).ready(function(){
jQuery('body').append("jquery loaded");
var w = window.open();
var htmlContent = document.documentElement;
w.document.writeln("<html>"+htmlContent.innerHTML+"</html>");
w.document.close();
});
This demostrates opening a clone of the jsfiddle result window that will include jquery as well as script content within head.
http://jsfiddle.net/6Qks8/
I'm trying my best to learn unobtrusive JavaScript. The code below is an attempt to change an existing practice piece I had with the getArrays function called from within the HTML tag. This is the only version I could get to work. While it's nice that it works, I feel like there may be some unnecessary parts and don't fully understand why I need the last line of code in there (kind of new to the addEventListener stuff). I was working off of the Mozilla DOM reference example and know how to do this in jQuery already. Thanks!
JavaScript:
<script>
function getArrays() {
var ddlValArray = new Array();
var ddlTextArray = new Array();
var ddl = document.getElementById("ddlFlavors");
for (i=0; i<ddl.options.length; i++) {
ddlValArray[i] = ddl.options[i].value;
ddlTextArray[i] = ddl.options[i].text;
alert(ddlValArray[i] + ' ' + ddlTextArray[i]);
}
}
function showArrays() {
var link = document.getElementById("clickHandler");
link.addEventListener("click", getArrays, false);
}
document.addEventListener("DOMContentLoaded", showArrays, false);
</script>
HTML
Select Flavor:
<select id='ddlFlavors'>
<option value="1">Vanilla</option>
<option value="2">Chocolate</option>
<option value="3">Strawberry</option>
<option value="4">Neopolitan</option>
</select>
<br/><br/>
<a id="clickHandler" href="#">Show Flavors</a>
You don't if you structure your HTML / JS correctly.
Move the script tag to the bottom of the body just before the </body> tag and you no longer need to wait for DOMContentLoaded event. The html above it will be parsed prior to the JS being executed.
This CAN cause other caveats but for your example it will work.
<body>
Select Flavor:
<select id='ddlFlavors'>
<option value="1">Vanilla</option>
<option value="2">Chocolate</option>
<option value="3">Strawberry</option>
<option value="4">Neopolitan</option>
</select>
<br/><br/>
<a id="clickHandler" href="#">Show Flavors</a>
<script>
function getArrays() {
var ddlValArray = new Array();
var ddlTextArray = new Array();
var ddl = document.getElementById("ddlFlavors");
for (i=0; i<ddl.options.length; i++) {
ddlValArray[i] = ddl.options[i].value;
ddlTextArray[i] = ddl.options[i].text;
alert(ddlValArray[i] + ' ' + ddlTextArray[i]);
}
}
function showArrays() {
var link = document.getElementById("clickHandler");
link.addEventListener("click", getArrays, false);
}
showArrays();
</script>
</body>
There are many other benefits to placing the scripts at the bottom as well. Read more about them here.
One thing to note: Even though the tags for iframes and images are parsed, the image itself might not be finished downloading, so you will have to wait for that. Also iframe content might not be loaded yet either. However DOMContentLoaded doesn't wait for these either. I just thought it would be nice to note.
Event listeners are crucial to JS. Every time a user interacts with your page, you need to pick-up on that event, and respond to it.
Of course, in this snippet, your functions are assuming there is an element with a given id in the DOM, ready, set and waiting. This might not be the case, that's why you have to wait until JS receives the OK (the DOMReady event).
That's just one, very simple, example of why might use addEventListener. But IMO, event-listeners really come into their own when you start using Ajax calls to add new content to your page as you go along:
Suppose the DOM is ready, but some elements might, or might not be requested later on (depending on user input). In jQuery, you'd use things like this:
$('#foo').on('click',functionRef);
This doesn't require the foo element to be available when this code is run, but as soon as the element is added to the dom, the click events will be dealt with as you desire.
In non-jQ, you'll use addEventListener for that. Because of the event model (see quirksmode for details on propagation and bubbling), the listener needn't be attached to the element directly:
document.body.addEventListener('click',function(e)
{
if ((e.target || e.srcElemet).id === 'foo')
{
//function that deals with clicks on foo element
}
},false);//last param is for bubbling/propagating events
This way, you can bind all events for elements that might be added to the dom asynchronously, without having to check each response of each ajax call...
Let's take it one step further even: delegation. As you can see, in the snippet above, we're listening for all click events that occure somewhere inside the body. That's as near as makes no difference: all clicks, so you don't need to add 101 distinct click listeners for 101 elements, just the one. The only thing you need to do, is write the handler in such a way that it deals with each element accordingly:
document.body.addEventListener('click',function(e)
{
e = e || window.event;
var elem = e.target || e.srcElement;
switch (true)
{
case elem.id === 'foo':
//either code here or:
return fooCallback.apply(elem,[e]);//use another function as though it were the handler
case elem.id.className.match(/\bsomeClass\b/):
return someClassCallback.apply(elem,[e]);//~= jQuery's $('.someClass').on('click',function);
case elem.type === 'text':
//and so on
}
},false);
Pretty powerful stuff. There is, of course, a lot more too it, and there are downsides too (mainly X-browser stuff), but the main benefits are:
Handling events for elements that are dynamically added/removed
A single event listener that deals with all events for all elements makes your code more efficient, sometimes boosting performance by a lot. here's a nice example of when delegation is in order, sadly, it also shows the pains that X-browser development brings to the party...
This line of code: document.addEventListener("DOMContentLoaded", showArrays, false); ensures that your function showArrays() doesn't start executing before the DOM is ready. In other words, the function showArrays() will start executing after the (X)HTML document has been loaded and parsed.
I'd recommend that you put your JavaScript code in a separate file and use the following HTML code in the head section: <script type="text/javascript" src="yourfilepath.js"></script>. Then you could also use window.onload and onclick events instead of the document.addEventListener() method.
When you do that, you can put the code within the showArrays function outside its body and remove the function.
For example:
window.onload = function()
{
var link = document.getElementById("clickHandler");
link.onclick = getArrays;
}
That is considered a much better practice.
Full Example
In script.js file:
//This is the beginning of the script...
function getArrays() {
var ddlValArray = new Array();
var ddlTextArray = new Array();
var ddl = document.getElementById("ddlFlavors");
for (i=0; i<ddl.options.length; i++) {
ddlValArray[i] = ddl.options[i].value;
ddlTextArray[i] = ddl.options[i].text;
alert(ddlValArray[i] + ' ' + ddlTextArray[i]);
}
}
window.onload = function()
{
var link = document.getElementById("clickHandler");
link.onclick = getArrays;
}
Inside your HTML head:
<script type="text/javascript" src="script.js"></script>
It's better to write JavaScript separate from the HTML file in most cases, just like CSS. That way, you make your HTML code look tidier and more organised.