I have a question about browser behavior when handling the elements inside of a div container. I have an Iframe that I use to make requests to the server to run PHP files, while the parent page remains dynamic. Requests made by the parent page to the child frame determines what eventually happens to certain elements on the parent page when the server returns the results to the child frame. I've tested everything, and it all works fine, except for one noticeable glitch. If someone happens to call a function on the main page that tries to read an element inside of a div container while the child frame is making a change to any element inside the same div container, the program gives a null error. When I check the elements of the div container with the debugger, all of the elements (14 in total) inside that specific div container are gone. I'm sure I can solve the problem by querying a random element first to see if it exists, and if not, put a call back function a second later once the child frame finishes with the parent page. But I'm curious as to why all elements inside that specific div container are gone at that exact moment in the first place, since the child frame only manipulates 5 of the elements inside that div, and the parent request is looking for a completely different element that is not being manipulated by the child frame. Am I correct to assume that making changes to an element inside of a div container forces a rewrite of all the elements inside that div? All other div container elements in the body of the document remain intact, just the one div that's being changed. This is not the exact code (too much to post) but an example.
// Parent
document.getElementById("heading").innerHTML = "Where did you go?";
// Child Frame
parent.document.getElementById("pic").src = "anotherpic.jpg";
<div id ="something">
<P id = "heading">blah blah blah</p>
<img id = "pic" src = "picture.jpg">
<p id = "picname">Picture Name</p>
</div>
If the Child runs first, and the Parent tries to read an element while the child is still making changes to an element inside the div, all elements disappear when I look for them in the debugger. Any insights into this behavior are appreciated. Thanks. (Hopefully, this was not a stupid question. lol)
Please mind there is no such thing as "at the same time" here. Both, the innerHTML and getElementById work synchronously.
Even if you used the innerHTML improperly (which I know isn't the case) eg. overriding the parent component - there's no "in-between state" that you'd be able to access with the functions, to notice that there are no elements for a "moment". So I'd look elsewhere to find a reason behind it.
eg. the Iframe content modifies the parent twice every time, first of which makes it empty.
Have you tried using:
target.addEventListener(type, listener [, options]);
Instead of:
getElementById
?
Looks like something is getting overwritten.
Related
I'm writing a chrome extension, part of the functionality of which requires me to hide every html element at times with the exception of one div which I've created. (I hide everything and add the div to the current website in javascript) Because setting document.body.style.display = "none" will not allow any of the body's children to be seen, I need to add the child that I want to be seen somewhere else. (I also tried using style.visibility but for some reason that didn't hide certain HTML elements/backgrounds on certain pages.) My fix is to add the div to document.documentElement, outside of document.body. This fix actually works perfectly, but seems strange. Is there anything wrong with adding a new child to the elements? Am I doing something wrong?
EDIT: A few answers have used the children of document.body, so I thought I should note that my code has to run at document_start, and though I wait for document.body to load before executing, I can't wait for all of its children to load. Hence I can't use/store the children of document.body.
Also, I'm grateful for all the answers providing alternate solutions, they're quite useful. But out of curiosity, does anybody know if there's anything wrong with what I'm currently doing? Why is it working, if so?
The W3C specification of HTML document structure says that it consists of the <head> and <body> elements, and the <body> contains the content that's intended to be rendered. Nothing is stated about elements outside these two elements.
If it seems to work it's probably just an accident of implementation -- for instances, many implementations are forgiving of things like malformed HTML.
It's perfectly fine to append elements or text nodes directly to document.documentElement.
DOM is not HTML, it has its own specification, which - being an Object Model - is naturally quite permissive:
document.documentElement is an Element [spec]:
The document element of a document is the element whose parent is that document, if it exists, and null otherwise.
Elements are allowed to have these children [spec]:
Zero or more nodes each of which is Element, Text, ProcessingInstruction, or Comment.
Create a new DIV to hold the children of the body, and hide that.
var saveDiv = document.createElement("DIV");
saveDiv.id = "saveDiv";
saveDiv.style.display = "none";
Array.from(document.body.children).forEach(el => saveDiv.appendChild(el));
document.body.appendChild(saveDiv);
A potential solution:
const body = document.querySelector('body');
body.innerHTML = `<div class="my-div"></div><div class="content">${body.innerHTML}</div>`;
Now you have the body content all snug alongside your div, both of which you can hide/show. As pointed out below, I completely spaced that this will destroy your listeners. If you want to preserve listeners, try the following:
const body = document.querySelector('body');
const fragment = document.createDocumentFragment();
const hideBody = () => {
for (let el of [...body.children]) (fragment.append(el))
}
const showBody = () => {
for (let el of [...fragment.children]) (body.append(el))
}
Attach the appropriate one to whatever your event is. This will preserve all your listeners. If you need any functionality, DocumentFragment has the querySelector() and querySelectorAll() methods.
Another method is the modal method, where you just have a div that covers the whole page. Check out the Bootstrap modals, for example. If you initialize it with data-backdrop="static" and data-keyboard="false" then it won't disappear by clicking outside or hitting esc. The element can be selected with document.querySelector('.modal-backdrop'). Set the opacity to 1 and the background to white or whatever aesthetic you're going for.
The API docs for appendTo list the method being able to select an HTML string for a target.
However, there seems to be no use to this since the set still includes the original elements, and the HTML string seems not to have been added anywhere in the DOM nor do I see a circumstance where it could be available.
var b = button.appendTo('<div>').appendTo('body');
b is a button, and yet it is not wrapped in a div or anything.
You can see this at http://jsfiddle.net/0dgLe5sj/
Where would it be useful to append to a HTML string (which doesn't yet exist on the page)?
appendTo() returns the item being appended.
So your code is:
var btn = button.appendTo('<div>');
btn.appendTo('body');
As you can see, you move it inside a div, then immediately move it inside the body. So you when you look at it at the end, it's inside the body.
Perhaps you meant:
var b = button.appendTo($('<div>').appendTo('body'));
which will append a div to the body and then append the btn to that div.
Updated fiddle: http://jsfiddle.net/0dgLe5sj/8/
or, if you wanted to add to the div first:
var b = button.appendTo("<div>");
b.parent().appendTo("body")
but if you combine it into a single line, you can't get the button back into the variable using .appendTo as you're adding the div to the body so you're going to get the div or the body back.
To address the 'where would this be useful part':
Being able to create detached DOM elements is extremely useful for parsing HTML strings and can also be used to 'batch' up some changes without forcing page redraws between.
Moving one button to a detached div and the back to the body doesn't have a lot of point, but it proves the principles.
I want to have a button which toggles element in the document.
I don't want to use class display: none if/else statement. Instead, I want to appendChild if it doesn't exist and if it exists, then I want to removeChild.
There is an idea of what I want to achieve, but I have some problem there. The element is shown, but on next click, it is not removed, instead, I get multiple copies of it. (I think so). Please, no jQuery. Vanilla JavaScript only. Don't know is it important, but my-element is HTML <template>.
<body>
<button id="my-button">Toggle</button>
<template id="my-element">
<div>
Some content
</div>
</template>
<script>
let element = document.getElementById('my-element');
let content = element.content;
function toggle () {
if (document.body.contains(content)) {
document.body.removeChild(content);
} else {
document.body.appendChild(content);
}
}
let button = document.getElementById('my-button');
button.addEventListener('click', toggle, false);
</script>
</body>
You should understand that template exists outside of the loaded DOM, so no matter where you physically locate it in the code really won't make any difference later. Also, understand that when you want to access content of a template, you use .content, but after that content is injected into the DOM, it's not template.content anymore, it's part of the DOM.
So, you can't search the document for template .content because, after it gets inserted, it won't be template content in your document, it will be actual DOM content. You'll need some way of identifying it and a class is the simplest way.
Also, the documentation on templates says that you bring template content into the document with document.importNode, which you aren't using.
Next, always remember that .removeChild does what its name implies, it removes child elements. document.body.removeChild() can therefore only remove children of the body element, so you need to remember this. Your code may be OK for finding the imported node as a child of body, but that may not always be the case depending on where you inserted it. The code below dynamically locates the imported content's parent node and will always remove it, regardless of where it winds up being located in the DOM.
Lastly, and this is very important, although you have indicated that you don't want to hide/show the element and would rather append it and remove it, doing so is very expensive in terms of performance. Every time you add or remove an element from the DOM, the entire DOM has to be rebuilt and the node(s) you remove don't necessarily get removed from memory even though they are not present in the DOM anymore. So, do this at your own risk. It's actually much better (from a performance standpoint) to simply hide/show content.
let element = document.getElementById('my-element');
function toggle () {
// Attempt to reference the element in the document, not the template content
var imported = document.querySelector(".imported");
// Check for the element, not the template content
if (document.body.contains(imported)) {
// Element exists, call removeChild on its parent
imported.parentNode.removeChild(imported);
} else {
// Use .importNode to bring template content in:
document.body.appendChild(document.importNode(element.content, true));
}
}
document.getElementById('my-button').addEventListener('click', toggle);
<button id="my-button">Toggle</button>
<template id="my-element">
<div class="imported">Some content</div>
</template>
I need to determine the height of a DOM element using javascript - specifically, in my case, a div containing some text. Due to the way that HTML works, I can only reliably do this if the element is visible. The general purpose solution is to show the element, get it's height, and then hide it - simple enough in the single element case.
However, in the general case, the element in question may be a child of other elements that are hidden, thus preventing the above solution from working - calling jQuery's show() function on the element in question doesn't actually cause it to be shown due to the hidden parent, so you still can't get the height.
How can I make an element visible long enough to get its height, taking into account any parent elements that need to be made visible to make it work?
Use case: I'm trying to write some code that I can apply to any table element, that creates some other elements whose height should match the height of the table header. I want to keep the code generic enough that it will work regardless of where in the DOM the table is located, or if it is currently visible. An alternate solution would be to have some javascript that simply adjusts the size of the created elements when the size of the table header changes (such as when it is shown), but conceptually that seems less efficient. Still, if it is easier, I will accept that as an answer.
Edit: To give an example, while keeping in mind I am going for a general solution that is not tied to this specific HTML layout, consider the following HTML:
<div style="display:none; line-height:22px; font-size:18px;">
...Some random text or other content...
<div id="desired_size">
I want to find the height of this div when visible
</div>
...Possibly some more content/other stuff...
</div>
The goal is to get the height of that inner div, but I can't do that because it isn't displayed - it is hidden due to the parent div being hidden. If all I know about the HTML is the desired_size div, how would I go about making it visible enough to get the height? Granted, this example is trivial, but I'm trying to generalize it.
Edit 2: One suggestion was to clone the element and move it to somewhere that is visible. This works, but with a caveat: any inherited CSS properties that would affect the size are lost.
Edit 3: I'm trying to write a block of code that I can re-use in a variety of web pages, not just coding to a specific layout. As such, I can't make any assumptions about or changes to the parent HTML. The example above shows one case where this can cause difficulties.
Edit 4: As has been pointed out, it would be trivial to change the HTML such that the visual appearance is the same, but the issue doesn't exist. However, I am trying to find a solution that works with the HTML as written, regardless of how the HTML is written.
demo - http://jsbin.com/tosusanowu/edit?html,js,output
Assuming you know that desired_size div has always a parent that is hidden.
$(function(){
var desired_size = getDesiredSize('#desired_size');
});
function getDesiredSize(el) {
var $el = $(el), $parent = $el.parent(), desired_size = 0;
$parent.attr('style', 'opacity:0;position:absolute');
desired_size = $el.height();
$parent.attr('style', 'display:none');
return desired_size;
}
<div style="display:none;">
...Some random text or other content...
<div id="desired_size">
I want to find the height of this div when visible
</div>
...Possibly some more content/other stuff...
</div>
The following javascript/jQuery function should work in the general case where the HTML structure is unknown, as requested:
function getHeight(objectID){
var object=$('#'+objectID);
var nextObject=object;
var changedObjects=[];
var counter=0; //to prevent infinite looping
while(!object.is(':visible') && counter<100){
counter+=1;
var curObject=nextObject; //store a reference for use in loop
nextObject=curObject.parent();
var curStyle=curObject.css('display') //see if current object is hidden
if(curStyle!='none')
continue; //don't mess with non-hidden objects
//see if the display style is inline, or from a CSS file
var inlineStyles=curObject.attr("style");
if(typeof(inlineStyles)!=='undefined'){
inlineStyles.split(";").forEach(function(element){
var style = element.split(":");
if ($.trim(style[0]) === 'display') {
//Record the fact that the display properly is an inline style
curObject.data('floatinghead_inline_style',true);
return false; //break out of the forEach loop
}
});
}
curObject.show(); //if object is hidden, show it for now
visitedObjects.push(curObject); //save a reference so we can put it back later
}
var height=object.height(); //this should work properly, since object should now be visible
visitedObjects.forEach(function(item){
if(item.data('floatinghead_inline_style')===true)
item.hide();
else
item.css('display','');
})
}
The above code makes no assumptions about the HTML structure, particularly the depth of the object in question from the hidden parent element. Also, unlike the "clone item to a different, visible, location in the DOM" option, it should properly take into account any inherited attributes that affect the height.
Step 1
I've two screens one is parent and the other one is child.
On click of a button in the parent window the child popup will open.
Step 2
On click of a button in child i'm displaying the html(viewsource) of parent window in a textbox(.net) and holding in a hidden variable hdnSource too.
Step 3
I've 4 checkboxes in the child window.
If the checkbox is not checked, then that part of html should be removed.
eg: cbxPersonal, cbxProfessional
if cbxProfessional is unchecked I should remove divProfessional from html which is in hdnSource and display in the textbox
Can anyone help me to do the 3rd part of coding.
Since the html is in the variable, I'm not able to find the div with document.getElementById
Trying to futz about hacking bits out of HTML strings is going to be annoying and buggy. Instead, do the removal on the DOM Nodes, before you've turned them into an HTML string.
To avoid actually removing the real divs from the parent page's visible DOM, clone the nodes before operating on them. eg.
var copy= opener.document.body.cloneNode(true);
if (!document.getElementById('cbxPersonal').checked) {
var div= copy.getElementById('divPersonal');
div.parentNode.removeChild(div);
}
var html= copy.innerHTML;