From 8a1140eff651084b2d71cc322d0a382f1d726776 Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Thu, 7 Apr 2016 19:03:49 +0200
Subject: [PATCH] client/api: convert messages to events

---
 client/js/api.js                            |  9 +++--
 client/js/controllers/auth_controller.js    | 34 ++++++++++--------
 client/js/controllers/top_nav_controller.js | 13 ++++---
 client/js/controllers/users_controller.js   |  5 +--
 client/js/event_listener.js                 | 24 -------------
 client/js/events.js                         | 28 +++++++++++++++
 client/js/main.js                           | 15 ++++++--
 client/js/views/base_view.js                | 39 +++++++++++----------
 client/js/views/login_view.js               |  3 +-
 client/js/views/password_reset_view.js      |  6 ++--
 client/js/views/registration_view.js        | 14 ++++----
 11 files changed, 110 insertions(+), 80 deletions(-)
 delete mode 100644 client/js/event_listener.js
 create mode 100644 client/js/events.js

diff --git a/client/js/api.js b/client/js/api.js
index 497525ea..24d4a7a4 100644
--- a/client/js/api.js
+++ b/client/js/api.js
@@ -2,14 +2,13 @@
 
 const request = require('superagent');
 const config = require('./config.js');
-const EventListener = require('./event_listener.js');
+const events = require('./events.js');
 
 class Api {
     constructor() {
         this.user = null;
         this.userName = null;
         this.userPassword = null;
-        this.authenticated = new EventListener();
     }
 
     get(url) {
@@ -70,11 +69,11 @@ class Api {
                 .then(response => {
                     this.user = response.user;
                     resolve();
-                    this.authenticated.fire();
+                    events.notify(events.Authentication);
                 }).catch(response => {
                     reject(response.description);
                     this.logout();
-                    this.authenticated.fire();
+                    events.notify(events.Authentication);
                 });
         });
     }
@@ -83,7 +82,7 @@ class Api {
         this.user = null;
         this.userName = null;
         this.userPassword = null;
-        this.authenticated.fire();
+        events.notify(events.Authentication);
     }
 
     isLoggedIn() {
diff --git a/client/js/controllers/auth_controller.js b/client/js/controllers/auth_controller.js
index 04cfec5a..d68cdb89 100644
--- a/client/js/controllers/auth_controller.js
+++ b/client/js/controllers/auth_controller.js
@@ -3,6 +3,7 @@
 const cookies = require('js-cookie');
 const page = require('page');
 const api = require('../api.js');
+const events = require('../events.js');
 const topNavController = require('../controllers/top_nav_controller.js');
 const LoginView = require('../views/login_view.js');
 const PasswordResetView = require('../views/password_reset_view.js');
@@ -11,16 +12,21 @@ class AuthController {
     constructor() {
         this.loginView = new LoginView();
         this.passwordResetView = new PasswordResetView();
+    }
 
-        const auth = cookies.getJSON('auth');
-        if (auth && auth.user && auth.password) {
-            api.login(auth.user, auth.password).catch(errorMessage => {
-                page('/');
-                this.loginView.notifyError(
-                    'An error happened while trying to log you in: ' +
-                    errorMessage);
-            });
-        }
+    login() {
+        return new Promise((resolve, reject) => {
+            const auth = cookies.getJSON('auth');
+            if (auth && auth.user && auth.password) {
+                api.login(auth.user, auth.password)
+                    .then(resolve)
+                    .catch(errorMessage => {
+                        reject(errorMessage);
+                    });
+            } else {
+                resolve();
+            }
+        });
     }
 
     registerRoutes() {
@@ -51,7 +57,7 @@ class AuthController {
                                 options);
                             resolve();
                             page('/');
-                            this.loginView.notifySuccess('Logged in');
+                            events.notify(events.Success, 'Logged in');
                         }).catch(errorMessage => { reject(errorMessage); });
                 });
             }});
@@ -61,7 +67,7 @@ class AuthController {
         api.logout();
         cookies.remove('auth');
         page('/');
-        this.loginView.notifySuccess('Logged out');
+        events.notify(events.Success, 'Logged out');
     }
 
     passwordResetRoute() {
@@ -89,15 +95,15 @@ class AuthController {
                         cookies.set(
                             'auth', {'user': name, 'password': password}, {});
                         page('/');
-                        this.passwordResetView.notifySuccess(
+                        events.notify(events.Success,
                             'New password: ' + password);
                     }).catch(errorMessage => {
                         page('/');
-                        this.passwordResetView.notifyError(errorMessage);
+                        events.notify(events.Error, errorMessage);
                     });
             }).catch(response => {
                 page('/');
-                this.passwordResetView.notifyError(response.description);
+                events.notify(events.Error, response.description);
             });
     }
 }
diff --git a/client/js/controllers/top_nav_controller.js b/client/js/controllers/top_nav_controller.js
index a6da22ff..9ee7f07a 100644
--- a/client/js/controllers/top_nav_controller.js
+++ b/client/js/controllers/top_nav_controller.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const api = require('../api.js');
+const events = require('../events.js');
 const TopNavView = require('../views/top_nav_view.js');
 
 class NavigationItem {
@@ -31,11 +32,13 @@ class TopNavController {
             'help':     new NavigationItem('E', 'Help',     '/help'),
         };
 
-        api.authenticated.listen(() => {
-            this.updateVisibility();
-            this.topNavView.render(this.items, this.activeItem);
-            this.topNavView.activate(this.activeItem);
-        });
+        events.listen(
+            events.Authentication,
+            () => {
+                this.updateVisibility();
+                this.topNavView.render(this.items, this.activeItem);
+                this.topNavView.activate(this.activeItem);
+            });
 
         this.updateVisibility();
         this.topNavView.render(this.items, this.activeItem);
diff --git a/client/js/controllers/users_controller.js b/client/js/controllers/users_controller.js
index b7d3e2b9..ce23f703 100644
--- a/client/js/controllers/users_controller.js
+++ b/client/js/controllers/users_controller.js
@@ -3,6 +3,7 @@
 const cookies = require('js-cookie');
 const page = require('page');
 const api = require('../api.js');
+const events = require('../events.js');
 const topNavController = require('../controllers/top_nav_controller.js');
 const RegistrationView = require('../views/registration_view.js');
 const UserView = require('../views/user_view.js');
@@ -50,7 +51,7 @@ class UsersController {
                     cookies.set('auth', {'user': name, 'password': password});
                     resolve();
                     page('/');
-                    this.registrationView.notifySuccess('Welcome aboard!');
+                    events.notify(events.Success, 'Welcome aboard!');
                 }).catch(response => {
                     reject(response.description);
                 });
@@ -74,7 +75,7 @@ class UsersController {
                 next();
             }).catch(response => {
                 this.userView.empty();
-                this.userView.notifyError(response.description);
+                events.notify(events.Error, response.description);
             });
         }
     }
diff --git a/client/js/event_listener.js b/client/js/event_listener.js
deleted file mode 100644
index e7eebffb..00000000
--- a/client/js/event_listener.js
+++ /dev/null
@@ -1,24 +0,0 @@
-class EventListener {
-    constructor() {
-        this.listeners = [];
-    }
-
-    listen(callback) {
-        this.listeners.push(callback);
-    }
-
-    unlisten(callback) {
-        const index = this.listeners.indexOf(callback);
-        if (index !== -1) {
-            this.listeners.splice(index, 1);
-        }
-    }
-
-    fire(data) {
-        for (let listener of this.listeners) {
-            listener(data);
-        }
-    }
-}
-
-module.exports = EventListener;
diff --git a/client/js/events.js b/client/js/events.js
new file mode 100644
index 00000000..8fb42d42
--- /dev/null
+++ b/client/js/events.js
@@ -0,0 +1,28 @@
+'use strict';
+
+let listeners = [];
+
+function listen(messageClass, handler) {
+    if (!(messageClass in listeners)) {
+        listeners[messageClass] = [];
+    }
+    listeners[messageClass].push(handler);
+}
+
+function notify(messageClass, message) {
+    if (!(messageClass in listeners)) {
+        return;
+    }
+    for (let handler of listeners[messageClass]) {
+        handler(message);
+    }
+}
+
+module.exports = {
+    Success: 1,
+    Error: 2,
+    Authentication: 3,
+
+    notify: notify,
+    listen: listen,
+};
diff --git a/client/js/main.js b/client/js/main.js
index b3706db6..03b73cc3 100644
--- a/client/js/main.js
+++ b/client/js/main.js
@@ -3,18 +3,29 @@
 require('./util/handlebars-helpers.js');
 
 let controllers = [];
+const authController = require('./controllers/auth_controller.js');
+controllers.push(authController);
 controllers.push(require('./controllers/posts_controller.js'));
 controllers.push(require('./controllers/users_controller.js'));
 controllers.push(require('./controllers/help_controller.js'));
-controllers.push(require('./controllers/auth_controller.js'));
 controllers.push(require('./controllers/comments_controller.js'));
 controllers.push(require('./controllers/history_controller.js'));
 controllers.push(require('./controllers/tags_controller.js'));
 
 controllers.push(require('./controllers/home_controller.js'));
 
+const events = require('./events.js');
 const page = require('page');
 for (let controller of controllers) {
     controller.registerRoutes();
 }
-page();
+
+authController.login().then(() => {
+    page();
+}).catch(errorMessage => {
+    page();
+    page('/');
+    events.notify(
+        events.Error,
+        'An error happened while trying to log you in: ' + errorMessage);
+});
diff --git a/client/js/views/base_view.js b/client/js/views/base_view.js
index c805d8a0..c5ed4657 100644
--- a/client/js/views/base_view.js
+++ b/client/js/views/base_view.js
@@ -1,13 +1,32 @@
 'use strict';
 
 const handlebars = require('handlebars');
+const events = require('../events.js');
+const contentHolder = document.getElementById('content-holder');
+
+function messageHandler(message, className) {
+    const messagesHolder = contentHolder.querySelector('.messages');
+    if (!messagesHolder) {
+        alert(message);
+        return;
+    }
+    /* TODO: animate this */
+    const node = document.createElement('div');
+    node.innerHTML = message.replace(/\n/g, '<br/>');
+    node.classList.add('message');
+    node.classList.add(className);
+    messagesHolder.appendChild(node);
+}
+
+events.listen(events.Success, msg => { messageHandler(msg, 'success'); });
+events.listen(events.Error, msg => { messageHandler(msg, 'error'); });
 
 // 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');
+        this.contentHolder = contentHolder;
     }
 
     getTemplate(templatePath) {
@@ -20,24 +39,6 @@ class BaseView {
         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.replace(/\n/g, '<br/>');
-        node.classList.add('message');
-        node.classList.add(className);
-        messagesHolder.appendChild(node);
-    }
-
     clearMessages() {
         const messagesHolder = this.contentHolder.querySelector('.messages');
         /* TODO: animate that */
diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js
index 3708e659..1d963e53 100644
--- a/client/js/views/login_view.js
+++ b/client/js/views/login_view.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const config = require('../config.js');
+const events = require('../events.js');
 const BaseView = require('./base_view.js');
 
 class LoginView extends BaseView {
@@ -34,7 +35,7 @@ class LoginView extends BaseView {
                 })
                 .catch(errorMessage => {
                     this.enableForm(form);
-                    this.notifyError(errorMessage);
+                    events.notify(events.Error, errorMessage);
                 });
         });
     }
diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js
index 23b17ee0..0bba9c84 100644
--- a/client/js/views/password_reset_view.js
+++ b/client/js/views/password_reset_view.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const events = require('../events.js');
 const BaseView = require('./base_view.js');
 
 class PasswordResetView extends BaseView {
@@ -22,13 +23,14 @@ class PasswordResetView extends BaseView {
             options
                 .proceed(userNameOrEmailField.value)
                 .then(() => {
-                    this.notifySuccess(
+                    events.notify(
+                        events.Success,
                         'E-mail has been sent. To finish the procedure, ' +
                         'please click the link it contains.');
                 })
                 .catch(errorMessage => {
                     this.enableForm(form);
-                    this.notifyError(errorMessage);
+                    events.notify(events.Error, errorMessage);
                 });
         });
     }
diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js
index 1a1583ea..597ade99 100644
--- a/client/js/views/registration_view.js
+++ b/client/js/views/registration_view.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const config = require('../config.js');
+const events = require('../events.js');
 const BaseView = require('./base_view.js');
 
 class RegistrationView extends BaseView {
@@ -11,12 +12,13 @@ class RegistrationView extends BaseView {
 
     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');
+        const form = this.contentHolder.querySelector('form');
+        const userNameField = this.contentHolder.querySelector('#user-name');
+        const passwordField = this.contentHolder.querySelector('#user-password');
+        const emailField = this.contentHolder.querySelector('#user-email');
+
+        this.decorateValidator(form);
         userNameField.setAttribute('pattern', config.userNameRegex);
         passwordField.setAttribute('pattern', config.passwordRegex);
 
@@ -34,7 +36,7 @@ class RegistrationView extends BaseView {
                 })
                 .catch(errorMessage => {
                     this.enableForm(form);
-                    this.notifyError(errorMessage);
+                    events.notify(events.Error, errorMessage);
                 });
         });
     }