CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: What is .innerHTML substitute when writing directly to DOM elements?  (Read 161 times)

jmrker

  • Junior Member
  • *
  • Posts: 32
  • Karma: +1/-0
In the code below, I attempt to create an HTML tag directly in a DOM element.
I am unable to do so without using the .innerHTML.
There are two other attempts in the code below.
What is the acceptable .innerHTML substitute when wanting to add HTML tags to a document?

Code: [Select]
<!DOCTYPE html><html lang="en"><head><title> Test Page </title>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width,initial-scale=1.0, user-scalable=yes"/>
<!-- link rel="stylesheet" href="common.css" media="screen" -->

<style>
 mark { background: orange; font-size: 1.5em; }
 div { border: 1px solid blue; }
 .hr { margin: 3em; background-color: tan; border: 0; }
</style>
</head><body>
<div class="hr">&nbsp;</div>

<div id="A">
 &lt;div id='A'&gt; element with 'textContent' used for added content. <p>
</div>
<div class="hr">&nbsp;</div>

<div id="B">
 &lt;div id='B'&gt; element with 'appendChild' used for added content. <p>
</div>
<div class="hr">&nbsp;</div>

<div id="C">
 &lt;div id='C'&gt; element with 'innerHTML' used for added content. <p>
</div>
<div class="hr">&nbsp;</div>

<script>
const info = '<b>Want to <mark>highlight</mark> this word</b>';

document.getElementById('A').textContent += info;

var tag = document.getElementById('B');
    tag.append(info);

document.getElementById('C').innerHTML += info;

</script>

</body></html>

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +49/-1
    • CutCodeDown -- Minimalist Semantic Markup
Setting aside the "DIV with the class HR that's doing border or box-shadow's job" and other failings at writing HTML...

What you are missing is that each textnode and element MUST be defined as DOM nodes. TextNode and ElementNode respectively. Hence your string has to be broken into three parts and you must createElement BOTH tags. Likewise for now I say avoid "append" and use appendChild instead, since there is no append in IE. (though you COULD polyfill)

Also given that's flow text and not a proper name, you should be using STRONG, not B.

Something more like this:
Code: [Select]
(function(d) {

var
strong = d.createElement('strong'),
mark = d.createElement('mark');

mark.appendChild(d.createTextNode('highlight'));
strong.appendChild(d.createTextNode('Want to '));
strong.appendChild(mark);
strong.appendChild(d.createTextNode(' this word'));

d.getElementById('C').appendChild(strong);

})(document);

It's simpler with append:

Code: [Select]
(function(d) {

var
strong = d.createElement('strong'),
mark = d.createElement('mark');

mark.append('highlight');
strong.append('Want to ');
strong.append(mark);
strong.append(' this word');

d.getElementById('C').append(strong);

})(document);

but again beware that no version of IE even knows what append() is. so...

Either way you MUST break it into the two separate element nodes and separate text nodes, as the internal structure must be:

Code: [Select]
STRONG
    TextNode "Want to "
    MARK
        TextNode "highlight"
    TextNode " this word"

This is why many of us make our own helper functions -- like the "make" I use -- to handle this type of complex element creation.

Good rule of thumb, if you're trying to put together markup in a string, you're doing it all wrong.

Though... wait, these are escaped code listings... are you doing this to preserve the markup, but you ALSO want the mark to be marked? If so that's more like:

Code: [Select]
(function(d) {

var
mark = d.createElement('mark'),
target = d.getElementById('C');

mark.append('highlight');
target.append('<b>Want to <mark>');
target.append(mark);
target.append('</mark> this word</b>');

})(document);

Which would be markup equivalent to:

Code: [Select]
&lt;b&gt;Want to &lt;mark&gt;<mark>highlight</mark>&lt;/mark&gt; this word&lt;/b&gt;

Since you seem to have escaped code. When working directly on the DOM if you want actual tags and not escaped text, you need to createElement.

« Last Edit: 6 Jun 2020, 02:44:22 am by Jason Knight »
Sorrow hides well in your shell. A fellow man with hurt to spare.
Dear one, here I am to share the fear. An act of kindness, without an amen.
Come in, the fire's warm. Burn the rope and dance some more.

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +49/-1
    • CutCodeDown -- Minimalist Semantic Markup
Actually, if using Append, MY BAD. I keep forgetting it can accept multiple parameters. Assuming the last snippet in the previous post is what you're aiming for:

Code: [Select]
var mark = document.createElement('mark');
mark.append('highlight');
document.getElementById('C').append('<b>Want to <mark>', mark, '</mark> this word</b>');

Would/should do the job... again assuming you don't care about IE or are willing to polyfill Element.append
« Last Edit: 6 Jun 2020, 04:11:34 am by Jason Knight »
Sorrow hides well in your shell. A fellow man with hurt to spare.
Dear one, here I am to share the fear. An act of kindness, without an amen.
Come in, the fire's warm. Burn the rope and dance some more.

jmrker

  • Junior Member
  • *
  • Posts: 32
  • Karma: +1/-0
Thanks for the detailed explanation. 
The HR stuff was for decoration only for division of the descriptions of the problem.

I did not know there was a difference between append and appendChild.  That also is helpful.

I had not considered any problems using escape codes versus tag codes in the presentation of the problem.  Again the escaped sequence was put in for descriptive purposes only.

Probably not much need for what I was posting about, but I have learned more about what is required for the dynamic presentation of HTML tags within DOM elements.  Thanks.

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +49/-1
    • CutCodeDown -- Minimalist Semantic Markup
I did not know there was a difference between append and appendChild.  That also is helpful.
I keep forgetting that [ Element || Document || DocumentFragment ].append even exists. The big differences are:

1) Append can accept multiple arguments

2) it can accept ANY element, if Node it's applied as Node, otherwise it is forced to String. (which can do interesting things if you pass it a function)

3) It doesn't exist in IE

Seriously, check out what this does:

Code: [Select]
<pre><code id="test"></code></pre>
Code: [Select]
function testFn(something) {
  console.log(something);
} // testFn

document.getElementById('test').append(testFn);

It will actually plug the source for the function into the page! The "old school" approach would be:

Code: [Select]
document.getElementById('test').appendChild(document.createTextNode(String(testFn)));

Which gives you a pretty good idea why Element.append was added to the language.

Hence a decent polyfill would go something like:

Code: [Select]
(function(targetObjects) {
for (var i = 0, target; target = targetObjects[i]; i++) {
if (target.hasOwnProperty('append')) continue;
Object.defineProperty(target.prototype, 'append', {
value : function() {
for (var i = 0, item; 'undefined' !== typeof (item = arguments[i]); i++) {
this.appendChild(
item instanceof Node ? item : document.createTextNode(String(item))
);
}
}
});
}
}([Element, Document, DocumentFragment]);

The laugh being that this version -- that many would poo-poo for being "inefficient" or "bloated" is actually less code, FASTER than the one provided by the likes of MDN, and works all the way back to IE 5.5 when the more "proper" ones barely even work in IE9.

Making me think that I have different definitions of words like inefficient and bloated.
« Last Edit: 6 Jun 2020, 01:57:58 pm by Jason Knight »
Sorrow hides well in your shell. A fellow man with hurt to spare.
Dear one, here I am to share the fear. An act of kindness, without an amen.
Come in, the fire's warm. Burn the rope and dance some more.

jmrker

  • Junior Member
  • *
  • Posts: 32
  • Karma: +1/-0
The first example was kind of neat,
Code: [Select]
<pre><code id="test"></code></pre>

<script>
function testFn(something) {
  console.log(something);
} // testFn

document.getElementById('test').append(testFn);
but I have a question. 
How is the console.log(something); working here? 
Plus, the information is NOT printed to the console.log either.  How does that magic occur?
I see the .append is calling the function which includes the console.log,
but I thought it would write to the console.log as well.

I see that your .apend substitute could be used in IE (where it does not exist) and could be applied to an Element. 
But what is a Document and DocumentFragment at the end of the anonymous function?
And are all there parameters required to be defined to function correctly?
Would it apply to a document.body.append(Something) the same way it appears to modify the <code> element via the ID?

I'm not sure I'm using the correct terminology to express my question, so correct that if necessary.

Jason Knight

  • Administrator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +49/-1
    • CutCodeDown -- Minimalist Semantic Markup
How is the console.log(something); working here? 
It isn't. It never gets called because we don't ever testFn();, we just stringify (testFn) and send as plaintext.

It was just some sample code inside the function, could be anything inside there.
 
Plus, the information is NOT printed to the console.log either.  How does that magic occur?
Again, the function is never called. That was just some sample code.

I see the .append is calling the function which includes the console.log,
See the lack of ()? It's NOT being called. It's being passed. Same as how callbacks work.

If I were to:
Code: [Select]
function testFn() {
  console.log(test);
}

function testTest(something) {
}

testTest(testFn);


TestFN never runs, it's being passed by reference to testTest but it is never explicitly (); IF we change testTest to:

Code: [Select]
function testTest(something) {
  something();
}

NOW it would be run.

But what is a Document and DocumentFragment at the end of the anonymous function?
And are all there parameters required to be defined to function correctly?
Pay VERY close attention to the code. See how the three Objects are passed in an array?

}([Element, Document, DocumentFragment]);

See the [], so it's an Array of those three objects.

Code: [Select]
(function(targetObjects) {
for (var i = 0, target; target = targetObjects[i]; i++) {

Which is then looped through. In the first loop target === Element, on the second loop it === Document, and on the third it === DocumentFragment. Also pay attention to the upper-case first letter. "Document" is the master class of which "document" (all lower case) is an instance.

.append is supposed to exists on all three ".prototype", so it is applied to all three objects.

Hence also the check for

Code: [Select]
if (target.hasOwnProperty('append')) continue;

Which is also run against all three. That way we don't try to overwrite it if it already exists.

And are all there parameters required to be defined to function correctly?
They are required if you want to document.append or documentFragment.append. Neither inherits from Element, but are supposed to support this function.

Would it apply to a document.body.append(Something) the same way it appears to modify the <code> element via the ID?
Document.body is the BODY tag and therefor an Element, so yes it works there.

document.append is ... strange, there is no real reason for it to exist,  DocumentFragment on the other hand does exist, but it too is something I've never seen a point to having/using/existing since you can just work off an Element. They exist as methods in the specification though so if you're gonna write a polyfill you should at least TRY to match the spec 100%.

As you were confused by how the parameters were handled, I assume you're not familiar with "for by result"?

Code: [Select]
var anchors = document.getElementsByTagName('a');
for (var i = 0, a; a = anchors[i]; i++) {
  // do something with each anchor here
}

So long as the members of an array-like -- such as a NodeList -- are "loose true" the above loop can assign the local VAR "a" to each anchor in the node list. If it's a valid node it's true and the loop keeps going, whilst if it's loose false -- like undefined -- we can stop looping as it didn't exist.

This is actually the fastest way across all browsers to loop through an array. It's faster than a normal "for" and makes Array.forEach look like it's standing still.

Same goes for if you had a normal array of values, we can evaluate the assignment.

Code: [Select]
var test = [0, false, ''];
for (var i = 0, value; 'undefined' === typeof (value = test[i]); i++) {
  console.log(i, ' === "', value, '");
}

The only way value could end up undefined is if it's "out of range" of the  array. Screwy as it is, this actually seems to break even on speed with the length compare:

Code: [Select]
var test = [0, false, ''];
for (var i = 0, iLen = test.length; i < iLen; i++) {
  console.log(i, '==="', test[i], '"');
}

IF there's only one use of the value. However, if value is used multiple times, it gets faster and faster and faster, even compared to just flat doing an assignment inside the loop.

It's a shame that client-side "for/of" isn't real world deployable yet, and there's still no operation that returns both key and value without resorting to the overhead of callbacks.

In hindsight, I probably should have written that function as:

Code: [Select]
(function() {
for (var i = 0, target; target = arguments[i]; i++) {
if (target.hasOwnProperty('append')) continue;
Object.defineProperty(target.prototype, 'append', {
value : function() {
for (var i = 0, item; 'undefined' !== typeof (item = arguments[i]); i++) {
this.appendChild(
item instanceof Node ? item : document.createTextNode(String(item))
);
}
}
});
}
}(Element, Document, DocumentFragment);

Since JS makes a perfectly good arguments array-like we can work from.

-- edit -- oh and excuse you must occasional use of Yodish expressions I have. Hrrm? gzip compress better do they, so minification such as closure it does. Write that way from start why not?

Joking aside a lot of these dipshit "Linters" will throw a conniption fit over the presence of "yodish" expressions, where the static is before a variable. It's utter pedantic BS on their part and another of the reasons that by the time I make a "linter" stop bitching about BS, they no longer do anything of value.  There's NOTHING wrong with putting the static before a value, it often in larger codebases results in smaller gzip compression (damned if I know why) hence why things like Google's "Closure Compiler" or other such minification software will actually switch your evaluations to using it!
« Last Edit: 8 Jun 2020, 02:57:14 am by Jason Knight »
Sorrow hides well in your shell. A fellow man with hurt to spare.
Dear one, here I am to share the fear. An act of kindness, without an amen.
Come in, the fire's warm. Burn the rope and dance some more.

 

Advertisement