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 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() {

View File

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

View File

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

View File

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

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');
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);
});

View File

@ -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 */

View File

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

View File

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

View File

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