2021-09-07 13:28:20 +02:00
import classnames , { Argument as ClassNames } from 'classnames' ;
import collect , { Collection } from 'collect.js' ;
import m , {
Children , ClassComponent ,
Vnode , VnodeDOM
} from 'mithril' ;
interface Attributes extends Collection {
addClassNames ( ... classNames : ClassNames [ ] ) : void ,
addStyles ( ... styles : string [ ] ) : void
}
2021-08-02 13:16:10 +02:00
2021-09-29 15:32:31 +02:00
// eslint-disable-next-line no-secrets/no-secrets
// noinspection SpellCheckingInspection,JSUnusedGlobalSymbols
2021-08-02 13:16:10 +02:00
/ * *
* @ abstract
*
* The ` Component ` class defines a user interface 'building block' . A component
* 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 foo = "bar" > < p > Hello World < / p > < / MyComponent > ) ;
*
* @ example
* return m ( 'div' , MyComponent . component ( { foo : 'bar' ) , m ( 'p' , 'Hello World!' ) ) ;
*
2021-09-07 13:28:20 +02:00
* @ see https : //js.org/components.html
2021-08-02 13:16:10 +02:00
* /
2021-09-07 13:28:20 +02:00
export default class Component implements ClassComponent {
2021-08-02 13:16:10 +02:00
/ * *
* The root DOM element for the component .
*
* @ protected
* /
element : Element ;
/ * *
2021-09-07 13:28:20 +02:00
* The attributes passed into the component . They are transformed into a collection by initAttrs .
*
* @ method < string > addClassNames ( )
*
* @ see https : //js.org/components.html#passing-data-to-components
* @ see initAttrs
2021-08-02 13:16:10 +02:00
*
* @ protected
* /
2021-09-07 13:28:20 +02:00
attrs : Attributes ;
2021-08-02 13:16:10 +02:00
/ * *
* @ inheritdoc
* @ abstract
* /
2021-09-07 13:28:20 +02:00
view ( vnode : Vnode ) : Children { }
2021-08-02 13:16:10 +02:00
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
oninit ( vnode : Vnode ) {
2021-08-02 13:16:10 +02:00
this . setAttrs ( vnode . attrs ) ;
}
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
oncreate ( vnode : VnodeDOM ) {
2021-08-02 13:16:10 +02:00
this . element = vnode . dom ;
}
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
onbeforeupdate ( vnode : VnodeDOM ) {
2021-08-02 13:16:10 +02:00
this . setAttrs ( vnode . attrs ) ;
}
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
onupdate ( vnode : VnodeDOM ) { }
2021-08-02 13:16:10 +02:00
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
onbeforeremove ( vnode : VnodeDOM ) { }
2021-08-02 13:16:10 +02:00
/ * *
* @ inheritdoc
* /
2021-09-07 13:28:20 +02:00
onremove ( vnode : VnodeDOM ) { }
2021-08-02 13:16:10 +02:00
/ * *
* 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
* element as its buffer .
*
* For example , calling ` component. $ ('li') ` will return a jQuery object
* containing all of the ` li ` elements inside the DOM element of this
* component .
*
* @ param [ selector ] a jQuery - compatible selector string
* @ returns the jQuery object for the DOM node
* @ final
* @ protected
* /
2021-09-07 13:28:20 +02:00
$ ( selector ? : string ) : JQuery {
2021-08-02 13:16:10 +02:00
const $element : JQuery < HTMLElement > = $ ( this . element ) ;
2021-09-29 15:32:31 +02:00
return selector ? $element . find ( element => selector ( element ) ) : $element ;
2021-09-07 13:28:20 +02:00
}
2021-08-02 13:16:10 +02:00
/ * *
* Convenience method to attach a component without JSX .
* Has the same effect as calling ` m(THIS_CLASS, attrs, children) ` .
*
2021-09-07 13:28:20 +02:00
* @ see https : //js.org/hyperscript.html#mselector,-attributes,-children
2021-08-02 13:16:10 +02:00
* /
2021-09-29 15:32:31 +02:00
static component ( attributes : { ... } = { } , children ) : Vnode {
2021-09-07 13:28:20 +02:00
const componentAttributes : Record < string , any > = { ... attributes } ;
2021-08-02 13:16:10 +02:00
2021-09-07 13:28:20 +02:00
return m ( this , componentAttributes , children ) ;
2021-08-02 13:16:10 +02:00
}
/ * *
* Saves a reference to the vnode attrs after running them through initAttrs ,
* and checking for common issues .
*
* @ private
* /
2021-09-29 15:32:31 +02:00
setAttrs ( attributes : { ... } = { } ) : void {
2021-09-07 13:28:20 +02:00
this . initAttrs ( attributes ) ;
if ( attributes ) {
if ( 'children' in attributes ) {
// noinspection JSUnresolvedVariable
2021-08-02 13:16:10 +02:00
throw new Error ( ` [ ${ this . constructor . name } ] The "children" attribute of attrs should never be used. Either pass children in as the vnode children or rename the attribute ` ) ;
}
2021-09-07 13:28:20 +02:00
if ( 'tag' in attributes ) {
// noinspection JSUnresolvedVariable
2021-08-02 13:16:10 +02:00
throw new Error ( ` [ ${ this . constructor . name } ] You cannot use the "tag" attribute name with Mithril 2. ` ) ;
}
}
2021-09-07 13:28:20 +02:00
this . attrs = collect ( attributes ) ;
this . attrs . macro ( 'addClassNames' , ( ... classNames : ClassNames [ ] ) => {
this . attrs . put ( 'className' , classnames ( this . attrs . get ( 'className' ) , ... classNames ) ) ;
} ) ;
this . attrs . macro ( 'addStyles' , ( ... styles : string [ ] ) => {
let s : string = this . attrs . get ( 'style' , '' ) ;
if ( ! s . trimEnd ( ) . endsWith ( ';' ) ) {
s += '; ' ;
}
s += styles . join ( '; ' ) ;
this . attrs . put ( 'style' , s ) ;
} ) ;
2021-08-02 13:16:10 +02:00
}
/ * *
* Initialize the component ' s attrs .
*
* This can be used to assign default values for missing , optional attrs .
*
* @ protected
* /
2021-09-29 15:32:31 +02:00
initAttrs ( attributes : { ... } ) : void { }
2021-08-02 13:16:10 +02:00
}