1
0
mirror of https://github.com/flarum/core.git synced 2025-08-09 18:07:02 +02:00

Significantly increase documentation for Component and Fragment

This commit is contained in:
Alexander Skvortsov
2020-09-04 17:35:21 -04:00
committed by Franz Liedke
parent e7f6e37799
commit 60dbd3f26c
2 changed files with 91 additions and 23 deletions

View File

@@ -8,30 +8,67 @@ export type ComponentAttrs = {
/** /**
* The `Component` class defines a user interface 'building block'. A component * The `Component` class defines a user interface 'building block'. A component
* can generate a virtual DOM to be rendered on each redraw. * generates a virtual DOM to be rendered on each redraw.
* *
* Essentially, this is a wrapper for Mithril's components that adds several useful features:
*
* - In the `oninit` and `onbeforeupdate` lifecycle hooks, we store vnode attrs in `this.attrs.
* This allows us to use attrs across components without having to pass the vnode to every single
* method.
* - The static `initAttrs` method allows a convenient way to provide defaults (or to otherwise modify)
* the attrs that have been passed into a component.
* - When the component is created in the DOM, we store its DOM element under `this.element`; this lets
* us use jQuery to modify child DOM state from internal methods via the `this.$()` method.
* - A convenience `component` method, which serves as an alternative to hyperscript and JSX.
*
* As with other Mithril components, components extending Component can be initialized
* and nested using JSX, hyperscript, or a combination of both. The `component` method can also
* be used.
* *
* @example * @example
* return m('div', MyComponent.component({foo: 'bar')); * return m('div', <MyComponent foo="bar"><p>Hello World</p></MyComponent>);
*
* @example
* return m('div', MyComponent.component({foo: 'bar'), m('p', 'Hello World!'));
* *
* @see https://mithril.js.org/components.html * @see https://mithril.js.org/components.html
*/ */
export default abstract class Component<T extends ComponentAttrs = any> implements Mithril.ClassComponent<T> { export default abstract class Component<T extends ComponentAttrs = any> implements Mithril.ClassComponent<T> {
element!: Element; /**
* The root DOM element for the component.
*/
protected element!: Element;
attrs: T; /**
* The attributes passed into the component.
*
* @see https://mithril.js.org/hyperscript.html#dom-attributes
*/
protected attrs: T;
/**
* @inheritdoc
*/
abstract view(vnode: Mithril.Vnode<T, this>): Mithril.Children; abstract view(vnode: Mithril.Vnode<T, this>): Mithril.Children;
oninit(vnode: Mithril.Vnode<T, this>) { /**
* @inheritdoc
*/
protected oninit(vnode: Mithril.Vnode<T, this>) {
this.setAttrs(vnode.attrs); this.setAttrs(vnode.attrs);
} }
oncreate(vnode: Mithril.VnodeDOM<T, this>) { /**
* @inheritdoc
*/
protected oncreate(vnode: Mithril.VnodeDOM<T, this>) {
this.element = vnode.dom; this.element = vnode.dom;
} }
onbeforeupdate(vnode: Mithril.VnodeDOM<T, this>) { /**
* @inheritdoc
*/
protected onbeforeupdate(vnode: Mithril.VnodeDOM<T, this>) {
this.setAttrs(vnode.attrs); this.setAttrs(vnode.attrs);
} }
@@ -48,7 +85,7 @@ export default abstract class Component<T extends ComponentAttrs = any> implemen
* @returns {jQuery} the jQuery object for the DOM node * @returns {jQuery} the jQuery object for the DOM node
* @final * @final
*/ */
$(selector) { protected $(selector) {
const $element = $(this.element); const $element = $(this.element);
return selector ? $element.find(selector) : $element; return selector ? $element.find(selector) : $element;
@@ -56,14 +93,21 @@ export default abstract class Component<T extends ComponentAttrs = any> implemen
/** /**
* Convenience method to attach a component without JSX. * Convenience method to attach a component without JSX.
* Has the same effect as calling `m(THIS_CLASS, attrs, children)`.
*
* @see https://mithril.js.org/hyperscript.html#mselector,-attributes,-children
*/ */
static component(attrs = {}, children = null) { static component(attrs = {}, children = null): Mithril.Vnode {
const componentProps = Object.assign({}, attrs); const componentProps = Object.assign({}, attrs);
return m(this as any, componentProps, children); return m(this as any, componentProps, children);
} }
private setAttrs(attrs: T = {} as T) { /**
* Saves a reference to the vnode attrs after running them through initAttrs,
* and checking for common issues.
*/
private setAttrs(attrs: T = {} as T): void {
this.constructor.initAttrs(attrs); this.constructor.initAttrs(attrs);
if (attrs) { if (attrs) {
@@ -83,7 +127,8 @@ export default abstract class Component<T extends ComponentAttrs = any> implemen
this.attrs = attrs; this.attrs = attrs;
} }
protected static initAttrs<T>(attrs: T): T { /**
return attrs; * Initialize the component's attrs.
} */
protected static initAttrs<T>(attrs: T): void {}
} }

View File

@@ -1,32 +1,54 @@
import * as Mithril from 'mithril';
/** /**
* Base class enabling jquery for mithril components attached with m.render(). * The `Fragment` class provides a wrapper class for Mithril components to be used with m.render().
* This is very similar to the `Component` wrapper class, but is used for more fine-grained control over
* the rendering and display of some significant chunks of the DOM.
*
* The main benefit of using this wrapper class as opposed to Mithril components directly
* is that it stores the vnode DOM, and provides a `$()` method allowing manipulation of said DOM.
*
* This should only be used when necessary, and only with `m.render`. If you are unsure whether you need
* this or `Component, you probably need `Component`.
*/ */
export default abstract class Fragment { export default abstract class Fragment implements Mithril.ClassComponent {
element!: Element; /**
* The root DOM element for the fragment.
*/
protected element!: Element;
/** /**
* Returns a jQuery object for this component's element. If you pass in a * Returns a jQuery object for this fragment's element. If you pass in a
* selector string, this method will return a jQuery object, using the current * selector string, this method will return a jQuery object, using the current
* element as its buffer. * element as its buffer.
* *
* For example, calling `component.$('li')` will return a jQuery object * For example, calling `fragment.$('li')` will return a jQuery object
* containing all of the `li` elements inside the DOM element of this * containing all of the `li` elements inside the DOM element of this
* component. * fragment.
* *
* @param {String} [selector] a jQuery-compatible selector string * @param {String} [selector] a jQuery-compatible selector string
* @returns {jQuery} the jQuery object for the DOM node * @returns {jQuery} the jQuery object for the DOM node
* @final * @final
*/ */
$(selector) { public $(selector) {
const $element = $(this.element); const $element = $(this.element);
return selector ? $element.find(selector) : $element; return selector ? $element.find(selector) : $element;
} }
/** /**
* Get the renderable virtual DOM that represents the fragment's view.
* *
* This should NOT be overridden by subclasses. Subclasses wishing to define
* their virtual DOM should override Fragment#view instead.
*
* @example
* const fragment = new MyFragment();
* m.render(document.body, fragment.render());
*
* @final
*/ */
render() { public render(): Mithril.Vnode {
const vdom = this.view(); const vdom = this.view();
vdom.attrs = vdom.attrs || {}; vdom.attrs = vdom.attrs || {};
@@ -42,7 +64,8 @@ export default abstract class Fragment {
return vdom; return vdom;
} }
oncreate: () => {}; /**
* @inheritdoc
*/
abstract view(); abstract view();
} }