aboutsummaryrefslogtreecommitdiff
path: root/client/js/views
diff options
context:
space:
mode:
Diffstat (limited to 'client/js/views')
-rw-r--r--client/js/views/base_view.js66
-rw-r--r--client/js/views/help_view.js44
-rw-r--r--client/js/views/home_view.js22
-rw-r--r--client/js/views/login_view.js43
-rw-r--r--client/js/views/registration_view.js43
-rw-r--r--client/js/views/top_nav_view.js37
6 files changed, 255 insertions, 0 deletions
diff --git a/client/js/views/base_view.js b/client/js/views/base_view.js
new file mode 100644
index 0000000..1a0f7d0
--- /dev/null
+++ b/client/js/views/base_view.js
@@ -0,0 +1,66 @@
+'use strict';
+
+const handlebars = require('handlebars');
+
+// fix iterating over NodeList in Chrome and Opera
+NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
+
+class BaseView {
+ constructor() {
+ this.contentHolder = document.getElementById('content-holder');
+ }
+
+ getTemplate(templatePath) {
+ const templateElement = document.getElementById(templatePath);
+ if (!templateElement) {
+ console.log('Missing template: ' + templatePath);
+ return null;
+ }
+ const templateText = templateElement.innerHTML;
+ return handlebars.compile(templateText);
+ }
+
+ notifyError(message) {
+ this.notify(message, 'error');
+ }
+
+ notifySuccess(message) {
+ this.notify(message, 'success');
+ }
+
+ notify(message, className) {
+ const messagesHolder = this.contentHolder.querySelector('.messages');
+ /* TODO: animate this */
+ const node = document.createElement('div');
+ node.innerHTML = message;
+ node.classList.add('message');
+ node.classList.add(className);
+ messagesHolder.appendChild(node);
+ }
+
+ clearMessages() {
+ const messagesHolder = this.contentHolder.querySelector('.messages');
+ /* TODO: animate that */
+ while (messagesHolder.lastChild) {
+ messagesHolder.removeChild(messagesHolder.lastChild);
+ }
+ }
+
+ decorateValidator(form) {
+ // postpone showing form fields validity until user actually tries
+ // to submit it (seeing red/green form w/o doing anything breaks POLA)
+ const submitButton = form.querySelector('.buttons input');
+ submitButton.addEventListener('click', e => {
+ form.classList.add('show-validation');
+ });
+ form.addEventListener('submit', e => {
+ form.classList.remove('show-validation');
+ });
+ }
+
+ showView(html) {
+ this.contentHolder.innerHTML = html;
+ }
+}
+
+module.exports = BaseView;
diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js
new file mode 100644
index 0000000..4f68a7d
--- /dev/null
+++ b/client/js/views/help_view.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const config = require('../config.js');
+const BaseView = require('./base_view.js');
+
+class HelpView extends BaseView {
+ constructor() {
+ super();
+ this.template = this.getTemplate('help-template');
+ this.sectionTemplates = {};
+ const sectionKeys = ['about', 'keyboard', 'search', 'comments', 'tos'];
+ for (let section of sectionKeys) {
+ const templateName = 'help-' + section + '-template';
+ this.sectionTemplates[section] = this.getTemplate(templateName);
+ }
+ }
+
+ render(section) {
+ if (!section) {
+ section = 'about';
+ }
+ if (!(section in this.sectionTemplates)) {
+ this.showView('');
+ return;
+ }
+
+ const content = this.sectionTemplates[section]({
+ 'name': config.basic.name,
+ });
+
+ this.showView(this.template({'content': content}));
+
+ const allItemsSelector = '#content-holder [data-name]';
+ for (let item of document.querySelectorAll(allItemsSelector)) {
+ if (item.getAttribute('data-name') === section) {
+ item.className = 'active';
+ } else {
+ item.className = '';
+ }
+ }
+ }
+}
+
+module.exports = HelpView;
diff --git a/client/js/views/home_view.js b/client/js/views/home_view.js
new file mode 100644
index 0000000..4beca60
--- /dev/null
+++ b/client/js/views/home_view.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const util = require('../util.js');
+const config = require('../config.js');
+const BaseView = require('./base_view.js');
+
+class HomeView extends BaseView {
+ constructor() {
+ super();
+ this.template = this.getTemplate('home-template');
+ }
+
+ render(section) {
+ this.showView(this.template({
+ name: config.basic.name,
+ version: config.meta.version,
+ buildDate: util.formatRelativeTime(config.meta.buildDate),
+ }));
+ }
+}
+
+module.exports = HomeView;
diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js
new file mode 100644
index 0000000..6ea29fe
--- /dev/null
+++ b/client/js/views/login_view.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const config = require('../config.js');
+const BaseView = require('./base_view.js');
+
+class LoginView extends BaseView {
+ constructor() {
+ super();
+ this.template = this.getTemplate('login-template');
+ }
+
+ render(options) {
+ this.showView(this.template());
+ const form = this.contentHolder.querySelector('form');
+ this.decorateValidator(form);
+
+ const userNameField = document.getElementById('user-name');
+ const passwordField = document.getElementById('user-password');
+ const rememberUserField = document.getElementById('remember-user');
+ userNameField.setAttribute('pattern', config.service.userNameRegex);
+ passwordField.setAttribute('pattern', config.service.passwordRegex);
+
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ this.clearMessages();
+ form.setAttribute('disabled', true);
+ options
+ .login(
+ userNameField.value,
+ passwordField.value,
+ rememberUserField.checked)
+ .then(() => {
+ form.setAttribute('disabled', false);
+ })
+ .catch(errorMessage => {
+ form.setAttribute('disabled', false);
+ this.notifyError(errorMessage);
+ });
+ });
+ }
+}
+
+module.exports = LoginView;
diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js
new file mode 100644
index 0000000..ad08e60
--- /dev/null
+++ b/client/js/views/registration_view.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const config = require('../config.js');
+const BaseView = require('./base_view.js');
+
+class RegistrationView extends BaseView {
+ constructor() {
+ super();
+ this.template = this.getTemplate('user-registration-template');
+ }
+
+ render(options) {
+ this.showView(this.template());
+ const form = document.querySelector('#content-holder form');
+ this.decorateValidator(form);
+
+ const userNameField = document.getElementById('user-name');
+ const passwordField = document.getElementById('user-password');
+ const emailField = document.getElementById('user-email');
+ userNameField.setAttribute('pattern', config.service.userNameRegex);
+ passwordField.setAttribute('pattern', config.service.passwordRegex);
+
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ this.clearMessages();
+ form.setAttribute('disabled', true);
+ options
+ .register(
+ userNameField.value,
+ passwordField.value,
+ emailField.value)
+ .then(() => {
+ form.setAttribute('disabled', false);
+ })
+ .catch(errorMessage => {
+ form.setAttribute('disabled', false);
+ this.notifyError(errorMessage);
+ });
+ });
+ }
+}
+
+module.exports = RegistrationView;
diff --git a/client/js/views/top_nav_view.js b/client/js/views/top_nav_view.js
new file mode 100644
index 0000000..6864c63
--- /dev/null
+++ b/client/js/views/top_nav_view.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const BaseView = require('./base_view.js');
+
+class TopNavView extends BaseView {
+ constructor() {
+ super();
+ this.template = this.getTemplate('top-nav-template');
+ this.navHolder = document.getElementById('top-nav-holder');
+ }
+
+ render(items) {
+ this.navHolder.innerHTML = this.template({items: items});
+ for (let link of this.navHolder.querySelectorAll('a')) {
+ const regex = new RegExp(
+ '(' + link.getAttribute('accesskey') + ')', 'i');
+ link.innerHTML = link.textContent.replace(
+ regex,
+ '<span class="access-key" data-accesskey="$1">$1</span>');
+ }
+ }
+
+ activate(itemName) {
+ const allItemsSelector = '#top-nav-holder [data-name]';
+ const currentItemSelector =
+ '#top-nav-holder [data-name="' + itemName + '"]';
+ for (let item of document.querySelectorAll(allItemsSelector)) {
+ item.className = '';
+ }
+ const currentItem = document.querySelectorAll(currentItemSelector);
+ if (currentItem.length > 0) {
+ currentItem[0].className = 'active';
+ }
+ }
+}
+
+module.exports = TopNavView;

© 2015 - 2026 Jakob L. Kreuze