I would like page.html to ajax-request the content of side.html and extract the content of two of its divs. But I cannot find the correct way to parse the response, despite everything I tried.
Here is side.html:
<!DOCTYPE html>
<html>
<head>
<title>Useless</title>
</head>
<body>
<div id="a">ContentA</div>
<div id="b">ContentB</div>
</body>
</html>
and here is page.html
<!DOCTYPE html>
<html>
<head>
<title>Useless</title>
<script type='text/javascript' src='jquery-1.9.0.min.js'></script>
</head>
<body>
Hello
<script type="text/javascript">
jQuery.ajax({
url: "side.html",
success: function(result) {
html = jQuery(result);
alert(html.find("div#a").attr("id"));
alert(html.find("div#a").html());
alert(html.find("div#a"));
},
});
</script>
</body>
</html>
When I access this page, I get no error, and the three alert()s yield undefined, undefined and [object Object]. What am I doing wrong? Example is live here.
You need to change this line:
html = jQuery(result);
To this:
html = jQuery('<div>').html(result);
And actually, even better you should declare this as a local variable:
var html = jQuery('<div>').html(result);
Explanation
When you do jQuery(result), jQuery pulls the children of the <body> element and returns a wrapper around those elements, as opposed to returning a jQuery wrapper for the <html> element, which I tend to agree would be pretty dumb.
In your case, the <body> of sidebar.html contains several elements and some text nodes. Therefore the jQuery object that is returned is a wrapper for those several elements and text nodes.
When you use .find(), it searches the descendants of the elements wrapped by the jQuery object that you call it on. In your case, the <div id="a"> is not one of these because it is actually one of the selected elements of the wrapper, and cannot be a descendant of itself.
By wrapping it in a <div> of your own, then you push those elements "down" a level. When you call .find() in my fixed code above, it looks for descendants of that <div> and therefore finds your <div id="a">.
Comment
If your <div id="a"> was not at the top level, i.e. an immediate child of the <body>, then your code would have worked. To me this is inconsistent and therefore incorrect behaviour. To solve this, jQuery should generate the container <div> for you, when it is working its <body> content extraction magic.
Try this :
$.get(url,function(content) {
var content = $(content).find('div.contentWrapper').html();
...
}
Related
<!DOCTYPE html>
<html lang="en">
<head>
<title>JavaScript Example</title>
<script>
function displayString() {
return "<h1>Main Heading</h1>"
}
displayString();
document.write("Execute during page load from the head<br>");
</script>
</head>
<body>
<script>
document.write("Execute during page load from the body<br>");
</script>
</body>
</html>
So this is my problem. No matter where I put the displayString(), the h1 just never seems to show up on the browser. Can anybody please help me see where I am wrong? I am new to JavaScript. Oh, and what I am trying to do is to call the function.
You need to write the returned String to the document:
<!DOCTYPE html>
<html lang="en">
<head>
<title>JavaScript Example</title>
<script>
function displayString() {
return "<h1>Main Heading</h1>"
}
document.write(displayString());
document.write("Execute during page load from the head<br>");
</script>
</head>
<body>
<script>
document.write("Execute during page load from the body<br>");
</script>
</body>
</html>
No matter where I put the displayString(), the h1 just never seems to
show up on the browser.
If you wish to add a new element to a document, several approaches are available:
document.write (effectively deprecated)
.innerHTML (sometimes useful, but can be slow)
DOM API - recommended approach
The recommended approach is to use the DOM API.
DOM stands for Document Object Model. Essentially it's the markup of your document represented as a tree-like structure of nodes. There are many DOM API functions which allow you to:
add
remove
append
prepend
insert
update
new DOM nodes.
Any DOM node may be added, removed or updated, including:
parent elements
child elements
sibling elements
element attributes
ids, classNames, classLists
custom data-* attributes
text nodes
Here is an example:
function displayMainHeading () {
let mainHeading = document.createElement('h1');
mainHeading.textContent = 'Main Heading';
document.body.prepend(mainHeading);
}
displayMainHeading();
<p>Paragraph 1</p>
<p>Paragraph 2</p>
Further Reading
This is a good primer to get you started:
A Beginners Guide To DOM Manipulation by Iqra Masroor
I got an html page through an AJAX request
$.ajax({
async: true,
method: 'GET',
url: linkPage,
// cache: true,
success: function (data) {
console.log(data);
}
});
The data format I get like this:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body id="sustainable" class='sustainable'>
<div id="wrap">
<main class="temp>
<section class="sec01">
...
</section>
</main>
</div>
</body>
</html>
Now I want to get the body id and class (which is "sustainable") via this code:
$(data).find('body').attr('class');
But I have no idea why I can't get that, it returns undefiend. But when I get html content or class of <main> by this code:
$(data).find('main').attr('class');
$(data).find('main').html();
It returns exactly what I want. Can anybody explain me why?
I've tried some solutions so far by create a virtual DOM like this, and I can select <body> and <main> from AJAX data as I want: Cannot get body element from ajax response
But I still wonder why I can't select <body> class and html as first case?
It looks like, when given a string like that, jQuery will only save the contents of the body into its collection:
const data = `<!DOCTYPE html>
<html>
<head>
...
</head>
<body id="sustainable" class='sustainable'>
<div id="wrap">
<main class="temp>
<section class="sec01">
...
</section>
</main>
</div>
</body>
</html>`;
console.log($(data)[0]);
console.log($(data)[1]);
console.log($(data)[2]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
(Check your browser console. It's selecting the text nodes around #wrap, and #wrap itself, but not the <head> or <body>)
You could use DOMParser instead, which will try to turn the whole string into a document, without trying to leave things out:
const data = `<!DOCTYPE html>
<html>
<head>
...
</head>
<body id="sustainable" class='sustainable'>
<div id="wrap">
<main class="temp>
<section class="sec01">
...
</section>
</main>
</div>
</body>
</html>`;
const doc = new DOMParser().parseFromString(data, 'text/html');
console.log(doc.body.className);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Another benefit of using DOMParser is that, unlike jQuery, it won't execute possibly-unsafe code in the HTML string:
const data = `<!DOCTYPE html>
<html>
<head>
...
</head>
<body id="sustainable" class='sustainable'>
<img src onerror="alert('evil')">
</body>
</html>`;
$(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
jQuery version, unsafe
const data = `<!DOCTYPE html>
<html>
<head>
...
</head>
<body id="sustainable" class='sustainable'>
<img src onerror="alert('evil')">
</body>
</html>`;
const doc = new DOMParser().parseFromString(data, 'text/html');
console.log(doc.body.className);
DOMParser version, safe
The reason it doesn't work the way you tried is explained in the jQuery documentation:
If the HTML is more complex than a single tag without attributes, as it is in the above example, the actual creation of the elements is handled by the browser's .innerHTML mechanism. In most cases, jQuery creates a new <div> element and sets the innerHTML property of the element to the HTML snippet that was passed in.
Since you can't have a <body> inside a <div>, the browser ignores the <body> tag.
The documentation goes on to say:
When passing in complex HTML, some browsers may not generate a DOM that exactly replicates the HTML source provided. As mentioned, jQuery uses the browser's .innerHTML property to parse the passed HTML and insert it into the current document. During this process, some browsers filter out certain elements such as <html>, <title>, or <head> elements. As a result, the elements inserted may not be representative of the original string passed.
The following jQuery Won't work:
$(data).find('sustainable');
as the divs are top level elements and data isn't an element but a string, to make it work you need to use .filter
$(data).filter('sustainable.wrap');
Using the code below, I am successfully getting the jQuery.load to pull the external element's html into the div ("tnc-import"), but according the console, the newElementHeight variable just returns the init information of the element (init [prevObject: init(1), context: document, selector: "div#tnc-import > div#tnc"]) instead of actually getting the true-height attribute of the external element.
The source document includes the text to be imported inside <div id="tnc">...</div>. The target document includes...
<div>
<iframe id="iframe" src="/bin/tnc-import" frameborder="0" scrolling="no"></iframe>
</div>`
The intermediary document doing the importing and formatting is...
<!DOCTYPE html>
<html>
<head>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2'></script>
</head>
<body>
<div id="tnc-import"></div>
</body>
<foot>
<script type='text/javascript'>
$("#tnc-import").load( "../terms-and-conditions #tnc" );
var newElementHeight = $("div#tnc-import > div#tnc").height(true);
console.log(newElementHeight);
$('#iframe', window.parent.document).width('100%');
$('#iframe', window.parent.document).height(newElementHeight);
</script>
</foot>
</html>
The reason why I'm trying this is that all attempts to get the height, clientHeight, offsetHeight, etc of the jQuery-loaded element always result in "0". I've literally spent more than 12 hours troubleshooting with Google on this one objective, but with no positive result. Perhaps there is something wrong with that script that I've just not noticed... Either way, neither method or any variation that I've tried has worked.
Here is one of the old method variants that I was trying...
<script type='text/javascript'>
$('#iframe', window.parent.document).width('100%');
$('#iframe', window.parent.document).height($("div").outerHeight() + 10);
</script>
According to the documentation of jQuery, .height() does not accept any arguments with Boolean parameters, try removing true from the function to receive height in newElementHeight element.
Reference :http://api.jquery.com/height/
This is actually two question. I was playing around with the .eq method in jquery and decided to test it out using jsfiddle. What puzzles me is that when I supply an index that is obviously out of bound it still returns me an obj instead of an index out of bound error.
console.log($("body").children("div").eq(2));
console.log($("body").children("div").eq(20));
So I did this using the .children method from jquery. Upon closer inspection if I specify a selector it gives me the correct children, but if I do not it also returns the title element which is outside of the body.
console.log($("body").children());
console.log($("body").children("div"));
Does anybody know why? Here is the jsfiddle
No Index out of bounds
This was just a choice of the jQuery developers. If a selector returns no results, it will be an empty jQuery collection rather than return an error.
$("body").children("div").eq(20).length === 0
<title> appears in <body>
jsfiddle.net automatically has <html>, <head>, and <body> elements. Everything in the HTML frame is already wrapped in <body>. The <body> you provide is ignored. I do find it odd that <title> ends up being acceptable there, though.
At least in the case of your jsfiddle, jsfiddle automatically injects everything you type in the html area into a body tag, this your example ends up rendering something like the following, so as you can see, title IS a child of body.
<html>
<head>
</head>
<body>
<title></title>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
I'm trying to write a javascript function that adds some DOM nodes to the document in the place it was called, like this:
...
<div>
<script type="text/javascript">
pushStuffToDOMHere(args);
</script>
</div>
...
i try to do it 'cleanly', without using node id property of the div, or innerHTML string manipulation. for that I need to know where in the document the script tag is located.
is there a way to do it?
Talking about cleanly, I don't think your approach is particularly clean. It is a much better idea to give the div a unique id and execute your javascript when the DocumentReady-event fires.
Do you have an overriding reason for doing it this way? If not the suggestion to use a unique id makes the most sense. And you can always use a library like jQuery to make this even easier for yourself.
However, the following quick test shows that if you use document.write() in the function then it writes the value into the place where the function was called from.
<html>
<head>
<script type="text/javascript">
function dosomething(arg){
document.write(arg);
}
</script>
</head>
<body>
<div>The first Div</div>
<div>The
<script type="text/javascript">
dosomething("Second");
</script>
Div
</div>
<div>The
<script type="text/javascript">
dosomething("Third");
</script>
Div
</div>
</body>
</html>
But, again the question, are you sure this is what you want to do?
Although I agree with n3rd and voted him up, I understand what you are saying that you have a specific challenge where you cannot add an id to the html divisions, unless by script.
So this would be my suggestion for inlining a script aware of its place in the DOM hierarchy, in that case:
Add an id to your script tag. (Yes, script tags can have ids, too.)
ex. <script id="specialagent" type="text/javascript">
Add one line to your inline script function that gets the script element by id.
ex. this.script = document.getElementById('specialagent');
...And another that gets the script element's parentNode.
ex. var targetEl = this.script.parentNode;
Consider restructuring your function to a self-executioning function, if you can.
Ideally it executes immediately, without the necessity for an 'onload' call.
see summary example, next.
SUMMARY EXAMPLE:
<script id="specialagent" type="text/javascript">
var callMe = function(arg1,arg2,arg3) {
this.script = document.getElementById('specialagent');
var targetEl = this.script.parentNode.nodeName=="DIV" && this.script.parentNode;
//...your node manipulation here...
}('arg1','arg2','arg3');
</script>
The following TEST code, when run, proves that the function has identified its place in the DOM, and, importantly, its parentNode. The test has division nodes with an id, only for the purpose of the test. They are not necessary for the function to identify them, other than for testing.
TEST CODE:
<html>
<head>
<title>Test In place node creation with JS</title>
</head>
<body>
<div id="one">
<h2>Child of one</h2>
<div id="two">
<h2>Child of two</h2>
<script id="specialagent" type="text/javascript">
var callMe = function(arg1,arg2,arg3) {
this.script = document.getElementById('specialagent');
var targetEl = this.script.parentNode;
/*BEGIN TEST*/
alert('this.script.id: ' + this.script.id);
alert('targetEl.nodeName: ' + targetEl.nodeName + '\ntargetEl.id: '+targetEl.id);
alert('targetEl.childNodes.length: ' + targetEl.childNodes.length);
var i = 0;
while (i < targetEl.childNodes.length) {
alert('targetEl.childNodes.'+i+'.nodeName = ' + targetEl.childNodes[i].nodeName);
++i;
}
/*END TEST - delete when done*/
//...rest of your code here...to manipulate nodes
}('arg1','arg2','etc');
</script>
</div>
</div>
</body>
</html>
Not really sure what your trying to achieve but this would pass the dom element to the function when clicked. You could then use jquery in the function to do what you wanted like so
...
<script type="text/javascript">
function pushStuffToDOMHere(element)
{
$(element).append("<p>Hello</p>"); // or whatever
}
</script>
<div onclick="pushStuffToDOMHere(this);">
</div>
...
my solution is a compbination of the (good) answers posted here:
as the function is called, it will document.write a div with a unique id.
then on document.onload that div's parent node can be easily located and appended new children.
I chose this approach because some unique restrictions: I'm not allowed to touch the HTML code other than adding script elements. really, ask my boss...
another approach that later came to mind:
function whereMI(node){
return (node.nodeName=='SCRIPT')? node : whereMI(node.lastChild);
}
var scriptNode = whereMI(document);
although, this should fail when things like fireBug append themselves as the last element in the HTML node before document is done loading.