I recently built a text editor web app in vanilla Javascript, and after coding for a few hours, I quickly became tired of constantly creating DOM elements. I was creating the elements on the fly because they required dynamic IDs, Event listeners and content every time they were rendered. I quickly realized that a template system would be better for my needs and clean up my classes.
我最近用香草Javascript构建了一个文本编辑器Web应用程序,在编码了几个小时之后,我很快就厌倦了不断创建DOM元素。 我正在动态创建元素,因为它们每次渲染时都需要动态ID,事件侦听器和内容。 我很快意识到模板系统会更好地满足我的需求并清理班级。
I decided to build my own templating engine which I decided to call DOMinator just for fun. I will walk through the basic steps I took and the code that I used to create this.
我决定构建自己的模板引擎,出于娱乐性考虑,我决定将其称为DOMinator。 我将逐步介绍我所采取的基本步骤以及用于创建此代码的代码。
First I wanted to create a simple format for templates so I created a new file called templates.js
in which I have a single object Templates
filled with each of the templates I use. I built the templates as functions so I can pass variables to them. this is a template for my Doc Icons.
首先,我想为模板创建一种简单的格式,所以我创建了一个名为templates.js
的新文件,在其中我有一个对象Templates
填充了我使用的每个模板。 我将模板构建为函数,以便可以将变量传递给它们。 这是我的文档图标的模板。
const Templates = {
docIcon: function(obj) {
return {
tag: 'div',
id: obj.id,
classes: ['doc-icon'],
properties: {
title: obj.name,
tabIndex: 0
},
children: [
{
tag: 'div',
classes: ['doc-icon-header'],
children: [
{ tag: 'h3', content: obj.name }
]
},
{
tag: 'p',
content: obj.exerp
},
{
tag: 'div',
id: 'docControls',
classes: ['doc-controls'],
children: [
{
tag: 'button',
classes: ['doc-control'],
id: 'moveDoc',
children: [
{
tag: 'i',
classes: ['fa', 'fa-folder']
}
]
},
{
tag: 'button',
classes: ['doc-control'],
id: 'deleteDoc',
children: [
{
tag: 'i',
classes: ['fa', 'fa-trash']
}
]
}
]
}
]
}
}
As you can see, the tag, id, and classes are explicitly defined as keys for our objects, but anything else is passed in the properties key. We also have an array of children, with each child having the same format as the parent. In theory we could infinitely nest DOM elements with this format.
如您所见,标记,标识和类被明确定义为对象的键,但是其他任何内容都通过属性键传递。 我们也有一系列子代,每个子代与父代的格式相同。 从理论上讲,我们可以使用这种格式无限嵌套DOM元素。
Once we have this simple Javascript Object format for our templates, it’s easy to build a ton of them pretty quickly, but how do we convert to HTML and add to the DOM?
一旦我们的模板有了这种简单的Javascript对象格式,就可以很容易地快速构建大量的模板,但是我们如何转换为HTML并添加到DOM中呢?
I created a class called Dominator
with instances representing a DOM element as an object. An instance would look very much like our template, except the dynamic values would be applied. I just use Object.assign()
to essentially replicate the template.
我创建了一个名为Dominator
的类,其实例将DOM元素表示为一个对象。 实例看起来非常类似于我们的模板,只是将应用动态值。 我只是使用Object.assign()
从本质上复制模板。
class Dominator {
constructor(object) {
Object.assign(this, object)
}
}
Next we need to figure out how to create the children. We can use a for loop to recursively create new instances of the class for the children. We then push the children into an empty initialized array, and after looping through we assign this.children
to equal our array.
接下来,我们需要弄清楚如何创建子代。 我们可以使用for循环为子代递归创建类的新实例。 然后,我们将子级推入一个空的初始化数组,并在循环之后将this.children
分配为等于我们的数组。
----
constructor(object) {
Object.assign(this, object)
let childObjects = []
if (object.children){
for (const child of object.children) {
let childObject = new Dominator(child)
childObjects.push(childObject)
}
}
this.children = childObjects
this.element = this.domElement
}
----
So far we have an object that is a copy of the template with our content applied to it. Not useful yet. Next up we need to turn these into DOM elements. I created a get function called domElement()
to return a generated DOM element. I used get so we can update our properties and regenerate the element whenever we want.
到目前为止,我们有一个对象,它是模板的副本,其内容已应用到模板。 还没有用。 接下来,我们需要将它们变成DOM元素。 我创建了一个称为domElement()
的get函数,以返回生成的DOM元素。 我使用了get,以便我们可以随时更新属性并重新生成元素。
The first part of the function creates the element and sets the basic properties.
函数的第一部分创建元素并设置基本属性。
get domElement() {
const domElement = document.createElement(this.tag)
if (this.id) domElement.id = this.id
if (this.content) domElement.innerText = this.content
----- return domElement
}
The next three things that need to happen are iterate and assign properties, iterate and set classes, and iterate and append children to our domElement.
接下来需要进行的三件事是:迭代和分配属性,迭代和设置类,以及将子代迭代并追加到domElement中。
get domElement() {
const domElement = document.createElement(this.tag)
if (this.id) domElement.id = this.id
if (this.content) domElement.innerText = this.content if (this.properties) for (const prop in this.properties) {
domElement[prop] = this.properties[prop]
}
if (this.classes) for (const cssClass of this.classes) {
domElement.classList.add(cssClass)
}
if (this.children) for (const child of this.children) {
domElement.append(child.domElement)
if (child.id) this[child.id] = child.domElement
}
this.element = domElement return domElement
}
As you can see, we are conditionally checking everything to be sure it exists in our template and then applying those things to our domElement and then returning it.
如您所见,我们将有条件地检查所有内容以确保其存在于模板中,然后将这些内容应用于domElement并返回它。
Our full class looks like this so far:
到目前为止,我们的全班看起来像这样:
class Dominator {
constructor(object) {
Object.assign(this, object)
let childObjects = []
if (object.children){
for (const child of object.children) {
let childObject = new Dominator(child)
childObjects.push(childObject)
}
}
this.children = childObjects
this.element = this.domElement
}
get domElement() {
const domElement = document.createElement(this.tag)
if (this.id) domElement.id = this.id
if (this.content) domElement.innerText = this.content
if (this.properties) for (const prop in this.properties) {
domElement[prop] = this.properties[prop]
}
if (this.classes) for (const cssClass of this.classes) {
domElement.classList.add(cssClass)
}
if (this.children) for (const child of this.children) {
domElement.append(child.domElement)
if (child.id) this[child.id] = child.domElement
}
this.element = domElement
return domElement
}
}
Let’s see it in action!
让我们看看它的作用!
get docIcon() {
const dominator = new Dominator(Templates.docIcon(this))
const div = dominator.domElement
div.addEventListener('click', this.openDoc.bind(this))
div.querySelector('#deleteDoc').addEventListener('click', this.deleteDoc.bind(this))
div.querySelector('#moveDoc').addEventListener('click', this.moveDoc.bind(this))
this.icon = div
return div
}
How can we simplify the event listener process? Let’s add a new function called findChildById()
.
我们如何简化事件监听器过程? 让我们添加一个名为findChildById()
的新函数。
findChildById(id) {
if (this.children) {
for (const child of this.children) {
if (child.id === id) {
return child
} else if (child.children) {
let found = child.findChildById(id)
if (found) return found
}
}
}
return false
}
Using a little recursion we can find the child instance of Dominator to make changes to it. Now we can create an array called this.eventListeners = []
in our constructor which we will add our event listeners to be assigned to the DOM element later on.
使用一点递归,我们可以找到Dominator的子实例来对其进行更改。 现在,我们可以在构造函数中创建一个名为this.eventListeners = []
的数组, this.eventListeners = []
将添加事件监听器以分配给DOM元素。
To add events I wanted it to be as simple as possible, so the callback is first, then the optional ID and finally the event type which defaults to click for my convenience. The ID argument assumes we are looking for a child with that ID.
为了添加事件,我希望它尽可能简单,所以首先是回调,然后是可选ID,最后是事件类型,为方便起见,默认单击该事件类型。 ID参数假设我们正在寻找具有该ID的孩子。
event(action, id, type = 'click') {
if (id) {
const node = this.findChildById(id)
if (node) {
node.eventListeners.push({type: type, action: action})
}
} else {
this.eventListeners.push({type: type, action: action})
}
}
Finally in our get domElement()
function, we need the following:
最后,在我们的get domElement()
函数中,我们需要以下内容:
if (this.eventListeners) for (const eventListener of this.eventListeners) {
domElement.addEventListener(eventListener.type, eventListener.action)
}
Now we can refactor our code from earlier:
现在,我们可以从早期重构代码:
get docIcon() {
const dominator = new Dominator(Templates.docIcon(this))
dominator.event(this.openDoc.bind(this))
dominator.event(this.deleteDoc.bind(this), 'deleteDoc')
dominator.event(this.moveDoc.bind(this), 'moveDoc')
const div = dominator.domElement
this.icon = div
return div
}
Viola! Much cleaner.
中提琴! 清洁得多。
Here is the entire code for our Dominator:
这是我们统治者的全部代码:
class Dominator {
constructor(object) {
Object.assign(this, object)
let childObjects = []
if (object.children){
for (const child of object.children) {
let childObject = new Dominator(child)
childObjects.push(childObject)
}
}
this.eventListeners = []
this.children = childObjects
this.element = this.domElement
}
get domElement() {
const domElement = document.createElement(this.tag)
if (this.id) domElement.id = this.id
if (this.content) domElement.innerText = this.content
if (this.properties) for (const prop in this.properties) {
domElement[prop] = this.properties[prop]
}
if (this.classes) for (const cssClass of this.classes) {
domElement.classList.add(cssClass)
}
if (this.children) for (const child of this.children) {
domElement.append(child.domElement)
if (child.id) this[child.id] = child.domElement
}
if (this.eventListeners) for (const eventListener of this.eventListeners) {
domElement.addEventListener(eventListener.type, eventListener.action)
}
this.element = domElement
return domElement
}
findChildById(id) {
if (this.children) {
for (const child of this.children) {
if (child.id === id) {
return child
} else if (child.children) {
let found = child.findChildById(id)
if (found) return found
}
}
}
return false
}
event(action, id, type = 'click') {
if (id) {
const node = this.findChildById(id)
if (node) {
node.eventListeners.push({type: type, action: action})
}
} else {
this.eventListeners.push({type: type, action: action})
}
}
}
翻译自: https://medium.com/@altmod/how-to-create-a-javascript-html-generator-439f52b88ccc