export type DataSetName = "Products" | "Customers";
export const categories = [
{
CategoryId: 1,
CategoryName: "Beverages",
},
{
CategoryId: 2,
CategoryName: "Condiments",
},
{
CategoryId: 3,
CategoryName: "Confections",
},
{
CategoryId: 4,
CategoryName: "Dairy Products",
},
{
CategoryId: 5,
CategoryName: "Grains/Cereals",
},
{
CategoryId: 6,
CategoryName: "Meat/Poultry",
},
{
CategoryId: 7,
CategoryName: "Produce",
},
{
CategoryId: 8,
CategoryName: "Seafood",
},
];
export const dataSetFields = {
Products: [
{ name: "CategoryName", type: "text", len: 15 },
{ name: "ProductName", type: "text", len: 21 },
{ name: "QuantityPerUnit", type: "number", len: 25 },
{ name: "UnitPrice", type: "currency", len: 11 },
{ name: "UnitsInStock", type: "number", len: 14 },
{ name: "UnitsOnOrder", type: "number", len: 14 },
],
Customers: [
{ name: "CompanyName", type: "text", len: 30 },
{ name: "ContactName", type: "text", len: 20 },
{ name: "Address", type: "text", len: 20 },
{ name: "City", type: "text", len: 15 },
{ name: "Country", type: "text", len: 15 },
],
};
export const filterValues = {
Products: categories.map((cat) => cat.CategoryName),
Customers: ["USA", "UK", "Germany", "France", "Canada"],
};
export const dataUrl = {
Customers: "https://demodata.mescius.io/northwind/odata/v1/Customers",
Products: "https://demodata.mescius.io/northwind/odata/v1/Products",
};
import { Injectable } from "@angular/core";
import { Rdl as ARJS } from "@mescius/activereportsjs/core";
import { dataSetFields, dataUrl, categories, DataSetName } from "./data";
/**
* Utility function that converts given number to a Length unit using inches as a unit of measure
* @see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Report-Items/Common-Properties#length
*/
function inches(val: number): string {
return `${val}in`;
}
/**
* Utility function that converts given number to a Length unit using points as a unit of measure
* @see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Report-Items/Common-Properties#length
*/
function points(val: number): string {
return `${val}pt`;
}
/**
* Utility function that converts given field name to an expression in Rdl format
*/
function fieldVal(fieldName: string): string {
return `=Fields!${fieldName}.Value`;
}
@Injectable({
providedIn: "root",
})
export class ReportService {
constructor() {}
/**
* Creates a data source with data embedded into it
*/
private async getDataSource(
dataSetName: DataSetName
): Promise<ARJS.DataSource> {
// use Web Api to retrieve the data from the demodata API
const data = await fetch(dataUrl[dataSetName]).then((res) => res.json());
// patch the product dataset to include category names
if (dataSetName === "Products") {
data.value = data.value.map((product: any) => ({
...product,
CategoryName: categories.filter(
(x) => x.CategoryId === product.CategoryId
)[0].CategoryName,
}));
}
// construct the data source instance and embed the retrieved data
// see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Databinding#embedded-json-data-source
const dataSource: ARJS.DataSource = {
Name: "DataSource",
ConnectionProperties: {
DataProvider: "JSONEMBED",
ConnectString: `jsondata=${JSON.stringify(data.value)}`,
},
};
return dataSource;
}
/**
* Creates a dataset that reads the data of the data source
* see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Databinding#data-set-configuration
*/
private getDataSet(dataSetName: DataSetName): ARJS.DataSet {
const dataSet: ARJS.DataSet = {
Name: dataSetName,
Query: {
CommandText: "$.*",
DataSourceName: "DataSource",
},
Fields: dataSetFields[dataSetName].map((f) => ({
Name: f.name,
DataField: f.name,
})),
};
return dataSet;
}
private getFieldInfo(dataSetName: DataSetName, fieldName: string): any {
return dataSetFields[dataSetName].filter(
(field: any) => field.name === fieldName
)[0];
}
private getGroupAndFilter(dataSetName: DataSetName): string {
return dataSetName === "Customers"
? fieldVal("Country")
: fieldVal("CategoryName");
}
public async generateReport(
structure: ReportStructure
): Promise<ARJS.Report> {
// create Table Data region
// see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Report-Items/Data-Regions/Table
const table: ARJS.Table = {
Name: `Table_${structure.dataSetName}`,
DataSetName: structure.dataSetName,
Type: "table",
TableColumns: structure.fields.map((f) => ({
Width: inches(
(7.5 * this.getFieldInfo(structure.dataSetName, f).len) / 100
),
})),
};
// set table filters if the corresponding option was selected
if (structure.filterValues.length) {
table.Filters = [
{
FilterExpression: this.getGroupAndFilter(structure.dataSetName),
FilterValues: structure.filterValues,
Operator: "In",
},
];
}
// create column headers for selected fields
const columnHeadersRow: ARJS.TableRow = {
Height: inches(0.5),
TableCells: structure.fields.map((f) => ({
Item: {
Type: "textbox",
Name: `textbox_header_${f}`,
Value: `${f}`,
CanGrow: true,
Style: {
BottomBorder: {
Width: points(0.25),
Style: "solid",
Color: "Gainsboro",
},
Color: "#3da7a8",
VerticalAlign: "middle",
FontWeight: "bold",
PaddingLeft: points(6),
FontSize: points(10),
TextAlign:
this.getFieldInfo(structure.dataSetName, f).type === "text"
? "left"
: "right",
},
},
})),
};
// if no grouping was set, then add the column headers into the table header
if (!structure.grouping) {
table.Header = {
RepeatOnNewPage: true,
TableRows: [columnHeadersRow],
};
}
// create table group that has the group header displaying the current group value
if (structure.grouping) {
table.TableGroups = [
{
Group: {
GroupExpressions: [this.getGroupAndFilter(structure.dataSetName)],
PageBreak: "Between",
},
Header: {
RepeatOnNewPage: true,
TableRows: [
{
Height: inches(0.6),
TableCells: [
{
ColSpan: structure.fields.length,
AutoMergeMode: "Always",
Item: {
Type: "textbox",
Name: "textbox_group_value",
Value: this.getGroupAndFilter(structure.dataSetName),
Style: {
VerticalAlign: "middle",
FontWeight: "bold",
PaddingLeft: points(6),
FontSize: points(16),
},
},
},
],
},
columnHeadersRow,
],
},
},
];
}
// create table details that display for each row of the data
table.Details = {
SortExpressions: structure.sortBy
? [
{
Value: fieldVal(structure.sortBy),
Direction: structure.isDescendingSorting
? "Descending"
: "Ascending",
},
]
: [],
TableRows: [
{
Height: inches(0.3),
TableCells: structure.fields.map((f: string) => ({
Item: {
Type: "textbox",
Name: `textbox_value_${f}`,
Value: fieldVal(f),
CanGrow: true,
KeepTogether: true,
Style: {
VerticalAlign: "middle",
PaddingLeft: points(6),
Format:
this.getFieldInfo(structure.dataSetName, f).type ===
"currency"
? "c2"
: "",
TextAlign:
this.getFieldInfo(structure.dataSetName, f).type === "text"
? "left"
: "right",
},
},
})),
},
],
};
// finally create a report with the table in the body and the report title in the page header
const report: ARJS.Report = {
DataSources: [await this.getDataSource(structure.dataSetName)],
DataSets: [this.getDataSet(structure.dataSetName)],
Page: {
TopMargin: inches(0.5),
BottomMargin: inches(0.5),
LeftMargin: inches(0.5),
RightMargin: inches(0.5),
PageWidth: inches(8.5),
PageHeight: inches(11),
},
Body: {
ReportItems: [table],
},
PageHeader: {
Height: inches(1),
Name: "PageHeader",
ReportItems: [
{
Type: "textbox",
Name: "textbox_report_name",
Value:
structure.dataSetName === "Customers"
? "Customer List"
: "Product List",
Style: {
FontSize: points(22),
Color: "#3da7a8",
VerticalAlign: "middle",
TextAlign: "left",
},
Left: points(6),
Top: points(0),
Width: inches(7.5),
Height: inches(1),
},
],
},
Width: inches(7.5),
};
//console.log(JSON.stringify(report));
return report;
}
}
export interface ReportStructure {
dataSetName: DataSetName;
fields: string[];
grouping: boolean;
sortBy: string;
isDescendingSorting: boolean;
filterValues: string[];
}
<div class="container" *ngIf="Mode == 'Design'">
<form [formGroup]="tableForm" (ngSubmit)="onSubmit()">
<div class="mb-3 mt-3 row">
<div class="col-md-12">
<label for="select-dataset" class="form-label">Choose Dataset</label>
<select
formControlName="dataSetName"
class="form-select"
id="select-dataset"
>
<option *ngFor="let ds of DataSets" [ngValue]="ds">{{ ds }}</option>
</select>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-6">
<label for="select-fields" class="form-label"
>Select fields to display</label
>
<select
multiple
formControlName="fields"
class="form-select"
id="select-fields"
size="6"
>
<option *ngFor="let field of Fields" [ngValue]="field">
{{ field }}
</option>
</select>
</div>
<div class="col-md-6">
<label for="select-filter" class="form-label"
>Select {{ FilterField }} to include</label
>
<select
multiple
formControlName="filterValues"
class="form-control"
id="select-filter"
size="6"
>
<option *ngFor="let filter of Filters" [ngValue]="filter">
{{ filter }}
</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-check mb-3">
<input
type="checkbox"
formControlName="grouping"
id="grouping"
class="form-check-input"
/>
<label for="grouping" class="form-check-label"
>Group By {{ GroupingField }}</label
>
</div>
</div>
</div>
<div class="row mb-3">
<label for="select-sorting" class="form-label"
>Select sorting field</label
>
<div class="col-md-6">
<select
formControlName="sortBy"
class="form-select"
id="select-sorting"
>
<option *ngFor="let field of Fields" [ngValue]="field">
{{ field }}
</option>
</select>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
<input
type="checkbox"
formControlName="isDescendingSorting"
id="isDescendingSorting"
class="form-check-input"
/>
<label for="isDescendingSorting" class="form-check-label"
>Sort Descending Order</label
>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button
type="submit"
[disabled]="!tableForm.valid"
class="btn btn-success btn-block"
>
Create Report
</button>
</div>
</div>
</form>
</div>
<div id="viewer-host" *ngIf="Mode == 'Preview'">
<gc-activereports-viewer (init)="onViewerInit()"> </gc-activereports-viewer>
</div>
import { Component, Inject, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ViewerComponent } from "@mescius/activereportsjs-angular";
import { filterValues, dataSetFields } from "./data";
import { ReportService } from "./report-service.service";
@Component({
selector: "app-root",
templateUrl: "src/app.component.html",
styleUrls: ["src/app.component.css"],
})
export class AppComponent {
private reportService = new ReportService();
private fb;
constructor() {
this.fb = new FormBuilder().nonNullable;
this.tableForm = this.fb.group({
dataSetName: ["", Validators.required],
fields: [{ value: [], disabled: true }, Validators.required],
grouping: [{ value: false, disabled: true }],
sortBy: [{ value: "", disabled: true }],
isDescendingSorting: [{ value: false, disabled: true }],
filterValues: [{ value: [], disabled: true }],
});
}
@ViewChild(ViewerComponent, { static: false })
reportViewer!: ViewerComponent;
Mode: "Design" | "Preview" = "Design";
DataSets: any = ["Products", "Customers"];
Fields: any = [];
Filters: any = [];
tableForm: any;
ngAfterViewInit() {
this.tableForm
.get("dataSetName")
?.valueChanges.subscribe((val: "Products" | "Customers") => {
this.Fields = dataSetFields[val].map((f) => f.name);
this.Filters = filterValues[val];
this.tableForm.get("fields")?.setValue([]);
this.tableForm.get("fields")?.enable();
this.tableForm.get("grouping")?.setValue(false);
this.tableForm.get("grouping")?.enable();
this.tableForm.get("sortBy")?.setValue("");
this.tableForm.get("sortBy")?.enable();
this.tableForm.get("isDescendingSorting")?.setValue(false);
this.tableForm.get("isDescendingSorting")?.enable();
this.tableForm.get("filterValues")?.setValue([]);
this.tableForm.get("filterValues")?.enable();
});
}
onSubmit() {
//console.log(this.tableForm.value);
this.Mode = "Preview";
}
get DataSetName(): string {
return this.tableForm.get("dataSetName")?.value;
}
get GroupingField(): string {
if (!this.DataSetName) return "...";
return this.DataSetName === "Products" ? "Product Category" : "Country";
}
get FilterField(): string {
if (!this.DataSetName) return "Values";
return this.DataSetName === "Products" ? "Product Categories" : "Countries";
}
updateToolbar() {
var designButton = {
key: "$openDesigner",
text: "Designer",
iconCssClass: "mdi mdi-reply",
enabled: true,
action: () => {
this.Mode = "Design";
},
};
this.reportViewer.toolbar.addItem(designButton);
this.reportViewer.toolbar.updateLayout({
default: [
"$openDesigner",
"$split",
"$navigation",
"$split",
"$refresh",
"$split",
"$history",
"$split",
"$zoom",
"$fullscreen",
"$split",
"$print",
"$split",
"$singlepagemode",
"$continuousmode",
"$galleymode",
],
});
}
onViewerInit() {
this.updateToolbar();
const report = this.reportService.generateReport(this.tableForm.value);
this.reportViewer.open("report", {
ResourceLocator: {
getResource(resource: string) {
return report as any;
},
},
});
}
}
#viewer-host {
width: 100%;
height: 600px;
}
(function (global) {
System.config({
transpiler: "./plugin-typescript.js",
typescriptOptions: {
"target": "ES2022",
"module": "system",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
},
baseURL: "./node_modules/",
meta: {
typescript: {
exports: "ts",
},
"*.css": { loader: "systemjs-plugin-css" },
},
packageConfigPaths: [
"./node_modules/*/package.json",
"./node_modules/@angular/*/package.json",
"./node_modules/@mescius/*/package.json",
],
map: {
'typescript': "typescript/lib/typescript.js",
'rxjs': "rxjs/dist/bundles/rxjs.umd.js",
"rxjs/operators": "rxjs/dist/bundles/rxjs.umd.js",
"@angular/core/primitives/signals": "@angular/core/fesm2022/primitives/signals.mjs",
"@angular/common/http": "@angular/common/fesm2022/http.mjs",
"@mescius/activereportsjs/core":
"@mescius/activereportsjs/dist/ar-js-core.js",
"@mescius/activereportsjs": "./activereports.js",
"@mescius/ar-js-pagereport":
"@mescius/activereportsjs/dist/ar-js-core.js",
"@mescius/activereportsjs-i18n-ja":
"@mescius/activereportsjs-i18n/dist/designer/ja-locale.js",
"@mescius/activereportsjs-i18n-zh":
"@mescius/activereportsjs-i18n/dist/designer/zh-locale.js",
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
"./src": {
defaultExtension: "ts",
},
rxjs: {
defaultExtension: "js",
// "main": "bundles/rxjs.umd.min.js"
},
node_modules: {
defaultExtension: "js",
},
"@angular/core": {
defaultExtension: "mjs",
main: "fesm2022/core.mjs",
},
"@angular/platform-browser": {
defaultExtension: "mjs",
main: "fesm2022/platform-browser.mjs",
},
"@angular/common": {
defaultExtension: "mjs",
main: "fesm2022/common.mjs",
},
"@angular/compiler": {
defaultExtension: "mjs",
main: "fesm2022/compiler.mjs",
},
"@angular/forms": {
defaultExtension: "mjs",
main: "fesm2022/forms.mjs",
},
"@angular/localize": {
defaultExtension: "mjs",
main: "fesm2022/localize.mjs",
},
"@angular/platform-browser-dynamic": {
defaultExtension: "mjs",
main: "fesm2022/platform-browser-dynamic.mjs",
},
"@mescius/activereportsjs-angular": {
defaultExtension: "mjs",
main: "fesm2022/grapecity-activereports-angular.mjs",
},
},
});
})(this);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>MESCIUS ActiveReports for Javascript sample</title>
<!-- angular sample -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Polyfills -->
<script src="node_modules/zone.js/fesm2015/zone.min.js"></script>
<!-- SystemJS -->
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap"
rel="stylesheet"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
<link
href="//cdn.materialdesignicons.com/5.4.55/css/materialdesignicons.min.css?jsobv=1678283360232"
rel="stylesheet"
/>
<link
rel="stylesheet"
type="text/css"
href="/activereportsjs/demos/arjs/styles/ar-js-ui.css?jsobv=1678283360232"
/>
<link
rel="stylesheet"
type="text/css"
href="/activereportsjs/demos/arjs/styles/ar-js-viewer.css?jsobv=1678283360232"
/>
<script>
System.import("./src/app.main");
</script>
</head>
<body>
<app-root></app-root>
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"
></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
crossorigin="anonymous"
></script>
</body>
</html>