Public API - Custom Table Designer

ActiveReportsJS provides a comprehensive API that enables developers to create reports programmatically. This example demonstrates how to generate a tabular report at runtime, showcasing the flexibility and capabilities of the API. Through this approach, developers can customize report layouts, data sources, and formatting to suit their specific needs, creating dynamic and adaptable report solutions for a wide range of applications. While the example presented here uses Angular, the same technique can be easily adapted and applied to any front-end framework.

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>