client/views: refactor rendering
This commit is contained in:
parent
7871c69aa3
commit
0ceaa8da42
|
@ -9,5 +9,5 @@
|
||||||
--></ul><!--
|
--></ul><!--
|
||||||
--></nav>
|
--></nav>
|
||||||
|
|
||||||
<div class='content'>{{{ this.content }}}</div>
|
<div class='content'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,9 @@ class HelpController {
|
||||||
|
|
||||||
showHelpRoute(section) {
|
showHelpRoute(section) {
|
||||||
topNavController.activate('help');
|
topNavController.activate('help');
|
||||||
this.helpView.render(section);
|
this.helpView.render({
|
||||||
|
section: section,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class HomeController {
|
||||||
|
|
||||||
indexRoute() {
|
indexRoute() {
|
||||||
topNavController.activate('home');
|
topNavController.activate('home');
|
||||||
this.homeView.render();
|
this.homeView.render({});
|
||||||
}
|
}
|
||||||
|
|
||||||
notFoundRoute() {
|
notFoundRoute() {
|
||||||
|
|
|
@ -32,17 +32,16 @@ class TopNavController {
|
||||||
'help': new NavigationItem('E', 'Help', '/help'),
|
'help': new NavigationItem('E', 'Help', '/help'),
|
||||||
};
|
};
|
||||||
|
|
||||||
events.listen(
|
const rerender = () => {
|
||||||
events.Authentication,
|
|
||||||
() => {
|
|
||||||
this.updateVisibility();
|
this.updateVisibility();
|
||||||
this.topNavView.render(this.items, this.activeItem);
|
this.topNavView.render({
|
||||||
|
items: this.items,
|
||||||
|
activeItem: this.activeItem});
|
||||||
this.topNavView.activate(this.activeItem);
|
this.topNavView.activate(this.activeItem);
|
||||||
});
|
};
|
||||||
|
|
||||||
this.updateVisibility();
|
events.listen(events.Authentication, rerender);
|
||||||
this.topNavView.render(this.items, this.activeItem);
|
rerender();
|
||||||
this.topNavView.activate(this.activeItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisibility() {
|
updateVisibility() {
|
||||||
|
|
|
@ -54,7 +54,7 @@ class UsersController {
|
||||||
this.user = response.user;
|
this.user = response.user;
|
||||||
next();
|
next();
|
||||||
}).catch(response => {
|
}).catch(response => {
|
||||||
this.userView.empty();
|
this.userView.emptyView(this.userView.contentHolder);
|
||||||
events.notify(events.Error, response.description);
|
events.notify(events.Error, response.description);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,16 +28,27 @@ events.listen(events.Error, msg => { messageHandler(msg, 'error'); });
|
||||||
class BaseView {
|
class BaseView {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.contentHolder = contentHolder;
|
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) {
|
getTemplate(templatePath) {
|
||||||
const templateElement = document.getElementById(templatePath);
|
const templateElement = document.getElementById(templatePath);
|
||||||
if (!templateElement) {
|
if (!templateElement) {
|
||||||
console.log('Missing template: ' + templatePath);
|
console.error('Missing template: ' + templatePath);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const templateText = templateElement.innerHTML;
|
const templateText = templateElement.innerHTML.trim();
|
||||||
return handlebars.compile(templateText);
|
const templateFactory = handlebars.compile(templateText);
|
||||||
|
return (...args) => {
|
||||||
|
return this.htmlToDom(templateFactory(...args));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessages() {
|
clearMessages() {
|
||||||
|
@ -72,12 +83,32 @@ class BaseView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
empty() {
|
emptyView(target) {
|
||||||
this.showView('<div class="messages"></div>');
|
return this.showView(
|
||||||
|
target,
|
||||||
|
this.htmlToDom('<div class="messages"></div>'));
|
||||||
}
|
}
|
||||||
|
|
||||||
showView(html) {
|
showView(target, source) {
|
||||||
this.contentHolder.innerHTML = html;
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,29 +15,32 @@ class HelpView extends BaseView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(section) {
|
render(ctx) {
|
||||||
if (!section) {
|
const target = this.contentHolder;
|
||||||
section = 'about';
|
const source = this.template();
|
||||||
}
|
|
||||||
if (!(section in this.sectionTemplates)) {
|
ctx.section = ctx.section || 'about';
|
||||||
this.showView('');
|
if (!(ctx.section in this.sectionTemplates)) {
|
||||||
|
this.emptyView(this.contentHolder);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.sectionTemplates[section]({
|
this.showView(
|
||||||
|
source.querySelector('.content'),
|
||||||
|
this.sectionTemplates[ctx.section]({
|
||||||
name: config.name,
|
name: config.name,
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.showView(this.template({'content': content}));
|
const allItemsSelector = '[data-name]';
|
||||||
|
for (let item of source.querySelectorAll(allItemsSelector)) {
|
||||||
const allItemsSelector = '#content-holder [data-name]';
|
if (item.getAttribute('data-name') === ctx.section) {
|
||||||
for (let item of document.querySelectorAll(allItemsSelector)) {
|
|
||||||
if (item.getAttribute('data-name') === section) {
|
|
||||||
item.className = 'active';
|
item.className = 'active';
|
||||||
} else {
|
} else {
|
||||||
item.className = '';
|
item.className = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,14 @@ class HomeView extends BaseView {
|
||||||
this.template = this.getTemplate('home-template');
|
this.template = this.getTemplate('home-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(section) {
|
render(ctx) {
|
||||||
this.showView(this.template({
|
const target = this.contentHolder;
|
||||||
|
const source = this.template({
|
||||||
name: config.name,
|
name: config.name,
|
||||||
version: config.meta.version,
|
version: config.meta.version,
|
||||||
buildDate: config.meta.buildDate,
|
buildDate: config.meta.buildDate,
|
||||||
}));
|
});
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,16 @@ class LoginView extends BaseView {
|
||||||
this.template = this.getTemplate('login-template');
|
this.template = this.getTemplate('login-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
this.showView(this.template());
|
const target = this.contentHolder;
|
||||||
const form = this.contentHolder.querySelector('form');
|
const source = this.template();
|
||||||
this.decorateValidator(form);
|
|
||||||
|
|
||||||
const userNameField = document.getElementById('user-name');
|
const form = source.querySelector('form');
|
||||||
const passwordField = document.getElementById('user-password');
|
const userNameField = source.querySelector('#user-name');
|
||||||
const rememberUserField = document.getElementById('remember-user');
|
const passwordField = source.querySelector('#user-password');
|
||||||
|
const rememberUserField = source.querySelector('#remember-user');
|
||||||
|
|
||||||
|
this.decorateValidator(form);
|
||||||
userNameField.setAttribute('pattern', config.userNameRegex);
|
userNameField.setAttribute('pattern', config.userNameRegex);
|
||||||
passwordField.setAttribute('pattern', config.passwordRegex);
|
passwordField.setAttribute('pattern', config.passwordRegex);
|
||||||
|
|
||||||
|
@ -25,8 +27,7 @@ class LoginView extends BaseView {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.disableForm(form);
|
this.disableForm(form);
|
||||||
options
|
ctx.login(
|
||||||
.login(
|
|
||||||
userNameField.value,
|
userNameField.value,
|
||||||
passwordField.value,
|
passwordField.value,
|
||||||
rememberUserField.checked)
|
rememberUserField.checked)
|
||||||
|
@ -38,6 +39,8 @@ class LoginView extends BaseView {
|
||||||
events.notify(events.Error, errorMessage);
|
events.notify(events.Error, errorMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,19 +9,20 @@ class PasswordResetView extends BaseView {
|
||||||
this.template = this.getTemplate('password-reset-template');
|
this.template = this.getTemplate('password-reset-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
this.showView(this.template());
|
const target = this.contentHolder;
|
||||||
const form = this.contentHolder.querySelector('form');
|
const source = this.template();
|
||||||
this.decorateValidator(form);
|
|
||||||
|
|
||||||
const userNameOrEmailField = document.getElementById('user-name');
|
const form = source.querySelector('form');
|
||||||
|
const userNameOrEmailField = source.querySelector('#user-name');
|
||||||
|
|
||||||
|
this.decorateValidator(form);
|
||||||
|
|
||||||
form.addEventListener('submit', e => {
|
form.addEventListener('submit', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.disableForm(form);
|
this.disableForm(form);
|
||||||
options
|
ctx.proceed(userNameOrEmailField.value)
|
||||||
.proceed(userNameOrEmailField.value)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
events.notify(
|
events.notify(
|
||||||
events.Success,
|
events.Success,
|
||||||
|
@ -33,6 +34,8 @@ class PasswordResetView extends BaseView {
|
||||||
events.notify(events.Error, errorMessage);
|
events.notify(events.Error, errorMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,14 @@ class RegistrationView extends BaseView {
|
||||||
this.template = this.getTemplate('user-registration-template');
|
this.template = this.getTemplate('user-registration-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
this.showView(this.template());
|
const target = this.contentHolder;
|
||||||
|
const source = this.template();
|
||||||
|
|
||||||
const form = this.contentHolder.querySelector('form');
|
const form = source.querySelector('form');
|
||||||
const userNameField = this.contentHolder.querySelector('#user-name');
|
const userNameField = source.querySelector('#user-name');
|
||||||
const passwordField = this.contentHolder.querySelector('#user-password');
|
const passwordField = source.querySelector('#user-password');
|
||||||
const emailField = this.contentHolder.querySelector('#user-email');
|
const emailField = source.querySelector('#user-email');
|
||||||
|
|
||||||
this.decorateValidator(form);
|
this.decorateValidator(form);
|
||||||
userNameField.setAttribute('pattern', config.userNameRegex);
|
userNameField.setAttribute('pattern', config.userNameRegex);
|
||||||
|
@ -26,8 +27,7 @@ class RegistrationView extends BaseView {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.disableForm(form);
|
this.disableForm(form);
|
||||||
options
|
ctx.register(
|
||||||
.register(
|
|
||||||
userNameField.value,
|
userNameField.value,
|
||||||
passwordField.value,
|
passwordField.value,
|
||||||
emailField.value)
|
emailField.value)
|
||||||
|
@ -39,6 +39,8 @@ class RegistrationView extends BaseView {
|
||||||
events.notify(events.Error, errorMessage);
|
events.notify(events.Error, errorMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,19 @@ class TopNavView extends BaseView {
|
||||||
this.navHolder = document.getElementById('top-nav-holder');
|
this.navHolder = document.getElementById('top-nav-holder');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(items) {
|
render(ctx) {
|
||||||
this.navHolder.innerHTML = this.template({items: items});
|
const target = this.navHolder;
|
||||||
for (let link of this.navHolder.querySelectorAll('a')) {
|
const source = this.template(ctx);
|
||||||
|
|
||||||
|
for (let link of source.querySelectorAll('a')) {
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
'(' + link.getAttribute('accesskey') + ')', 'i');
|
'(' + link.getAttribute('accesskey') + ')', 'i');
|
||||||
link.innerHTML = link.textContent.replace(
|
link.innerHTML = link.textContent.replace(
|
||||||
regex,
|
regex,
|
||||||
'<span class="access-key" data-accesskey="$1">$1</span>');
|
'<span class="access-key" data-accesskey="$1">$1</span>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.showView(this.navHolder, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
activate(itemName) {
|
activate(itemName) {
|
||||||
|
|
|
@ -9,14 +9,15 @@ class UserEditView extends BaseView {
|
||||||
this.template = this.getTemplate('user-edit-template');
|
this.template = this.getTemplate('user-edit-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
options.target.innerHTML = this.template(options);
|
const target = ctx.target;
|
||||||
|
const source = this.template(ctx);
|
||||||
|
|
||||||
const form = options.target.querySelector('form');
|
const form = source.querySelector('form');
|
||||||
const rankField = options.target.querySelector('#user-rank');
|
const rankField = source.querySelector('#user-rank');
|
||||||
const emailField = options.target.querySelector('#user-email');
|
const emailField = source.querySelector('#user-email');
|
||||||
const userNameField = options.target.querySelector('#user-name');
|
const userNameField = source.querySelector('#user-name');
|
||||||
const passwordField = options.target.querySelector('#user-password');
|
const passwordField = source.querySelector('#user-password');
|
||||||
|
|
||||||
this.decorateValidator(form);
|
this.decorateValidator(form);
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ class UserEditView extends BaseView {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rankField) {
|
if (rankField) {
|
||||||
rankField.value = options.user.rank;
|
rankField.value = ctx.user.rank;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: avatar */
|
/* TODO: avatar */
|
||||||
|
@ -42,8 +43,7 @@ class UserEditView extends BaseView {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.disableForm(form);
|
this.disableForm(form);
|
||||||
options
|
ctx.edit(
|
||||||
.edit(
|
|
||||||
userNameField.value,
|
userNameField.value,
|
||||||
passwordField.value,
|
passwordField.value,
|
||||||
emailField.value,
|
emailField.value,
|
||||||
|
@ -51,6 +51,8 @@ class UserEditView extends BaseView {
|
||||||
.then(user => { this.enableForm(form); })
|
.then(user => { this.enableForm(form); })
|
||||||
.catch(() => { this.enableForm(form); });
|
.catch(() => { this.enableForm(form); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ class UserSummaryView extends BaseView {
|
||||||
this.template = this.getTemplate('user-summary-template');
|
this.template = this.getTemplate('user-summary-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
options.target.innerHTML = this.template(options);
|
const target = ctx.target;
|
||||||
|
const source = this.template(ctx);
|
||||||
|
this.showView(target, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,33 +12,30 @@ class UserView extends BaseView {
|
||||||
this.editView = new UserEditView();
|
this.editView = new UserEditView();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(options) {
|
render(ctx) {
|
||||||
let section = options.section;
|
const target = this.contentHolder;
|
||||||
if (!section) {
|
const source = this.template(ctx);
|
||||||
section = 'summary';
|
|
||||||
}
|
|
||||||
|
|
||||||
let view = null;
|
ctx.section = ctx.section || 'summary';
|
||||||
if (section == 'edit') {
|
|
||||||
view = this.editView;
|
|
||||||
} else {
|
|
||||||
view = this.summaryView;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showView(this.template(options));
|
for (let item of source.querySelectorAll('[data-name]')) {
|
||||||
|
if (item.getAttribute('data-name') === ctx.section) {
|
||||||
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) {
|
|
||||||
item.className = 'active';
|
item.className = 'active';
|
||||||
} else {
|
} else {
|
||||||
item.className = '';
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue