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

0.5.0 • Public • Published

RedAgate

Static HTML | XML | SVG renderer using JSX, suitable for report output.

RedAgate is static HTML | XML | SVG renderer.
You can start easily because we are using JSX and semantics similar to React.

npm GitHub release Travis GitHub forks GitHub stars

Advantages:

  • Easily to bundle resources (images, stylesheets, fonts, scripts, ...) .
    RedAgate.renderAsHtml() API and component lifecycle defer() method return promise objects.
    You can use standard Tag-Libs (e.g. Image, Style, Font, SingleFont, Script, Asset) to bundle them.

  • Many standard Tag-Libs (e.g. If, Repeat, ForEach, Template, Html5, Svg, SVG shapes, Barcodes (QR Code, Code39, Code128, EAN/UPC, ITF, NW7/Codabar, postal barcode) and complex objects) are bundled.

  • Html5 Canvas API is available in the sub tree of the Svg component.

  • Running on both server side (Node.js) and modern browsers (Chrome, Firefox, Safari, Edge).

RedAgate

Install

$ npm install red-agate --save

Note

To import this from your code, you need to use babel + webpack and import red-agate-*/modules/* paths.
(We have used the import statements for doing the tree-shaking. The import statements in the .js not the .mjs files cannot import from the vanilla node.js.)

You can also import from the .mjs file on a node with the --experimental-modules option enabled.

NOTICE:
Use with webpack >= 5

If you get the error:

Module not found: Error: Can't resolve '(importing/path/to/filename)'
in '(path/to/node_modules/path/to/dirname)'
Did you mean '(filename).js'?`

Add following setting to your webpack.config.js.

{
    test: /\.m?js/,
    resolve: {
        fullySpecified: false,
    },
},

On webpack >= 5, the extension in the request is mandatory for it to be fully specified if the origin is a '.mjs' file or a '.js' file where the package.json contains '"type": "module"'.

Usage

See live demo on browser (code) and Node.js example.

Hello, world:

/** @jsx RedAgate.createElement */
import * as RedAgate from 'red-agate/modules/red-agate';
 
interface HelloProps extends RedAgate.ComponentProps {
    name: string;
}
 
const Hello = (props: HelloProps) => {
    return (<div>Hello, {props.name}!</div>);
};
 
RedAgate.renderAsHtml(<Hello name={'😈RedAgate😈'}/>)
.then(html => console.log(html))
.catch(error => console.log(error))

Defining element by using lambda:

export interface IfProps extends RedAgate.ComponentProps {
    condition: boolean;
}
 
export const If = (props: IfProps) => {
    if (this.props.condition) return this.props.children;
    else return [];
};

Defining element by using component:

export interface IfProps extends RedAgate.ComponentProps {
    condition: boolean;
}
 
export class If extends RedAgate.RedAgateComponent<IfProps> {
    public constructor(props: IfProps) {
        super(props);
    }
 
    // Equivalent to React's render() .
    public transform() {
        if (this.props.condition) return this.props.children;
        else return [];
    }
}

Defining SVG element by using component:

import { SvgCanvas }          from 'red-agate-svg-canvas/modules/drawing/canvas/SvgCanvas';
import { Shape,
         CONTEXT_SVG_CANVAS } from 'red-agate/modules/red-agate/tags/Shape';
 
export interface RectProps extends ShapeProps {
    width: number;
    height: number;
}
 
export const rectPropsDefault: RectProps = Object.assign({}, shapePropsDefault, {
    width: 10,
    height: 10
});
 
export class Rect extends Shape<RectProps> {
    public constructor(props: RectProps) {
        super(Object.assign({}, rectPropsDefault, props));
    }
 
    public render(contexts: Map<string, any>, children: string) {
        const canvas: SvgCanvas = this.getContext(contexts, CONTEXT_SVG_CANVAS);
        canvas.rect(0, 0, this.props.width, this.props.height);
        return ``;
    }
}

Complete example:

/** @jsx RedAgate.createElement */
import * as RedAgate     from 'red-agate/modules/red-agate';
import { ForEach,
         If,
         Template }      from 'red-agate/modules/red-agate/taglib';
import { Html5 }         from 'red-agate/modules/red-agate/html';
import { Svg,
         Group,
         Rect,
         Text,
         GridLine,
         SvgImposition } from 'red-agate/modules/red-agate/svg';
import { Font,
         Image,
         Style }         from 'red-agate/modules/red-agate/bundler';
import { query }         from 'red-agate/modules/red-agate/data';
import { Lambda }        from 'red-agate/modules/red-agate/app';
import { HtmlRenderer }  from 'red-agate/modules/red-agate/renderer';
 
interface FbaDetail {
    id: string;
    name: string;
    condition: string;
}
interface PrintJob {
    details: FbaDetail[];
}
 
const designerMode = true;
const font = "'Noto Sans', sans-serif";
const Fba = (props: {leaf: FbaDetail}) =>
    <Template>
        <Group x={0} y={0}>
            <Text x={27} y={11.5}
                textAlign="center" font={`11.5px 'Libre Barcode 128 Text', cursive`} fill
                text={leaf.id} />
            <Text x={4} y={18 + 3.5}
                font={`3.5px ${font}`} fill
                text={leaf.name} />
            <Text x={4} y={22 + 3.5}
                font={`3.5px ${font}`} fill
                text={leaf.condition} />
        </Group>
    </Template>;
 
export const fbaA4ReportHandler: Lambda = (event: PrintJob, context, callback) => RedAgate.renderOnAwsLambda(
<Html5>
    <head>
        <title>FBA</title>
        <link href="https://fonts.googleapis.com/css?family=Noto+Sans" rel="stylesheet"/>
        <link href="https://fonts.googleapis.com/css?family=Libre+Barcode+128+Text" rel="stylesheet"/>
        <Style src="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.css"/>
        <Style src="https://cdnjs.cloudflare.com/ajax/libs/paper-css/0.3.0/paper.css"/>
        <style dangerouslySetInnerHTML={{ __html: require('./fba-a4.style.css') }}/>
    </head>
 
    <body class="A4">
        <ForEach items={query(event.details).groupEvery(40).select()}> { (items: FbaDetail[]) =>
            <section class="sheet" style="position: relative; top: 0mm; left: 0mm;">
                <Svg width={210 - 1} height={297 - 2} unit='mm'>
                    <SvgImposition items={items} paperWidth={210} paperHeight={297} cols={4} rows={10}> { (item: FbaDetail) =>
                        <Template>
                            <If condition={designerMode}>
                                <Rect x={0} y={0} width={210 / 4} height={297 / 10} lineWidth={0.5} stroke/>
                                <GridLine startX={0} startY={0} endX={210 / 4} endY={297 / 10} gridSize={5} bleed={0} lineWidth={0.1}/>
                            </If>
 
                            <Fba leaf={item} />
                        </Template> }
                    </SvgImposition>
                </Svg>
            </section> }
        </ForEach>
    </body>
</Html5>, callback);
const event = {
    details: [{
        // ...
    }]
};
 
fbaA4ReportHandler(event /* PrintJob */, {} as any /* Context */, (error, result) => {
    if (error) {
        console.log(error);
    } else {
        console.log(result);
    }
});

Render html into PDF:

/** @jsx RedAgate.createElement */
import * as RedAgate    from 'red-agate/modules/red-agate';
import { Html5 }        from 'red-agate/modules/red-agate/html';
import { Lambda }       from 'red-agate/modules/red-agate/app';
import { HtmlRenderer } from 'red-agate/modules/red-agate/renderer';
 
interface PrintJob { /*  */ }
 
export const reportHandler: Lambda = (event: PrintJob, context, callback) => RedAgate.renderOnAwsLambda(
<Html5>
    hello, { event.name }!
</Html5>, callback);
 
export const pdfHandler = HtmlRenderer.toPdfHandler(reportHandler, {}, {
    width: '210mm',
    height: '297mm',
    printBackground: true,
});
 
pdfHandler(event /* PrintJob */, {} as any /* Context */, (error, result) => {
    if (error) {
        console.log(error);
    } else {
        console.log(result);
    }
});

Call from another process:

/** @jsx RedAgate.createElement */
import * as RedAgate     from 'red-agate/modules/red-agate';
import { Html5 }         from 'red-agate/modules/red-agate/html';
import { App }           from 'red-agate/modules/red-agate/app';
 
export const billngReportHandler = (event: BillingPrintJob, context, callback) => RedAgate.renderOnAwsLambda(
<Html5>billng</Html5>, callback);
 
export const kanbanReportHandler = (event: KanbanPrintJob, context, callback) => RedAgate.renderOnAwsLambda(
<Html5>kanban</Html5>, callback);
 
App.route('/', (evt, ctx, cb) => cb(null, 'Hello, Node!'))
   .route('/billing', billngReportHandler)
   .route('/kanban', kanbanReportHandler)
   .run({});
#!/usr/bin/env python3 
 
import json
import os
import sys
 
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/node_modules/red-agate/')
from redagate_lambda import callLambdaInternalErrorException
 
 
if __name__ == '__main__':
    from flask import Flaskabort
    app = Flask(__name__)
 
    @app.errorhandler(LambdaInternalErrorException)
    def internal_error_handler(e):
        return 'Internal Server Error'500
 
    @app.route('/billing')
    def run_billing_report():
        with open('./src/reports/billing.data.json') as f:
            event = json.loads(f.read())
            event['eventName'] = '/billing'
            return call(command=["node", "dist/app.js"], event=event)
 
    @app.route('/kanban')
    def run_barcode_test_report():
        with open('./src/reports/kanban.data.json') as f:
            event = json.loads(f.read())
            event['eventName'] = '/kanban'
            return call(command=["node", "dist/app.js"], event=event)
 
    port = int(os.environ['PORT']) if os.environ.get('PORT') is not None else None
    app.run(debug=True, port=port)

Mix react elements:

/** @jsx react.createElement */
import * as react from 'react';
 
interface ReactHelloProps {
    name: string;
}
 
export const ReactHello: React.SFC<ReactHelloProps> = (props) => {
    return (<span>Hello, {props.name}!</span>);
};
/** @jsx RedAgate.createElement */
import * as RedAgate          from 'red-agate/modules/red-agate';
import { Html5 }              from 'red-agate/modules/red-agate/html';
 
import { ReactHost }          from 'red-agate-react-host/modules/react-host';
import { ReactHello }         from './hello';
import { createElement as $ } from 'react';
 
RedAgate.renderAsHtml(
<Html5>
    <ReactHost element={$(ReactHello, {name: '😎React😎'})} />
</Html5>)
.then(html => console.log(html))
.catch(error => console.log(error))

We provide ES6 module files under red-agate*/modules/* path.
You can get the benefits of tree shaking when using webpack.
Instead, you can also import the whole by simply specifying red-agate* as the import path.

Component Lifecycle

call order method description
0 earlyConstruct(): void This method is marker and it will be NEVER called.
If it defined, constructor will be called in createElement().
Otherwise constructor will be called in render???() APIs.
1 constructor(props) /
lambda(props)
Construct a component.
If it is lambda, transform myself and children DOM tree.
2 transform(): RedAgateNode Transform myself and children DOM tree.
This method is equivalent to render() of React method.
3 defer(): Promise<any> Wait for asynchronous resources.
4 beforeRender(
    contexts: Map<string, any>
): void
Get contexts provided by parent elements.
Preparing something for child elements.
5 render(
    contexts: Map<string, any>,
    children: string
): string
Return rendering result as string.
6 afterRender(
    contexts: Map<string, any>
): void
Clean up contexts, graphic states, ...

APIs

/** @jsx RedAgate.createElement */
import * as RedAgate from 'red-agate/modules/red-agate'

method description
RedAgate.createElement(
    type: ComponentFactory<P>,
    props: P or null or undefined,
    ...children: RedAgateNode[]
): RedAgateElement<P>
Create a element.
This function is called from JSX compiled code.
RedAgate.renderAsHtml(
    element: RedAgateNode
): Promise<string>
Render elements to string.
RedAgate.render(
    element: RedAgateNode,
    container: HTMLElement,
    callback?: (
        html: string or null,
        error: any or null
    ) => void
): void
Render elements and apply to DOM.
RedAgate.renderOnAwsLambda(
    element: RedAgateNode,
    callback: (
        error: any or null,
        result: any or null
    ) => void
): void
Render elements to string.
Return result via AWS lambda callback.
RedAgate.renderOnExpress(
    element: RedAgateNode,
    req: any,
    res: any
): void
Render elements to string.
Return result via Express web server callback.

import { query } from 'red-agate/modules/red-agate/data'

method description
query(
    data: T[]
): Query<T>
Transform an array.
Query<T>#orderBy(
    condition: Array<string or
    string[
        /* colName: string,
        ('asc' or 'desc') */
    ]> or
        ((a: T, b: T) =>
        number)
): Query<T>
Sort an array.
Query<T>#groupBy(
    condition: string[
        /* colName: string */
    ] or
        ((a: T, b: T,
        index: number, array: T[]) =>
        boolean)
): Query<T[]>
Grouping and transform an array.
Query<T>#groupEvery(
    n: number or
    {
        single: number,
        first?: number,
        intermediate: number,
        last?: number
    }
): Query<T[]>
Grouping and transform an array.
Query<T>#where(
    fn: (
        value: T,
        index: number,
        array: T[]
    ) => boolean
): Query<T>
Filter an array.
Query<T>#select<R>(
    fn?: (
        value: T,
        index: number,
        array: T[]
    ) => R
): Array<R or T>
Map an array.

import { App } from 'red-agate/modules/red-agate/app'

method description
App.cli(
    options: string[]
    handler: (
        opts: Map<string, string>
        ) => void
): App
Add CLI routing.
If options[i] starts with ? it is a optional parameter.
If options[i] ends with * it is a wildcard.
App.route(
    name: string
    lambda: Lambda
): App
Add routing to lambda.
name parameter is used as routing path.
When request event is received call the lambda that name equals to event.eventName.
App.run(
    context: any
    lambda?: Lambda
): App
Run routing.
event is received from stdin as JSON and send response to stdout.
Exit process by calling exit() when response is ended.
If lambda is specified, ignore route() and call lambda.

import { Lambdas } from 'red-agate/modules/red-agate/app'

method description
Lambdas.pipe(
    handler1: Lambda,
    handler2: Lambda
): Lambda
Pipe 2 lambdas.
Return a composite function that piping 2 lambdas.
2nd lambda's event is 1st lambda's callback result.

import { HtmlRenderer } from 'red-agate/modules/red-agate/renderer'

$ npm install puppeteer --save
method description
HtmlRenderer.toPdf(
    html: string or Promise<string>,
    navigateOptions: any,
    pdfOptions: any
): Promise<Buffer>
Render HTML into PDF using puppeteer.
See puppeteer#page.goto about navigateOptions.
See puppeteer#page.pdf about pdfOptions.
HtmlRenderer.toImage(
    html: string or Promise<string>,
    navigateOptions: any,
    imageOptions: any
): Promise<Buffer>
Render HTML into image using puppeteer.
See puppeteer#page.goto about navigateOptions.
See puppeteer#page.screenshot about imageOptions.
HtmlRenderer.toPdfHandler(
    handler: Lambda,
    navigateOptions: any,
    pdfOptions: any
): Lambda
Create composite function returning pdf as callback result.
HtmlRenderer.toImageHandler(
    handler: Lambda,
    navigateOptions: any,
    imageOptions: any
): Lambda
Create composite function returning image as callback result.

Standard Tag-Libs

red-agate/modules/red-agate/taglib

tag description
Repeat Loop N times.
ForEach Iterate an array.
If Conditional branch.
Do Call a lambda function when createElement .
Facet Grouping child elements.
Give a name to group.
Template Synonym for Facet .

red-agate/modules/red-agate/bundler

tag description
Asset Fetch a external resource.
Fetched resource is referred from other tags.
Image Fetch a external image resource.
Script Fetch a external script resource.
Style Fetch a external stylesheet resource.
Font Synonym for Style .
SingleFont Fetch a external single font-family font resource.

red-agate/modules/red-agate/html

tag description
Html4_01_Strict Output doctype declaration and html tag.
Html4_01_Transitional Output doctype declaration and html tag.
Html4_01_Frameset Output doctype declaration and html tag.
Xhtml1_0_Strict Output doctype declaration and html tag.
Xhtml1_0_Transitional Output doctype declaration and html tag.
Xhtml1_0_Frameset Output doctype declaration and html tag.
Html5 Output doctype declaration and html tag.
Xml Output xml declaration.
HtmlImposition Impose pages in a physical page.

red-agate/modules/red-agate/svg

tag description
Svg Output svg tag.
Children can use a Canvas context.
Ambient Change current graphic state properties.
Arc Draw an arc.
Canvas Call a lambda function and draw by using Canvas context object.
Circle Draw a circle.
Curve Draw bezier curve(s).
GridLine Draw grid lines for design time.
Group Group children.
Output g tag.
Line Draw line(s).
Path Group path fragments (e.g. Arc, Circle, Curve, Line, Rect, ...) .
Pie Draw a pie.
Polygon Draw a polygon.
Rect Draw a rectangle.
RoundRect Draw a rounded rectangle.
SvgAssetFragment Append raw SVG tags into defs.
SvgFragment Append raw SVG tags.
Text Draw text line(s).
SvgImposition Impose pages in a physical page.

red-agate/modules/red-agate/printing

tag description
PrinterMarksProps Draw printer marks (crop mark, bleed mark, center mark, fold mark).

red-agate-barcode/modules/barcode/(Code39|Code128|Ean|Itf|JapanPostal|Nw7|Qr)

$ npm install red-agate-barcode --save
tag description
Code39 Draw a CODE39 barcode.
Code128 Draw a CODE128 barcode. (GS1-128 is available)
Ean13 Draw a EAN-13 (GTIN-13 / JAN-13) barcode.
Ean8 Draw a EAN-8 (GTIN-8 / JAN-8) barcode.
Ean5 Draw a EAN-5 (JAN-5) barcode.
Ean2 Draw a EAN-2 (JAN-2) barcode.
UpcA Draw a UPC-A (GTIN-12) barcode.
UpcE Draw a UPC-E barcode.
Itf Draw a ITF barcode. (GTIN-14 is available)
JapanPostal Draw a Japan Post Customer barcode.
Nw7 Draw a NW7 (Codabar) barcode.
Qr Draw a QR Code (model 2) barcode.

red-agate-react-host/modules/react-host

$ npm install react --save
$ npm install react-dom --save
$ npm install red-agate-react-host --save
tag description
ReactHost Host a react element and render as static markup.

Configurations for building application

If you want to use red-agate w/o jsx pragma comment (/** @jsx RedAgate.createElement */),
You should configure tsconfig or .babelrc for building JSX.
Prease see typescript docs , babel docs or example.

FAQ

  • Can I receive element events (e.g. onclick) ?
    • No. RedAgate is static renderer. Please use React, Vue, Riot, Angular, knockout, ...
  • Can I change DOM via API after rendered to real DOM?
    • No. Please use React, Vue, Riot, Angular, knockout, ...
  • Can I build print preview window by using RedAgate?
    • paper-css may help you to build print previews.

License

ISC
Copyright (c) 2017, Shellyl_N and Authors.

Package Sidebar

Install

npm i red-agate

Weekly Downloads

19

Version

0.5.0

License

ISC

Unpacked Size

598 kB

Total Files

104

Last publish

Collaborators

  • shellyln