From 60dbd3f26c0710381b564feb50e0fc1c3a76371f Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov Date: Fri, 4 Sep 2020 17:35:21 -0400 Subject: [PATCH] Significantly increase documentation for Component and Fragment --- js/src/common/Component.ts | 71 +++++++++++++++++++++++++++++++------- js/src/common/Fragment.ts | 43 +++++++++++++++++------ 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/js/src/common/Component.ts b/js/src/common/Component.ts index 765abc60e..7b450f3c9 100644 --- a/js/src/common/Component.ts +++ b/js/src/common/Component.ts @@ -8,30 +8,67 @@ export type ComponentAttrs = { /** * 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 - * return m('div', MyComponent.component({foo: 'bar')); + * return m('div',

Hello World

); + * + * @example + * return m('div', MyComponent.component({foo: 'bar'), m('p', 'Hello World!')); * * @see https://mithril.js.org/components.html */ export default abstract class Component implements Mithril.ClassComponent { - 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): Mithril.Children; - oninit(vnode: Mithril.Vnode) { + /** + * @inheritdoc + */ + protected oninit(vnode: Mithril.Vnode) { this.setAttrs(vnode.attrs); } - oncreate(vnode: Mithril.VnodeDOM) { + /** + * @inheritdoc + */ + protected oncreate(vnode: Mithril.VnodeDOM) { this.element = vnode.dom; } - onbeforeupdate(vnode: Mithril.VnodeDOM) { + /** + * @inheritdoc + */ + protected onbeforeupdate(vnode: Mithril.VnodeDOM) { this.setAttrs(vnode.attrs); } @@ -48,7 +85,7 @@ export default abstract class Component implemen * @returns {jQuery} the jQuery object for the DOM node * @final */ - $(selector) { + protected $(selector) { const $element = $(this.element); return selector ? $element.find(selector) : $element; @@ -56,14 +93,21 @@ export default abstract class Component implemen /** * 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); 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); if (attrs) { @@ -83,7 +127,8 @@ export default abstract class Component implemen this.attrs = attrs; } - protected static initAttrs(attrs: T): T { - return attrs; - } + /** + * Initialize the component's attrs. + */ + protected static initAttrs(attrs: T): void {} } diff --git a/js/src/common/Fragment.ts b/js/src/common/Fragment.ts index 7eab011ea..a929ce066 100644 --- a/js/src/common/Fragment.ts +++ b/js/src/common/Fragment.ts @@ -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 { - element!: Element; +export default abstract class Fragment implements Mithril.ClassComponent { + /** + * 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 * 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 - * component. + * fragment. * * @param {String} [selector] a jQuery-compatible selector string * @returns {jQuery} the jQuery object for the DOM node * @final */ - $(selector) { + public $(selector) { const $element = $(this.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(); vdom.attrs = vdom.attrs || {}; @@ -42,7 +64,8 @@ export default abstract class Fragment { return vdom; } - oncreate: () => {}; - + /** + * @inheritdoc + */ abstract view(); }