Expenses Report

This sample shows how to create an expense report and save it to a PDF file using the FlexGridPdfConverter and PdfDocument API. The sample creates two FlexGrid instances (data & footer) internally and exports them to a document using the FlexGridPdfConverter.draw method in order to display an expense table. drawText and other vector graphics methods of PdfDocument are used to draw captions and expected handwritten entries.

Learn about FlexGrid | FlexGrid API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; import * as wijmo from '@mescius/wijmo'; import * as grid from '@mescius/wijmo.grid'; import * as pdf from '@mescius/wijmo.pdf'; import * as gridPdf from '@mescius/wijmo.grid.pdf'; import { getEmployees } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { document.querySelector('#btnExport').addEventListener('click', () => { let doc = new pdf.PdfDocument({ header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (sender, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // getEmployees().forEach((employee, i, arr) => { drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); }); } // function drawEmployee(doc, employee) { let tot = employee.expenses.totals; let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()); // let minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', width: 105 }, { header: 'Description', binding: 'description', format: 'c', width: 105 }, { header: 'Hotel', binding: 'hotel', format: 'c', width: 105 }, { header: 'Transport', binding: 'transport', format: 'c', width: 105 }, { header: 'Meal', binding: 'meal', format: 'c', width: 105 }, { header: 'Fuel', binding: 'fuel', format: 'c', width: 105 }, { header: 'Misc', binding: 'misc', format: 'c', width: 105 }, { header: 'Total', binding: 'total', format: 'c', width: 105 } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'), thinPen = new pdf.PdfPen('#000000', 0.5); // let flexGrid, footer; // try { // * setup FlexGrid * flexGrid = new grid.FlexGrid('#flexGrid'); footer = new grid.FlexGrid('#flexGridFooter'); // flexGrid.initialize({ autoGenerateColumns: false, allowMerging: grid.AllowMerging.All, columns: columns, headersVisibility: grid.HeadersVisibility.Column, itemsSource: expenses }); // footer.initialize({ allowMerging: grid.AllowMerging.All, autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.None, columns: columns, itemsSource: [{ date: null, description: null, hotel: tot.hotel, transport: tot.transport, meal: tot.meal, fuel: tot.fuel, misc: tot.misc, total: tot.total }] }); // footer.cells.setCellData(0, 'date', 'Total', false); footer.cells.setCellData(0, 'description', 'Total', false); footer.cells.rows[0].allowMerging = true; // // * draw captions * doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); // doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); // doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(maxDate, wijmo.DataType.String, 'd')); // doc.moveDown(2); // let y = doc.y; doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); // doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); // doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); // y = doc.y; doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); // doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); // doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); // doc.moveDown(2); // // draw FlexGrid gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // // draw footer gridPdf.FlexGridPdfConverter.draw(footer, doc, doc.width, null, { styles: { cellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd', borderColor: '#c6c6c6' } } }); // doc.moveDown(2); // // * draw captions * doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total - employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total, wijmo.DataType.String, 'c')); doc.moveDown(2); // checkLineAvailable(doc); // y = doc.y; let sz = doc.drawText('Employee signature: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width + 5, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); doc.moveDown(); // checkLineAvailable(doc); // y = doc.y; sz = doc.drawText('Approved by: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); } finally { if (flexGrid) { flexGrid.dispose(); } // if (footer) { footer.dispose(); } } } // function checkLineAvailable(doc) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expenses report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" id="btnExport">Export</button> <!-- FlexGrid --> <div id="flexGrid" class="grid"></div> <div id="flexGridFooter" class="gridFooter"></div> </div> </body> </html>
// export function getEmployees() { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: getExpenses() } ]; } // function getExpenses() { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; }
body { margin-bottom: 24px; } .grid, .gridFooter { visibility: hidden; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);