Simple Template Pattern

The simple template pattern is to avoid a large number of node operations when creating views by splicing the formatted string. The simple template pattern does not belong to the category of the 23 commonly defined design patterns, but it is usually regarded as a broad-spectrum technical design pattern.

Description

In contrast to the template method pattern, which defines the framework for how to execute certain algorithms and allows subclasses to implement or call it through the interfaces or methods exposed by the parent class, the simple template pattern is used to solve the problem of a large number of node operations required to create views, and to solve the strong coupling between data and structure based on this.

Node Operations

If we want to create a list, it's relatively troublesome to do it directly through various node operations.

<!DOCTYPE html>
<html>
<head>
    <title>Node Operation</title>
</head>
<body>
    <div id="root"></div>
</body>
    <script type="text/javascript">
        (function(){
            const container = document.getElementById("root");
            const ul = document.createElement("ul");
            const list = [{
                "name": "google",
                "url": "https://www.google.com"
            }, {
                "name": "baidu",
                "url": "https://www.baidu.com"
            }, {
                "name": "bing",
                "url": "https://cn.bing.com"
            }];
            list.forEach(v => {
                let li = document.createElement("li");
                let a = document.createElement("a");
                a.href = v.url;
                a.target = "_blank";
                a.innerText = v.name;
                li.appendChild(a);
                ul.appendChild(li);
            });
            container.appendChild(ul);
        })();
    </script>
</html>

Based on String Splicing

Although using string splicing can reduce the apparent complexity, the actual maintainability is usually poor due to the strong coupling of data and structure, which means that changes to the data or structure will require code modification. In addition, the dynamic creation of a ul list using the template string syntax of ES6 seems simple, but if you use string splicing directly, it will be much more cumbersome.

<!DOCTYPE html>
<html>
<head>
    <title>String Concatenation</title>
</head>
<body>
    <div id="root"></div>
</body>
    <script type="text/javascript">
        (function(){
            const container = document.getElementById("root");
            const list = [{
                "name": "google",
                "url": "https://www.google.com"
            }, {
                "name": "baidu",
                "url": "https://www.baidu.com"
            }, {
                "name": "bing",
                "url": "https://cn.bing.com"
            }];
            let template = `<ul>`;
            list.forEach(v => {
                template += `<li>
                    <a href="${v.url}" target="_blank" >${v.name}</a>
                </li>`;
            });
            template += "</ul>";
            container.innerHTML = template.replace(/[\s]+/g, " ");
        })();
    </script>
</html>

Template Rendering

By creating a template, we can use data to format strings to render views and insert them into containers, making the solution much more readable.

<!DOCTYPE html>
<html>
<head>
    <title>Template Rendering</title>
</head>
<body>
    <div id="root"></div>
</body>
    <script type="text/javascript">
        (function(){
            const container = document.getElementById("root");
            const formatString = function(str, data){
                return str.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] === void 0 ? "" : data[key]);
            }
            const list = [{
                "name": "google",
                "url": "https://www.google.com"
            }, {
                "name": "baidu",
                "url": "https://www.baidu.com"
            }, {
                "name": "bing",
                "url": "https://cn.bing.com"
            }];
            let template = ["<ul>"];
            list.forEach(v => {
                template.push("<li>");
                template.push(formatString('<a href="{{url}}" target="_blank" >{{name}}</a>', v));
                template.push("</li>");
            });
            template.push("</ul>");
            console.log(template)
            container.innerHTML = template.join("");
        })();
    </script>
</html>

Simple Implementation of Template Engine

A simple implementation is made for the mustcache style {{}}, only the data display aspect is implemented, and the directives such as loops are not. By processing the string, converting it into a function, and executing it with parameters, the data display can be achieved. By handling the strings and using Function to implement the template syntax, it is entirely possible to generate a more complete template syntax filter using regular expressions, including Js expressions and built-in directives such as the laytpl module in mustcache.js and layui.js.

<!DOCTYPE html>
<html>
<head>
    <title>Template Engine</title>
</head>
<body>
    <div id="root">
        <div>{{show}}</div>
        <div>{{description}}</div>
    </div>
</body>
    <script type="text/javascript">
        var data = { 
            show: 1,
            description: "A simple template engine"
        };

        function render(element, data) {
            var originString = element.innerHTML;
            var html = String(originString || '').replace(/"/g, '\\"').replace(/\s+|\r|\t|\n/g, ' ')
            .replace(/\{\{(.)*?\}\}/g, function (value) { 
                return  value.replace("{{", '"+(').replace("}}",')+"');
            });
            html = `var targetHTML = "${html}";return targetHTML;`;
            var parsedHTML = new Function(...Object.keys(data), html)(...Object.values(data));
            element.innerHTML = parsedHTML;
        }

        render(document.getElementById("root"), data);
    </script>
</html>

AST

The template syntax based on AST requires parsing the HTML into an AST, then converting the AST into a string. This string is executed as a function, and this process still requires the use of Function. The example below only utilizes Js to obtain the DOM structure-generated AST and does not parse HTML independently. Although it seems that using Function to handle strings is necessary in the end, and AST still needs to parse HTML and then splice the string, increasing the computation time, if the template syntax is implemented solely based on processing strings, rendering is required when the data changes, and each render needs to re-render the entire DOM. Although the above simple implementation also re-renders the entire template through AST, mainstream Js frameworks such as Vue are now based on AST. First, the template is parsed into AST, and then static node markings are applied to the AST to mark the static nodes for reuse and skip comparison, thus optimizing rendering. Then a virtual DOM is generated, and when the data changes, the virtual DOM performs a diff algorithm comparison to find the nodes with data changes, and then minimizes rendering. This way, the entire template does not need to be re-rendered when the data changes, thereby increasing rendering efficiency.

<!DOCTYPE html>
<html>
<head>
    <title>AST</title>
</head>
<body>
    <div id="root" class="root-node">
        <div>{{show}}</div>
        <div>{{description}}</div>
    </div>
</body>
    <script type="text/javascript">
        var data = { 
            show: 1,
            description: "A simple template syntax"
        };
 

```javascript
function parseAST(root){
    var node = {};
    node.parent = null;
    if(root.nodeName === "#text"){
        node.type = "text";
        node.tagName = "text";
        node.content = root.textContent.replace(/\s+|\r|\t|\n/g, ' ').replace(/"/g,'\\"');
    }else{
        node.type = "tag";
        node.tagName = root.localName;
        node.children = [];
        node.attr = {};
        Array.prototype.forEach.call(root.attributes, item => node.attr[item.nodeName] = item.nodeValue );
    }
    Array.prototype.forEach.call(root.childNodes, element => {
        var parsedNode = parseAST(element);
        parsedNode.parent = root;
        node.children.push(parsedNode);
    });
    return node;
}

function render(element, template, data) {
    html = `var targetHTML = "${template}";return targetHTML;`;
    var parsedHTML = new Function(...Object.keys(data), html)(...Object.values(data));
    element.innerHTML = parsedHTML;
}

function generateHTMLTemplate(AST){
    var template = "";
    AST.forEach( node => {
        if(node.type === "tag"){
            template += `<${node.tagName}>`;
            template += generateHTMLTemplate(node.children);
            template += `</${node.tagName}>`;
        }else{
            if(node.content.match(/\{\{(.)*?\}\}/)){
                var expression = node.content.replace(/\{\{(.)*?\}\}/g, function(value){ 
                    return  value.replace("{{",'"+(').replace("}}",')+"');
                })
                template += expression;
            }else{
                template += node.content;
            }
        }
    })
    return template;
}

var root  = document.getElementById("root");
var AST = parseAST(root);
var template = generateHTMLTemplate([AST]);
render(root, template, data);
</script>
</html>

Daily Challenge

https://github.com/WindrunnerMax/EveryDay

Reference

[Link to the juejin post](https://juejin.cn/post/6844903633000087560) [Link to the cnblogs page](https://www.cnblogs.com/libin-1/p/6544519.html) [Link to the laytpl.js file on Github](https://github.com/sentsin/layui/blob/master/src/lay/modules/laytpl.js)