Defining a HTML template to append using JQuery

Defining a HTML template to append using JQuery

I have got an array which I am looping through. Every time a condition is true, I want to append a copy of the HTML code below to a container element with some values.
Where can I put this HTML to re-use in a smart way?

JQuery
$(‘.search’).keyup(function() {
$(‘.list-items’).html(null);

$.each(items, function(index) {
// APPENDING CODE HERE
});
});

Solutions/Answers:

Solution 1:

You could decide to make use of a templating engine in your project, such as:

If you don’t want to include another library, John Resig offers a jQuery solution, similar to the one below.


Browsers and screen readers ignore unrecognized script types:

<script id="hidden-template" type="text/x-custom-template">
    <tr>
        <td>Foo</td>
        <td>Bar</td>
    <tr>
</script>

Using jQuery, adding rows based on the template would resemble:

var template = $('#hidden-template').html();

$('button.addRow').click(function() {
    $('#targetTable').append(template);
});

Solution 2:

Old question, but since the question asks “using jQuery”, I thought I’d provide an option that lets you do this without introducing any vendor dependency.

While there are a lot of templating engines out there, many of their features have fallen in to disfavour recently, with iteration (<% for), conditionals (<% if) and transforms (<%= myString | uppercase %>) seen as microlanguage at best, and anti-patterns at worst. Modern templating practices encourage simply mapping an object to its DOM (or other) representation, e.g. what we see with properties mapped to components in ReactJS (especially stateless components).

Templates Inside HTML

One property you can rely on for keeping the HTML for your template next to the rest of your HTML, is by using a non-executing <script> type, e.g. <script type="text/template">. For your case:

<script type="text/template" data-template="listitem">
    <a href="${url}" class="list-group-item">
        <table>
            <tr>
                <td><img src="${img}"></td>
                <td><p class="list-group-item-text">${title}</p></td>
            </tr>
        </table>
    </a>
</script>

On document load, read your template and tokenize it using a simple String#split

var itemTpl = $('script[data-template="listitem"]').text().split(/\$\{(.+?)\}/g);

Notice that with our token, you get it in the alternating [text, property, text, property] format. This lets us nicely map it using an Array#map, with a mapping function:

function render(props) {
  return function(tok, i) { return (i % 2) ? props[tok] : tok; };
}

Where props could look like { url: 'http://foo.com', img: '/images/bar.png', title: 'Lorem Ipsum' }.

Putting it all together assuming you’ve parsed and loaded your itemTpl as above, and you have an items array in-scope:

$('.search').keyup(function () {
  $('.list-items').append(items.map(function (item) {
    return itemTpl.map(render(item)).join('');
  }));
});

This approach is also only just barely jQuery – you should be able to take the same approach using vanilla javascript with document.querySelector and .innerHTML.

jsfiddle

Templates inside JS

A question to ask yourself is: do you really want/need to define templates as HTML files? You can always componentize + re-use a template the same way you’d re-use most things you want to repeat: with a function.

In es7-land, using destructuring, template strings, and arrow-functions, you can write downright pretty looking component functions that can be easily loaded using the $.fn.html method above.

const Item = ({ url, img, title }) => `
  <a href="${url}" class="list-group-item">
    <div class="image">
      <img src="${img}" />
    </div>
    <p class="list-group-item-text">${title}</p>
  </a>
`;

Then you could easily render it, even mapped from an array, like so:

$('.list-items').html([
  { url: '/foo', img: 'foo.png', title: 'Foo item' },
  { url: '/bar', img: 'bar.png', title: 'Bar item' },
].map(Item).join(''));

Oh and final note: don’t forget to sanitize your properties passed to a template, if they’re read from a DB, or someone could pass in HTML (and then run scripts, etc.) from your page.

Solution 3:

Use HTML template instead!

Since the accepted answer would represent overloading script method, I would like to suggest another which is, in my opinion, much cleaner and more secure due to XSS risks which come with overloading scripts.

I made a demo to show you how to use it in an action and how to inject one template into another, edit and then add to the document DOM.

example html

<template id="mytemplate">
  <style>
     .image{
        width: 100%;
        height: auto;
     }
  </style>
  <a href="#" class="list-group-item">
    <div class="image">
      <img src="" />
    </div>
    <p class="list-group-item-text"></p>
  </a>
</template>

example js

// select
var t = document.querySelector('#mytemplate');

// set
t.content.querySelector('img').src = 'demo.png';
t.content.querySelector('p').textContent= 'demo text';

// add to document DOM
var clone = document.importNode(t.content, true); // where true means deep copy
document.body.appendChild(clone);

HTML &lttemplate>

  • +Its content is effectively inert until activated. Essentially, your
    markup is hidden DOM and does not render.

  • +Any content within a template won’t have side effects. Scripts don’t run, images don’t load, audio doesn’t play …until the template is used.

  • +Content is considered not to be in the document. Using document.getElementById() or querySelector() in the main page won’t return child nodes of a template.

  • +Templates can be placed anywhere inside of <head>, <body>, or <frameset> and can contain any type of content which is allowed in those elements. Note that “anywhere” means that <template> can safely be used in places that the HTML parser disallows.

Fall back

Browser support should not be an issue but if you want to cover all possibilities you can make an easy check:

To feature detect <template>, create the DOM element and check that
the .content property exists:

function supportsTemplate() {
  return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
  // Good to go!
} else {
  // Use old templating techniques or libraries.
}

Some insights about Overloading script method

  • +Nothing is rendered – the browser doesn’t render this block because the <script> tag has display:none by default.
  • +Inert – the browser doesn’t parse the script content as JS because its type is set to something other than "text/javascript".
  • -Security issues – encourages the use of .innerHTML. Run-time string parsing of user-supplied data can easily lead to XSS vulnerabilities.

Full article: https://www.html5rocks.com/en/tutorials/webcomponents/template/#toc-old

Useful reference:
https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode
http://caniuse.com/#feat=queryselector

Solution 4:

Add somewhere in body

<div class="hide">
<a href="#" class="list-group-item">
    <table>
        <tr>
            <td><img src=""></td>
            <td><p class="list-group-item-text"></p></td>
        </tr>
    </table>
</a>
</div>

then create css

.hide { display: none; }

and add to your js

$('#output').append( $('.hide').html() );

Solution 5:

Other alternative: Pure

I use it and it has helped me a lot.
An example shown on their website:

HTML

<div class="who">
</div>

JSON

{
  "who": "Hello Wrrrld"
}

Result

<div class="who">
  Hello Wrrrld
</div>

Solution 6:

In order to solve this problem, I recognize two solutions:

  • The first one goes with AJAX, with which you’ll have to load the template from another file and just add everytime you want with .clone().

    $.get('url/to/template', function(data) {
        temp = data
        $('.search').keyup(function() {
            $('.list-items').html(null);
    
            $.each(items, function(index) {
                 $(this).append(temp.clone())
            });
    
        });
    });
    

    Take into account that the event should be added once the ajax has completed to be sure the data is available!

  • The second one would be to directly add it anywhere in the original html, select it and hide it in jQuery:

    temp = $('.list_group_item').hide()
    

    You can after add a new instance of the template with

    $('.search').keyup(function() {
        $('.list-items').html(null);
    
        $.each(items, function(index) {
            $(this).append(temp.clone())
        });
    });
    
  • Same as the previous one, but if you don’t want the template to remain there, but just in the javascript, I think you can use (have not tested it!) .detach() instead of hide.

    temp = $('.list_group_item').detach()
    

    .detach() removes elements from the DOM while keeping the data and events alive (.remove() does not!).