commit f8464192e4745a74f2dd50bb5e888d56605aeaef
Author: Pavel Fidranský <fidransky@outlook.com>
Date:   Tue Oct 23 21:06:06 2018 +0200

    reworked LoginPopup and RegisterPopup to extend newly created Popup JS class

diff --git a/sources/src/main/webapp/css/main.css b/sources/src/main/webapp/css/main.css
index 85f3e6c..e46ba95 100644
--- a/sources/src/main/webapp/css/main.css
+++ b/sources/src/main/webapp/css/main.css
@@ -17,6 +17,7 @@ td {
 }
 
 form input[type="text"],
+form input[type="email"],
 form input[type="password"] {
 	width: 150px;
 }
diff --git a/sources/src/main/webapp/js/components/loginPopup.js b/sources/src/main/webapp/js/components/loginPopup.js
new file mode 100644
index 0000000..1c33bbd
--- /dev/null
+++ b/sources/src/main/webapp/js/components/loginPopup.js
@@ -0,0 +1,127 @@
+class LoginPopup extends Popup {
+	/**
+	 * @inheritdoc
+	 */
+	render() {
+		super.render();
+
+		this._rootElement.classList.add('login_popup');
+
+		this._usernameInput = DOM.h('input', {
+			type: 'text',
+			name: 'username',
+			id: 'loginUsername',
+		});
+
+		this._rootElement.appendChild(DOM.h('form', {
+			action: Constants.API.logIn,
+			method: 'post',
+			onSubmit: this._onFormSubmit.bind(this),
+		}, [
+			DOM.h('table', {}, [
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'loginUsername',
+							innerText: 'Login name:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						this._usernameInput,
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'loginPassword',
+							innerText: 'Password:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						DOM.h('input', {
+							type: 'password',
+							name: 'password',
+							id: 'loginPassword',
+						}),
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td'),
+					DOM.h('td', {}, [
+						DOM.h('button', {
+							type: 'submit',
+							class: 'button',
+							innerText: 'Log in',
+						})
+					]),
+				]),
+			]),
+		]));
+
+		return this._rootElement;
+	}
+
+	/**
+	 * Opens the popup and sets focus on username field.
+	 */
+	open() {
+		super.open();
+
+		this._usernameInput.focus();
+	}
+
+	/**
+	 * Logs the user in.
+	 * @param {Event} e Form submit event.
+	 */
+	async _onFormSubmit(e) {
+		e.preventDefault();
+
+		const body = new URLSearchParams;
+		body.set('username', e.target.username.value);
+		body.set('password', e.target.password.value);
+
+		try {
+			const response = await AJAX.do(e.target.action, {
+				method: e.target.method,
+				credentials: 'same-origin',
+				body,
+			});
+			const data = await response.json();
+
+			document.dispatchEvent(new CustomEvent('imiger.userLoggedIn', {
+				detail: data,
+			}));
+
+			document.body.classList.remove('loggedOut');
+			document.body.classList.add('loggedIn');
+
+			this.close();
+
+		} catch (error) {
+			if (error instanceof HttpError) {
+				switch (error.response.status) {
+					case 400:
+						this._printErrors(error.response);
+						break;
+					case 401:
+						alert('Invalid credentials.');
+						break;
+					default:
+						alert('Something went wrong.');
+				}
+			} else {
+				alert('Server has probably gone away.');
+			}
+		}
+	}
+
+	async _printErrors(response) {
+		const data = await response.json();
+		for (let key in data.error) {
+			if (data.error.hasOwnProperty(key) === false) continue;
+
+			alert(data.error[key]);
+		}
+	}
+}
diff --git a/sources/src/main/webapp/js/components/registerPopup.js b/sources/src/main/webapp/js/components/registerPopup.js
new file mode 100644
index 0000000..f5f415d
--- /dev/null
+++ b/sources/src/main/webapp/js/components/registerPopup.js
@@ -0,0 +1,168 @@
+class RegisterPopup extends Popup {
+	/**
+	 * @inheritdoc
+	 */
+	render() {
+		super.render();
+
+		this._rootElement.classList.add('register_popup');
+
+		this._nameInput = DOM.h('input', {
+			'type': 'text',
+			'name': 'name',
+			'id': 'registerName',
+		});
+
+		this._rootElement.appendChild(DOM.h('form', {
+			'action': Constants.API.register,
+			'method': 'post',
+			'onSubmit': this._onFormSubmit.bind(this),
+		}, [
+			DOM.h('table', {}, [
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'registerName',
+							innerText: 'Name:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						this._nameInput,
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'registerEmail',
+							innerText: 'Email:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						DOM.h('input', {
+							type: 'email',
+							name: 'email',
+							id: 'registerEmail',
+						}),
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'registerUsername',
+							innerText: 'Username:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						DOM.h('input', {
+							type: 'text',
+							name: 'username',
+							id: 'registerUsername',
+						}),
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'registerPassword',
+							innerText: 'Password:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						DOM.h('input', {
+							type: 'password',
+							name: 'password',
+							id: 'registerPassword',
+						}),
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td', {}, [
+						DOM.h('label', {
+							for: 'registerPasswordCheck',
+							innerText: 'Password again:',
+						}),
+					]),
+					DOM.h('td', {}, [
+						DOM.h('input', {
+							type: 'password',
+							name: 'passwordCheck',
+							id: 'registerPasswordCheck',
+						}),
+					]),
+				]),
+				DOM.h('tr', {}, [
+					DOM.h('td'),
+					DOM.h('td', {}, [
+						DOM.h('button', {
+							type: 'submit',
+							class: 'button',
+							innerText: 'Register',
+						})
+					]),
+				]),
+			]),
+		]));
+
+		return this._rootElement;
+	}
+
+	/**
+	 * Opens the popup and sets focus on name field.
+	 */
+	open() {
+		super.open();
+
+		this._nameInput.focus();
+	}
+
+	/**
+	 * Signs the user up.
+	 * @param {Event} e Form submit event.
+	 */
+	async _onFormSubmit(e) {
+		e.preventDefault();
+
+		const body = new URLSearchParams;
+		body.set('name', e.target.name.value);
+		body.set('email', e.target.email.value);
+		body.set('username', e.target.username.value);
+		body.set('password', e.target.password.value);
+		body.set('passwordCheck', e.target.passwordCheck.value);
+
+		try {
+			await AJAX.do(e.target.action, {
+				method: e.target.method,
+				credentials: 'same-origin',
+				body,
+			});
+
+			document.dispatchEvent(new CustomEvent('imiger.userRegistered'));
+
+			this.close();
+			alert('You were successfully registered.');
+
+		} catch (error) {
+			if (error instanceof HttpError) {
+				switch (error.response.status) {
+					case 400:
+						this._printErrors(error.response);
+						break;
+					default:
+						alert('Something went wrong.');
+				}
+			} else {
+				alert('Server has probably gone away.');
+			}
+		}
+	}
+
+	async _printErrors(response) {
+		const data = await response.json();
+		for (let key in data.error) {
+			if (data.error.hasOwnProperty(key) === false) continue;
+
+			alert(data.error[key]);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/sources/src/main/webapp/js/constants.js b/sources/src/main/webapp/js/constants.js
index e846d18..75f614b 100644
--- a/sources/src/main/webapp/js/constants.js
+++ b/sources/src/main/webapp/js/constants.js
@@ -10,6 +10,7 @@ Constants.notFoundVertexName = 'NOT_FOUND';
 /** @static @prop {object} API Map of application programming interface paths. */
 Constants.API = {
 	logIn: 'api/log-in',
+	logOut: 'api/log-out',
 	register: 'api/register',
 	loadGraphData: 'api/load-graph-data',
 	getDiagram: 'api/get-diagram',
diff --git a/sources/src/main/webapp/js/showGraphApp.js b/sources/src/main/webapp/js/showGraphApp.js
index 73eb756..c481269 100644
--- a/sources/src/main/webapp/js/showGraphApp.js
+++ b/sources/src/main/webapp/js/showGraphApp.js
@@ -139,6 +139,16 @@ function ShowGraphApp() {
 		self.modalWindowComponent = new ModalWindow;
 		content.appendChild(self.modalWindowComponent.render());
 
+		// auth events
+		const usernameLabel = document.getElementById('usernameLabel');
+
+		document.addEventListener('imiger.userLoggedIn', e => {
+			usernameLabel.innerText = e.detail.user.username;
+		});
+		document.addEventListener('imiger.userLoggedOut', () => {
+			usernameLabel.innerText = '';
+		});
+
 		// context menu
 		document.body.addEventListener('mousedown', function() {
 			self.closeFloatingComponents();
diff --git a/sources/src/main/webapp/js/uploadFilesApp.js b/sources/src/main/webapp/js/uploadFilesApp.js
index 174440e..fb8f115 100644
--- a/sources/src/main/webapp/js/uploadFilesApp.js
+++ b/sources/src/main/webapp/js/uploadFilesApp.js
@@ -14,8 +14,17 @@ class UploadFilesApp extends App {
 			button.addEventListener('click', this._removeDiagram);
 		});
 
-		document.addEventListener('imiger.userLoggedIn', () => this._loadPrivateDiagrams());
-		document.addEventListener('imiger.userLoggedOut', () => privateDiagramList.innerHTML = '');
+		// auth events
+		const usernameLabel = document.getElementById('usernameLabel');
+
+		document.addEventListener('imiger.userLoggedIn', e => {
+			this._loadPrivateDiagrams();
+			usernameLabel.innerText = e.detail.user.username;
+		});
+		document.addEventListener('imiger.userLoggedOut', () => {
+			privateDiagramList.innerHTML = '';
+			usernameLabel.innerText = '';
+		});
 	}
 
 	/**
diff --git a/sources/src/main/webapp/js/userMenu.js b/sources/src/main/webapp/js/userMenu.js
index 18dd262..8910b01 100644
--- a/sources/src/main/webapp/js/userMenu.js
+++ b/sources/src/main/webapp/js/userMenu.js
@@ -1,39 +1,29 @@
 document.addEventListener('DOMContentLoaded', function() {
-	var toggleLoginPopupButton = document.getElementById('toggleLoginPopupButton');
-	var toggleRegisterPopupButton = document.getElementById('toggleRegisterPopupButton');
-	var usernameLabel = document.getElementById('usernameLabel');
-	var logoutButton = document.getElementById('logoutButton');
+	const loginPopup = new LoginPopup;
+	const registerPopup = new RegisterPopup;
 
-	var loginPopup = document.getElementById('loginPopup');
-	var registerPopup = document.getElementById('registerPopup');
+	const header = document.getElementById('header');
+	header.appendChild(loginPopup.render());
+	header.appendChild(registerPopup.render());
 
-	var loginForm = document.forms['loginForm'];
-	var registerForm = document.forms['registerForm'];
-
-	toggleLoginPopupButton.addEventListener('click', function() {
-		registerPopup.classList.add('hidden');
-		loginPopup.classList.toggle('hidden');
-
-		loginForm.username.focus();
+	document.getElementById('toggleLoginPopupButton').addEventListener('click', function() {
+		registerPopup.close();
+		loginPopup.toggle();
 	});
 
-	toggleRegisterPopupButton.addEventListener('click', function() {
-		loginPopup.classList.add('hidden');
-		registerPopup.classList.toggle('hidden');
-
-		registerForm.name.focus();
+	document.getElementById('toggleRegisterPopupButton').addEventListener('click', function() {
+		loginPopup.close();
+		registerPopup.toggle();
 	});
 
-	logoutButton.addEventListener('click', async function(e) {
+	document.getElementById('logoutButton').addEventListener('click', async function(e) {
 		e.preventDefault();
 
 		try {
-			await AJAX.get(logoutButton.href);
+			await AJAX.get(Constants.API.logOut);
 
 			document.dispatchEvent(new CustomEvent('imiger.userLoggedOut'));
 
-			usernameLabel.innerText = '';
-
 			document.body.classList.remove('loggedIn');
 			document.body.classList.add('loggedOut');
 
@@ -45,92 +35,4 @@ document.addEventListener('DOMContentLoaded', function() {
 			}
 		}
 	});
-
-	loginForm.addEventListener('submit', async function(e) {
-		e.preventDefault();
-
-		const body = new URLSearchParams;
-		body.set('username', loginForm.username.value);
-		body.set('password', loginForm.password.value);
-
-		try {
-			const response = await AJAX.do(loginForm.action, {
-				method: loginForm.method,
-				credentials: 'same-origin',
-				body,
-			});
-			const data = await response.json();
-
-			document.dispatchEvent(new CustomEvent('imiger.userLoggedIn'));
-
-			usernameLabel.innerText = data.user.username;
-
-			document.body.classList.remove('loggedOut');
-			document.body.classList.add('loggedIn');
-
-			loginPopup.classList.add('hidden');
-
-		} catch (error) {
-			if (error instanceof HttpError) {
-				switch (error.response.status) {
-					case 400:
-						printErrors(error.response);
-						break;
-					case 401:
-						alert('Invalid credentials.');
-						break;
-					default:
-						alert('Something went wrong.');
-				}
-			} else {
-				alert('Server has probably gone away.');
-			}
-		}
-	});
-
-	registerForm.addEventListener('submit', async function(e) {
-		e.preventDefault();
-
-		const body = new URLSearchParams;
-		body.set('name', registerForm.name.value);
-		body.set('email', registerForm.email.value);
-		body.set('username', registerForm.username.value);
-		body.set('password', registerForm.password.value);
-		body.set('passwordCheck', registerForm.passwordCheck.value);
-
-		try {
-			await AJAX.do(registerForm.action, {
-				method: registerForm.method,
-				credentials: 'same-origin',
-				body,
-			});
-
-			document.dispatchEvent(new CustomEvent('imiger.userRegistered'));
-
-			registerPopup.classList.add('hidden');
-			alert('You were successfully registered.');
-
-		} catch (error) {
-			if (error instanceof HttpError) {
-				switch (error.response.status) {
-					case 400:
-						printErrors(error.response);
-						break;
-					default:
-						alert('Something went wrong.');
-				}
-			} else {
-				alert('Server has probably gone away.');
-			}
-		}
-	});
 });
-
-async function printErrors(response) {
-	const data = await response.json();
-	for (let key in data.error) {
-		if (data.error.hasOwnProperty(key) === false) continue;
-
-		alert(data.error[key]);
-	}
-}
diff --git a/sources/src/main/webapp/showGraph.jsp b/sources/src/main/webapp/showGraph.jsp
index 394a046..5fd9669 100644
--- a/sources/src/main/webapp/showGraph.jsp
+++ b/sources/src/main/webapp/showGraph.jsp
@@ -21,14 +21,19 @@
 		<script src="js/libs/saveSvgAsPng.js"></script>
 		<script src="js/libs/jstree.min.js"></script>
 
+		<script src="js/components/generic/modalWindow.js"></script>
+		<script src="js/components/generic/popover.js"></script>
+		<script src="js/components/generic/popup.js"></script>
 		<script src="js/components/attribute.js"></script>
 		<script src="js/components/edge.js"></script>
 		<script src="js/components/edgePopover.js"></script>
 		<script src="js/components/floatingPoint.js"></script>
 		<script src="js/components/group.js"></script>
 		<script src="js/components/groupVertexList.js"></script>
+		<script src="js/components/loginPopup.js"></script>
 		<script src="js/components/minimap.js"></script>
 		<script src="js/components/modalWindow.js"></script>
+		<script src="js/components/registerPopup.js"></script>
 		<script src="js/components/sidebar.js"></script>
 		<script src="js/components/sidebarExcludedNodeList.js"></script>
 		<script src="js/components/sidebarUnconnectedNodeList.js"></script>
diff --git a/sources/src/main/webapp/uploadFiles.jsp b/sources/src/main/webapp/uploadFiles.jsp
index f1e6ea9..5400092 100644
--- a/sources/src/main/webapp/uploadFiles.jsp
+++ b/sources/src/main/webapp/uploadFiles.jsp
@@ -13,10 +13,18 @@
 
 		<link rel="stylesheet" href="css/main.css">
 
+		<script id="htmlTags" type="application/json"><%@ include file="node_modules/html-tags/html-tags.json" %></script>
+		<script id="svgTags" type="application/json"><%@ include file="node_modules/svg-tags/lib/svg-tags.json" %></script>
+
+		<script src="js/components/generic/popup.js"></script>
+		<script src="js/components/loginPopup.js"></script>
+		<script src="js/components/registerPopup.js"></script>
+
 		<script src="js/errors/abstractMethodError.js"></script>
 		<script src="js/errors/httpError.js"></script>
 
 		<script src="js/utils/ajax.js"></script>
+		<script src="js/utils/dom.js"></script>
 
 		<script src="js/constants.js"></script>
 
diff --git a/sources/src/main/webapp/userMenu.jsp b/sources/src/main/webapp/userMenu.jsp
index 0bc0dfd..ffbf89f 100644
--- a/sources/src/main/webapp/userMenu.jsp
+++ b/sources/src/main/webapp/userMenu.jsp
@@ -1,92 +1,9 @@
 <div class="user-menu loggedInOnly">
-	<span id="usernameLabel">${user.username}</span> <a href="api/log-out" class="button" id="logoutButton">Log out</a>
+	<span id="usernameLabel">${user.username}</span>
+	<button class="button" id="logoutButton">Log out</button>
 </div>
 
 <div class="user-menu loggedOutOnly">
-	<button class="button" id="toggleLoginPopupButton">Log in</a>
-	<button class="button" id="toggleRegisterPopupButton">Register</a>
-</div>
-
-<div class="login_popup hidden" id="loginPopup">
-	<form action="api/log-in" method="post" name="loginForm">
-		<div class="err_msg"></div>
-		<div class="not_msg"></div>
-		<table>
-			<tr>
-				<td>
-					<label for="loginUsername">Login name:</label>
-				</td>
-				<td>
-					<input type="text" name="username" id="loginUsername">
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<label for="loginPassword">Password:</label>
-				</td>
-				<td>
-					<input type="password" name="password" id="loginPassword">
-				</td>
-			</tr>
-			<tr>
-				<td></td>
-				<td>
-					<button type="submit" class="button">Login</button>
-				</td>
-			</tr>
-		</table>
-	</form>
-</div>
-
-<div class="register_popup hidden" id="registerPopup">
-	<form action="api/register" method="post" name="registerForm">
-		<table>	  		
-			<tr>
-				<td>
-					<label for="registerName">Name:</label>
-				</td>
-				<td>
-					<input type="text" name="name" id="registerName">
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<label for="registerEmail">E-mail:</label>
-				</td>
-				<td>
-					<input type="text" name="email" id="registerEmail">
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<label for="registerUsername">Login name:</label>
-				</td>
-				<td>
-					<input type="text" name="username" id="registerUsername">
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<label for="registerPassword">Password:</label>
-				</td>
-				<td>
-					<input type="password" name="password" id="registerPassword">
-				</td>
-			</tr>
-			<tr>
-				<td>
-					<label for="registerPasswordCheck">Password again:</label>
-				</td>
-				<td>
-					<input type="password" name="passwordCheck" id="registerPasswordCheck">
-				</td>
-			</tr>
-			<tr>
-				<td></td>
-				<td>
-					<button type="submit" class="button">Register</button>
-				</td>
-			</tr>
-		</table>
-	</form>
+	<button class="button" id="toggleLoginPopupButton">Log in</button>
+	<button class="button" id="toggleRegisterPopupButton">Register</button>
 </div>
