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.
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).
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:
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);
}
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:
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 , {} as any , (error, result) => {
if (error) {
console.log(error);
} else {
console.log(result);
}
});
Render html into PDF:
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 , {} as any , (error, result) => {
if (error) {
console.log(error);
} else {
console.log(result);
}
});
Call from another process:
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({});
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:
import * as react from 'react';
interface ReactHelloProps {
name: string;
}
export const ReactHello: React.SFC<ReactHelloProps> = (props) => {
return (<span>Hello, {props.name}!</span>);
};
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
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.
- Can I output rendered result as PDF, PNG, or other formats?
License
ISC
Copyright (c) 2017, Shellyl_N and Authors.