diff options
Diffstat (limited to 'client/js/views')
| -rw-r--r-- | client/js/views/base_view.js | 66 | ||||
| -rw-r--r-- | client/js/views/help_view.js | 44 | ||||
| -rw-r--r-- | client/js/views/home_view.js | 22 | ||||
| -rw-r--r-- | client/js/views/login_view.js | 43 | ||||
| -rw-r--r-- | client/js/views/registration_view.js | 43 | ||||
| -rw-r--r-- | client/js/views/top_nav_view.js | 37 |
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; |