59

If I need to create a couple of nested DOM elements, I know one way to do it, is to write them as long string and then place them in the document using a suitable jQuery function. Something like:

elem.html(
'<div class="wrapper">
    <div class="inner">
        <span>Some text<span>
    </div>
    <div class="inner">
        <span>Other text<span>
    </div>
</div>'); 

This way is obviously not the cleanest. The sting doesn't take too long to get messy and editing becomes a problem. I much more prefer this notation:

$('<div></div>', {
    class : 'inner'
})
.appendTo( elem );

The problem is I don't know how to implement it efficiently when creating nested elements on the fly like above. So if there is way to do the 1st example with the 2nd notation, I'll be glad to learn about it.

Basically, the question is, whats the best way to create nested HTML elements on the fly, without having to deal wit messy long strings?

Note : I am aware of templating engines. However this is a question concerning the creation of just a couple of HTML elements on the fly. Like while building the DOM dependencies for a plugin or similar cases.

Mohd Abdul Mujib
  • 13,071
  • 8
  • 64
  • 88
Maverick
  • 1,988
  • 5
  • 24
  • 31
  • 2
    You could use a template system, or store the "template" within a ` – Jared Farrish Jun 23 '12 at 22:48
  • 4
    [Moustache](http://mustache.github.com/) is a template library you could use. – Mihai Stancu Jun 23 '12 at 22:50
  • Thanks, but I am aware of those already. The question is for cases when you just need a couple of elements created on the fly, like when building the dom for a plugin or similar cases. – Maverick Jun 23 '12 at 22:52
  • what is "best" for one person may not be for another. Try different methods and settle on what works best for you – charlietfl Jun 23 '12 at 22:54
  • @charlietfl I agree. But you see, I only know of one method for this kind of task and I don't like that method. So I am asking the question to learn about something new and better. – Maverick Jun 23 '12 at 22:57
  • What are you looking for? A "better" way of putting markup in a reader-friendly way within Javascript, like `HEREDOC` in PHP? Or an element emitter (like your second example) where you can create a common call to get a cloned element? – Jared Farrish Jun 23 '12 at 23:01

6 Answers6

71

write them as long string and than place them in the document using a suitable jQuery function. Something like:

The problem with this approach is that you'll need a multi-line string - something Javascript doesn't support - so in reality you'll end up with:

elem.html(
'<div class="wrapper">'+
    '<div class="inner">'+
        '<span>Some text<span>'+
    '</div>'+
    '<div class="inner">'+
        '<span>Other text<span>'+
    '</div>'+
'</div>');

Using the method you suggested above, this is about as clean as I could manage to get it:

elem.append(
    $('<div/>', {'class': 'wrapper'}).append(
        $('<div/>', {'class': 'inner'}).append(
            $('<span/>', {text: 'Some text'})
        )
    )
    .append(
        $('<div/>', {'class': 'inner'}).append(
            $('<span/>', {text: 'Other text'})
        )
    )
);

The other advantage to doing this is that you can (if desired) get direct references to each newly created element without having to re-query the DOM.

I like to write polyglots, so to make my code re-usuable I usually do something like this, (as jQuery's .html() doesn't support XML):

// Define shorthand utility method
$.extend({
    el: function(el, props) {
        var $el = $(document.createElement(el));
        $el.attr(props);
        return $el;
    }
});

elem.append(
    $.el('div', {'class': 'wrapper'}).append(
        $.el('div', {'class': 'inner'}).append(
            $.el('span').text('Some text')
        )
    )
    .append(
        $.el('div', {'class': 'inner'}).append(
            $.el('span').text('Other text')
        )
    )
);

This isn't very different to method #2 but it gives you more portable code and doesn't rely internally on innerHTML.

lucideer
  • 3,842
  • 25
  • 31
  • Hi,I think the last one with the utility method isn't right... I might be wrong though. Firstly, when you do the `$.el('span', {text: 'Other text'})` the text will be an attribute of the span and not the actual text. Secondly, it seems that whatever event bindings you do inline for example `$.el('div', {'class': 'inner'}).click(function(){alert('O hai');});` do not seem to fire... I might be doing something wrotng though – Xeroxoid Oct 08 '12 at 14:41
  • 3
    @Xeroxoid You're right. I'm guessing this is/was something undocumented (and therefore unstable) with a particular version of jQuery - there's an internal property in jquery - $.attrFn - which is/was checked for hook functions on certain keywords (like "text"), which is what this was aimed at. I'll update now ... – lucideer Oct 08 '12 at 23:14
  • 1
    Using this extends gives you the ability to use $.el('div') without the options. $.extend({ el: function(el, props) { var $el = $(document.createElement(el)); if(typeof(props) !== 'undefined'){ $el.attr(props); } return $el; } }); – Mathias Dewelde Jan 10 '14 at 14:31
  • I was wondering how you can add multiple elements as siblings. I tried to use the `.after()` methods but I found out that the `.append()` method can take an array (an array of `$.el()` in this case). `container.append([$.el('span').text('First'), $.el('span').text('Second')]);` – dominicbri7 Jun 10 '14 at 17:27
  • .append() can take an array, but can alternatively just take an unlimited number of arguments which it treats in the same way, so you can omit the [ and the ] if you like. – lucideer Jun 11 '14 at 12:20
  • I like this method but in some cases I can not call this $.el, it catchs error "nodeType of undefined" – May'Habit Dec 30 '19 at 02:06
  • Now with es6 Javascript DOES support multi line text. so in first approach instead of single or double quote marks we can just use back-ticks ``. – arm Jul 23 '20 at 07:57
22

I found this solution while I was researching something else. It's part of an "Introduction to jQuery" by the creator of jQuery and uses the end() function.

$("<li><a></a></li>") // li 
  .find("a") // a 
  .attr("href", "http://ejohn.org/") // a 
  .html("John Resig") // a 
  .end() // li 
  .appendTo("ul");

Applying to your question it would be ...

$("<div><span></span></div>") // div 
  .addClass("inner") // div 
  .find("span") // span 
  .html("whatever you want here.") // span 
  .end() // div 
  .appendTo( elem ); 
miken32
  • 42,008
  • 16
  • 111
  • 154
Hugh Chapman
  • 321
  • 2
  • 3
21

I like the following approach myself:

$('<div>',{
  'class' : 'wrapper',
  'html': $('<div>',{
    'class' : 'inner',
    'html' : $('<span>').text('Some text')
  }).add($('<div>',{
    'class' : 'inner',
    'html' : $('<span>').text('Other text')
  }))
}).appendTo('body');

Alternatively, create your wrapper first, and keep adding to it:

var $wrapper = $('<div>',{
    'class':'wrapper'
}).appendTo('body');
$('<div>',{
    'class':'inner',
    'html':$('<span>').text('Some text')
}).appendTo($wrapper);
$('<div>',{
    'class':'inner',
    'html':$('<span>').text('Other text')
}).appendTo($wrapper);
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
13

There is a third way besides long ugly strings or huge methods chained together. You can use plain old variables to hold the individual elements:

var $wrapper = $("<div/>", { class: "wrapper" }),
    $inner = $("<p/>", { class: "inner" }),
    $text = $("<span/>", { class: "text", text: "Some text" });

Then, tie it all together with append:

$wrapper.append($inner.append($text)).appendTo("#whatever");

Result:

<div class="wrapper">
  <p class="inner">
    <span class="text">Some text</span>
  </p>
</div>

In my opinion this is by far the cleanest and most readable approach, and it also has the advantage of separating the data from the code.

EDIT: One caveat, however, is that there is no easy way to mix textContent with nested elements (e.g., <p>Hello, <b>world!</b></p>.) In that case you would probably need to use one of the other techniques such as a string literal.

jackvsworld
  • 1,453
  • 15
  • 17
8

I like this approach

    $("<div>", {class: "wrapper"}).append(
        $("<div>", {class: "inner"}).append(
            $("<span>").text(
                "Some text"
            )
        ), 
        $("<div>", {class: "inner"}).append(
            $("<span>").text(
                "Some text"
            )
        )
    ).appendTo("body")
matt3141
  • 4,303
  • 1
  • 19
  • 24
  • Can we have an onclick and onblur event into it ? If yes, how ? – ArunRaj Apr 10 '14 at 06:34
  • 1
    @ArunRaj Please see [my answer](http://stackoverflow.com/a/21035650/1097054) for an example. You could do something like `$text.click(function () { alert("You clicked on the span element."); });` – jackvsworld Oct 08 '14 at 23:43
1

Long string is obviously the cleanest way possible. You see the code properly nested without all those unnecessary extra noise like brackets, dollar signs and what not. Sure, it'd be advantages to be able to write it as a multiline string, but that's not possible for now. To me the point is to get rid of all the unneeded symbols. I don't know how comes the string gets messy and editing becomes a problem. If you've assigned a lot of classes you can put them on separate line:

'<div id="id-1"'
+ ' class="class-1 class-2 class-3 class-4 class-5 class-6">'
+ '</div>'

or this way:

'<div id="id-1" class="'
    + 'class-1 class-2 class-3 class-4 class-5 class-6'
+ '">'
+ '</div>'

Another option is probably to use haml on the client side, if that's possible. That way you'll have even less unneeded noise.

x-yuri
  • 16,722
  • 15
  • 114
  • 161