Elementals.js

Latest Version: 3.7 Final 20 December 2018

DOM-JON, a JSON rule set for DOM creation

Summary

DOM-JON is for creating DOM fragments that can be appended to a live page from a JSON compliant structure. This is desirable as directly manipulating the DOM is faster, less prone to side effects, and often less bandwidth and memory inensive than methods like innerHTML. It's even more secure since createTextNode for text content has ZERO risks of XSS exploitation, a common mistake people make with innerHTML.

Whenever you use innerHTML the browser has to get the parser involved to turn that new markup into a DOM structure, but also to make sure the new markup doesn't change the behavior of any surrounding markup. This long string processing can create vast overhead in the browser; an overhead you can't even measure from the JavaScript as MOST of it takes place AFTER scripting execution ends. It also often means the browser will create an all-new DOM and re-apply all existing rules to it, doubling down on the memory footprint and processing time.

BUT, if we go directly to the DOM by creating nodes using document.createElement or document.createTextNode and adding them using methods like Element.appendChild or Element.insertBefore we can bypass all that. Even if it ends up taking more JavaScript execution time, it is overall faster and cleaner.

Finally because it is just JSON it is surprisingly easy to use to create templates server-side for scripted generation WITHOUt getting the excessive overhead of complex string processing such as regex involved. More so it makes it easier to keep values separate from the markup itself -- a constant woe in the age of XSS exploits. ANY time content is added it client-side it should be created with document.createTextNode meaning no arbitrary code in your content strings can EVER execute!

Overall Structure

A DOM-JON is an Array of values where each entry contains instructions for creating a new element. All array entries when added to the page -- such as with elementals.js' _.Node.write or _.make -- will be treated as siblings under any declared target.

If the entry is a string, it will be treated as a delimited selector string (see below). If it is an array, the first entry is your delimited selector string and the second entry is information about how to handle that new element based on the value type as follows:

  1. Array -- it is assumed to be another DOM-JON containing instructions for making even more elements which will be added to this one as children.
  2. Object -- it is assumed to be a list of attributes to be applied to the new element, as well as containing 'command attributes' for the creation of even more content, as well as appending the element directly to the DOM.
  3. String -- will be added as a textNode to this element.

Delimiters and Element Values

In DOM-JON you create elements with what we call a delimited selector string -- "DSS" for short. -- as it is separated by delimiters. It follows a similar notation to CSS selectors for tagname, id, and className, but there are two extra commands involved.

While all the delimiters are optional, they and their contents should appear in the following order when used:

tagName
The tagname (if any)must be declared FIRST. If you omit a tagName during Element creation DIV is used as the default. unless creating a textNode, see the ~ fast text delimiter
# -- id
An optional ID assigned to the new element. If present it should be after the tagname (if any) and before any other delimiters.
. -- className
A list of period separated classes can be chained to the selector. All will be applied to the new element.
@ -- Special Value Delimiter
Tag/Atrributes for @ Special Value Delimiter
tagName attribute
a href
button type
img src
input type
option value
script src
td headers
th scope

Some tags have attributes that it is handy to be able to declare on the fly, without getting the complex attributes object involved. The "Tag/Atrributes for @ Special Value Delimiter" table shows which tags accept this delimiter value and what attribute the value will be assigned to.

Using this delimiter on tags not on the table will have no effect.

Example:

var sampleDOMJON = ['script@library.js'];

Is roughly equivalent to this markup:

<script src="library.js"></script>
~ -- Fast Content
Attributes for the "~" Fast Content Delimiter
TagName Attribute
For all other tags "fast-text" will be appended as a textNode.
img alt
input value
textaraea value

Shorthand for adding text content or values to an element without getting an attribute object involved. In the majority of cases the specified text will be inserted into the new element as its content. Certain tags however will assign it to specific attributes as per the "Attributes for the "~" Fast Content Delimiter" table.

In addition if the tilde is the first character in a selector with no tagName specified, the entire remainder of the string will be created as a textNode instead of an Element node; basically using document.createTextNode instead of document.createElement.

Some typical usage examples would be: (combined with the ~ delimiter)

var sampleDOMJON = ['img@test.png~Test Image'];

Which results in a DOM similar to what this creates:

<img src="test.png" alt="Test Image">

Whist this:

var sampleDOMJON = ['span#wow.alpha.beta~Some Text'];

Gives us the same thing as:

<span id="wow" class="alpha beta">Some Text</span>

.. and of course this:

var sampleDOMJON = ['~Some Text'];

Would when run through a DOM-JON processor result in the same thing as:

document.createTextNode('Some Text');

Attributes and Commands

When an object is passed as the second index in a 'element' array (where the first index is a delimited selector string) it contains any attributes you wish to set on the element, as well as any commands you want to send. All DOMDocumentElement are valid when creating an Element.

A word about innerHTML

You can pass innerHTML to plug in content as well. Whilst normally writing to innerHTML is generally a bad idea and should be avoided. _.make() ensures that if you use any of the _.nodeAdd methods they are done LAST, so that things like innerHTML (and Element.type for IE) are applies AFTER all other attributes. This means innerHTML will be parsed into the element BEFORE it is added to the live document DOM. As a smaller sub-dom it parses faster and will not triggeer a reparse of the entire document, avoiding the headache/woes that normal innerHTML writes can create.

Still it is NOT a method that we recommeded and should be avoided when possible, reserving it for corner cases where you really have no other choice.

onevent attributes

The elementals.js library will normalize onevent attributes like onclick to be applied via Element.addEventListener with a fallback to element.attachEvent that polyfills the callback using _.Event.add. This results in the "event" being properly passed to the callback with Event.target and Event.currentTarget resolving as with modern browsers in legacy IE.

content Command

Primarily the content command exists to belt out large DOM models quickly and easily. It has three behaviors based on what you pass it for a variable type:

  1. Array -- a DOM-JON that will be used to populate the element.
  2. Object -- assumed to be an instance of Element and will be appended to the newly created element.
  3. String -- it will be added to the element as its content as a textNode.

Placement Commands

These exist to allow elements from a DOM-JON to be added to the live DOM. Typically you would only use them on a parent-level Attribute object in elementals.js' _.make or as a parameter for _.Node.write. As such it is NOT recommended you place them into a static DOM-JON structure, and you REALLY shouldn't try to use them inside any DOM-JON that's nested inside another!

In all cases they expect to be passed a DOMDocumentElement as their value.

  • after -- The new Element will be placed after the value as a sibling
  • before -- The new Element will be placed before the value as a sibling
  • first -- The new Element will be placed before the Element.firstChild of the value
  • last -- The new Element will be placed after the Element.lastChild of the value
  • replace -- The new Element will replace the Element stated as the value of this attribute.

DOM-JON Examples

The following are implemented using elementals.js' _.make and _.Node.write methods.

Example #1 - A simple _.make

JavaScript

_.make('div#test~This is a test', { last : document.body });

Is roughly equivalent to:

JavaScript

document.body.innerHTML += '<div id="test">This is a test</div>';

Except that it uses the DOM to do it, bypassing the parsing engine.

Example #2, different placement

JavaScript

_.make('h2.scriptHeading', {
	content : 'Test',
	after : document.getElementsByTagName('h1')[0];
});

Inserts after the first h1 on the page the equivalent of <h2 class="scriptHeading">Test</h2>, using the DOM instead of browser parsing.

Example #3, a Complex DOM-JON with _.make

JavaScript

_.make('div#test', {
	content : [
		[ 'h2~This is a DOM created subsection' ],
		[ 'p', { innerHTML : 'A child paragraph <strong>that can use markup!</strong>' } ],
		[ 'p', [
			'A child paragraph',
			[ 'strong', 'that directly assigns the <strong> tag.'],
			[ '~ Note that markup is escaped when you pass a normal string!' ]
		] ],
		[ 'div.details' , { content : [
			[ 'span' : '13 November 2017' ],
			' Jason M. Knight'
		] } ]
	],
	last : document.body
});

Basically creates the same thing on the DOM at the bottom of document.body as the following markup. (just without the whitespace I've added for clarity.)

HTML

<div id="test">
	<h2>This is a DOM created subsection</h2>
	<p>
		A child paragraph <strong>that can use markup!</strong>
	</p>
	<p>
		A child paragraph <strong>that directly assigns the &lt;strong&gt; tag.!</strong> Note that markup is escaped when you pass a normal string!
	</p>
	<div class="details">
		<span>13 November 2017</span>
		Jason M. Knight
	</div>
</div>

Which is very handy when adding large sections of DOM elements and you want to avoid doing an innerHTML directly onto the live document. Quite often -- most of the time in fact -- writing to the live page with innerHTML can introduce large amounts of overhead in processing time and memory footprint as the entire document has to be reparsed as markup with an entirely new re-organized DOM created. Writing directly to the DOM with _.make bypasses this "problem".

Example #4, using _.Node.write

elementals.js' _.Node.write method accepts a Element as its first parameter to which the write will occur, then either a string, object, or DOM-JON as its second parameter as per a DOM-JON sub-array's second index, and a 'placement command' string as it's final value. If you omit placement command 'last' is the default behavior.

HTML

<h1>Test</h1>

JavaScript

var
	h1 = document.getElementsByTagName('h1')[0],
	h2 = document.createElement('h2');
_.Node.write(h1, "\r\nAdded after the H1\r\n", 'after');
_.Node.write(h1, "Added before the H1\r\n", 'before');
_.Node.write(h1, "\r\nAdded before H1 content\r\n", 'first');
_.Node.write(h1, h2, 'after');
_.Node.write(h1, "\r\nAdded after H1 content\r\n", 'last');
_.Node.write(h2, 'Second Heading');
_.Node.write(h2, [
	[ 'p~Dynamically added paragraph after the second heading' ],
	[ 'p.test', [
		content : [
			'Another way of adding a paragraph, this time with a class ',
			[ 'a', { content : 'and an anchor!', href : '/' } ]
		]
	] ]
], 'after');

RESULT HTML (equivalent)

Added before the h1
<h1>
Added before H1 content
Test
Added after H1 content
</h1>
Added after the H1
<h2>Second Heading</h2>
<p>Dynamically added paragraph after the second heading</p>
<p class="test">Another way of adding a paragraph, this time with a class <a href="/">and an anchor!</a></p>

In practice much of the above would actually be coded using _.make. In fact, several of _.make's attributes Object properties call _.Node.write and vice-versa.

Advertisement