red-agate
v0.5.0
Published
Static HTML|XML|SVG renderer using JSX, suitable for report output.
Downloads
79
Maintainers
Readme
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 lifecycledefer()
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 importred-agate-*/modules/*
paths.
(We have used theimport
statements for doing the tree-shaking. Theimport
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 withwebpack >= 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 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.
- Can I output rendered result as PDF, PNG, or other formats?
- Please use puppeteer via HtmlRenderer.toPdf() and HtmlRenderer.toImage().
- Or you can convert from html to any formats using other libraries (e.g. ~~html-pdf (wrapper of PhantomJS)~~, wkhtmltopdf) .
License
ISC
Copyright (c) 2017, Shellyl_N and Authors.