0.5.0 • Published 4 years ago

red-agate v0.5.0

Weekly downloads
101
License
ISC
Repository
github
Last release
4 years ago

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 call, LambdaInternalErrorException


if __name__ == '__main__':
    from flask import Flask, abort
    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 ordermethoddescription
0earlyConstruct(): voidThis 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.
1constructor(props) /lambda(props)Construct a component.If it is lambda, transform myself and children DOM tree.
2transform(): RedAgateNodeTransform myself and children DOM tree.This method is equivalent to render() of React method.
3defer(): Promise<any>Wait for asynchronous resources.
4beforeRender(    contexts: Map<string, any>): voidGet contexts provided by parent elements.Preparing something for child elements.
5render(    contexts: Map<string, any>,    children: string): stringReturn rendering result as string.
6afterRender(    contexts: Map<string, any>): voidClean up contexts, graphic states, ...

APIs

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

methoddescription
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): voidRender elements and apply to DOM.
RedAgate.renderOnAwsLambda(    element: RedAgateNode,    callback: (        error: any or null,        result: any or null    ) => void): voidRender elements to string.Return result via AWS lambda callback.
RedAgate.renderOnExpress(    element: RedAgateNode,    req: any,    res: any): voidRender elements to string.Return result via Express web server callback.

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

methoddescription
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'

methoddescription
App.cli(    options: string[]    handler: (        opts: Map<string, string>        ) => void): AppAdd 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): AppAdd 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): AppRun 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'

methoddescription
Lambdas.pipe(    handler1: Lambda,    handler2: Lambda): LambdaPipe 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
methoddescription
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): LambdaCreate composite function returning pdf as callback result.
HtmlRenderer.toImageHandler(    handler: Lambda,    navigateOptions: any,    imageOptions: any): LambdaCreate composite function returning image as callback result.

Standard Tag-Libs

red-agate/modules/red-agate/taglib

tagdescription
RepeatLoop N times.
ForEachIterate an array.
IfConditional branch.
DoCall a lambda function when createElement .
FacetGrouping child elements.Give a name to group.
TemplateSynonym for Facet .

red-agate/modules/red-agate/bundler

tagdescription
AssetFetch a external resource.Fetched resource is referred from other tags.
ImageFetch a external image resource.
ScriptFetch a external script resource.
StyleFetch a external stylesheet resource.
FontSynonym for Style .
SingleFontFetch a external single font-family font resource.

red-agate/modules/red-agate/html

tagdescription
Html4_01_StrictOutput doctype declaration and html tag.
Html4_01_TransitionalOutput doctype declaration and html tag.
Html4_01_FramesetOutput doctype declaration and html tag.
Xhtml1_0_StrictOutput doctype declaration and html tag.
Xhtml1_0_TransitionalOutput doctype declaration and html tag.
Xhtml1_0_FramesetOutput doctype declaration and html tag.
Html5Output doctype declaration and html tag.
XmlOutput xml declaration.
HtmlImpositionImpose pages in a physical page.

red-agate/modules/red-agate/svg

tagdescription
SvgOutput svg tag.Children can use a Canvas context.
AmbientChange current graphic state properties.
ArcDraw an arc.
CanvasCall a lambda function and draw by using Canvas context object.
CircleDraw a circle.
CurveDraw bezier curve(s).
GridLineDraw grid lines for design time.
GroupGroup children.Output g tag.
LineDraw line(s).
PathGroup path fragments (e.g. Arc, Circle, Curve, Line, Rect, ...) .
PieDraw a pie.
PolygonDraw a polygon.
RectDraw a rectangle.
RoundRectDraw a rounded rectangle.
SvgAssetFragmentAppend raw SVG tags into defs.
SvgFragmentAppend raw SVG tags.
TextDraw text line(s).
SvgImpositionImpose pages in a physical page.

red-agate/modules/red-agate/printing

tagdescription
PrinterMarksPropsDraw 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
tagdescription
Code39Draw a CODE39 barcode.
Code128Draw a CODE128 barcode. (GS1-128 is available)
Ean13Draw a EAN-13 (GTIN-13 / JAN-13) barcode.
Ean8Draw a EAN-8 (GTIN-8 / JAN-8) barcode.
Ean5Draw a EAN-5 (JAN-5) barcode.
Ean2Draw a EAN-2 (JAN-2) barcode.
UpcADraw a UPC-A (GTIN-12) barcode.
UpcEDraw a UPC-E barcode.
ItfDraw a ITF barcode. (GTIN-14 is available)
JapanPostalDraw a Japan Post Customer barcode.
Nw7Draw a NW7 (Codabar) barcode.
QrDraw 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
tagdescription
ReactHostHost 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.

0.5.0

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.20

4 years ago

0.2.19

5 years ago

0.2.18

5 years ago

0.2.17

5 years ago

0.2.16

5 years ago

0.2.15

5 years ago

0.2.14

5 years ago

0.2.13

6 years ago

0.2.12

6 years ago

0.2.11

6 years ago

0.2.8

6 years ago

0.2.7

6 years ago

0.2.6

6 years ago

0.2.5

6 years ago

0.2.4

6 years ago

0.2.3

6 years ago

0.2.2

6 years ago

0.2.0

6 years ago

0.1.2

6 years ago

0.1.1

6 years ago

0.0.14

6 years ago

0.0.13

6 years ago

0.0.8

6 years ago

0.0.7

6 years ago

0.0.6

6 years ago

0.0.5

6 years ago

0.0.4

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago