From dcd14821c23d9843977aa87cbcd428255d5e25a3 Mon Sep 17 00:00:00 2001 From: Matthew Kilgore Date: Fri, 7 Aug 2020 19:25:34 -0400 Subject: [PATCH] infrastructure: revert to using this.attrs --- js/src/common/Component.ts | 32 ++++++++++-- js/src/common/components/Button.js | 10 ++-- js/src/common/components/Dropdown.js | 52 +++++++++---------- js/src/common/components/LoadingIndicator.js | 6 +-- js/src/common/components/Modal.js | 30 +++++------ js/src/common/components/ModalManager.js | 10 ++-- js/src/common/components/SelectDropdown.js | 6 +-- js/src/common/components/SplitDropdown.js | 8 +-- .../forum/components/ForgotPasswordModal.js | 8 +-- js/src/forum/components/LogInModal.js | 8 +-- js/src/forum/components/SignUpModal.js | 40 +++++++------- 11 files changed, 118 insertions(+), 92 deletions(-) diff --git a/js/src/common/Component.ts b/js/src/common/Component.ts index a4dd6fe66..47f28aef4 100644 --- a/js/src/common/Component.ts +++ b/js/src/common/Component.ts @@ -1,4 +1,10 @@ -import { ClassComponent, VnodeDOM } from 'mithril'; +import * as Mithril from 'mithril'; + +export type ComponentAttrs = { + className?: string; + + [key: string]: any; +}; /** * The `Component` class defines a user interface 'building block'. A component @@ -10,15 +16,29 @@ import { ClassComponent, VnodeDOM } from 'mithril'; * * @see https://mithril.js.org/components.html */ -export default abstract class Component implements ClassComponent { +export default abstract class Component implements Mithril.ClassComponent { element!: Element; - abstract view(); + attrs: T; - oncreate(vnode: VnodeDOM) { + abstract view(vnode: Mithril.Vnode): Mithril.Children; + + oninit(vnode: Mithril.Vnode) { + this.initAttrs(vnode.attrs); + + this.attrs = vnode.attrs; + } + + oncreate(vnode: Mithril.VnodeDOM) { this.element = vnode.dom; } + onbeforeupdate(vnode: Mithril.VnodeDOM) { + this.initAttrs(vnode.attrs); + + this.attrs = vnode.attrs; + } + /** * Returns a jQuery object for this component's element. If you pass in a * selector string, this method will return a jQuery object, using the current @@ -46,4 +66,8 @@ export default abstract class Component implements ClassComponent { return m(this as any, componentProps, children); } + + protected initAttrs(attrs: T): T { + return attrs; + } } diff --git a/js/src/common/components/Button.js b/js/src/common/components/Button.js index e3d49d50c..15eadc7b9 100644 --- a/js/src/common/components/Button.js +++ b/js/src/common/components/Button.js @@ -22,7 +22,7 @@ import LoadingIndicator from './LoadingIndicator'; */ export default class Button extends Component { view(vnode) { - const attrs = Object.assign({}, vnode.attrs); + const attrs = Object.assign({}, this.attrs); attrs.className = attrs.className || ''; attrs.type = attrs.type || 'button'; @@ -47,7 +47,7 @@ export default class Button extends Component { delete attrs.onclick; } - return ; + return ; } /** @@ -56,13 +56,13 @@ export default class Button extends Component { * @return {*} * @protected */ - getButtonContent(attrs, children) { - const iconName = attrs.icon; + getButtonContent(children) { + const iconName = this.attrs.icon; return [ iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '', children ? {children} : '', - attrs.loading ? : '', + this.attrs.loading ? : '', ]; } } diff --git a/js/src/common/components/Dropdown.js b/js/src/common/components/Dropdown.js index 04f64fb9d..8d61b77bb 100644 --- a/js/src/common/components/Dropdown.js +++ b/js/src/common/components/Dropdown.js @@ -19,19 +19,27 @@ import listItems from '../helpers/listItems'; * The children will be displayed as a list inside of the dropdown menu. */ export default class Dropdown extends Component { + initAttrs(attrs) { + attrs.className = attrs.className || ''; + attrs.buttonClassName = attrs.buttonClassName || ''; + attrs.menuClassName = attrs.menuClassName || ''; + attrs.label = attrs.label || ''; + attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down'; + } + oninit(vnode) { + super.oninit(vnode); + this.showing = false; } view(vnode) { const items = vnode.children ? listItems(vnode.children) : []; - this.initAttrs(vnode.attrs); - return ( -
- {this.getButton(vnode.attrs, vnode.children)} - {this.getMenu(vnode.attrs.menuClassName, items)} +
+ {this.getButton(vnode.children)} + {this.getMenu(items)}
); } @@ -45,8 +53,8 @@ export default class Dropdown extends Component { this.$().on('shown.bs.dropdown', () => { this.showing = true; - if (vnode.attrs.onshow) { - vnode.attrs.onshow(); + if (this.attrs.onshow) { + this.attrs.onshow(); } m.redraw(); @@ -68,32 +76,24 @@ export default class Dropdown extends Component { this.$().on('hidden.bs.dropdown', () => { this.showing = false; - if (vnode.attrs.onhide) { - vnode.attrs.onhide(); + if (this.attrs.onhide) { + this.attrs.onhide(); } m.redraw(); }); } - initAttrs(attrs) { - attrs.className = attrs.className || ''; - attrs.buttonClassName = attrs.buttonClassName || ''; - attrs.menuClassName = attrs.menuClassName || ''; - attrs.label = attrs.label || ''; - attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down'; - } - /** * Get the template for the button. * * @return {*} * @protected */ - getButton(attrs, children) { + getButton(children) { return ( - ); } @@ -104,15 +104,15 @@ export default class Dropdown extends Component { * @return {*} * @protected */ - getButtonContent(attrs, children) { + getButtonContent(children) { return [ - attrs.icon ? icon(attrs.icon, { className: 'Button-icon' }) : '', - {attrs.label}, - attrs.caretIcon ? icon(attrs.caretIcon, { className: 'Button-caret' }) : '', + this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '', + {this.attrs.label}, + this.attrs.caretIcon ? icon(this.attrs.caretIcon, { className: 'Button-caret' }) : '', ]; } - getMenu(menuClassName, items) { - return
    {items}
; + getMenu(items) { + return
    {items}
; } } diff --git a/js/src/common/components/LoadingIndicator.js b/js/src/common/components/LoadingIndicator.js index b7ec3e3e3..007a1c64e 100644 --- a/js/src/common/components/LoadingIndicator.js +++ b/js/src/common/components/LoadingIndicator.js @@ -10,8 +10,8 @@ import { Spinner } from 'spin.js'; * All other props will be assigned as attributes on the element. */ export default class LoadingIndicator extends Component { - view(vnode) { - const attrs = Object.assign({}, vnode.attrs); + view() { + const attrs = Object.assign({}, this.attrs); attrs.className = 'LoadingIndicator ' + (attrs.className || ''); delete attrs.size; @@ -24,7 +24,7 @@ export default class LoadingIndicator extends Component { const options = { zIndex: 'auto', color: this.$().css('color') }; - switch (vnode.size) { + switch (this.attrs.size) { case 'large': Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 }); break; diff --git a/js/src/common/components/Modal.js b/js/src/common/components/Modal.js index d1a5adc77..a805d64ff 100644 --- a/js/src/common/components/Modal.js +++ b/js/src/common/components/Modal.js @@ -14,17 +14,17 @@ export default class Modal extends Component { */ static isDismissible = true; - oninit() { - /** - * Attributes for an alert component to show below the header. - * - * @type {object} - */ - this.alertAttrs = null; - } + /** + * Attributes for an alert component to show below the header. + * + * @type {object} + */ + alertAttrs = null; oncreate(vnode) { - vnode.attrs.onshow(() => this.onready(vnode.attrs)); + super.oncreate(vnode); + + this.attrs.onshow(() => this.onready()); } view(vnode) { @@ -39,7 +39,7 @@ export default class Modal extends Component {
{Button.component({ icon: 'fas fa-times', - onclick: this.hide.bind(this, vnode.attrs), + onclick: this.hide.bind(this), className: 'Button Button--icon Button--link', })}
@@ -54,7 +54,7 @@ export default class Modal extends Component { {this.alertAttrs ?
{Alert.component(this.alertAttrs)}
: ''} - {this.content(vnode.attrs)} + {this.content(this.attrs)}
@@ -83,7 +83,7 @@ export default class Modal extends Component { * @return {VirtualElement} * @abstract */ - content(attrs) {} + content() {} /** * Handle the modal form's submit event. @@ -95,15 +95,15 @@ export default class Modal extends Component { /** * Focus on the first input when the modal is ready to be used. */ - onready(attrs) { + onready() { this.$('form').find('input, select, textarea').first().focus().select(); } /** * Hide the modal. */ - hide(attrs) { - attrs.onhide(); + hide() { + this.attrs.onhide(); } /** diff --git a/js/src/common/components/ModalManager.js b/js/src/common/components/ModalManager.js index 6624e38a1..b357e938e 100644 --- a/js/src/common/components/ModalManager.js +++ b/js/src/common/components/ModalManager.js @@ -6,12 +6,8 @@ import Component from '../Component'; * overwrite the previous one. */ export default class ModalManager extends Component { - oninit(vnode) { - this.state = vnode.attrs.state; - } - view(vnode) { - const modal = vnode.attrs.state.modal; + const modal = this.attrs.state.modal; return (
@@ -26,11 +22,11 @@ export default class ModalManager extends Component { // Ensure the modal state is notified about a closed modal, even when the // DOM-based Bootstrap JavaScript code triggered the closing of the modal, // e.g. via ESC key or a click on the modal backdrop. - this.$().on('hidden.bs.modal', this.state.close.bind(this.state)); + this.$().on('hidden.bs.modal', this.attrs.state.close.bind(this.attrs.state)); } animateShow(readyCallback) { - const dismissible = !!this.state.modal.componentClass.isDismissible; + const dismissible = !!this.attrs.state.modal.componentClass.isDismissible; this.$() .one('shown.bs.modal', readyCallback) diff --git a/js/src/common/components/SelectDropdown.js b/js/src/common/components/SelectDropdown.js index 62766fc5b..feca24f36 100644 --- a/js/src/common/components/SelectDropdown.js +++ b/js/src/common/components/SelectDropdown.js @@ -20,12 +20,12 @@ export default class SelectDropdown extends Dropdown { attrs.className += ' Dropdown--select'; } - getButtonContent(attrs, children) { + getButtonContent(children) { const activeChild = children.filter((child) => child.attrs.active)[0]; - let label = (activeChild && activeChild.children) || attrs.defaultLabel; + let label = (activeChild && activeChild.children) || this.attrs.defaultLabel; if (label instanceof Array) label = label[0]; - return [{label}, icon(attrs.caretIcon, { className: 'Button-caret' })]; + return [{label}, icon(this.attrs.caretIcon, { className: 'Button-caret' })]; } } diff --git a/js/src/common/components/SplitDropdown.js b/js/src/common/components/SplitDropdown.js index 94d3235c5..75533813a 100644 --- a/js/src/common/components/SplitDropdown.js +++ b/js/src/common/components/SplitDropdown.js @@ -7,11 +7,11 @@ import icon from '../helpers/icon'; * is displayed as its own button prior to the toggle button. */ export default class SplitDropdown extends Dropdown { - static initProps(props) { - super.initProps(props); + initAttrs(attrs) { + super.initAttrs(attrs); - props.className += ' Dropdown--split'; - props.menuClassName += ' Dropdown-menu--right'; + attrs.className += ' Dropdown--split'; + attrs.menuClassName += ' Dropdown-menu--right'; } getButton() { diff --git a/js/src/forum/components/ForgotPasswordModal.js b/js/src/forum/components/ForgotPasswordModal.js index 0322c7fdd..f8fc9aac9 100644 --- a/js/src/forum/components/ForgotPasswordModal.js +++ b/js/src/forum/components/ForgotPasswordModal.js @@ -12,13 +12,15 @@ import extractText from '../../common/utils/extractText'; * - `email` */ export default class ForgotPasswordModal extends Modal { - oninit(attrs) { + oninit(vnode) { + super.oninit(vnode); + /** * The value of the email input. * * @type {Function} */ - this.email = Stream(attrs.email || ''); + this.email = Stream(this.attrs.email || ''); /** * Whether or not the password reset email was sent successfully. @@ -91,7 +93,7 @@ export default class ForgotPasswordModal extends Modal { .request({ method: 'POST', url: app.forum.attribute('apiUrl') + '/forgot', - data: { email: this.email() }, + body: { email: this.email() }, errorHandler: this.onerror.bind(this), }) .then(() => { diff --git a/js/src/forum/components/LogInModal.js b/js/src/forum/components/LogInModal.js index 0fd3d1bd4..de589036c 100644 --- a/js/src/forum/components/LogInModal.js +++ b/js/src/forum/components/LogInModal.js @@ -17,26 +17,28 @@ import ItemList from '../../common/utils/ItemList'; */ export default class LogInModal extends Modal { oninit(vnode) { + super.oninit(vnode); + /** * The value of the identification input. * * @type {Function} */ - this.identification = Stream(vnode.attrs.identification || ''); + this.identification = Stream(this.attrs.identification || ''); /** * The value of the password input. * * @type {Function} */ - this.password = Stream(vnode.attrs.password || ''); + this.password = Stream(this.attrs.password || ''); /** * The value of the remember me input. * * @type {Function} */ - this.remember = Stream(!!vnode.attrs.remember); + this.remember = Stream(!!this.attrs.remember); } className() { diff --git a/js/src/forum/components/SignUpModal.js b/js/src/forum/components/SignUpModal.js index 35957cddd..31622b89c 100644 --- a/js/src/forum/components/SignUpModal.js +++ b/js/src/forum/components/SignUpModal.js @@ -18,26 +18,28 @@ import ItemList from '../../common/utils/ItemList'; */ export default class SignUpModal extends Modal { oninit(vnode) { + super.oninit(vnode); + /** * The value of the username input. * * @type {Function} */ - this.username = Stream(vnode.attrs.username || ''); + this.username = Stream(this.attrs.username || ''); /** * The value of the email input. * * @type {Function} */ - this.email = Stream(vnode.attrs.email || ''); + this.email = Stream(this.attrs.email || ''); /** * The value of the password input. * * @type {Function} */ - this.password = Stream(vnode.attrs.password || ''); + this.password = Stream(this.attrs.password || ''); } className() { @@ -48,19 +50,19 @@ export default class SignUpModal extends Modal { return app.translator.trans('core.forum.sign_up.title'); } - content(attrs) { - return [
{this.body(attrs)}
,
{this.footer()}
]; + content() { + return [
{this.body()}
,
{this.footer()}
]; } - isProvided(field, attrs) { - return attrs.provided && attrs.provided.indexOf(field) !== -1; + isProvided(field) { + return this.attrs.provided && this.attrs.provided.indexOf(field) !== -1; } - body(attrs) { - return [attrs.token ? '' : ,
{this.fields(attrs).toArray()}
]; + body() { + return [this.attrs.token ? '' : ,
{this.fields().toArray()}
]; } - fields(attrs) { + fields() { const items = new ItemList(); items.add( @@ -73,7 +75,7 @@ export default class SignUpModal extends Modal { placeholder={extractText(app.translator.trans('core.forum.sign_up.username_placeholder'))} value={this.username()} bidi={this.username} - disabled={this.loading || this.isProvided('username', attrs)} + disabled={this.loading || this.isProvided('username', this.attrs)} />
, 30 @@ -89,13 +91,13 @@ export default class SignUpModal extends Modal { placeholder={extractText(app.translator.trans('core.forum.sign_up.email_placeholder'))} value={this.email()} bidi={this.email} - disabled={this.loading || this.isProvided('email', attrs)} + disabled={this.loading || this.isProvided('email', this.attrs)} /> , 20 ); - if (!attrs.token) { + if (!this.attrs.token) { items.add( 'password',
@@ -147,8 +149,8 @@ export default class SignUpModal extends Modal { app.modal.show(LogInModal, props); } - onready(attrs) { - if (attrs.username && !attrs.email) { + onready() { + if (this.attrs.username && !this.attrs.email) { this.$('[name=email]').select(); } else { this.$('[name=username]').select(); @@ -160,7 +162,7 @@ export default class SignUpModal extends Modal { this.loading = true; - const body = this.submitData(attrs); + const body = this.submitData(); app .request({ @@ -178,14 +180,14 @@ export default class SignUpModal extends Modal { * @return {Object} * @protected */ - submitData(attrs) { + submitData() { const data = { username: this.username(), email: this.email(), }; - if (attrs.token) { - data.token = attrs.token; + if (this.attrs.token) { + data.token = this.attrs.token; } else { data.password = this.password(); }