I recently switched to modular JavaScript and really like the idea of having the state of your application in JavaScript and not in the DOM. I want to know if what I am doing is considered best practices or not and how I can avoid re-rendering the entire list after each change.
First, the code:
HTML
<form id="add-user-form">
<input type="text" placeholder="username" name="username">
<button>add User</button>
</form>
<hr>
<ul id="user-list"></ul>
<script id="user-template" type="text/html">
<li data-key="${key}">${name}</li>
</script>
JavaScript
// lets you get templates from HTML ( as you can see in the HTML above ).
// This is to avoid writing out HTML inside JavaScript
const Template = {
cached: {},
get(templateId, placeholders) {
let templateHtml = ''
if (!this.cached[templateId]) {
templateHtml = document.getElementById(templateId).innerHTML.trim()
this.cached[templateId] = templateHtml
}
else {
templateHtml = this.cached[templateId]
}
for (key in placeholders) {
templateHtml = templateHtml.replace(new RegExp("\\${\\s*" + key + "\\s*}", "g"), placeholders[key]);
}
return templateHtml
}
}
const Users = {
users: [],
init() {
this.cacheDom()
this.bindEvents()
},
cacheDom() {
this.addUserFormEl = document.getElementById('add-user-form')
this.userListEl = document.getElementById('user-list')
},
bindEvents() {
this.addUserFormEl.addEventListener('submit', this.handleAddUser.bind(this))
},
render() {
// gets the HTML from ALL users of our user list and replaces the current user list
const userHtml = this.users.map( user => Template.get('user-template', user) ).join('')
this.userListEl.innerHTML = userHtml
},
handleAddUser(e) {
e.preventDefault()
const username = e.currentTarget.elements['username'].value
if (!username) return
e.currentTarget.elements['username'].value = ''
const key = this.users.length // I know I know this is bad practice, please ignore :)
this.users.push({
key: key,
name: username
})
this.render()
}
}
Users.init()
The code has a form with an input field where you can type in a name that, once the form gets submitted, will be added to the user list below. It would probably be a good idea to separate the userlist and the userform into seperate concerns, but for the sake of this example I kept it simple.
I want to keep DOM modifications to a minimum so I put them inside the method Users.render. This however has one side-effect. Whenever I add an user, it reloads the entire list. It might work with this amount of data, but let's say I have thousands of records, maybe even with input fields. These would all be emptied again.
I could of course just add the new entry to the DOM inside the Users.handleAddUser method but what if later I also want to delete users? I would have to delete the entry within Users.handleRemoveUser and would then have two methods that handle DOM modifications for the same thing.
I know this all can be easily and beautifully achieved with frameworks such as VueJs that use a vDOM but I am looking for a vanillaJs way of handling this. (Please note that the code is just an example. Yes Unit testing and typeScript are missing and I am well aware of the fact that the code will not run in browsers that do not support ES-6).