ThinkGeek
ThinkGeek

Reputation: 5157

HTML String via jQuery append

I want to generate following HTML string using jQuery append. Writing code manually looks too cumbersome to me.

 <div>

    <label>Name (Optional)</label>
    <input type='text' class='form-control' id='job-name'/><br />

    <label>Quick Schedule</label><br />

    <a class="btn btn-primary" onclick="schedule = '@hourly'; job_string();">Hourly</a>
    <a class="btn btn-primary" onclick="schedule = '@daily'; job_string();">Daily</a>
    <a class="btn btn-primary" onclick="schedule = '@weekly'; job_string();">Weekly</a>
    <a class="btn btn-primary" onclick="schedule = '@monthly'; job_string();">Monthly</a>
    <a class="btn btn-primary" onclick="schedule = '@yearly'; job_string();">Yearly</a><br /><br />

    <div class="row">
        <div class="col-md-2">Minute</div>
        <div class="col-md-2">Hour</div>
        <div class="col-md-2">Day</div>
        <div class="col-md-2">Month</div>
        <div class="col-md-2">Week</div>
    </div>

    <div class="row">
        <div class="col-md-2"><input type="text" class="form-control" id="job-minute" value="*" onclick="this.select();"/></div>
        <div class="col-md-2"><input type="text" class="form-control" id="job-hour" value="*" onclick="this.select();"/></div>
        <div class="col-md-2"><input type="text" class="form-control" id="job-day" value="*" onclick="this.select();"/></div>
        <div class="col-md-2"><input type="text" class="form-control" id="job-month" value="*" onclick="this.select();"/></div>
        <div class="col-md-2"><input type="text" class="form-control" id="job-week" value="*" onclick="this.select();"/></div>
        <div class="col-md-2"><a class="btn btn-primary" onclick="set_schedule();">Set</a></div>
    </div>
  </div>

For example, it should be written something like in the format below.

$('<div/>').append(

        ).append(

        ) ....

What is the best possible way to create HTMLs like above using jQuery append? Lead here is really appreciated.

Upvotes: 7

Views: 1329

Answers (1)

fardjad
fardjad

Reputation: 20424

You can represent the HTML with a tree-like data structure. Once that's done, you can traverse the tree, and for every node, create the corresponding element and append it to your target element.

Functional style programming seems like a good fit for creating the aforementioned object with minimum amount of code. You can abstract creating complex structures with function composition. Additionally, you can use arrays and higher order functions (like map) to batch-create the elements.

To give you an idea of how it's done, consider the following model (interface) for representing the nodes (elements):

interface Node {
  tag: string;
  classNames: string[];
  attrs: {
    [key: string]: string;
  };
  eventHandlers: {
    [key: string]: (...params: any[]) => any;
  };
  children: Node[];
  textChildren: string[];
}

Note: The type definition above, is written in Typescript. Obviously, you can ignore types and implement what I described in plain JavaScript.

Now consider the following markup:

<div class="row">
    <div class="col-md-2"><input type="text" class="form-control" id="job-minute" value="*" onclick="this.select();"/></div>
    <div class="col-md-2"><input type="text" class="form-control" id="job-hour" value="*" onclick="this.select();"/></div>
    <div class="col-md-2"><input type="text" class="form-control" id="job-day" value="*" onclick="this.select();"/></div>
    <div class="col-md-2"><input type="text" class="form-control" id="job-month" value="*" onclick="this.select();"/></div>
    <div class="col-md-2"><input type="text" class="form-control" id="job-week" value="*" onclick="this.select();"/></div>
    <div class="col-md-2"><a class="btn btn-primary" onclick="set_schedule();">Set</a></div>
</div>

Let's define a few helper functions, so we can create the equivalent tree easier:

const createRow = (children) => ({
  tag: "div",
  classNames: ["row"],
  attrs: {},
  eventHandlers: {},
  children,
  textChildren: []
});

const createCol = (cls, children) => ({
  tag: "div",
  classNames: [cls],
  attrs: {},
  eventHandlers: {},
  children,
  textChildren: []
});

const createFormInput = (attrs, eventHandlers) => ({
  tag: "input",
  attrs,
  classNames: ["form-control"],
  eventHandlers,
  children: [],
  textChildren: []
});

const createFormInputTextInCol = id =>
  createCol("col-md-2", [
    createFormInput(
      {
        type: "text",
        id,
        value: "*"
      },
      {
        click() {
          this.select();
        }
      }
    )
  ]);

const createAnchorButton = (text, eventHandlers) => ({
  tag: "a",
  attrs: {},
  classNames: ["btn", "btn-primary"],
  eventHandlers,
  children: [],
  textChildren: [text]
});

Using the functions defined above, creating the equivalent tree is as easy as:

const row = createRow([
  ...["job-minute", "job-hour", "job-day", "job-month", "job-week"].map(
    createFormInputTextInCol
  ),
  createCol("col-md-2", [
    createAnchorButton("Set", {
      click() {
        // TODO: define set_schedule
        // set_schedule();
      }
    })
  ])
]);

And to convert this object to a (JQuery wrapped) element you can do:

const toElement = node => {
  const element = $(`<${node.tag}>`);
  Object.keys(node.attrs).forEach(key => {
    element.attr(key, node.attrs[key]);
  });
  element.addClass(node.classNames.join(" "));
  Object.keys(node.eventHandlers).forEach(key => {
    element.on(key, function(...args) {
      node.eventHandlers[key].call(this, ...args);
    });
  });
  node.textChildren.map(text => document.createTextNode(text)).forEach(e => element.append(e));
  node.children.map(toElement).forEach(e => element.append(e));
  return element;
};
$('<div />').append(toElement(row));

Demo

const createRow = (children) => ({
  tag: "div",
  classNames: ["row"],
  attrs: {},
  eventHandlers: {},
  children,
  textChildren: []
});

const createCol = (cls, children) => ({
  tag: "div",
  classNames: [cls],
  attrs: {},
  eventHandlers: {},
  children,
  textChildren: []
});

const createFormInput = (attrs, eventHandlers) => ({
  tag: "input",
  attrs,
  classNames: ["form-control"],
  eventHandlers,
  children: [],
  textChildren: []
});

const createFormInputTextInCol = id =>
  createCol("col-md-2", [
    createFormInput({
      type: "text",
      id,
      value: "*"
    }, {
      click() {
        this.select();
      }
    })
  ]);

const createAnchorButton = (text, eventHandlers) => ({
  tag: "a",
  attrs: {},
  classNames: ["btn", "btn-primary"],
  eventHandlers,
  children: [],
  textChildren: [text]
});

const row = createRow([
  ...["job-minute", "job-hour", "job-day", "job-month", "job-week"].map(
    createFormInputTextInCol
  ),
  createCol("col-md-2", [
    createAnchorButton("Set", {
      click() {
        // TODO: define set_schedule
        // set_schedule();
      }
    })
  ])
]);

const toElement = node => {
  const element = $(`<${node.tag}>`);
  Object.keys(node.attrs).forEach(key => {
    element.attr(key, node.attrs[key]);
  });
  element.addClass(node.classNames.join(" "));
  Object.keys(node.eventHandlers).forEach(key => {
    element.on(key, function(...args) {
      node.eventHandlers[key].call(this, ...args);
    });
  });
  node.textChildren.map(text => document.createTextNode(text)).forEach(e => element.append(e));
  node.children.map(toElement).forEach(e => element.append(e));
  return element;
};

$(document).ready(() => {
  const rowElement = toElement(row);
  $("#wrapper").html(rowElement);
  $("#outerHtml").text($("#wrapper").html());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<h2>Generated HTML</h2>
<pre id="outerHtml"></pre>

<h2>Appended Element</h2>
<div id="wrapper"></div>

Upvotes: 6

Related Questions