import { WidgetDoneCallback } from "../../types/WidgetDoneCallback"; const FORMATTED_POSTFIX = '_Formatted'; export function handleImplicitData(data: { implicitData?: object }, widgetDoneCallback: WidgetDoneCallback) { const columnKeys = Object.keys(data.implicitData || data); let rowStartIndex: number; let dataRowStartIndex: number; let rowCount: number; let tableHeaders: string[] = []; let rawColumnKeys: string[] = []; let formattedColumnKeys: string[] = []; Excel.run((context) => { let range = context.workbook.getSelectedRange(); let rangeTables = range.getTables(); rangeTables.load(); let table = rangeTables.getFirst(); table.load(); range.load('address'); range.load('rowIndex'); range.load('rowCount'); const tableHeaderRowRange = table.getHeaderRowRange(); tableHeaderRowRange.load('values'); tableHeaderRowRange.load('rowIndex'); tableHeaderRowRange.load('columnIndex'); return context.sync().then(() => { tableHeaders = tableHeaderRowRange.values[0]; rowStartIndex = range.rowIndex; // Table row index is relative to table position dataRowStartIndex = rowStartIndex - tableHeaderRowRange.rowIndex - 1; rowCount = range.rowCount; rawColumnKeys = columnKeys.filter((columnKey) => tableHeaders.includes(columnKey)); formattedColumnKeys = columnKeys.filter( (columnKey) => rawColumnKeys.indexOf(columnKey) < 0 && columnKey.endsWith(FORMATTED_POSTFIX) ); }); }) .then(() => { // No need to do exception handling here when invalid data is selected // We won't allow you to run the flow with invalid table selection Excel.run((ctx) => { let range = ctx.workbook.getSelectedRange(); let rangeTables = range.getTables(); rangeTables.load(); let table = rangeTables.getFirst(); return sendUserData( ctx, tableHeaders, formattedColumnKeys, rawColumnKeys, widgetDoneCallback, table, rowStartIndex, dataRowStartIndex, rowCount ); }); }) .catch((error) => { // If there was an issue getting data (for example, the customer has not selected a row in the table), // return an empty object to the widget, this will cause the flow to run without any data but will // not block any widget usage. widgetDoneCallback(null, null); console.error(error); }); } function sendUserData( ctx: Excel.RequestContext, tableHeaders: string[], formattedColumnKeys: string[], rawColumnKeys: string[], widgetDoneCallback: WidgetDoneCallback, table: Excel.Table, rowStartIndex: number, dataRowStartIndex: number, rowCount: number ): OfficeExtension.IPromise { let rows = table.rows; let rowRanges: Excel.Range[] = []; // This is for displaying selected rows in the confirmation page let selectedRows: number[] = []; let boundImplicitData: Array> & { selectedRows?: number[] } = []; for (let i = 0; i < rowCount; i++) { const row = rows.getItemAt(dataRowStartIndex + i); selectedRows.push(rowStartIndex + i + 1); const rowRange = row.getRange(); rowRange.load('text'); rowRange.load('values'); rowRanges.push(rowRange); } return ctx .sync() .then(() => { const FORMATTED_POSTFIXRegex = new RegExp(FORMATTED_POSTFIX + '+$'); boundImplicitData.selectedRows = selectedRows; const columnMap: number[] = []; rawColumnKeys.forEach((columnKey) => { columnMap[columnKey] = tableHeaders.indexOf(columnKey); }); formattedColumnKeys.forEach((columnKey) => { columnMap[columnKey] = tableHeaders.indexOf(columnKey.replace(FORMATTED_POSTFIXRegex, '')); }); rowRanges.forEach((currentRow, rowIndex) => { // Row data has to be an object to be correctly stringified during flow run const rowData = {}; rawColumnKeys.forEach((rawColumnKey) => { rowData[rawColumnKey] = columnMap[rawColumnKey] !== -1 ? rowRanges[rowIndex].values[0][columnMap[rawColumnKey]] : ''; }); formattedColumnKeys.forEach((formattedColumnKey) => { rowData[formattedColumnKey] = columnMap[formattedColumnKey] !== -1 ? rowRanges[rowIndex].text[0][columnMap[formattedColumnKey]].trim() : ''; }); boundImplicitData.push(rowData); }); widgetDoneCallback(null, { implicitData: boundImplicitData }); }) .catch((error) => { console.error('SendUserData failed. Error: ' + error); widgetDoneCallback(null, { implicitData: boundImplicitData }); }); }