Skip to content

The static website with vanilla javascript

Olav Grønås Gjerde edited this page Oct 6, 2018 · 5 revisions

Introduction

In this example I will show how to build something very simple. A table with books rendered with vanilla javascript. More dynamic functionality will be implemented in example3

Object design

First we start with a model for authors:

class AuthorModel{

	/**
	 * @param {id: Number, name: String}
	 * @return AuthorModel
	 */
	constructor(obj){
		this.updateProperties(obj);
	}

	/**
	 * Map properties to this instance
	 *
	 * @param {id: Number, name: String}
	 * @return void
	 */
	updateProperties(obj){
		this.id = obj.id;
		if(obj.name) this.name = obj.name;
	}

}

We also need a model for books:

class BookModel{

	/**
	 * @param {id: Number, author: String, title: String, isbn: String}
	 * @return AuthorModel
	 */
	constructor(obj) {
		this.updateProperties(obj);
	}

	/**
	 * Map properties to this instance
	 *
	 * @param {id: Number, author: String, title: String, isbn: String}
	 * @return void
	 */
	updateProperties(obj) {
		this.id = obj.id;
		if (obj.author) this.author = obj.author;
		if (obj.title) this.title = obj.title;
		if (obj.isbn) this.isbn = obj.isbn;
	}

	/**
	 * Get a list of properties for this class
	 *
	 * @returns {string[]}
	 */
	static getFields() {
		return ['id', 'title', 'isbn', 'author'];
	}
}

Notice the updateProperties method. The idea behind this is that when you need to update an instance of this class you run this method. I will explain more later why I've done this. But for now don't worry too much.

Another thing I would like to point out. When I update an instance I want to update all fields. The reason for this is simplicity. There is no reason to over-engineer with setters and getters. It will also make more sense later.

Finally we need to implement the table component

/**
 * Book table component. We call this a component as its behaviour is a
 * reusable component for web composition.
 *
 * With this design it is also easier to map it over to a true web-component,
 * which will hopefully soon become a standard in all the major browsers.
 */
class BookTableComponent{

	constructor(obj){
		this.containerElement = obj.containerElement;
		this.fields = BookModel.getFields();
		this.updateProperties(obj);
		this.buildDOMElements();
		this.render();
	}

	updateProperties(obj) {
		this.books = obj.books;
	}

	buildDOMElements() {
		this.tableElement = document.createElement('TABLE');

		this.tableHeaderElement = this.tableElement.createTHead();

		// There is no createTBody function
		this.tableBodyElement = document.createElement('TBODY');
		this.tableElement.appendChild(this.tableBodyElement);
	}

	renderHead(){
		// map() will loop the fields property and create the <th> elements
		this.tableHeaderElement.innerHTML = `
			<tr>
				${this.fields.map(item => `<th>${item}</th>`).join('')}
			</tr>
		`;
	}

	renderBody(){
		this.tableBodyElement.innerHTML = `
			${this.books.map(book => `
				<tr>
					<td>${book.id}</td>
					<td>${book.title}</td>
					<td>${book.isbn}</td>
					<td>${book.author.name}</td>
				</tr>
			`).join('')}
		`;
	}

	render(){
		this.renderHead();
		this.renderBody();

		this.containerElement.innerHTML = "";
		this.containerElement.appendChild(this.tableElement);
	}

}

This is all the code you need to render books into a table. We build the DOM elements, and render the table header and table body. The most interesting part here is the template literals with a loop using map().

All you need now is a html page to show the results. Lets create books.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>List of books</title>
	<link type="text/css" rel="stylesheet" href="css/style.css"/>
</head>
<body>

<section>
	<h1>Book Administration with Vanilla JS</h1>
	<div data-container="books-table"></div>
</section>

<script type="application/javascript" src="js/application.js"></script>
<script type="application/javascript">
	
	// Create book objects
	let books = [
		new BookModel({
			id: 1,
			title: 'Horror stories with React',
			author: new AuthorModel({name: 'Alfred Angular'}),
			isbn: "1111-222222-6666"
		}),
		new BookModel({
			id: 2,
			title: 'The Angular Fiasco',
			author: new AuthorModel({name: 'Robert React'}),
			isbn: "1331-123456-7777"
		}),
		new BookModel({
			id: 3,
			title: 'The Vue Skyfall',
			author: new AuthorModel({name: 'Vanessa Vanilla'}),
			isbn: "1411-987654-6666"
		}),
		new BookModel({
			id: 4,
			title: 'jQuery the forgotten gem',
			author: new AuthorModel({name: 'Ivar Explorer'}),
			isbn: "2111-123789-5678"
		}),
		new BookModel({
			id: 5,
			title: 'Angular Reboot 3',
			author: new AuthorModel({name: 'Steven Syntax'}),
			isbn: "3311-000112-4545"
		})
	];

	// Find all DOM elements which we want to render to
	const bookTables =
		document.querySelectorAll('[data-container="books-table"]');
	for(let i = 0; i < bookTables.length; i++){
		// Initialize the table component
		new BookTableComponent({
			books: books,
			containerElement: bookTables[i]
		});
	}
</script>
</body>
</html>

Check the example1 folder for the complete working source code. When all this is implemented you will have the following table: books table

Next step is to implement state management. We need to make it possible to add, edit and delete books and reflect changes in the DOM. Head over to example2.