@metric-im/componentry

1.3.11 • Public • Published

Componentry

Componentry is a lightweight component harness built for expressjs that enables packages to mix routing paths, resources and client components into the host

See also metric-im/common-mixin. Common mixin is a Componentry module that provides many common widgets such as tables and input fields.

Usage

import express from 'express';
import Componentry from '@metric-im/componentry'
import Profile from './profile.mjs' // shared resources such as a database connection
import PackageA from 'PackageA'; // module imported through npm
import PackageB from './modules/PackageA/index.mjs'; // module imported through relative path

let app = express();
let componentry = new Componentry(app,await Profile());
await componentry.init(PackageA,PackageB,...); 

constructor(app,profile,options)

  • app is the instance of express. Componentry will add routes with app.use()
  • profile is an arbitrary object of state data that is available to each component as this.connector.profile.
  • options an optional object that will be passed to each module provided to init() as the second parameter

Profile can be used to initialize context and data such as a database handle through to all modules. Options are:

componentry.init([modules...])

Provide one or more modules as arguments. Modules can be referenced either as npm module names or relative paths from the root of the host project. The construction of a module is discussed below.

The order of modules determines the resolution of name conflicts. Modules named later may override routes and components of modules named before. The application that invokes componentry.init may have components (in ./components) and routes of it's own. These take precedence of any elements of modules imported through init.

routes

The express instance provide through app will export endpoints the client can use to access the integrated resources of each modules

  • /components/:componentName - The referenced component is returned as javascript after parsing ACL blocks options.acl is set
  • /styles/component.css - Returns a composite of all the .css files that match .mjs files in the modules components directory
  • /lib/:node_module/:path - Returns a declared module from node_modules, optionally with a path to a specific resource in that module.

Module Structure

A module is an imported class that declares any routes the module would like to add to the express app. It may also reference packages in node_modules which it would like to be made available to its client components through /lib/{module_name}

A module that only provides routes could be a single file, such as MyModule.mjs. Most modules, however, are packaged in a folder with a subdirectory for client components.

Constructor

The module constructor is passed the "connector" object which includes the profile provided to the Componentry constructor as well as the componentry instance itself. The class should extend the super class Componentry.Module. In order to support various constructs, pass the module path to the super class. This has to be provided from the source file for node to identify the relative location of the components folder. Use import.meta.url.

import express from 'express';
import Componentry from "@metric-im/componentry";

export default class MyComponent extends Componentry.Module {
    constructor(connector) {
        super(connector,import.meta.url)
    }
    static async mint() {
        return new MyComponent(connector)
    }
}

mint() is optional. It can be used if the class constructor needs to make asynchronous calls. Componentry will use new if it doesn't find ClassName.mint();

module.Routes()

If the module declares a method named routes() this is expected to return a router that will be merged into the app. For example:

routes() {
    const router = express.Router();
    router.get('/moduleName/hello',(req,res,next) => {
        res.send('hello');
    })
    return router();
}

module.library

Most npm modules are designed to be used either by the client browser or the nodejs host. For convenience, a Componentry module make declare npm modules it would like to be available to the client.

The library attribute should return an object where each attribute is a path to the npm module. For example

get library() {
    return {
        'moment':'/moment/min/moment-with-locales.min.js'
    };
}

By adding to the library, the module's components may then import these npm packages.

import moment from '/lib/moment';

Components Folder

If the module includes a components folder, all components are loaded and made available to the client with /components/:componentName. Component name should include the .mjs extension. Note that modules are loaded in order. If there are components of the same name, the one loaded last overwrites earlier references. After loading all named modules, the components folder of the host project is loaded, if it exists.

Componentry includes a few of its own components. Component.mjs, is a super class that compoments should extend. It provides a number of common features useful to each component.

NOTE: IdForge.mjs is an internal class, but may be useful for generating random id's

All components must at least implement a method named render() which expends to be given a DOM element on which to attach its features.

import Component from './Component.mjs'
export default class MyComponent extends Component {
    constructor(props) {
        super(props);
    }
    async render(element) {
        await super.render(element);
        this.element.innerHTML = "hello";
    }
}

Invoking the static method Component.init() when the app loads will add two helpers to the window object, window.toast and window.popup

Toast

Toast is used to send a brief notification message to the screen. It can also be used to prompt the user with an "ok" or "cancel" message.

window.toast.[status|warning|error|success](string message)
window.prompt(string message)

Popup

Popup provides a modal display that covers the screen. It expects a title, a DOM element and an optional array of buttons. Buttons are expected to e components themselves which are rendered in the popup.

let elem = document.createElement('DIV');
elem.innerHTML = "hello";
window.popup("say hello",elem);

Common Styling

In the components folder, each component mjs file can have a corresponding css file. The CSS is merged into a single components.css file. To avoid clashes, each css tag is prepended with a class name matching the component name.

For example:

. {
    color:blue;
}
TABLE {
    margin:3px;
}

becomes

.CompName {
    color:blue;
}
.CompName TABLE {
    margin:3px;
}

Feature Redaction Based on ACL

If the app attaches an account object to the request object, it can be used to filter component code delivered to the client demarked with /*ACL>X*/ ... /*ENDACL*/. Any code within the ACL block will be redacted if given account.level is less than X.

For example, a request to the app for /compoment/SomewhatSensitive.mjs could contain elements only meant for certain users. The app is expected to have added an account object to the request which declares a level attribute, such as req.account.level=2.

async render(element) {
    await super.render(element);
    this.element.innerHTML = "<p>Hello</p>";
    /*ACL>1*/ this.element.innerHTML += "<p>you are important</p>" /*ENDACL*/
}

Readme

Keywords

none

Package Sidebar

Install

npm i @metric-im/componentry

Weekly Downloads

50

Version

1.3.11

License

MIT

Unpacked Size

36.2 kB

Total Files

11

Last publish

Collaborators

  • dehax
  • mespr