I'm trying to get the contents of a div with a specific Id. However, if the div is nested inside a tag such as <template> it can't be found. I don't have control over the html output of the page, so I need to find a way to find this element no matter where its nested. It's my understanding that document.getElementById is supposed to be able to find an element with the given Id no matter where it is located. The javascript code is always located in script tags at the end of the body.
Below it is working as expected, nested within a div.
const str = document.getElementById("somethingsomething").innerText;
console.log(str);
<html>
<body>
<div>
<div hidden="" id="somethingsomething">
<b>dark side</b>
</div>
</div>
</body>
</html>
Here is the example that I would expect to work in the same manner but doesn't.
const str = document.getElementById("somethingsomething").innerText;
console.log(str);
<html>
<body>
<template>
<div hidden="" id="somethingsomething">
<b>dark side</b>
</div>
</template>
</body>
</html>
A <template>'s children don't get rendered - they aren't really on the page. As MDN says:
Think of a template as a content fragment that is being stored for subsequent use in the document. While the parser does process the contents of the <template> element while loading the page, it does so only to ensure that those contents are valid; the element's contents are not rendered, however.
It doesn't have children in the DOM.
console.log(document.querySelector('template').children.length);
<template>
<p>foo</p>
</template>
So, any descendant of a template won't be found with document.querySelector or any of the other methods to find elements that start with the document as the root.
I think you'll need to do two things here:
First check if the element exists on the page
If it doesn't, it may be in a template; iterate over the templates and their .content to see if what you're looking for is inside there
const getText = doc => doc.getElementById("somethingsomething")?.textContent;
const getStr = () => {
const textOnPage = getText(document);
if (textOnPage !== undefined) return textOnPage;
for (const template of document.querySelectorAll('template')) {
const textInTemplate = getText(template.content);
if (textInTemplate !== undefined) return textInTemplate;
}
};
const str = getStr();
console.log(str);
<html>
<body>
<template>
<div hidden="" id="somethingsomething">
<b>dark side</b>
</div>
</template>
</body>
</html>
A <template> element contains markup, but the contents are not part of the DOM. The content can be added to the DOM, one or more times, by JavaScript after the page has loaded.
The <template> tag keeps all content within it hidden from being rendered until it is actually needed. So you need to activate the template to get any elements defined within it otherwise they will return null.
You can do that by using document.importNode(externalNode, deep). Where externalNode references the template element itself and deep indicates if you want the entire subtree of the template being imported.
Once you've imported node then you need to append it to the document body for it to be rendered onto the page so getElementById can see it.
You can find more in depth examples in the following links:
document.getElementById doesn't work with <template> custom tag
https://www.html5rocks.com/en/tutorials/webcomponents/template/#toc-using
https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode
Related
I'm trying to achieve something similar to what JSRender does, but I'm not sure how to go about it. Consider the HTML "template" below:
<!DOCTYPE html>
<body>
<div class="content">
<div class="notifications">{{:notifications}} notifications</div>
<div class="something else">this is {{:something_else}} to show</div>
</div>
</body>
</html>
Supposed I have JSON data like so:
{"notifications": "3", "something_else": "some arbitrary data"}
How do I populated this data into the HTML page? The way JSRender does it seems to involve creating a separate template in a <script> tag, then populating the data into the template and finally copying the template into an empty container. Is there a way to avoid this template redefinition? I believe my HTML page can already act like a template as demonstrated above.
The Question: is it possible to display JSON data into a ready HTML page (such as above) with defined "data positions"? As part of the challenge, using $('.notifications').html()-related methods should be avoided since this would be cumbersome when handling large extensive data.
You can do that using top-level JsViews top-level data-linking - with an element such as a <span> for each insertion point.
<div class="content">
<div >this is <span data-link="something_else></span> to show</div>
...
Code:
$.link(true, ".content", data);
In addition, the data is data-bound to the HTML.
Here is a sample which shows the data-binding by letting you actually change a data property dynamically:
It also shows data-linking to the src and title attributes of an <img> tag. See here for more information about different data-link targets.
var data = {notifications: "3", something_else: "some arbitrary data",
imgData: {img1: {src: "http://www.jsviews.com//icons/android-chrome-36x36.png",
desc: "some image"}}};
$.link(true, ".content", data, {replace: true});
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsviews/0.9.90/jsviews.js"></script>
<div class="content">
<div ><span data-link="notifications"></span> notifications</div>
<div >this is <span data-link="something_else"></span> to show</div>
<img data-link="src{:imgData.img1.src} title{:imgData.img1.desc}"/>
<br/>Edit: <input data-link="something_else"/>
</div>
While BorisMoore's answer addresses the question adequately, I crafted a "hack" that also appears to work with the ability to support attributes on almost all elements, though I don't know to what extent it is reliable.
However, this requires one to change the data structure to also indicate the type of element and even the part of it (attribute) where the data is to be inserted. The data would need to look like so:
{"notifications": "span:|3", "something_else": "span:|some arbitrary data", "avatar":"img.alt:|A"}
Then in JQuery, one could do something like so:
$.each(data, function(key, value) {
value = value.split(":|");
var element = value[0];
value = value[1];
if(element.indexOf('.') == -1){
var content = $(element + ':contains("{{:'+key+'}}")').last().html().replace("{{:"+key+"}}", value);
$(element + ':contains("{{:'+key+'}}")').html(content);
}else{
element = element.split('.');
var attribute = element[1];
element = element[0];
$(element + '['+attribute+'="{{:'+key+'}}"]').last().attr(attribute, value);
}
});
EDIT: The main drawback of this method is that it unbinds all attached events when an elements property is modifed this way.
I need to get the object of second level html element in my page.
<html>
<div id="out">
jasoidjisa
<html>
<head>//This object
<div id="in">
hihisdhi
</div>
</head>
</html>
</div>
<script>
alert(document.getElementsByTagName('html'));
</script>
Help me to access this html element via js
HTML is a reserved tag name and so you can't use it in the manner in which you have used it here. Which particular value from above are you trying to get exactly ? It might make sense to use the <div> tag with a class or I'd to identify it instead. If you specify which value you are specifically looking to get I can write up a theoretical solution.
vsank7787 was totally correct. Maybe you could use iFrames instead of nested html inside a HTML document since html is a reserved keyword.
I am building a custom element with Polymer 1.0 and need to get the content inside the tag using javascipt without it being displayed on the page. I tried using the <content> tag and fetching it inside the javascript with a query selector. This works except it displays on the page even if I use a style="display: none;" attribute. How can I get the content inside the tag without it displaying on the page?
Not sure what exactly your use-case is, but how about simply wrapping your <content> tag with a hidden <div>?
<dom-module id="x-test">
<template>
<div hidden>
<content id="content"></content>
</div>
</template>
<script>
Polymer({
is: "x-test",
attached: function () {
// access distributed content like this
var myContent = Polymer.dom(this.$.content).getDistributedNodes();
}
});
<script>
</dom-module>
I am in the process of AJAX-ing a WordPress theme with a persistent music player. Wordpress uses dynamic classes on the <body> tag. The basic structure is as follows:
<html>
<head>
</head>
<body class="unique-class-1 unique-class-2 unique-class-3">
<div id="site-container">
<nav class="nav-primary">
Other Page 01
Other Page 02
</nav>
<div class="site-inner">
<p>Site Content Here</p>
</div>
</div>
<div id="music-player"></div>
</body>
</html>
I am currently successfully loading the content of /other-page-01/, /other-page-02/, etc, using load('/other-page-01/ #site-container'). However, I need to extract all <body> classes from the AJAX loaded page and replace the current page's <body> classes with them dynamically.
Note: Replacing the entire <body> element is not an option due to the persistent <div id="music-player">. I've tried jQuery.get(), but couldn't get it to work.
How do I extract the <body> classes from the AJAX requested page and replace the current page's <body> classes with them?
I am not very familiar with jQuery or Javascript, so the exact code would be extremely helpful. Any help is greatly appreciated.
Thanks,
Aaron
My typical solution would have been to tell you to throw the AJAX code in to a jQuery object and then read it out like normal:
$(ajaxResult).attr('class');
Interestingly though, it appears you can't do this with a <body> element.
I'd say the easiest solution (if you have control over the resulting HTML) is to just use some good ol' regex:
var matches = ajaxResult.match(/<body.*class=["']([^"']*)["'].*>/),
classes = matches && matches[1];
I say "if you have control over the resulting HTML", because this relies on the HTML being reasonably well formed.
The other method would involve parsing it as a DOMDocument and then extracting what you need, but this would take a lot more and is usually overkill in simple cases like this.
Convert the body within your returned html to a div with a specific ID, then target that id to get the classes of the body (which is now a div.)
modifiedAjaxResult = ajaxResult.replace(/<body/i,'<div id="re_body"')
.replace(/<\/body/i,'</div');
$(modifiedAjaxResult).filter("#re_body").attr("class");
Of course, if the body has an id, this will conflict with it, so an arbitrary data attribute might be less likely to break.
modifiedAjaxResult = ajaxResult.replace(/<body/i,'<div data-re-id="re_body"')
.replace(/<\/body/i,'</div');
$(modifiedAjaxResult).filter("[data-re-id=re_body]").attr("class");
http://jsfiddle.net/N68St/
Of course, to use this method, you'll have to switch to using $.get instead.
$.get("/other-page-01/",function(ajaxResult){
var modifiedAjaxResult = ajaxResult.replace(/<body/i,'<div data-re-id="re_body"')
.replace(/<\/body/i,'</div');
alert($(modifiedAjaxResult).filter("[data-re-id=re_body]").attr("class"));
// the following line replicates what `.load` was doing.
$(someElement).append( $("<div>").html(ajaxResult).find("#site-container") );
});
(I ask my question again after the first one was terribly formulated)
I face the following problem:
<div class="testA" id="test1"></div>
The above written element is predefined. I now load a xml tree via XMLHttpRequest & Co. delivering the following response:
<response>
<div class="colorSelector" id="0-0">
<div class="gbSelector" id="1-0">
<table style="none" id="2-0"></table>
</div>
</div>
</response>
I now append the first div using
request.responseXML.getElementsByTagName("response")[0]
.getElementsByTagName("div")[0]
into the predefined div
<div class="testA" id="test1">
The final document looks like this (checked using development tools):
<div class="testA" id="test1">
<div class="colorSelector" id="0-0">
<div class="gbSelector" id="1-0">
<table style="none" id="2-0"></table>
</div>
</div>
</div>
When I now try to get the element <div class="colorSelector" id="0-0"> using getElementById("0-0") I get the expected result.
But using getElementsByClassName("colorSelector") returns [].
Did I miss something? Is it probably a leftover of the fact the nodes were of type Element and not HTMLElement?
colorSelector is commented out. JavaScript only works within the DOM, and commented out portions aren't in the DOM.
Since you said that your getElementById("0-0") is successful, then clearly you don't actually have the nodes commented out.
I'm guessing you're doing:
document.getElementById("0-0").getElementsByClassName('colorSelector');
...which will not work because the element selected by ID does not have any descendants with that class.
Since you show HTML comments in the markup, I'd also wonder if you have some different element on the page with the ID "0-0". Take a look for that.
If your nodes are actually commented out, you'll need to first select the comment, and replace it with the markup contained inside:
var container = document.getElementById('test1'),
comment = container.firstChild;
while( comment && comment.nodeType !== 8 ) {
comment = comment.nextSibling;
}
if( comment ) {
container.innerHTML = comment.nodeValue;
}
...resulting in:
<div class="testA" id="test1">
<div class="colorSelector" id="0-0">
<div class="gbSelector" id="1-0">
<table style="none" id="2-0"></table>
</div>
</div>
</div>
...but there again, this doesn't seem likely since your getElementsById does work.
Here's a way to do it for Firefox, Opera, Chrome and Safari. Basically, you just do div.innerHTML = div.innerHTML to reinterpret its content as HTML, which will make that class attribute from the XML file be treated as an HTML class name.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script>
window.addEventListener("DOMContentLoaded", function() {
var div = document.getElementsByTagName("div")[0];
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var doc = this.responseXML;
div.appendChild(document.importNode(doc.getElementsByTagName("response")[0].getElementsByTagName("div")[0], true));
div.innerHTML = div.innerHTML;
alert(document.getElementsByClassName("colorSelector").length);
}
};
req.open("GET", "div.xml");
req.send();
}, false);
</script>
</head>
<body>
<div class="testA"></div>
</body>
</html>
Remove the this.status === 200 if you're testing locally in browsers that support xhr locally.
The importNode() function doesn't seem to work in IE (9 for example). I get a vague "interface not supported" error.
You could also do it this way:
var doc = this.responseXML;
var markup = (new XMLSerializer()).serializeToString(doc.getElementsByTagName("response")[0].getElementsByTagName("div")[0]);
div.innerHTML = markup;
as long as the markup is HTML-friendly as far as end tags for empty elements are concerned.
<!--<div class="colorSelector" id="0-0">
<div class="gbSelector" id="1-0">
<table style="none" id="2-0"></table>
</div>
</div>-->
The above code is gray for a reason: it's a comment. Comments aren't parsed by the browser at all and have no influence on the page whatsoever.
You'll have to parse the HTML, read the comments, and make a new DOM object with the contents of the comment.
Please describe what you are doing with the returned results. There is a significant difference between a nodeList and a node, nodeLists are LIVE.
So if you assign a nodeList returned by getElementsByClassName() (or similar) to a variable, this variable will change when you remove the nodes inside the nodeList from the DOM.
I now append the first div
How do you do that? What you have in the responseXML are XML elements, and not HTML elements.
You shouldn't be able to appendChild them into a non-XHTML HTML document;
actually you shouldn't be able to appendChild them into another document at all, you're supposed to use importNode to get elements from one document to another, otherwise you should get WRONG_DOCUMENT_ERR;
even if you managed to insert them into an HTML due to browser laxness, they're still XML elements and are not semantically HTML elements. Consequently there is nothing special about the class attributes; just having that name doesn't make the attribute actually represent a class. getElementsByClassName won't return elements just because they have attributes with the name class; they have to be elements whose language definition associates the attributes with the concept of classness (which in general means HTML, XHTML or SVG).
(The same should be true of the id attributes; just having an attribute called id doesn't make it conceptually an ID. So getElementById shouldn't be working. There is a way to associate arbitrary XML attributes with ID-ness, which you don't get with class-ness, by using an <!ATTLIST declaration in the doctype. Not usually worth bothering with though. Also xml:id is a special case, in implementations that support XML ID.)
You could potentially make it work if you were using a native-XHTML page by putting suitable xmlns attributes on the content to make it actual-XHTML and not just arbitrary-XML, and then using importNode. But in general this isn't worth it; it tends to be simpler to return HTML markup strings (typically in JSON), or raw XML data from which the client-side script can construct the HTML elements itself.