Murray
Murray

Reputation: 313

Efficient method of inserting jQuery elements within template literals

Dabbling in jQuery, there's an awkward situation I find myself running into often. Say I want to construct a big chunk of HTML, with a jQuery-wrapped element somewhere in the middle. I want to be able to use a template literal to do the HTML, and then efficiently drop the jQuery-wrapped element in, to avoid writing loads of JavaScript code to build each element individually in the tree.

For example, say I wanted to insert a button at the position described:

const $btn = $(`<button>Click Me</button>`).click(() => { /* complex handler... */ });
const $ele = $(`<div><h1>Some content</h1><p>Press this button: [button should go here]</p></div>`);

I could laboriously create the outer div, the p, append the p to the div, and append the button to the p. This feels like a lot of clunky boilerplate.

I could, instead, add the button directly to the template literal:

<div><h1>Some content</h1><p>Press this button: <button>Click Me</button></p></div>

And then find() it and bind handlers that way--this seems a little better, but I'm still going to have to give my button a unique id or class in order to be able to find it, depending on the context. It also doesn't "chain" well, as, for example above, tacking a find() to the end of my const $ele = .. statement will result in $ele storing the button, and not the div. This is undesirable more often than not.

So, is there a better solution?

Upvotes: -1

Views: 2674

Answers (3)

Bergi
Bergi

Reputation: 664548

Let's have some fun with tagged template literals:

const $btn = jQuery(`<button>Click Me</button>`).click(e => alert("complex handler…"));
const $ele = $`<div><h1>Some content</h1><p>Press this button: ${$btn}</p></div>`;
//           ^^                                                ^^^^^^^          ^
jQuery("body").append($ele);

function $(parts, ...args) {
  const uid = Math.round(Math.random()*0xFFFF).toString(16).padStart(4, "0");
  const res = jQuery(parts.reduce((html, p, i) => {
    return html + "<slot name=x-replace-"+uid+"></slot>" + p;
  }));
  res.find("slot[name=x-replace-"+uid+"]").replaceWith(i => args[i]);
  return res;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Upvotes: 4

Murray
Murray

Reputation: 313

Seems this is a hot topic, so, combining some of the ideas here, is this stupid?:

$.fn.extend({
    swapIn: function (toSwap) {
        Object.entries(toSwap)
            .forEach(([k, $v]) => this.find(`[data-swap="${k}"]`).replaceWith($v));
        return this;
    }
});

const $btn1 = $(`<button>Click Me</button>`).click(() => { /* complex handler... */ });
const $btn2 = $(`<button>Don't Click Me</button>`).click(() => { /* complex handler... */ });
const $ele = $(`<div><h1>Some content</h1><p>Press this button: <div data-swap="$btn1"/> but not this button: <div data-swap="$btn2"/></p></div>`)
    .swapIn({$btn1, $btn2});

console.log($ele[0].innerHTML); // the desired resut, while keeping the jQuery objects handy for further manipulation

It satisfies the requirements of:

  • Easy to use/minimal boilterplate
  • Easy-ish to read (in my opinion)--can be read top-to-bottom
  • Chain friendly
  • Maintains jQuery objects
  • Allows anything that doesn't need to be jQuery-accessible (text, headers, whatever), to be a part of the template literal

Upvotes: 1

Racil Hilan
Racil Hilan

Reputation: 25351

Instead of find() it in string and then add properties to it, you can find() the p element and append the button to it. This way an id is not required like in the other case as you mentioned:

const $btn = $("<button>Click Me</button>").click(() => { /* complex handler... */ });
const $ele = $("<div><h1>Some content</h1><p>Press this button: </p></div>");
$ele.find("p").append($btn)

//This line is only for testing:
$ele.appendTo(document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

Upvotes: 1

Related Questions