Defining a HTML template to append using JQuery

JavascriptJqueryJson

Javascript Problem Overview


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?

<a href="#" class="list-group-item">
    <div class="image">
	     <img src="" />
    </div>
	<p class="list-group-item-text"></p>
</a>

JQuery

$('.search').keyup(function() {
	$('.list-items').html(null);

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

Javascript Solutions


Solution 1 - Javascript

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.

https://jsfiddle.net/jkoudys/5hcjo31u/">jsfiddle</a>

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 2 - Javascript

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 3 - Javascript

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 <template>

  • +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

CREATING WEB COMPONENTS Creating custom web components tutorial using HTML templates by Trawersy Media: https://youtu.be/PCWaFLy3VUo

Solution 4 - Javascript

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 - Javascript

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!).

Solution 6 - Javascript

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 7 - Javascript

Very good answer from DevWL about using the native HTML5 template element. To contribute to this good question from the OP I would like to add on how to use this template element using jQuery, for example:

$($('template').html()).insertAfter( $('#somewhere_else') );

The content of the template is not html, but just treated as data, so you need to wrap the content into a jQuery object to then access jQuery's methods.

Solution 8 - Javascript

Here's how to use the <template> element and jQuery a little more efficiently, since the other jQuery answers as of the time of writing use .html() which forces the HTML to be serialized and re-parsed.

First, the template to serve as an example:

<template id="example"><div class="item">
    <h1 class="title"></h1>
    <p class="description"></p>
</div></template>

Note that there is no space between the <template> tag and its content, nor between the end of the content and </template>. If you put space in there, it will be copied too.

Now, the JS:

// example data
const items = [
    {name: "Some item", description: "Some description"},
    // ...
];

const template = $("template#example").contents();
const templateTitle = template.find("h1.title");
const templateDescription = template.find("p.description");
const target = $("body");

for (const item of items) {
    // fill in the placeholders
    templateTitle.text(item.name);
    templateDescription.text(item.description);
    // copy the template and add it to the page
    target.append(template.clone());
}

This will add a copy of the template, with placeholders replaced, for every item in the items array. Note that several elements, like template and target, are only retrieved once.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPatrick ReckView Question on Stackoverflow
Solution 1 - JavascriptJosh from QaribouView Answer on Stackoverflow
Solution 2 - JavascriptMathijs FlietstraView Answer on Stackoverflow
Solution 3 - JavascriptDevWLView Answer on Stackoverflow
Solution 4 - JavascriptMateusz NowakView Answer on Stackoverflow
Solution 5 - JavascriptSebastian NeiraView Answer on Stackoverflow
Solution 6 - JavascriptalditisView Answer on Stackoverflow
Solution 7 - JavascriptJacquesView Answer on Stackoverflow
Solution 8 - JavascriptkbolinoView Answer on Stackoverflow