A very useful method to create live markup references from HTML strings

I decided to present this as an idea since I started using this method everywhere I need to create/manipulate simple markup.

This is a suggestion for natively implementing a simple method that would allow to deprecate the usage of 'hard to read' code patterns such as:

function createMarkupObject(name, value)
    var rootNode = document.createElement('div');
    rootNode.className = 'root-node';
    var labelNode = document.createElement('label');
    labelNode.className = 'label-node';
    labelNode.innerHTML = name;
    var valueNode = document.createElement('span');
    valueNode.className = 'value-node';
    valueNode.innerHTML = value;
    rootNode.appendChild(labelNode);
    rootNode.appendChild(valueNode);
    return {
       root: rootNode,
       name: labelNode,
       value: valueNode
    }
}

let refs = createMarkupObject('Code generated', 'hard to read');
document.body.appendChild(refs.root);

refs['name'].innerHTML = 'Still code generated';
refs['value'].innerHTML = 'Still hard to read';

In favour of a more elegant approach that would allow the creation of markup using strings as 'easy to read' templates for blocks of code:

const template = `
    <div class="root-node">
        <label class="label-node" ref="name"></label>
        <span class="value-node" ref="value"></span>
    </div>
`;

let refs = JSR.build(template);
document.body.appendChild(refs['root']);

refs['name'].innerHTML = 'Template generated';
refs['value'].innerHTML = 'easy to read';


Description

The working principle is very simple:

the template nodes whose references are to be returned in the output object, must contain a 'special' attribute named 'ref';

The value assigned to the node's 'ref' attribute will be the name of the respective reference property in the returned ouptut object.

For this reason, every 'ref' value should be unique to each node in the same template.

When a single root node exists in a HTML template string, one does not need to explicitly assign a 'ref' attribute to the template root node as this is always included in the returned output object, only child nodes of the template root node should be assigned with 'ref' attributes with the following exception:

If the template string contains more then 1 root node, only the first root node is returned as 'root' in the output object. The rest of the root nodes in the template string will require the assignment of 'ref' attributes in order to be returned in the output object.

The returned root reference will be specially usefull to place the resulting markup somewhere in the page body.
The resulting markup won't contain any 'ref' attributes as this would make the resulting markup invalid.

Benefits

The obvious advantage this feature has to offer, is the ability to work with 'easy to read' blocks of code.

HTML template strings can be directly defined in the code, or imported from other JS files or JSONs from server responses, or local html files, making it easier to build and organise multiple sets of templates.

Specially useful to render and update elements on a component based front end architecture.

The method

I originally implemented this as a js module, the principle is very simple yet very effective:

// define the namespace
const JSR = (function() {
    // create an element to temporarily place the markup generated from string templates
    var elementWrapper = document.createElement('div');
    
    /** PUBLIC JSrefs > build(htmlStr)
     * Converts a string containing HTML content into an object containing references to the referenced HTML nodes.
     * Nodes can be referenced by adding an attribute named 'ref' and setting its value to the desired name for the referece in the returned object: 
     * Input HTML string: '<div><ul ref="list"></ul></div>'
     * Output returned object: { root: HTMLnodeRef div, list: HTMLnodeRef ul }
     * Notice that the 'root' property is always returned, without requiring the addition of ref="root" to the HTML template root node.
     * @param {htmlStr} htmlStr a string containing valid html
     * @returns an object containing references to the nodes in the template
     */
    function _buildHtmlStringTemplate(htmlStr) {
        elementWrapper.innerHTML = htmlStr;
        var nodeList = elementWrapper.getElementsByTagName('*');
        var referencedNodes = {}; 
        for (var i=0; i<nodeList.length; i++) {
            for (var a=0; a<nodeList[i].attributes.length; a++) {
                if (nodeList[i].attributes[a].nodeName.toLowerCase() === 'ref') {
                    referencedNodes[nodeList[i].attributes[a].nodeValue] = nodeList[i];
                    nodeList[i].removeAttribute('ref');
                    break;
                }
            }
        }
        referencedNodes['root'] = elementWrapper.children[0];
        elementWrapper.innerHTML = null;
        return referencedNodes;
    }

    // export public methods
    return {
        build: _buildHtmlStringTemplate
    }
})()

Would this be something usefull in your opinion?

The original code can be found on the repo GitHub - diogomoreda/js-refs: lightweight JS module to facilitate the creation and manipulation of HTML nodes in the browser, using strings containing HTML code as input models

1 Like

Using a tagged template literal seems like a worse version of JSX syntax to me - but ofc you can use the tagged literal approach right now without any language changes.

Thank you for your reply, and your insight.

This single method allows me to store all my "views" as local variables, or individual html files I can "fetch" from the browser in real time. This "views" don't contain any JS logic, or curly brackets for interpolation. They just contain markup, with the addition of a "special" attribute that will mark a particular node as a HTMLreference to be returned.

Yes, this is a very simple approach, yet very effective, and it proved to be a game changer to me in respect to maintaining my code very straightforward, readable and clean, while keeping "views" and "controllers" independent of one another without having to use any frameworks.

Could you please expand on the tagged literal approach? As I reckon this requires a function to return something, and that being the case, you no longer have the option to have clean markup.

Better then going into arguments, I'll try and provide an application example.

Thank you

Related: webcomponents/proposals/Template-Instantiation.md at 1b75f7516e9901c26f1eb639d929aa82402c2fe0 ยท WICG/webcomponents ยท GitHub

Thank you, currently reading the proposal.