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>
clone()andappend()it when needed. I'd also advise against the use ofon*event attributes and global variables. Use delegated handlers instead.