From 0ceaa8da42173f4b3d82d55cebca511a4d9a4684 Mon Sep 17 00:00:00 2001 From: rr- Date: Fri, 8 Apr 2016 10:35:38 +0200 Subject: [PATCH] client/views: refactor rendering --- client/html/help.hbs | 2 +- client/js/controllers/help_controller.js | 4 +- client/js/controllers/home_controller.js | 2 +- client/js/controllers/top_nav_controller.js | 19 +++++---- client/js/controllers/users_controller.js | 2 +- client/js/views/base_view.js | 45 +++++++++++++++++---- client/js/views/help_view.js | 31 +++++++------- client/js/views/home_view.js | 8 ++-- client/js/views/login_view.js | 21 +++++----- client/js/views/password_reset_view.js | 17 ++++---- client/js/views/registration_view.js | 18 +++++---- client/js/views/top_nav_view.js | 10 +++-- client/js/views/user_edit_view.js | 22 +++++----- client/js/views/user_summary_view.js | 6 ++- client/js/views/user_view.js | 37 ++++++++--------- 15 files changed, 147 insertions(+), 97 deletions(-) diff --git a/client/html/help.hbs b/client/html/help.hbs index 5a2fccd..2656f18 100644 --- a/client/html/help.hbs +++ b/client/html/help.hbs @@ -9,5 +9,5 @@ --> -
{{{ this.content }}}
+
diff --git a/client/js/controllers/help_controller.js b/client/js/controllers/help_controller.js index 9ee980d..2cc53a2 100644 --- a/client/js/controllers/help_controller.js +++ b/client/js/controllers/help_controller.js @@ -18,7 +18,9 @@ class HelpController { showHelpRoute(section) { topNavController.activate('help'); - this.helpView.render(section); + this.helpView.render({ + section: section, + }); } } diff --git a/client/js/controllers/home_controller.js b/client/js/controllers/home_controller.js index 1384e15..32144e2 100644 --- a/client/js/controllers/home_controller.js +++ b/client/js/controllers/home_controller.js @@ -16,7 +16,7 @@ class HomeController { indexRoute() { topNavController.activate('home'); - this.homeView.render(); + this.homeView.render({}); } notFoundRoute() { diff --git a/client/js/controllers/top_nav_controller.js b/client/js/controllers/top_nav_controller.js index 9ee7f07..4e27395 100644 --- a/client/js/controllers/top_nav_controller.js +++ b/client/js/controllers/top_nav_controller.js @@ -32,17 +32,16 @@ class TopNavController { 'help': new NavigationItem('E', 'Help', '/help'), }; - events.listen( - events.Authentication, - () => { - this.updateVisibility(); - this.topNavView.render(this.items, this.activeItem); - this.topNavView.activate(this.activeItem); - }); + const rerender = () => { + this.updateVisibility(); + this.topNavView.render({ + items: this.items, + activeItem: this.activeItem}); + this.topNavView.activate(this.activeItem); + }; - this.updateVisibility(); - this.topNavView.render(this.items, this.activeItem); - this.topNavView.activate(this.activeItem); + events.listen(events.Authentication, rerender); + rerender(); } updateVisibility() { diff --git a/client/js/controllers/users_controller.js b/client/js/controllers/users_controller.js index eb89b02..8b84d18 100644 --- a/client/js/controllers/users_controller.js +++ b/client/js/controllers/users_controller.js @@ -54,7 +54,7 @@ class UsersController { this.user = response.user; next(); }).catch(response => { - this.userView.empty(); + this.userView.emptyView(this.userView.contentHolder); events.notify(events.Error, response.description); }); } diff --git a/client/js/views/base_view.js b/client/js/views/base_view.js index bdeef9d..98ae406 100644 --- a/client/js/views/base_view.js +++ b/client/js/views/base_view.js @@ -28,16 +28,27 @@ events.listen(events.Error, msg => { messageHandler(msg, 'error'); }); class BaseView { constructor() { this.contentHolder = contentHolder; + this.domParser = new DOMParser(); + } + + htmlToDom(html) { + const parsed = this.domParser.parseFromString(html, 'text/html').body; + return parsed.childNodes.length > 1 ? + parsed.childNodes : + parsed.firstChild; } getTemplate(templatePath) { const templateElement = document.getElementById(templatePath); if (!templateElement) { - console.log('Missing template: ' + templatePath); + console.error('Missing template: ' + templatePath); return null; } - const templateText = templateElement.innerHTML; - return handlebars.compile(templateText); + const templateText = templateElement.innerHTML.trim(); + const templateFactory = handlebars.compile(templateText); + return (...args) => { + return this.htmlToDom(templateFactory(...args)); + }; } clearMessages() { @@ -72,12 +83,32 @@ class BaseView { } } - empty() { - this.showView('
'); + emptyView(target) { + return this.showView( + target, + this.htmlToDom('
')); } - showView(html) { - this.contentHolder.innerHTML = html; + showView(target, source) { + return new Promise((resolve, reject) => { + let observer = new MutationObserver(mutations => { + resolve(); + observer.disconnect(); + }); + observer.observe(target, {childList: true}); + while (target.lastChild) { + target.removeChild(target.lastChild); + } + if (source instanceof NodeList) { + for (let child of source) { + target.appendChild(child); + } + } else if (source instanceof Node) { + target.appendChild(source); + } else { + console.error('Invalid view source', source); + } + }); } } diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js index 52adfd0..d3f198c 100644 --- a/client/js/views/help_view.js +++ b/client/js/views/help_view.js @@ -15,29 +15,32 @@ class HelpView extends BaseView { } } - render(section) { - if (!section) { - section = 'about'; - } - if (!(section in this.sectionTemplates)) { - this.showView(''); + render(ctx) { + const target = this.contentHolder; + const source = this.template(); + + ctx.section = ctx.section || 'about'; + if (!(ctx.section in this.sectionTemplates)) { + this.emptyView(this.contentHolder); return; } - const content = this.sectionTemplates[section]({ - name: config.name, - }); + this.showView( + source.querySelector('.content'), + this.sectionTemplates[ctx.section]({ + name: config.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) { + const allItemsSelector = '[data-name]'; + for (let item of source.querySelectorAll(allItemsSelector)) { + if (item.getAttribute('data-name') === ctx.section) { item.className = 'active'; } else { item.className = ''; } } + + this.showView(target, source); } } diff --git a/client/js/views/home_view.js b/client/js/views/home_view.js index 9a8603d..341982a 100644 --- a/client/js/views/home_view.js +++ b/client/js/views/home_view.js @@ -9,12 +9,14 @@ class HomeView extends BaseView { this.template = this.getTemplate('home-template'); } - render(section) { - this.showView(this.template({ + render(ctx) { + const target = this.contentHolder; + const source = this.template({ name: config.name, version: config.meta.version, buildDate: config.meta.buildDate, - })); + }); + this.showView(target, source); } } diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js index 1d963e5..e757416 100644 --- a/client/js/views/login_view.js +++ b/client/js/views/login_view.js @@ -10,14 +10,16 @@ class LoginView extends BaseView { this.template = this.getTemplate('login-template'); } - render(options) { - this.showView(this.template()); - const form = this.contentHolder.querySelector('form'); - this.decorateValidator(form); + render(ctx) { + const target = this.contentHolder; + const source = this.template(); - const userNameField = document.getElementById('user-name'); - const passwordField = document.getElementById('user-password'); - const rememberUserField = document.getElementById('remember-user'); + const form = source.querySelector('form'); + const userNameField = source.querySelector('#user-name'); + const passwordField = source.querySelector('#user-password'); + const rememberUserField = source.querySelector('#remember-user'); + + this.decorateValidator(form); userNameField.setAttribute('pattern', config.userNameRegex); passwordField.setAttribute('pattern', config.passwordRegex); @@ -25,8 +27,7 @@ class LoginView extends BaseView { e.preventDefault(); this.clearMessages(); this.disableForm(form); - options - .login( + ctx.login( userNameField.value, passwordField.value, rememberUserField.checked) @@ -38,6 +39,8 @@ class LoginView extends BaseView { events.notify(events.Error, errorMessage); }); }); + + this.showView(target, source); } } diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js index 0bba9c8..389b29d 100644 --- a/client/js/views/password_reset_view.js +++ b/client/js/views/password_reset_view.js @@ -9,19 +9,20 @@ class PasswordResetView extends BaseView { this.template = this.getTemplate('password-reset-template'); } - render(options) { - this.showView(this.template()); - const form = this.contentHolder.querySelector('form'); - this.decorateValidator(form); + render(ctx) { + const target = this.contentHolder; + const source = this.template(); - const userNameOrEmailField = document.getElementById('user-name'); + const form = source.querySelector('form'); + const userNameOrEmailField = source.querySelector('#user-name'); + + this.decorateValidator(form); form.addEventListener('submit', e => { e.preventDefault(); this.clearMessages(); this.disableForm(form); - options - .proceed(userNameOrEmailField.value) + ctx.proceed(userNameOrEmailField.value) .then(() => { events.notify( events.Success, @@ -33,6 +34,8 @@ class PasswordResetView extends BaseView { events.notify(events.Error, errorMessage); }); }); + + this.showView(target, source); } } diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 597ade9..77511e1 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -10,13 +10,14 @@ class RegistrationView extends BaseView { this.template = this.getTemplate('user-registration-template'); } - render(options) { - this.showView(this.template()); + render(ctx) { + const target = this.contentHolder; + const source = this.template(); - 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'); + const form = source.querySelector('form'); + const userNameField = source.querySelector('#user-name'); + const passwordField = source.querySelector('#user-password'); + const emailField = source.querySelector('#user-email'); this.decorateValidator(form); userNameField.setAttribute('pattern', config.userNameRegex); @@ -26,8 +27,7 @@ class RegistrationView extends BaseView { e.preventDefault(); this.clearMessages(); this.disableForm(form); - options - .register( + ctx.register( userNameField.value, passwordField.value, emailField.value) @@ -39,6 +39,8 @@ class RegistrationView extends BaseView { events.notify(events.Error, errorMessage); }); }); + + this.showView(target, source); } } diff --git a/client/js/views/top_nav_view.js b/client/js/views/top_nav_view.js index 6864c63..7e9c271 100644 --- a/client/js/views/top_nav_view.js +++ b/client/js/views/top_nav_view.js @@ -9,15 +9,19 @@ class TopNavView extends BaseView { this.navHolder = document.getElementById('top-nav-holder'); } - render(items) { - this.navHolder.innerHTML = this.template({items: items}); - for (let link of this.navHolder.querySelectorAll('a')) { + render(ctx) { + const target = this.navHolder; + const source = this.template(ctx); + + for (let link of source.querySelectorAll('a')) { const regex = new RegExp( '(' + link.getAttribute('accesskey') + ')', 'i'); link.innerHTML = link.textContent.replace( regex, '$1'); } + + this.showView(this.navHolder, source); } activate(itemName) { diff --git a/client/js/views/user_edit_view.js b/client/js/views/user_edit_view.js index 6e1486c..07d6e0d 100644 --- a/client/js/views/user_edit_view.js +++ b/client/js/views/user_edit_view.js @@ -9,14 +9,15 @@ class UserEditView extends BaseView { this.template = this.getTemplate('user-edit-template'); } - render(options) { - options.target.innerHTML = this.template(options); + render(ctx) { + const target = ctx.target; + const source = this.template(ctx); - const form = options.target.querySelector('form'); - const rankField = options.target.querySelector('#user-rank'); - const emailField = options.target.querySelector('#user-email'); - const userNameField = options.target.querySelector('#user-name'); - const passwordField = options.target.querySelector('#user-password'); + const form = source.querySelector('form'); + const rankField = source.querySelector('#user-rank'); + const emailField = source.querySelector('#user-email'); + const userNameField = source.querySelector('#user-name'); + const passwordField = source.querySelector('#user-password'); this.decorateValidator(form); @@ -33,7 +34,7 @@ class UserEditView extends BaseView { } if (rankField) { - rankField.value = options.user.rank; + rankField.value = ctx.user.rank; } /* TODO: avatar */ @@ -42,8 +43,7 @@ class UserEditView extends BaseView { e.preventDefault(); this.clearMessages(); this.disableForm(form); - options - .edit( + ctx.edit( userNameField.value, passwordField.value, emailField.value, @@ -51,6 +51,8 @@ class UserEditView extends BaseView { .then(user => { this.enableForm(form); }) .catch(() => { this.enableForm(form); }); }); + + this.showView(target, source); } } diff --git a/client/js/views/user_summary_view.js b/client/js/views/user_summary_view.js index 4c57046..ef3029a 100644 --- a/client/js/views/user_summary_view.js +++ b/client/js/views/user_summary_view.js @@ -8,8 +8,10 @@ class UserSummaryView extends BaseView { this.template = this.getTemplate('user-summary-template'); } - render(options) { - options.target.innerHTML = this.template(options); + render(ctx) { + const target = ctx.target; + const source = this.template(ctx); + this.showView(target, source); } } diff --git a/client/js/views/user_view.js b/client/js/views/user_view.js index a9b9a18..89cd9c8 100644 --- a/client/js/views/user_view.js +++ b/client/js/views/user_view.js @@ -12,33 +12,30 @@ class UserView extends BaseView { this.editView = new UserEditView(); } - render(options) { - let section = options.section; - if (!section) { - section = 'summary'; - } + render(ctx) { + const target = this.contentHolder; + const source = this.template(ctx); - let view = null; - if (section == 'edit') { - view = this.editView; - } else { - view = this.summaryView; - } + ctx.section = ctx.section || 'summary'; - this.showView(this.template(options)); - - options.target = this.contentHolder.querySelector( - '#user-content-holder'); - view.render(options); - - const allItemsSelector = '#content-holder [data-name]'; - for (let item of document.querySelectorAll(allItemsSelector)) { - if (item.getAttribute('data-name') === section) { + for (let item of source.querySelectorAll('[data-name]')) { + if (item.getAttribute('data-name') === ctx.section) { item.className = 'active'; } else { item.className = ''; } } + + let view = null; + if (ctx.section == 'edit') { + view = this.editView; + } else { + view = this.summaryView; + } + ctx.target = source.querySelector('#user-content-holder'); + view.render(ctx); + + this.showView(target, source); } }