client/api: convert messages to events

This commit is contained in:
rr- 2016-04-07 19:03:49 +02:00
parent c1816a292f
commit 8a1140eff6
11 changed files with 110 additions and 80 deletions

View file

@ -2,14 +2,13 @@
const request = require('superagent'); const request = require('superagent');
const config = require('./config.js'); const config = require('./config.js');
const EventListener = require('./event_listener.js'); const events = require('./events.js');
class Api { class Api {
constructor() { constructor() {
this.user = null; this.user = null;
this.userName = null; this.userName = null;
this.userPassword = null; this.userPassword = null;
this.authenticated = new EventListener();
} }
get(url) { get(url) {
@ -70,11 +69,11 @@ class Api {
.then(response => { .then(response => {
this.user = response.user; this.user = response.user;
resolve(); resolve();
this.authenticated.fire(); events.notify(events.Authentication);
}).catch(response => { }).catch(response => {
reject(response.description); reject(response.description);
this.logout(); this.logout();
this.authenticated.fire(); events.notify(events.Authentication);
}); });
}); });
} }
@ -83,7 +82,7 @@ class Api {
this.user = null; this.user = null;
this.userName = null; this.userName = null;
this.userPassword = null; this.userPassword = null;
this.authenticated.fire(); events.notify(events.Authentication);
} }
isLoggedIn() { isLoggedIn() {

View file

@ -3,6 +3,7 @@
const cookies = require('js-cookie'); const cookies = require('js-cookie');
const page = require('page'); const page = require('page');
const api = require('../api.js'); const api = require('../api.js');
const events = require('../events.js');
const topNavController = require('../controllers/top_nav_controller.js'); const topNavController = require('../controllers/top_nav_controller.js');
const LoginView = require('../views/login_view.js'); const LoginView = require('../views/login_view.js');
const PasswordResetView = require('../views/password_reset_view.js'); const PasswordResetView = require('../views/password_reset_view.js');
@ -11,16 +12,21 @@ class AuthController {
constructor() { constructor() {
this.loginView = new LoginView(); this.loginView = new LoginView();
this.passwordResetView = new PasswordResetView(); this.passwordResetView = new PasswordResetView();
}
login() {
return new Promise((resolve, reject) => {
const auth = cookies.getJSON('auth'); const auth = cookies.getJSON('auth');
if (auth && auth.user && auth.password) { if (auth && auth.user && auth.password) {
api.login(auth.user, auth.password).catch(errorMessage => { api.login(auth.user, auth.password)
page('/'); .then(resolve)
this.loginView.notifyError( .catch(errorMessage => {
'An error happened while trying to log you in: ' + reject(errorMessage);
errorMessage);
}); });
} else {
resolve();
} }
});
} }
registerRoutes() { registerRoutes() {
@ -51,7 +57,7 @@ class AuthController {
options); options);
resolve(); resolve();
page('/'); page('/');
this.loginView.notifySuccess('Logged in'); events.notify(events.Success, 'Logged in');
}).catch(errorMessage => { reject(errorMessage); }); }).catch(errorMessage => { reject(errorMessage); });
}); });
}}); }});
@ -61,7 +67,7 @@ class AuthController {
api.logout(); api.logout();
cookies.remove('auth'); cookies.remove('auth');
page('/'); page('/');
this.loginView.notifySuccess('Logged out'); events.notify(events.Success, 'Logged out');
} }
passwordResetRoute() { passwordResetRoute() {
@ -89,15 +95,15 @@ class AuthController {
cookies.set( cookies.set(
'auth', {'user': name, 'password': password}, {}); 'auth', {'user': name, 'password': password}, {});
page('/'); page('/');
this.passwordResetView.notifySuccess( events.notify(events.Success,
'New password: ' + password); 'New password: ' + password);
}).catch(errorMessage => { }).catch(errorMessage => {
page('/'); page('/');
this.passwordResetView.notifyError(errorMessage); events.notify(events.Error, errorMessage);
}); });
}).catch(response => { }).catch(response => {
page('/'); page('/');
this.passwordResetView.notifyError(response.description); events.notify(events.Error, response.description);
}); });
} }
} }

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const api = require('../api.js'); const api = require('../api.js');
const events = require('../events.js');
const TopNavView = require('../views/top_nav_view.js'); const TopNavView = require('../views/top_nav_view.js');
class NavigationItem { class NavigationItem {
@ -31,7 +32,9 @@ class TopNavController {
'help': new NavigationItem('E', 'Help', '/help'), 'help': new NavigationItem('E', 'Help', '/help'),
}; };
api.authenticated.listen(() => { events.listen(
events.Authentication,
() => {
this.updateVisibility(); this.updateVisibility();
this.topNavView.render(this.items, this.activeItem); this.topNavView.render(this.items, this.activeItem);
this.topNavView.activate(this.activeItem); this.topNavView.activate(this.activeItem);

View file

@ -3,6 +3,7 @@
const cookies = require('js-cookie'); const cookies = require('js-cookie');
const page = require('page'); const page = require('page');
const api = require('../api.js'); const api = require('../api.js');
const events = require('../events.js');
const topNavController = require('../controllers/top_nav_controller.js'); const topNavController = require('../controllers/top_nav_controller.js');
const RegistrationView = require('../views/registration_view.js'); const RegistrationView = require('../views/registration_view.js');
const UserView = require('../views/user_view.js'); const UserView = require('../views/user_view.js');
@ -50,7 +51,7 @@ class UsersController {
cookies.set('auth', {'user': name, 'password': password}); cookies.set('auth', {'user': name, 'password': password});
resolve(); resolve();
page('/'); page('/');
this.registrationView.notifySuccess('Welcome aboard!'); events.notify(events.Success, 'Welcome aboard!');
}).catch(response => { }).catch(response => {
reject(response.description); reject(response.description);
}); });
@ -74,7 +75,7 @@ class UsersController {
next(); next();
}).catch(response => { }).catch(response => {
this.userView.empty(); this.userView.empty();
this.userView.notifyError(response.description); events.notify(events.Error, response.description);
}); });
} }
} }

View file

@ -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;

28
client/js/events.js Normal file
View file

@ -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,
};

View file

@ -3,18 +3,29 @@
require('./util/handlebars-helpers.js'); require('./util/handlebars-helpers.js');
let controllers = []; let controllers = [];
const authController = require('./controllers/auth_controller.js');
controllers.push(authController);
controllers.push(require('./controllers/posts_controller.js')); controllers.push(require('./controllers/posts_controller.js'));
controllers.push(require('./controllers/users_controller.js')); controllers.push(require('./controllers/users_controller.js'));
controllers.push(require('./controllers/help_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/comments_controller.js'));
controllers.push(require('./controllers/history_controller.js')); controllers.push(require('./controllers/history_controller.js'));
controllers.push(require('./controllers/tags_controller.js')); controllers.push(require('./controllers/tags_controller.js'));
controllers.push(require('./controllers/home_controller.js')); controllers.push(require('./controllers/home_controller.js'));
const events = require('./events.js');
const page = require('page'); const page = require('page');
for (let controller of controllers) { for (let controller of controllers) {
controller.registerRoutes(); 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);
});

View file

@ -1,13 +1,32 @@
'use strict'; 'use strict';
const handlebars = require('handlebars'); 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 // fix iterating over NodeList in Chrome and Opera
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
class BaseView { class BaseView {
constructor() { constructor() {
this.contentHolder = document.getElementById('content-holder'); this.contentHolder = contentHolder;
} }
getTemplate(templatePath) { getTemplate(templatePath) {
@ -20,24 +39,6 @@ class BaseView {
return handlebars.compile(templateText); 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() { clearMessages() {
const messagesHolder = this.contentHolder.querySelector('.messages'); const messagesHolder = this.contentHolder.querySelector('.messages');
/* TODO: animate that */ /* TODO: animate that */

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const config = require('../config.js'); const config = require('../config.js');
const events = require('../events.js');
const BaseView = require('./base_view.js'); const BaseView = require('./base_view.js');
class LoginView extends BaseView { class LoginView extends BaseView {
@ -34,7 +35,7 @@ class LoginView extends BaseView {
}) })
.catch(errorMessage => { .catch(errorMessage => {
this.enableForm(form); this.enableForm(form);
this.notifyError(errorMessage); events.notify(events.Error, errorMessage);
}); });
}); });
} }

View file

@ -1,5 +1,6 @@
'use strict'; 'use strict';
const events = require('../events.js');
const BaseView = require('./base_view.js'); const BaseView = require('./base_view.js');
class PasswordResetView extends BaseView { class PasswordResetView extends BaseView {
@ -22,13 +23,14 @@ class PasswordResetView extends BaseView {
options options
.proceed(userNameOrEmailField.value) .proceed(userNameOrEmailField.value)
.then(() => { .then(() => {
this.notifySuccess( events.notify(
events.Success,
'E-mail has been sent. To finish the procedure, ' + 'E-mail has been sent. To finish the procedure, ' +
'please click the link it contains.'); 'please click the link it contains.');
}) })
.catch(errorMessage => { .catch(errorMessage => {
this.enableForm(form); this.enableForm(form);
this.notifyError(errorMessage); events.notify(events.Error, errorMessage);
}); });
}); });
} }

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const config = require('../config.js'); const config = require('../config.js');
const events = require('../events.js');
const BaseView = require('./base_view.js'); const BaseView = require('./base_view.js');
class RegistrationView extends BaseView { class RegistrationView extends BaseView {
@ -11,12 +12,13 @@ class RegistrationView extends BaseView {
render(options) { render(options) {
this.showView(this.template()); this.showView(this.template());
const form = document.querySelector('#content-holder form');
this.decorateValidator(form);
const userNameField = document.getElementById('user-name'); const form = this.contentHolder.querySelector('form');
const passwordField = document.getElementById('user-password'); const userNameField = this.contentHolder.querySelector('#user-name');
const emailField = document.getElementById('user-email'); const passwordField = this.contentHolder.querySelector('#user-password');
const emailField = this.contentHolder.querySelector('#user-email');
this.decorateValidator(form);
userNameField.setAttribute('pattern', config.userNameRegex); userNameField.setAttribute('pattern', config.userNameRegex);
passwordField.setAttribute('pattern', config.passwordRegex); passwordField.setAttribute('pattern', config.passwordRegex);
@ -34,7 +36,7 @@ class RegistrationView extends BaseView {
}) })
.catch(errorMessage => { .catch(errorMessage => {
this.enableForm(form); this.enableForm(form);
this.notifyError(errorMessage); events.notify(events.Error, errorMessage);
}); });
}); });
} }