somatic
TypeScript icon, indicating that this package has built-in type declarations

4.1.0 • Public • Published

somatic

Create composable (x)html components with pure javascript functions

Description

  • Composable components can be generated by calling createComponent() with a template function argument and an options object

  • Template functions passed to createComponent return a markup string that can make use of other component in the form of cutom tags. ES6 template strings are especially useful for defining these template functions

  • Lower-case HTML tags are recognized automatically; other tags are resolved using options passed to the createComponent() function

  • Component attributes which are not primitive can be specified in encoded form (JSON stringified then base64 encoded); template functions passed to createComponent() can access these attributes, already decoded, in the argument

API

/**
 * Main API function; returns pure js composable html 
 * component wrapper function based on a markup renderer function.
 * @param baseRenderer: specifies the function that returns the raw markup for the component.
 * @param options: options used to pre-configure wrapper function
 */
createComponent<T>(baseRenderer: Renderer<T>, options: Partial<ComponentOptions<T>>): Component<T>;

/**
 * create module with default options applied when options not supplied or fails
 */
setOptions(defaultOptions: Partial<Options>): Module

/**
 * resolve the component <name> argument to a component function,
 * using the supplied resolution <options> argument
 */
resolveComponent(name: string, options?: Options): AnyRenderer;

/**
 *  Converts ast to markup consisting solely of primitive components (html elements)
 */
evalAst(ast: AbstractSyntaxTree, options?: Options): string;

/**
 * generate JSON AST from xml markup
 */
generateAST(markup: string): AbstractSyntaxTree;

/**
 * Helper method to stringify object argument passed to template function
 */
stringifyProps<T>(props: T, encode?: boolean): string;

/**
 * Helper method to stringify data-{x} arguments passed to template function
 */
stringifyDataProps(props: AnyObject): string;

/**
 * Helper method to stringify CSS object or passthrough CSS string
 */
stringifyCSS(style: CssStyle | string): string;

/**
 * Parse CSS string into JSON-style object
 */
parseCSS(style: string): CssStyle;

/**
 * Generate HTMl markup
 */
generateHTML(tag: string, props?: ObjectDictionary<string>, children?: any): string;

/**
 * Formats html
 */
formatHTML(htmlOrAst: string | AbstractSyntaxTree, options?: AnyObject): string;

Types

interface AbstractSyntaxTree {
    name: string,
    type: string,
    value: string,
    children: any[],
    attributes: AnyObject
}

interface Options {
    readonly failAsDiv: boolean, // render tags that cannot be resolved as html divs?
    readonly resolution: Partial<{
        readonly dict: ObjectDictionary<AnyComponent> // resolution dictionary (first priority)
        readonly func: (string) => AnyComponent // resolution function (second priority)
    }>
    readonly formatMarkup: boolean // format the output as xml/html?
    readonly debugMode: boolean
}

interface ComponentOptions<T> extends Options {
    readonly name: string, // component name, for documentation and debugging purposes
    readonly defaultProps: Partial<T>,
    readonly isContainer: boolean // whether component have can children elements
}

type Renderer<T> = (props?: T, children?: any) => string;

type AnyRenderer = Renderer<any>;

type Component<T> = Renderer<T> & {
    readonly componentOptions: ComponentOptions<T>, // for documentation and inspection purposes
    setOptions: (opts: ComponentOptions<T>) => Component<T> // override initial options
}

type AnyComponent = Component<any>;

interface ObjectDictionary<T> { [key: string]: T}

type AnyObject = ObjectDictionary<any>;

Example

Note that we are able to compose components from both base html elements and other custom higher-order components. Any component references in the markup are resolved using the resolution member of the options passed to the createComponent function.

let components = () => _components;

const somatic: Module = (require("./index") as Module).setOptions({
    resolution: {
        func: (name) => _components[name]
    },
    debugMode: false
})
const _ = somatic.stringifyProps;


let _components = {
    Banner: somatic.createComponent(function (props: { logo, title }, children) {
        return `
            <div style="display: flex; justify-content: flex-start">
                <img src="${props.logo}"/>
                <span>${props.title}</span>
            </div>`;
    }, { name: "Banner", resolution: { func: (ref) => components()[ref] } }),

    StackPanelHorizontal: somatic.createComponent(function (props, children) {
        return `
            <div style="display: flex; justify-content: flex-start">
                ${children.map(child => `<div>${child}</div>`).join("")}
            </div>`;
    }, { name: "StackPanelHorizontal", resolution: { func: (ref) => components()[ref] } }),

    StackPanelVertical: somatic.createComponent(function (props, children) {
        var stringifiedProps = _({
            style: {
                display: "flex",
                flexDirection: "column",
                justifyContent: "flex-start"
            }
        }, false);

        return `
            <div ${stringifiedProps}>
                ${children.map(child => `<div>${child}</div>`).join("")
            }
            </div>`;
    }, { name: "StackPanelVertical", resolution: { func: (ref) => components()[ref] } }),

    Footer: somatic.createComponent(function (props, children) {
        return `
            <StackPanelHorizontal>
                <StackPanelVertical>
                    <div>col1,row1</div>
                    <div>col1,row2</div>
                </StackPanelVertical>
                <StackPanelVertical>
                    <div>col2,row1</div>
                    <div>col2,row2</div>
                </StackPanelVertical>
            </StackPanelHorizontal>`;
    }, { name: "Footer", resolution: { func: (ref) => components()[ref] } }),

    Page: somatic.createComponent(function (props, children) {
        return `
            <StackPanelVertical>Eureka
                <Banner ${_({ title: "YCombinator", logo: "https://news.ycombinator.com/y18.gif" })} />
                ${children.join("")}
                <Footer/>
            </StackPanelVertical>`;
    }, { name: "Page", resolution: { func: (ref) => components()[ref] } })

};

try {
    let markup = components().Page();
    console.log("Page markup: " + markup);
}
catch (e) {
    console.log(e.toString());
}

Output is

Page markup: <div style='display: flex; flex-direction: column; justify-content: flex-start'>
    <div>
         Eureka
    </div>
    <div>
        <div style='display: flex; justify-content: flex-start'>
            <img src='https://news.ycombinator.com/y18.gif'></img>
            <span>
                 YCombinator
            </span>
        </div>
    </div>
    <div>
        <div style='display: flex; justify-content: flex-start'>
            <div>
                <div style='display: flex; flex-direction: column; justify-content: flex-start'>
                    <div>
                        <div>
                             col1,row1
                        </div>
                    </div>
                    <div>
                        <div>
                             col1,row2
                        </div>
                    </div>
                </div>
            </div>
            <div>
                <div style='display: flex; flex-direction: column; justify-content: flex-start'>
                    <div>
                        <div>
                             col2,row1
                        </div>
                    </div>
                    <div>
                        <div>
                             col2,row2
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Install

npm install somatic --save

Package Sidebar

Install

npm i somatic

Weekly Downloads

57

Version

4.1.0

License

MIT

Last publish

Collaborators

  • prmph