Losing a node reference? (javascript) - javascript

So I have this JS code :
var pElt = document.createElement("p");
var aElt = document.createElement("a");
aElt.textContent = "Text";
aElt.href = "#";
pElt.appendChild(aElt);
aElt.style.color = "red";
pElt.innerHTML += "<span> and more text</span>";
//aElt.style.color = "red";
document.getElementById("content").appendChild(pElt);
console.log(aElt); // always show the red attribute
There's probably some answer around here, but I cannot even describe the problem ; so I went with "losing node reference", even though it's not what happens here. (edit: in fact, that's what happens here, silly :))
So... Please try the code as it is. It works, the link is red, everyone is happy. Now comment the "aElt.style.color = "red";" line, then uncomment the other one, two lines below.
...
It does not work, the link still appear in black. What I thought is that the pointer linked to my node was either not valid anymore or the aElt was moved to a different memory address. But when I type "console.log(aElt)", it outputs the node correctly (well... I think it does), so I don't get why I can't access it after the .innerHTML change.
What interests me is what happens under the hood :)
Thanks!
index.html :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Question!</title>
</head>
<body>
<div id="content"></div>
<script src="script.js"></script>
</body>
</html>

When you overwrite the content of the <p> element by setting it's innerHTML, you're effectively turning the <a> back into HTML text, appending the <span> (as text), and then recreating new DOM nodes in the <p>. Your old reference still refers to the original <a> you created.
You could instead create that <span> the same way you created the <a>, and append that node to the <p> instead of overwriting .innerHTML.

Related

Trying to change an element's DOM's properties, but changes ignored after I use document.body.innerHTML +=. If order switched, things work fine

I had a hard time phrasing that question's title, but could not find anything more concise.
I want to attach some event oninput to an element (here an input field). But for some reason it didn't work. I narrowed the issue to the schematic MWE (complete MWE at the end).
addEvent();
document.body.innerHTML += "a";
addEvent() was simply a function which changed the oninput property of an input field. My issue was that addEvent() was ignored.
To make sure addEvent(); ran normally, I also modified the value of the input field, and its backgroundColor in the body of the function. Yet, when I ran the code, the oninput and value modifications were nowhere to be found, but the backgroundColor had been modified as per the function.
More mystifying to me, if I write
document.body.innerHTML += "a";
addEvent();
Things work as expected.
My question is in two parts:
how to I fix the code of addEvent(), so that no matter if I write document.body.innerHTML += "a" before or after, the result would be the same?
why does the backgroundColor run fine, while the rest seems to be ignored?
Here is my complete MWE below:
function addEvent() {
var fieldScore = document.getElementById("foo");
fieldScore.style.backgroundColor = "rgb(0,255,0)";
fieldScore.value = "a";
fieldScore.oninput = function () {
console.log("bar");
}
}
// document.body.innerHTML = buildForm();
addEvent();
document.body.innerHTML += "a";
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<form><input type="text" value="" name="foo" id="foo"></form>
<script type="text/javascript" src="mwe.js"></script>
</body>
</html>
Expected: same, but with a in the input field as well.
innerHTML += is almost always an anti-pattern. It makes the browser do this:
Loop through all the children and the children's children, etc., of the element on which you do it and build an HTML string.
Append text to that string.
Destroy all of those child elements (and their children, etc.), parse the HTML string, and replace them with new elements created as a result of parsing the string.
In that process, event handlers and any other non-HTML information on the elements that are destroyed is lost.
If you need to append to an element, use appendChild or insertAdjacentHTML. For instance:
document.body.appendChild(document.createTextNode("a"));
or
document.body.insertAdjacentHTML("beforeend", "a");

font,font size, and color change javascript

I am trying to make this code change the pre-written text font, font size, and color with an onclick button but am unable to make it work this is what i have so far and im stuck. anyone have any ideas?
<html>
<head>
<meta charset=utf-8 />
<title>Change Paragraph Text</title>
</head>
<body>
<p id ='text'>I am going to change this text, I hope.</p>
<div>
<button id="jschange" onclick="DoAll">Style</button>
</div>
<script type="text/javascript">
var style = 'text';
function DoAll() {
One(document.write.style.big());
Two(document.write.style.fontsize(7));
Three(document.write.style.fontcolor("red"));
}
</script>
</body>
</html>
Try this, it's a much simpler approach and won't make anyone's eyes bleed:
<button onclick="restyle()">Click me to see some results</button>
<p id="changeable">Text that will change.</p>
<script>
function restyle() {
var element = document.getElementById("changeable");
element.style.fontsize(7);
element.style.fontcolor("red");
element.innerHTML = "changed text";
}
</script>
I'm still learning Javascript too, so if there are any experts out there I'd love to hear what they think! :)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<p id="style-target">This is the element which will change.</p>
<button id="change-styles">Change Styles</button>
<script>
window.onload = function () {
var changeStyles = query("#change-styles");
var target = query("#style-target");
changeStyles.onclick = function () {
style(target, "fontSize", "18px");
style(target, "color", "blue");
style(target, "fontWeight", "bold");
};
};
function style (el, property, value) {
el.style[property] = value;
}
function query (selector) {
return document.querySelector(selector);
}
</script>
</body>
</html>
Have a look;
I've taken the liberty of adding the rest of the "required" HTML bits and pieces, there (especially the DOCTYPE). You don't need to know what it's there for, right now, but it will solve a lot of problems in the future, if you always include it at the top of every HTML page you write, if you intend real people to use that page (basically, it makes Internet Explorer < IE10 suck less).
I've broken this down into bits that are a little more sensible, in terms of real-world JavaScript.
In most programming languages, you want to break your code down into smaller bits, to make it easier to read and work with.
JavaScript isn't really much different.
I have broken apart apart the act of setting the style, into its own helper function
el.style.color = "purple"; // takes `el` and makes an el{color:purple} rule
The catch here is that any CSS "style" that has a hyphen ("font-size", "background-color") needs to use camelCase, when setting the value in JavaScript.
el.style.backgroundColor = "black";
I've created a helper function called style, which I then refer to inside of my window.onload code.
In this particular case, I'm not saving a lot, in terms of what I'm typing (in fact, I'm typing more), but what it would be saving me, in a more complex case, is the chance of missing something, in repeating myself, or in copy/pasting...
So by calling style(el, "fontWeight", "bold"); I don't have to remember how to set the style for old-browsers, versus new browsers, versus styles that have been set using JS earlier on, versus those that haven't (a topic for people concerned with professional websites that have to work on ancient browsers).
If you look inside of the definition for style that I wrote, you'll see that I'm calling el.style[property]; normally, when we know the name of the thing we're looking for, on an object, we use a dot to separate them person.name; //"Bob".
In circumstances where we might want to look for different properties, we use [<property-name>] to separate them.
var property = "age";
person[property]; // 32
Next, I am using document.querySelector( selector ); to find the elements that I want, by passing it a CSS-style selector.
document.querySelector works on practically all browsers made in the past 6 years.
I'm grabbing the element I want to change the styles of, and I'm grabbing the element I'm listening to (waiting for a user to click).
Then I'm setting the onclick of the button to a function which will fire off a bunch of changes that I specify.
In this case, the onclick will change some styles.
You don't typically want to use onclick or similar properties; normally, you want to use a process called event-registration or "listening", but that goes a little too far, for such a simple example.
For now, grab the elements, separate your "how you do it" implementation details from "when 'X' do 'Y'" runtime details, and watch magic happen.
Funny enough, this isn't much more code than the jQuery suggestion provided in another answer...
...but that's a whole, gigantic library that you'd have to load (if you were even allowed), just to select a thing and change its styles.
Also, by using the "jQuery solution" to common problems, you frequently learn bad habits, or alternatively, don't learn good habits which you would need to learn, had you not had the quick and easy solution in front of you.
jQuery, as used by most people, is particularly bad about reference-caching (or a lack thereof). If widely used jQuery patterns are employed on a production website, without thought put into them (especially if you're not using jQuery, but some other library like it), you can murder your website's performance.
Try this instead that js code:
var sstyle = 'text';
function DoAll() {
var elem = document.getEelementById(sstyle);
elem.style.fontSize = "7px";
elem.style.color= "red";
}
I think you should do it like this:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<p id ='text'>I am going to change this text, I hope.</p>
<div>
<button id="jschange" onclick="DoAll()">Style</button>
</div>
<script>
function DoAll() {
$('#text').css('font-size', '7').css('color', 'red');
}
</script>
</body>
</html>
You can try this:
<!DOCTYPE html>
<html>
<body>
<p>Click the button to display a string in a specified size.</p>
<button onclick="myFunction()">Try it</button>
<p id="demo"></p>
<script>
function myFunction() {
var str = "Hello World!";
var result = str.fontsize(7);
document.getElementById("demo").innerHTML = result;
}
</script>
</body>
</html>

jQuery "add" Only Evaluated When "appendTo" Called

this has been driving me crazy since yesterday afternoon. I am trying to concatenate two bodies of selected HTML using jQuery's "add" method. I am obviously missing something fundamental. Here's some sample code that illustrated the problem:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<p id="para1">This is a test.</p>
<p id="para2">This is also a test.</p>
<script>
var para1 = $("#para1").clone();
var para2 = $("#para2").clone();
var para3 = para1.add(para2);
alert("Joined para: " + para3.html());
para3.appendTo('body');
</script>
</body>
</html>
I need to do some more manipulation to "para3" before the append, but the alert above displays only the contents of "para1." However, the "appendTo appends the correct, "added" content of para1 and para2 (which subsequently appears on the page).
Any ideas what's going on here?
As per the $.add,
Create a new jQuery object with elements added to the set of matched elements.
Thus, after the add, $para3 represents a jQuery result set of two elements ~> [$para1, $para2]. Then, per $.html,
Get the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element.
So the HTML content of the first item in the jQuery result ($para1) is returned and subsequent elements (including $para2) are ignored. This behavior is consistent across jQuery "value reading" functions.
Reading $.appendTo will explain how it works differently from $.html.
A simple map and array-concat can be used to get the HTML of "all items in the result set":
$.map($para3, function (e) { return $(e).html() }).join("")
Array.prototype.map.call($para3, function (e) { return $(e).html() }).join("")
Or in this case, just:
$para1.html() + $para2.html()
Another approach would be to get the inner HTML of a parent Element, after the children have been added.

After cloning an element find the original element in the document

I clone my mainSection like this (I have to clone it because, there are new elements added to #main over AJAX, and I don't want to search through them):
$mainSection = $('#main').clone(true);
then i search through the cloned main section for an element:
var searchTermHtml = 'test';
$foundElement = $mainSection.filter(":contains('"+searchTermHtml+"')");
When I find the string 'test' in the #mainSection I want to get the original element from it in the $mainSection so I can scroll to it via:
var stop = $foundElementOriginal.offset().top;
window.scrollTo(0, stop);
The question is: how do I get the $foundElementOriginal?
Since you're changing the content of #main after cloning it, using structural things (where child elements are within their parents and such) won't be reliable.
You'll need to put markers of some kind on the elements in #main before cloning it, so you can use those markers later to relate the cloned elements you've found back to the original elements in #main. You could mark all elements by adding a data-* attribute to them, but with greater knowledge of the actual problem domain, I expect you can avoid being quite that profligate.
Here's a complete example: Live Copy
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<meta charset="utf-8">
<title>Example</title>
</head>
<body>
<div id="main">
<p>This is the main section</p>
<p>It has three paragraphs in it</p>
<p>We'll find the one with the number word in the previous paragraph after cloning and highlight that paragraph.</p>
</div>
<script>
(function() {
"use strict";
// Mark all elements within `#main` -- again, this may be
// overkill, better knowledge of the problem domain should
// let you narrow this down
$("#main *").each(function(index) {
this.setAttribute("data-original-position", String(index));
});
// Clone it -- be sure not to append this to the DOM
// anywhere, since it still has the `id` on it (and
// `id` values have to be unique within the DOM)
var $mainSection = $("#main").clone(true);
// Now add something to the real main
$("#main").prepend("<p>I'm a new first paragraph, I also have the word 'three' but I won't be found</p>");
// Find the paragraph with "three" in it, get its original
// position
var originalPos = $mainSection.find("*:contains(three)").attr("data-original-position");
// Now highlight it in the real #main
$("#main *[data-original-position=" + originalPos + "]").css("background-color", "yellow");
})();
</script>
</body>
</html>

IE7/8 + <time> tag + jQuery .clone() =?

I'll preface this by saying that I already solved this issue by fundamentally changing my approach. But in the process of solving it, I put together a test case that fascinates and vexes me.
I have a string returned from an AJAX call. The string contains HTML, most of which is useless. I want one element from the string (and all its children) inserted into the DOM. A simulation of this is:
<!DOCTYPE html>
<html>
<head>
<title>wtf?</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(document).ready(function() {
var markup = '<div class="junk"><div class="good"><time datetime="2013-03-29">March 29, 2013</time></div></div>',
output = $(markup).find('.good').clone().wrap('<div />').parent().html();
$('body').append(output);
});
</script>
</head>
<body></body>
</html>
I have a hosted copy of this file up here: http://alala.smitelli.com/temp/wtf_ie.html (won't be up forever).
What this should do is extract the .good div and the child <time> element, then insert them into the body. I do .wrap().parent() to extract the element I selected in addition to its children (see this question). The .clone() and .html() are contrivances that demonstrate the problem.
To the user, it should show today's date. And it works in Chrome, Firefox, IE9, etc.:
March 29, 2013
But in IE7 and 8, the displayed text is:
<:time datetime="2013-03-29">March 29, 2013
The opening < is shown, and a colon has somehow been inserted. The closing </time> tag looks unaffected, and is not shown escaped.
What's going on here? Is this some sort of bug, or an expected behavior? Where is the colon coming from?
EDIT: As far as suggestions to add document.createElement('time') or html5shiv, neither of those seemed to change the behavior.
Very much to my surprise, I find that if I remove jQuery from the equation in terms of actually parsing the markup, the problem goes away (on both IE7 and IE8), even without createElement('time') or a shim/shiv:
<!DOCTYPE html>
<html>
<head>
<title>wtf?</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(document).ready(function() {
var div, markup, output;
markup = '<div class="junk"><div class="good"><time datetime="2013-03-29">March 29, 2013</time></div></div>';
div = document.createElement('div');
div.innerHTML = markup;
output = $(div).find('.good').clone().wrap('<div />').parent().html();
$('body').append(output);
});
</script>
</head>
<body></body>
</html>
Live Copy | Source
The change there is that I just use the browser's own handling of innerHTML and a disconnected div to parse markup, rather than letting jQuery do it for me.
So I'd have to say this may be a problem with jQuery's handling of HTML fragments on older browsers without support for HTML5 elements. But that would be a significant claim, and significant claims require significant evidence...
But if I change these lines:
div = document.createElement('div');
div.innerHTML = markup;
output = $(div).find('.good').clone().wrap('<div />').parent().html();
to:
div = $(markup);
output = div.find('.good').clone().wrap('<div />').parent().html();
I get the problem (on both IE7 and IE8): Live Copy | Source
So it does start seeming like a jQuery issue...

Categories

Resources