├── .gitignore ├── demo ├── splash_excel.png ├── openfin-excel.ico ├── dev-app.json ├── app.json ├── index.html ├── styles.css └── main.js ├── provider ├── add-in.zip ├── src │ ├── provider.d.ts │ └── provider.js ├── provider.html ├── app.json ├── dev-app.json └── provider.js ├── client ├── src │ ├── ILog.js │ ├── ILogger.js │ ├── index.d.ts │ ├── ILog.d.ts │ ├── ILogger.d.ts │ ├── index.js │ ├── NoOpLogger.d.ts │ ├── DefaultLogger.d.ts │ ├── EventEmitter.d.ts │ ├── NoOpLogger.js │ ├── ExcelWorkbook.d.ts │ ├── RpcDispatcher.d.ts │ ├── DefaultLogger.js │ ├── ExcelRtd.d.ts │ ├── ExcelApplication.d.ts │ ├── EventEmitter.js │ ├── ExcelApi.d.ts │ ├── ExcelWorksheet.d.ts │ ├── ExcelWorkbook.js │ ├── RpcDispatcher.js │ ├── ExcelWorksheet.js │ ├── ExcelRtd.js │ ├── ExcelApplication.js │ └── ExcelApi.js └── fin.desktop.Excel.js ├── VERSIONS.md ├── webpack.config.js ├── package.json ├── LOGS.md ├── README.md └── DOCS.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /demo/splash_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfin/excel-api-example/HEAD/demo/splash_excel.png -------------------------------------------------------------------------------- /provider/add-in.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfin/excel-api-example/HEAD/provider/add-in.zip -------------------------------------------------------------------------------- /demo/openfin-excel.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfin/excel-api-example/HEAD/demo/openfin-excel.ico -------------------------------------------------------------------------------- /client/src/ILog.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=ILog.js.map -------------------------------------------------------------------------------- /client/src/ILogger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=ILogger.js.map -------------------------------------------------------------------------------- /provider/src/provider.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace fin.desktop { 2 | var Application: any; 3 | var InterApplicationBus: any; 4 | var ExternalApplication: any; 5 | var System: any; 6 | var main: any; 7 | } 8 | declare var chrome: any; 9 | -------------------------------------------------------------------------------- /client/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ExcelService } from './ExcelApi'; 2 | import { ExcelApplication } from './ExcelApplication'; 3 | declare global { 4 | interface Window { 5 | fin: { 6 | desktop: { 7 | ExcelService: ExcelService; 8 | Excel: ExcelApplication; 9 | }; 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/src/ILog.d.ts: -------------------------------------------------------------------------------- 1 | export interface ILog { 2 | name?: string; 3 | trace?: (message: any, ...args: any[]) => void; 4 | debug?: (message: any, ...args: any[]) => void; 5 | info?: (message: any, ...args: any[]) => void; 6 | warn?: (message: any, ...args: any[]) => void; 7 | error?: (message: any, error: any, ...args: any[]) => void; 8 | fatal?: (message: any, error: any, ...args: any[]) => void; 9 | } 10 | -------------------------------------------------------------------------------- /client/src/ILogger.d.ts: -------------------------------------------------------------------------------- 1 | export interface ILogger { 2 | name?: string; 3 | trace?: (message: any, ...args: any[]) => void; 4 | debug?: (message: any, ...args: any[]) => void; 5 | info?: (message: any, ...args: any[]) => void; 6 | warn?: (message: any, ...args: any[]) => void; 7 | error?: (message: any, error: any, ...args: any[]) => void; 8 | fatal?: (message: any, error: any, ...args: any[]) => void; 9 | } 10 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // This is the entry point of the Plugin script 4 | const ExcelApi_1 = require("./ExcelApi"); 5 | window.fin.desktop.ExcelService = ExcelApi_1.ExcelService.instance; 6 | Object.defineProperty(window.fin.desktop, 'Excel', { 7 | get() { return ExcelApi_1.ExcelService.instance.defaultApplicationObj; } 8 | }); 9 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /client/src/NoOpLogger.d.ts: -------------------------------------------------------------------------------- 1 | import { ILog } from './ILog'; 2 | export declare class NoOpLogger implements ILog { 3 | name: string; 4 | constructor(); 5 | trace(message: any, ...args: any[]): void; 6 | debug(message: any, ...args: any[]): void; 7 | info(message: any, ...args: any[]): void; 8 | warn(message: any, ...args: any[]): void; 9 | error(message: any, error: any, ...args: any[]): void; 10 | fatal(message: any, error: any, ...args: any[]): void; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/DefaultLogger.d.ts: -------------------------------------------------------------------------------- 1 | import { ILog } from './ILog'; 2 | export declare class DefaultLogger implements ILog { 3 | name: string; 4 | constructor(name: string); 5 | trace(message: any, ...args: any[]): void; 6 | debug(message: any, ...args: any[]): void; 7 | info(message: any, ...args: any[]): void; 8 | warn(message: any, ...args: any[]): void; 9 | error(message: any, error: any, ...args: any[]): void; 10 | fatal(message: any, error: any, ...args: any[]): void; 11 | } 12 | -------------------------------------------------------------------------------- /provider/provider.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Excel Service 8 | 9 | 10 |

Excel Service Provider

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/EventEmitter.d.ts: -------------------------------------------------------------------------------- 1 | export declare abstract class EventEmitter implements EventTarget { 2 | listeners: { 3 | [eventType: string]: Function[]; 4 | }; 5 | addEventListener(type: string, listener: (data?: any) => any): void; 6 | removeEventListener(type: string, listener: (data?: any) => any): void; 7 | protected hasEventListener(type: string, listener: () => any): boolean; 8 | dispatchEvent(evt: Event): boolean; 9 | dispatchEvent(typeArg: string, data?: any): boolean; 10 | abstract toObject(): any; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/NoOpLogger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.NoOpLogger = void 0; 4 | class NoOpLogger { 5 | constructor() { 6 | } 7 | trace(message, ...args) { 8 | } 9 | debug(message, ...args) { 10 | } 11 | info(message, ...args) { 12 | } 13 | warn(message, ...args) { 14 | } 15 | error(message, error, ...args) { 16 | } 17 | fatal(message, error, ...args) { 18 | } 19 | } 20 | exports.NoOpLogger = NoOpLogger; 21 | //# sourceMappingURL=NoOpLogger.js.map -------------------------------------------------------------------------------- /VERSIONS.md: -------------------------------------------------------------------------------- 1 | # OpenFin Excel API Version Numbers 2 | 3 | ## From Version 4 4 | 5 | ### Client Version 6 | 7 | From version 4.0 onwards you can find out the client version (for log purposes etc) by logging: 8 | 9 | ```javascript 10 | fin.desktop.Excel.version; 11 | ``` 12 | 13 | ### Excel Provider Version 14 | 15 | To confirm you are connecting to the ExcelService you expect you can log/check: 16 | 17 | ```javascript 18 | fin.desktop.ExcelService.version; 19 | ``` 20 | 21 | ### Excel Helper/Plugin Version 22 | 23 | From version 4 you and your end users can confirm you are using the right excel plugin version by looking at Excel. 24 | 25 | The message will now say Connected to OpenFin (v x.x.x.x) -------------------------------------------------------------------------------- /provider/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "licenseKey": "64605fac-add3-48a0-8710-64b38e96a2dd", 3 | "startup_app": { 4 | "name": "Excel-Service-Manager", 5 | "url": "https://cdn.openfin.co/release/exceljs/4.1.2/provider/provider.html", 6 | "uuid": "excel-service-manager", 7 | "permissions": { 8 | "System": { 9 | "launchExternalProcess": true 10 | } 11 | }, 12 | "enableAppLogging": true 13 | }, 14 | "runtime": { 15 | "arguments": "", 16 | "version": "14.78.48.16" 17 | }, 18 | "appAssets": [ 19 | { 20 | "src": "https://cdn.openfin.co/release/exceljs/4.1.2/provider/add-in.zip", 21 | "alias": "excel-api-addin", 22 | "version": "4.1.2" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /provider/dev-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "licenseKey": "64605fac-add3-48a0-8710-64b38e96a2dd", 3 | "startup_app": { 4 | "name": "Excel-Service-Manager", 5 | "url": "http://localhost:8080/provider/provider.html", 6 | "uuid": "excel-service-manager", 7 | "permissions": { 8 | "System": { 9 | "launchExternalProcess": true 10 | } 11 | }, 12 | "enableAppLogging": true, 13 | "autoShow": true 14 | }, 15 | "runtime": { 16 | "arguments": "", 17 | "version": "14.78.48.16" 18 | }, 19 | "appAssets": [ 20 | { 21 | "src": "http://localhost:8080/provider/add-in.zip", 22 | "alias": "excel-api-addin", 23 | "version": "4.1.2", 24 | "forceDownload": true 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | resolve: { 3 | extensions: ['.js'] 4 | }, 5 | module: { 6 | loaders: [ 7 | { 8 | exclude: /(node_modules|bower_components)/ 9 | } 10 | ] 11 | } 12 | }; 13 | 14 | var pluginConfig = Object.assign({}, config, { 15 | entry: './client/src/index.js', 16 | output: { 17 | filename: './client/fin.desktop.Excel.js' 18 | } 19 | }); 20 | 21 | var loaderConfig = Object.assign({}, config, { 22 | entry: './provider/src/provider.js', 23 | output: { 24 | filename: './provider/provider.js' 25 | } 26 | }); 27 | 28 | module.exports = [ 29 | pluginConfig, 30 | loaderConfig 31 | ]; -------------------------------------------------------------------------------- /client/src/ExcelWorkbook.d.ts: -------------------------------------------------------------------------------- 1 | import { RpcDispatcher } from './RpcDispatcher'; 2 | import { ExcelApplication } from './ExcelApplication'; 3 | import { ExcelWorksheet } from './ExcelWorksheet'; 4 | export declare class ExcelWorkbook extends RpcDispatcher { 5 | application: ExcelApplication; 6 | workbookName: string; 7 | worksheets: { 8 | [worksheetName: string]: ExcelWorksheet; 9 | }; 10 | private objectInstance; 11 | constructor(application: ExcelApplication, name: string); 12 | getDefaultMessage(): any; 13 | getWorksheets(callback: Function): Promise; 14 | getWorksheetByName(name: string): ExcelWorksheet; 15 | addWorksheet(callback: Function): Promise; 16 | activate(): Promise; 17 | save(): Promise; 18 | close(): Promise; 19 | refreshObject(): void; 20 | toObject(): any; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/RpcDispatcher.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './EventEmitter'; 2 | import { ILog } from './ILog'; 3 | export declare abstract class RpcDispatcher extends EventEmitter { 4 | protected static messageId: number; 5 | protected static promiseExecutors: { 6 | [messageId: number]: { 7 | resolve: Function; 8 | reject: Function; 9 | }; 10 | }; 11 | logger: ILog; 12 | constructor(logger: ILog); 13 | connectionUuid: string; 14 | getDefaultMessage(): any; 15 | protected invokeExcelCall(functionName: string, data?: any, callback?: Function): Promise; 16 | protected invokeServiceCall(functionName: string, data?: any, callback?: Function): Promise; 17 | private invokeRemoteCall; 18 | protected applyCallbackToPromise(promise: Promise, callback: Function): Promise; 19 | abstract toObject(): any; 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excel-api-example", 3 | "version": "4.1.1", 4 | "description": "Excel integration for OpenFin", 5 | "main": "main.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "openfin-cli": "*", 9 | "http-server": "*", 10 | "webpack": "^2.5.1" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "start node node_modules\\http-server\\bin\\http-server -p 8080 -c-1 & node node_modules\\openfin-cli\\cli.js -l -c demo\\dev-app.json", 15 | "webpack": "webpack" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/openfin/excel-api-example.git" 20 | }, 21 | "keywords": [ 22 | "OpenFin", 23 | "Excel" 24 | ], 25 | "author": "OpenFin", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/openfin/excel-api-example/issues" 29 | }, 30 | "homepage": "https://github.com/openfin/excel-api-example#readme" 31 | } -------------------------------------------------------------------------------- /client/src/DefaultLogger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DefaultLogger = void 0; 4 | class DefaultLogger { 5 | constructor(name) { 6 | this.name = name || "logger"; 7 | } 8 | trace(message, ...args) { 9 | console.log(this.name + ": " + message, ...args); 10 | } 11 | debug(message, ...args) { 12 | console.log(this.name + ": " + message, ...args); 13 | } 14 | info(message, ...args) { 15 | console.info(this.name + ": " + message, ...args); 16 | } 17 | warn(message, ...args) { 18 | console.warn(this.name + ": " + message, ...args); 19 | } 20 | error(message, error, ...args) { 21 | console.error(this.name + ": " + message, error, ...args); 22 | } 23 | fatal(message, error, ...args) { 24 | console.error(this.name + ": " + message, error, ...args); 25 | } 26 | } 27 | exports.DefaultLogger = DefaultLogger; 28 | //# sourceMappingURL=DefaultLogger.js.map -------------------------------------------------------------------------------- /demo/dev-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "licenseKey": "64605fac-add3-48a0-8710-64b38e96a2dd", 3 | "devtools_port": 9090, 4 | "startup_app": { 5 | "name": "Excel-API-Example", 6 | "url": "http://localhost:8080/demo/index.html", 7 | "uuid": "excel-api-example", 8 | "applicationIcon": "http://localhost:8080/demo/openfin-excel.ico", 9 | "autoShow": true, 10 | "defaultWidth": 760, 11 | "defaultHeight": 704, 12 | "defaultTop": 100, 13 | "defaultLeft": 100, 14 | "frame": true, 15 | "permissions": { 16 | "System": { 17 | "launchExternalProcess": true 18 | } 19 | } 20 | }, 21 | "runtime": { 22 | "arguments": "", 23 | "version": "14.78.48.16" 24 | }, 25 | "splashScreenImage": "http://localhost:8080/demo/splash_excel.png", 26 | "shortcut": { 27 | "company": "OpenFin", 28 | "description": "excel-api-example", 29 | "icon": "http://localhost:8080/demo/openfin-excel.ico", 30 | "name": "excel-api-example" 31 | }, 32 | "services": [ 33 | { 34 | "name": "excel", 35 | "manifestUrl": "http://localhost:8080/provider/dev-app.json" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "licenseKey": "64605fac-add3-48a0-8710-64b38e96a2dd", 3 | "startup_app": { 4 | "name": "Excel-API-Example", 5 | "url": "https://cdn.openfin.co/release/exceljs/4.1.2/demo/index.html", 6 | "uuid": "excel-api-example", 7 | "applicationIcon": "https://cdn.openfin.co/release/exceljs/4.1.2/demo/openfin-excel.ico", 8 | "autoShow": true, 9 | "defaultWidth": 760, 10 | "defaultHeight": 704, 11 | "defaultTop": 100, 12 | "defaultLeft": 100, 13 | "frame": true, 14 | "permissions": { 15 | "System": { 16 | "launchExternalProcess": true 17 | } 18 | } 19 | }, 20 | "runtime": { 21 | "arguments": "", 22 | "version": "14.78.48.16" 23 | }, 24 | "splashScreenImage": "https://cdn.openfin.co/release/exceljs/4.1.2/demo/splash_excel.png", 25 | "shortcut": { 26 | "company": "OpenFin", 27 | "description": "excel-api-example", 28 | "icon": "https://cdn.openfin.co/release/exceljs/4.1.2/demo/openfin-excel.ico", 29 | "name": "excel-api-example" 30 | }, 31 | "services": [ 32 | { 33 | "name": "excel", 34 | "manifestUrl": "https://cdn.openfin.co/release/exceljs/4.1.2/provider/app.json" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /client/src/ExcelRtd.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './EventEmitter'; 2 | import { ILog } from './ILog'; 3 | export declare class ExcelRtd extends EventEmitter { 4 | private heartbeatIntervalInMilliseconds; 5 | providerName: string; 6 | provider: any; 7 | logger: ILog; 8 | listeners: { 9 | [eventType: string]: Function[]; 10 | }; 11 | pingPath: string; 12 | heartbeatPath: string; 13 | connectedTopics: {}; 14 | connectedKey: string; 15 | disconnectedKey: string; 16 | loggerName: string; 17 | private initialized; 18 | private disposed; 19 | heartbeatToken: number; 20 | static create(providerName: any, logger: ILog, heartbeatIntervalInMilliseconds?: number): Promise; 21 | constructor(providerName: any, logger: ILog, heartbeatIntervalInMilliseconds?: number); 22 | init(): Promise; 23 | get isDisposed(): boolean; 24 | get isInitialized(): boolean; 25 | setValue(topic: any, value: any): void; 26 | dispose(): Promise; 27 | addEventListener(type: string, listener: (data?: any) => any): void; 28 | dispatchEvent(evt: Event): boolean; 29 | dispatchEvent(typeArg: string, data?: any): boolean; 30 | toObject(): this; 31 | private ping; 32 | private establishHeartbeat; 33 | private onSubscribe; 34 | private onUnsubscribe; 35 | private clear; 36 | } 37 | -------------------------------------------------------------------------------- /LOGS.md: -------------------------------------------------------------------------------- 1 | # OpenFin Excel API Logs 2 | 3 | ## From Version 4 4 | 5 | ### Client Logs 6 | 7 | From version 4.0 onwards you can pass true to the init function and it will enable logging (you will see the excel api console.log entries). 8 | 9 | ```javascript 10 | await fin.desktop.ExcelService.init(true); 11 | ``` 12 | 13 | If you wish to add these logs to your own logging setup you can pass an object with the following implementation (the following is the Typescript interface to give you an idea): 14 | 15 | ```javascript 16 | export interface ILog { 17 | name?: string; 18 | trace?: (message, ...args) => void; 19 | debug?: (message, ...args) => void; 20 | info?: (message, ...args) => void; 21 | warn?: (message, ...args) => void; 22 | error?: (message, error, ...args) => void; 23 | fatal?: (message, error, ...args) => void; 24 | } 25 | ``` 26 | If you pass an object but do not provide a full implementation then we will use our default implementation that logs to the console for the functions that are not provided. 27 | 28 | This is to help with debugging if you wish to verify something. 29 | 30 | ### Excel Provider Logs 31 | 32 | The excel provider will log information to a log file. If you go to your OpenFin/apps directory you should find the Excel-Service-Manager app and this should have an app.log file within it's folder. 33 | 34 | ### Excel Helper/Plugin Logs 35 | 36 | These produce logs to help with debugging and can be found by going to your OpenFin\shared\assets\excel-api-addin\logging folder. -------------------------------------------------------------------------------- /client/src/ExcelApplication.d.ts: -------------------------------------------------------------------------------- 1 | import { RpcDispatcher } from './RpcDispatcher'; 2 | import { ExcelWorkbook } from './ExcelWorkbook'; 3 | import { ILog } from './ILog'; 4 | export declare class ExcelApplication extends RpcDispatcher { 5 | static defaultInstance: ExcelApplication; 6 | workbooks: { 7 | [workbookName: string]: ExcelWorkbook; 8 | }; 9 | connected: boolean; 10 | initialized: boolean; 11 | version: { 12 | clientVersion: string; 13 | buildVersion: string; 14 | }; 15 | loggerName: string; 16 | private objectInstance; 17 | constructor(connectionUuid: string, logger?: ILog); 18 | init(): Promise; 19 | release(): Promise; 20 | processExcelEvent: (data: any, uuid: string) => void; 21 | processExcelResult: (result: any) => void; 22 | subscribeToExcelMessages(): Promise<[void, void]>; 23 | unsubscribeToExcelMessages(): Promise<[void, void]>; 24 | monitorDisconnect(): Promise; 25 | run(callback?: Function): Promise; 26 | getWorkbooks(callback?: Function): Promise; 27 | getWorkbookByName(name: string): ExcelWorkbook; 28 | addWorkbook(callback?: Function): Promise; 29 | openWorkbook(path: string, callback?: Function): Promise; 30 | getConnectionStatus(callback?: Function): Promise; 31 | getCalculationMode(callback: Function): Promise; 32 | calculateAll(callback: Function): Promise; 33 | toObject(): any; 34 | } 35 | -------------------------------------------------------------------------------- /client/src/EventEmitter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.EventEmitter = void 0; 4 | class EventEmitter { 5 | constructor() { 6 | this.listeners = {}; 7 | } 8 | addEventListener(type, listener) { 9 | if (this.hasEventListener(type, listener)) { 10 | return; 11 | } 12 | if (!this.listeners[type]) { 13 | this.listeners[type] = []; 14 | } 15 | this.listeners[type].push(listener); 16 | } 17 | removeEventListener(type, listener) { 18 | if (!this.hasEventListener(type, listener)) { 19 | return; 20 | } 21 | var callbacksOfType = this.listeners[type]; 22 | callbacksOfType.splice(callbacksOfType.indexOf(listener), 1); 23 | } 24 | hasEventListener(type, listener) { 25 | if (!this.listeners[type]) { 26 | return false; 27 | } 28 | if (!listener) { 29 | return true; 30 | } 31 | return (this.listeners[type].indexOf(listener) >= 0); 32 | } 33 | dispatchEvent(evtOrTypeArg, data) { 34 | var event; 35 | if (typeof evtOrTypeArg == "string") { 36 | event = Object.assign({ 37 | target: this.toObject(), 38 | type: evtOrTypeArg, 39 | defaultPrevented: false 40 | }, data); 41 | } 42 | else { 43 | event = evtOrTypeArg; 44 | } 45 | var callbacks = this.listeners[event.type] || []; 46 | callbacks.forEach(callback => callback(event)); 47 | return event.defaultPrevented; 48 | } 49 | } 50 | exports.EventEmitter = EventEmitter; 51 | //# sourceMappingURL=EventEmitter.js.map -------------------------------------------------------------------------------- /client/src/ExcelApi.d.ts: -------------------------------------------------------------------------------- 1 | import { RpcDispatcher } from './RpcDispatcher'; 2 | import { ExcelApplication } from './ExcelApplication'; 3 | import { ExcelRtd } from './ExcelRtd'; 4 | import { ILog } from './ILog'; 5 | export declare class ExcelService extends RpcDispatcher { 6 | static instance: ExcelService; 7 | defaultApplicationUuid: string; 8 | defaultApplicationObj: any; 9 | logger: ILog; 10 | loggerName: string; 11 | initialized: boolean; 12 | mainChannelCreated: Promise; 13 | applications: { 14 | [connectionUuid: string]: ExcelApplication; 15 | }; 16 | version: { 17 | buildVersion: string; 18 | providerVersion: string; 19 | }; 20 | mainChannelResolve: (value: boolean | PromiseLike) => void; 21 | mainChannelReject: (reason?: any) => void; 22 | constructor(); 23 | private setMainChanelCreated; 24 | init(logger: ILog | boolean): Promise; 25 | processExcelServiceEvent: (data: any) => Promise; 26 | processExcelServiceResult: (result: any) => Promise; 27 | subscribeToServiceMessages(): Promise<[void, void]>; 28 | monitorDisconnect(): Promise; 29 | registerWindowInstance: (callback?: Function) => Promise; 30 | configureDefaultApplication(): Promise; 31 | processExcelConnectedEvent(data: any): Promise; 32 | processExcelDisconnectedEvent(data: any): Promise; 33 | processGetExcelInstancesResult(connectionUuids: string[]): Promise; 34 | install(callback?: Function): Promise; 35 | getInstallationStatus(callback?: Function): Promise; 36 | getExcelInstances(callback?: Function): Promise; 37 | createRtd(providerName: string, heartbeatIntervalInMilliseconds?: number): Promise; 38 | toObject(): any; 39 | } 40 | -------------------------------------------------------------------------------- /client/src/ExcelWorksheet.d.ts: -------------------------------------------------------------------------------- 1 | import { RpcDispatcher } from './RpcDispatcher'; 2 | import { ExcelWorkbook } from './ExcelWorkbook'; 3 | export declare class ExcelWorksheet extends RpcDispatcher { 4 | workbook: ExcelWorkbook; 5 | worksheetName: string; 6 | private objectInstance; 7 | constructor(name: string, workbook: ExcelWorkbook); 8 | getDefaultMessage(): any; 9 | setCells(values: any[][], offset: string): Promise; 10 | getCells(start: string, offsetWidth: number, offsetHeight: number, callback: Function): Promise; 11 | getRow(start: string, width: number, callback: Function): Promise; 12 | getColumn(start: string, offsetHeight: number, callback: Function): Promise; 13 | activate(): Promise; 14 | activateCell(cellAddress: string): Promise; 15 | addButton(name: string, caption: string, cellAddress: string): Promise; 16 | setFilter(start: string, offsetWidth: number, offsetHeight: number, field: number, criteria1: string, op: string, criteria2: string, visibleDropDown: string): Promise; 17 | formatRange(rangeCode: string, format: any, callback: Function): Promise; 18 | clearRange(rangeCode: string, callback: Function): Promise; 19 | clearRangeContents(rangeCode: string, callback: Function): Promise; 20 | clearRangeFormats(rangeCode: string, callback: Function): Promise; 21 | clearAllCells(callback: Function): Promise; 22 | clearAllCellContents(callback: Function): Promise; 23 | clearAllCellFormats(callback: Function): Promise; 24 | setCellName(cellAddress: string, cellName: string): Promise; 25 | calculate(): Promise; 26 | getCellByName(cellName: string, callback: Function): Promise; 27 | protect(password: string): Promise; 28 | renameSheet(name: string): Promise; 29 | toObject(): any; 30 | } 31 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Excel API 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
Welcome to the OpenFin Excel demo!
14 |
The purpose of this demo is to demonstrate two-way, event-driven communication between OpenFin and Excel.
15 |
16 | 17 |
18 | Excel not connected. Start by launching Excel. 19 |
20 | 21 |
22 | No Workbooks found. Start with creating New Workbook. 23 |
24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 |
Formula:
32 | 33 | 34 | 35 |
36 |
37 |
38 | 39 |
Excel not connected
40 | 41 |
42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /client/src/ExcelWorkbook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ExcelWorkbook = void 0; 4 | const RpcDispatcher_1 = require("./RpcDispatcher"); 5 | class ExcelWorkbook extends RpcDispatcher_1.RpcDispatcher { 6 | constructor(application, name) { 7 | super(application.logger); 8 | this.worksheets = {}; 9 | this.connectionUuid = application.connectionUuid; 10 | this.application = application; 11 | this.workbookName = name; 12 | } 13 | getDefaultMessage() { 14 | return { 15 | workbook: this.workbookName 16 | }; 17 | } 18 | getWorksheets(callback) { 19 | return this.invokeExcelCall("getWorksheets", null, callback); 20 | } 21 | getWorksheetByName(name) { 22 | return this.worksheets[name]; 23 | } 24 | addWorksheet(callback) { 25 | return this.invokeExcelCall("addSheet", null, callback); 26 | } 27 | activate() { 28 | return this.invokeExcelCall("activateWorkbook"); 29 | } 30 | save() { 31 | return this.invokeExcelCall("saveWorkbook"); 32 | } 33 | close() { 34 | return this.invokeExcelCall("closeWorkbook"); 35 | } 36 | refreshObject() { 37 | this.objectInstance = null; 38 | this.toObject(); 39 | } 40 | toObject() { 41 | return this.objectInstance || (this.objectInstance = { 42 | addEventListener: this.addEventListener.bind(this), 43 | removeEventListener: this.removeEventListener.bind(this), 44 | name: this.workbookName, 45 | activate: this.activate.bind(this), 46 | addWorksheet: this.addWorksheet.bind(this), 47 | close: this.close.bind(this), 48 | getWorksheetByName: name => this.getWorksheetByName(name).toObject(), 49 | getWorksheets: this.getWorksheets.bind(this), 50 | save: this.save.bind(this) 51 | }); 52 | } 53 | } 54 | exports.ExcelWorkbook = ExcelWorkbook; 55 | //# sourceMappingURL=ExcelWorkbook.js.map -------------------------------------------------------------------------------- /client/src/RpcDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RpcDispatcher = void 0; 4 | const EventEmitter_1 = require("./EventEmitter"); 5 | const NoOpLogger_1 = require("./NoOpLogger"); 6 | class RpcDispatcher extends EventEmitter_1.EventEmitter { 7 | constructor(logger) { 8 | super(); 9 | this.logger = new NoOpLogger_1.NoOpLogger(); 10 | if (logger !== undefined) { 11 | this.logger = logger; 12 | } 13 | } 14 | getDefaultMessage() { 15 | return {}; 16 | } 17 | invokeExcelCall(functionName, data, callback) { 18 | return this.invokeRemoteCall('excelCall', functionName, data, callback); 19 | } 20 | invokeServiceCall(functionName, data, callback) { 21 | return this.invokeRemoteCall('excelServiceCall', functionName, data, callback); 22 | } 23 | invokeRemoteCall(topic, functionName, data, callback) { 24 | var message = this.getDefaultMessage(); 25 | var args = data || {}; 26 | var invoker = this; 27 | Object.assign(message, { 28 | messageId: RpcDispatcher.messageId, 29 | target: { 30 | connectionUuid: this.connectionUuid, 31 | workbookName: invoker.workbookName || (invoker.workbook && invoker.workbook.workbookName) || args.workbookName || args.workbook, 32 | worksheetName: invoker.worksheetName || args.worksheetName || args.worksheet, 33 | rangeCode: args.rangeCode 34 | }, 35 | action: functionName, 36 | data: data 37 | }); 38 | var executor; 39 | var promise = new Promise((resolve, reject) => { 40 | executor = { 41 | resolve, 42 | reject 43 | }; 44 | }); 45 | // Legacy Callback-style API 46 | promise = this.applyCallbackToPromise(promise, callback); 47 | var currentMessageId = RpcDispatcher.messageId; 48 | RpcDispatcher.messageId++; 49 | if (this.connectionUuid !== undefined) { 50 | RpcDispatcher.promiseExecutors[currentMessageId] = executor; 51 | fin.desktop.InterApplicationBus.send(this.connectionUuid, topic, message, ack => { 52 | // TODO: log once we support configurable logging. 53 | }, nak => { 54 | delete RpcDispatcher.promiseExecutors[currentMessageId]; 55 | executor.reject(new Error(nak)); 56 | }); 57 | } 58 | else { 59 | executor.reject(new Error('The target UUID of the remote call is undefined.')); 60 | } 61 | return promise; 62 | } 63 | applyCallbackToPromise(promise, callback) { 64 | if (callback) { 65 | promise 66 | .then(result => { 67 | callback(result); 68 | return result; 69 | }).catch(err => { 70 | this.logger.error("unable to apply callback to promise", err); 71 | }); 72 | promise = undefined; 73 | } 74 | return promise; 75 | } 76 | } 77 | exports.RpcDispatcher = RpcDispatcher; 78 | RpcDispatcher.messageId = 1; 79 | RpcDispatcher.promiseExecutors = {}; 80 | //# sourceMappingURL=RpcDispatcher.js.map -------------------------------------------------------------------------------- /client/src/ExcelWorksheet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ExcelWorksheet = void 0; 4 | const RpcDispatcher_1 = require("./RpcDispatcher"); 5 | class ExcelWorksheet extends RpcDispatcher_1.RpcDispatcher { 6 | constructor(name, workbook) { 7 | super(workbook.logger); 8 | this.connectionUuid = workbook.connectionUuid; 9 | this.workbook = workbook; 10 | this.worksheetName = name; 11 | } 12 | getDefaultMessage() { 13 | return { 14 | workbook: this.workbook.workbookName, 15 | worksheet: this.worksheetName 16 | }; 17 | } 18 | setCells(values, offset) { 19 | if (!offset) 20 | offset = "A1"; 21 | return this.invokeExcelCall("setCells", { offset: offset, values: values }); 22 | } 23 | getCells(start, offsetWidth, offsetHeight, callback) { 24 | return this.invokeExcelCall("getCells", { start: start, offsetWidth: offsetWidth, offsetHeight: offsetHeight }, callback); 25 | } 26 | getRow(start, width, callback) { 27 | return this.invokeExcelCall("getCellsRow", { start: start, offsetWidth: width }, callback); 28 | } 29 | getColumn(start, offsetHeight, callback) { 30 | return this.invokeExcelCall("getCellsColumn", { start: start, offsetHeight: offsetHeight }, callback); 31 | } 32 | activate() { 33 | return this.invokeExcelCall("activateSheet"); 34 | } 35 | activateCell(cellAddress) { 36 | return this.invokeExcelCall("activateCell", { address: cellAddress }); 37 | } 38 | addButton(name, caption, cellAddress) { 39 | return this.invokeExcelCall("addButton", { address: cellAddress, buttonName: name, buttonCaption: caption }); 40 | } 41 | setFilter(start, offsetWidth, offsetHeight, field, criteria1, op, criteria2, visibleDropDown) { 42 | return this.invokeExcelCall("setFilter", { 43 | start: start, 44 | offsetWidth: offsetWidth, 45 | offsetHeight: offsetHeight, 46 | field: field, 47 | criteria1: criteria1, 48 | op: op, 49 | criteria2: criteria2, 50 | visibleDropDown: visibleDropDown 51 | }); 52 | } 53 | formatRange(rangeCode, format, callback) { 54 | return this.invokeExcelCall("formatRange", { rangeCode: rangeCode, format: format }, callback); 55 | } 56 | clearRange(rangeCode, callback) { 57 | return this.invokeExcelCall("clearRange", { rangeCode: rangeCode }, callback); 58 | } 59 | clearRangeContents(rangeCode, callback) { 60 | return this.invokeExcelCall("clearRangeContents", { rangeCode: rangeCode }, callback); 61 | } 62 | clearRangeFormats(rangeCode, callback) { 63 | return this.invokeExcelCall("clearRangeFormats", { rangeCode: rangeCode }, callback); 64 | } 65 | clearAllCells(callback) { 66 | return this.invokeExcelCall("clearAllCells", null, callback); 67 | } 68 | clearAllCellContents(callback) { 69 | return this.invokeExcelCall("clearAllCellContents", null, callback); 70 | } 71 | clearAllCellFormats(callback) { 72 | return this.invokeExcelCall("clearAllCellFormats", null, callback); 73 | } 74 | setCellName(cellAddress, cellName) { 75 | return this.invokeExcelCall("setCellName", { address: cellAddress, cellName: cellName }); 76 | } 77 | calculate() { 78 | return this.invokeExcelCall("calculateSheet"); 79 | } 80 | getCellByName(cellName, callback) { 81 | return this.invokeExcelCall("getCellByName", { cellName: cellName }, callback); 82 | } 83 | protect(password) { 84 | return this.invokeExcelCall("protectSheet", { password: password ? password : null }); 85 | } 86 | renameSheet(name) { 87 | return this.invokeExcelCall("renameSheet", { worksheetName: name }); 88 | } 89 | toObject() { 90 | return this.objectInstance || (this.objectInstance = { 91 | addEventListener: this.addEventListener.bind(this), 92 | removeEventListener: this.removeEventListener.bind(this), 93 | name: this.worksheetName, 94 | activate: this.activate.bind(this), 95 | activateCell: this.activateCell.bind(this), 96 | addButton: this.addButton.bind(this), 97 | calculate: this.calculate.bind(this), 98 | clearAllCellContents: this.clearAllCellContents.bind(this), 99 | clearAllCellFormats: this.clearAllCellFormats.bind(this), 100 | clearAllCells: this.clearAllCells.bind(this), 101 | clearRange: this.clearRange.bind(this), 102 | clearRangeContents: this.clearRangeContents.bind(this), 103 | clearRangeFormats: this.clearRangeFormats.bind(this), 104 | formatRange: this.formatRange.bind(this), 105 | getCellByName: this.getCellByName.bind(this), 106 | getCells: this.getCells.bind(this), 107 | getColumn: this.getColumn.bind(this), 108 | getRow: this.getRow.bind(this), 109 | protect: this.protect.bind(this), 110 | renameSheet: this.renameSheet.bind(this), 111 | setCellName: this.setCellName.bind(this), 112 | setCells: this.setCells.bind(this), 113 | setFilter: this.setFilter.bind(this) 114 | }); 115 | } 116 | } 117 | exports.ExcelWorksheet = ExcelWorksheet; 118 | //# sourceMappingURL=ExcelWorksheet.js.map -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | .greenBar { 2 | border-top: 1px solid #217346; 3 | width: 100%; 4 | height: 1px; 5 | } 6 | 7 | .cell { 8 | width: 55px; 9 | height: 15px; 10 | border: none; 11 | border-right: 1px solid lightgray; 12 | border-bottom: 1px solid lightgray; 13 | border-collapse: collapse; 14 | text-align: center; 15 | font-family: "Verdana", sans-serif; 16 | font-size: 9pt; 17 | } 18 | 19 | .cellHeader { 20 | width: 55px; 21 | height: 15px; 22 | border: none; 23 | border-right: 1px solid lightgray; 24 | border-bottom: 1px solid lightgray; 25 | border-collapse: collapse; 26 | text-align: center; 27 | font-family: "Verdana", sans-serif; 28 | font-size: 9pt; 29 | } 30 | 31 | .cellHeaderSelected { 32 | width: 55px; 33 | height: 15px; 34 | border: none; 35 | border-right: 1px solid lightgray; 36 | border-bottom: 2px solid #217346; 37 | border-collapse: collapse; 38 | text-align: center; 39 | font-family: "Verdana", sans-serif; 40 | font-size: 9pt; 41 | font-weight: bold; 42 | color: #217346; 43 | background-color: #e5e5e5; 44 | ; 45 | } 46 | 47 | .cellSelected { 48 | width: 55px; 49 | height: 15px; 50 | border: 2px solid #217346; 51 | text-align: center; 52 | font-family: "Verdana", sans-serif; 53 | font-size: 9pt; 54 | } 55 | 56 | .rowNumber { 57 | width: 30px; 58 | height: 15px; 59 | border-right: 1px solid lightgray; 60 | border-bottom: 1px solid lightgray; 61 | text-align: center; 62 | font-family: "Verdana", sans-serif; 63 | font-size: 9pt; 64 | color: #3a3a3a; 65 | } 66 | 67 | .rowNumberSelected { 68 | width: 30px; 69 | height: 15px; 70 | border-right: 2px solid #217346; 71 | border-bottom: 1px solid lightgray; 72 | text-align: center; 73 | font-family: "Verdana", sans-serif; 74 | font-size: 9pt; 75 | color: #217346; 76 | font-weight: bold; 77 | background-color: #e5e5e5; 78 | } 79 | 80 | .tabSelected { 81 | border: 1px solid gray; 82 | border-bottom: 3px solid #217346; 83 | border-top: none; 84 | padding: 5px; 85 | border-right: 2px solid gray; 86 | padding-left: 15px; 87 | padding-right: 15px; 88 | color: #217346; 89 | font-weight: bold; 90 | background-color: white; 91 | } 92 | 93 | .tabSelected:focus { 94 | outline: none; 95 | } 96 | 97 | .tab:focus { 98 | outline: none; 99 | } 100 | 101 | .tab { 102 | border: none; 103 | border-top: none; 104 | border-right: 1px solid gray; 105 | padding: 5px; 106 | padding-left: 15px; 107 | padding-right: 15px; 108 | background-color: white; 109 | border-bottom: 1px solid gray; 110 | border-top: 1px solid gray; 111 | } 112 | 113 | .workbookTab:focus { 114 | outline: none; 115 | } 116 | 117 | .workbookTabSelected:focus { 118 | outline: none; 119 | } 120 | 121 | .workbookTab { 122 | border: none; 123 | border-top: none; 124 | border-right: 1px solid gray; 125 | padding: 5px; 126 | padding-left: 20px; 127 | padding-right: 20px; 128 | background-color: white; 129 | color: #217346; 130 | border-bottom: none; 131 | border-top: 1px solid gray; 132 | font-size: 16px; 133 | } 134 | 135 | .workbookTabSelected { 136 | border: 1px solid #217346; 137 | padding: 5px; 138 | padding-left: 20px; 139 | padding-right: 20px; 140 | background-color: #217346; 141 | color: white; 142 | font-size: 16px; 143 | font-weight: bold; 144 | } 145 | 146 | #sheets { 147 | } 148 | 149 | #formulaInput { 150 | width: 500px; 151 | border: 1px solid lightgray; 152 | height: 20px; 153 | margin: 10px; 154 | ; 155 | margin-bottom: 10px; 156 | ; 157 | margin-top: 10px; 158 | padding: 5px; 159 | font-family: "Verdana", sans-serif; 160 | font-size: 10pt; 161 | } 162 | 163 | #connectionStatus { 164 | background-color: #217346; 165 | color: white; 166 | width: 100%; 167 | margin-top: 5px; 168 | padding: 5px; 169 | font-family: "Verdana", sans-serif; 170 | font-size: 8pt; 171 | padding-left: 10px; 172 | bottom: 0px; 173 | position: fixed; 174 | } 175 | 176 | body { 177 | overflow: hidden; 178 | margin: 0px; 179 | } 180 | 181 | #intro { 182 | padding: 10px; 183 | } 184 | 185 | .introHeader { 186 | font-family: Calibri, Helvetica, Arial; 187 | color: #217346; 188 | clear: right; 189 | font-size: 20pt; 190 | font-weight: lighter; 191 | text-align: center; 192 | } 193 | 194 | .introDescription { 195 | font-family: Calibri, Helvetica, Arial; 196 | font-size: 11pt; 197 | padding-top: 10px; 198 | color: #282828; 199 | text-align: center; 200 | } 201 | 202 | .noWorkbooks { 203 | font-family: Calibri, Helvetica, Arial; 204 | font-size: 11pt; 205 | padding: 15px; 206 | text-align: center; 207 | color: #0e77ed; 208 | display: none; 209 | } 210 | 211 | .noWorkbooks a { 212 | font-family: Calibri, Helvetica, Arial; 213 | font-size: 11pt; 214 | text-align: center; 215 | color: #0e77ed; 216 | } 217 | 218 | .workbooksContainer { 219 | display: none; 220 | } 221 | 222 | .fullOverlay { 223 | background-color: rgba(0,0,0,0.5); 224 | visibility: hidden; 225 | position: fixed; 226 | width: 100%; 227 | height: 100%; 228 | top: 0; 229 | left: 0; 230 | right: 0; 231 | bottom: 0; 232 | } 233 | 234 | .modalDialog { 235 | background-color: white; 236 | display: table; 237 | width: 80%; 238 | height: 120px; 239 | margin: 120px auto; 240 | } 241 | 242 | .modalDialog span { 243 | display: table-cell; 244 | vertical-align: middle; 245 | } 246 | 247 | #addWorkbookDialog input { 248 | width: 600px; 249 | margin: 40px; 250 | } 251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **IMPORTANT NOTE**: The code in this repo has now been **deprecated** as it has been replaced by our new Excel integration. Please refer to the [developer docs](https://developers.openfin.co/of-docs/docs/excel-integration) for more information. 2 | 3 | # Excel Service API Demo 4 | 5 | This repo provides a demonstration of the OpenFin Excel Service and its JavaScript API. 6 | 7 | Note: This main source code for this demo is intentionally coded in plain JavaScript so that its easy to follow, without any need to understand other technologies/ frameworks. The API libraries are generated from TypeScript, and the end-product utilizes webpack to achieve a single-file web application. 8 | 9 | This demo uses [ExcelDna](https://github.com/Excel-DNA/ExcelDna) to create the Excel addin. 10 | 11 | # Running the Demo 12 | 13 | ## Quick Start 14 | 15 | 1) Download and run the installer: 16 | 17 | [openfin installer download](https://install.openfin.co/download/?config=http%3A%2F%2Fopenfin.github.io%2Fexcel-api-example%2Fdemo%2Fapp.json&fileName=excel-api-example-installer) 18 | 19 | 2) After the installer runs, the OpenFin application should launch and either connect to Excel if it already running or present the option to launch Excel. 20 | 21 | 3) Once Excel is running you can create workbooks or open existing workbooks and observe two-way data synchronization between Excel and the demo app. 22 | 23 | ## Modifying and Building Locally 24 | 25 | For development purposes you may wish to clone this repository and run on a local computer. The Excel Add-In is only compatible with Excel for Windows. 26 | 27 | Pre-requisite: Node and NPM must be installed ( [https://nodejs.org/en/](https://nodejs.org/en/) ). 28 | 29 | Clone the repository and, in the Command Prompt, navigate into the _excel-api-example_ directory created. 30 | 31 | In the Command Prompt run: 32 | 33 | 34 | ``` 35 | > npm install 36 | ``` 37 | 38 | Once the Node packages have installed, it is now possible to make modifications to files in the _excel-api-example\src_ folder and rebuild the project by running: 39 | 40 | 41 | ``` 42 | > npm run webpack 43 | ``` 44 | 45 | After rebuilding, start the application by running: 46 | 47 | ``` 48 | > npm start 49 | ``` 50 | 51 | This will start a simple HTTP server on port 8080 and launch the OpenFin App automatically. 52 | 53 | 54 | # Including the API in Your Own Project 55 | 56 | ## Manifest Declaration 57 | 58 | Declare the Excel Service by including the following declaration in your application manifest: 59 | 60 | ```javascript 61 | "services": 62 | [ 63 | { "name": "excel" } 64 | ] 65 | ``` 66 | 67 | ## Including the Client 68 | 69 | Unlike other services, currently the Excel API client is only provided as a script tag. Include the following script tag on each page that requires API access: 70 | 71 | ```javascript 72 | 73 | ``` 74 | 75 | ## Waiting for the Excel Service to be Running 76 | 77 | During startup, an application which wishes to utilize the Excel Service should ensure the service is running and ready to receive commands by doing *two* things: 78 | 79 | ### Setup event listeners so you are notified when Excel is connected/disconnected before trying to interact with Excel 80 | ```javascript 81 | async function onExcelConnected(data) { 82 | console.log("Excel Connected: " + data.connectionUuid); 83 | let connected = await window.fin.desktop.Excel.getConnectionStatus(); 84 | console.log("Connected: " + connected); 85 | 86 | // do some work with the excel api 87 | } 88 | 89 | async function onExcelDisconnected(data) { 90 | console.log("Excel Disconnected: " + data.connectionUuid); 91 | } 92 | 93 | function initializeExcelEvents() { 94 | console.log("Initialising excel connected/disconnected events"); 95 | fin.desktop.ExcelService.addEventListener("excelConnected", onExcelConnected); 96 | fin.desktop.ExcelService.addEventListener("excelDisconnected", onExcelDisconnected); 97 | } 98 | initializeExcelEvents(); 99 | ``` 100 | 101 | ### Invoke the Excel service after setting up your listeners by invoking: 102 | 103 | ```javascript 104 | await fin.desktop.ExcelService.init(); 105 | ``` 106 | 107 | It is advisable to place this call before any calls on the `fin.desktop.Excel` namespace. 108 | 109 | ## New Support Capabilities from Version 4.0+ 110 | 111 | - [Logging](LOGS.md) 112 | - [Versioning Information](VERSIONS.md) 113 | 114 | # Getting Started with the API 115 | 116 | ## Writing to and Reading from a Spreadsheet: 117 | 118 | After a connection has been established between Excel and the OpenFin application, pushing data to a spreadsheet and reading back the calculated values can be performed as follows: 119 | 120 | ```javascript 121 | var sheet1 = fin.desktop.Excel.getWorkbookByName('Book1').getWorksheetByName('Sheet1'); 122 | 123 | // A little fun with Pythagorean triples: 124 | sheet1.setCells([ 125 | ["A", "B", "C"], 126 | [ 3, 4, "=SQRT(A2^2+B2^2)"], 127 | [ 5, 12, "=SQRT(A3^2+B3^2)"], 128 | [ 8, 15, "=SQRT(A4^2+B4^2)"], 129 | ], "A1"); 130 | 131 | // Write the computed values to console: 132 | sheet1.getCells("C2", 0, 2, cells => { 133 | console.log(cells[0][0].value); 134 | console.log(cells[1][0].value); 135 | console.log(cells[2][0].value); 136 | }); 137 | 138 | ``` 139 | 140 | ## Subscribing to Events: 141 | 142 | Monitoring various application, workbook, and sheet events are done via the `addEventListener` functions on their respective objects. For example: 143 | 144 | ```javascript 145 | sheet1.getCells("C2", 0, 2, cells => { 146 | var lastValue = cells[0][0].value; 147 | 148 | fin.desktop.Excel.addEventListener('afterCalculation', () => { 149 | sheet1.getCells("C2", 0, 2, cells => { 150 | if(cells[0][0].value !== lastValue) { 151 | console.log('Value Changed!'); 152 | } 153 | 154 | lastValue = cells[0][0].value; 155 | }); 156 | }); 157 | }) 158 | ``` 159 | 160 | ## Legacy Callback-Based API 161 | 162 | Version 1 of the Demo API utilized callbacks to handle asynchronous actions and return values. Starting in Version 2, all asynchronous functions are instead handled via Promises, however, for reverse compatibility the legacy callback-style calls are still supported. 163 | 164 | All functions which return promises can also take a callback as the final argument. The following three calls are identical: 165 | 166 | ```javascript 167 | // Version 1 - Callback style [deprecated] 168 | fin.desktop.Excel.getWorkbooks(workbooks => { 169 | console.log('Number of open workbooks: ', workbooks.length); 170 | }); 171 | 172 | // Version 2 - Promise then callback 173 | fin.desktop.Excel.getWorkbooks().then(workbooks => { 174 | console.log('Number of open workbooks: ', workbooks.length); 175 | }); 176 | 177 | // Version 2 - Promise await 178 | var workbooks = await fin.desktop.Excel.getWorkbooks(); 179 | console.log('Number of open workbooks: ', workbooks.length); 180 | 181 | ``` 182 | 183 | ## Full API Documentation 184 | 185 | The complete Excel Service API Documentation is available [here](DOCS.md). 186 | 187 | In the future, type definition files will be available for public consumption via an NPM types package. 188 | 189 | ## License 190 | MIT 191 | 192 | The code in this repository is covered by the included license. 193 | 194 | However, if you run this code, it may call on the OpenFin RVM or OpenFin Runtime, which are covered by OpenFin’s Developer, Community, and Enterprise licenses. You can learn more about OpenFin licensing at the links listed below or just email us at support@openfin.co with questions. 195 | 196 | https://openfin.co/developer-agreement/
197 | https://openfin.co/licensing/ 198 | 199 | ## Support 200 | Please enter an issue in the repo for any questions or problems. Alternatively, please contact us at support@openfin.co 201 | -------------------------------------------------------------------------------- /provider/src/provider.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | // This script determines if the .NET OpenFin.ExcelService is running and if the 11 | // XLL Add-In has been installed. If not, it will perform the deployment, registration, 12 | // and start the service process 13 | fin.desktop.main(() => __awaiter(this, void 0, void 0, function* () { 14 | const providerVersion = "[EXCEL_CLIENT_VERSION]"; 15 | const buildVersion = "[EXCEL_BUILD_VERSION]"; 16 | const excelAssetAlias = 'excel-api-addin'; 17 | const excelServiceUuid = '886834D1-4651-4872-996C-7B2578E953B9'; 18 | const installFolder = '%localappdata%\\OpenFin\\shared\\assets\\excel-api-addin'; 19 | const servicePath = 'OpenFin.ExcelService.exe'; 20 | const addInPath = 'OpenFin.ExcelApi-AddIn.xll'; 21 | const excelServiceEventTopic = 'excelServiceEvent'; 22 | try { 23 | console.log("Starting up excel provider: " + providerVersion + " build: " + buildVersion); 24 | let serviceIsRunning = yield isServiceRunning(); 25 | let assetInfo = yield getAppAssetInfo(); 26 | console.log("Provider Configured Asset Info: " + JSON.stringify(assetInfo)); 27 | if (serviceIsRunning) { 28 | console.log('Service Already Running: Skipping Deployment and Registration'); 29 | return; 30 | } 31 | if (assetInfo.version === localStorage.installedAssetVersion && !assetInfo.forceDownload) { 32 | console.log('Current Add-In version previously installed: Skipping Deployment and Registration'); 33 | } 34 | else { 35 | yield deploySharedAssets(); 36 | yield tryInstallAddIn(); 37 | console.log("Updating locally stored version number to: " + assetInfo.version); 38 | localStorage.installedAssetVersion = assetInfo.version; 39 | } 40 | yield startExcelService(); 41 | console.log('Excel Service Started'); 42 | } 43 | catch (err) { 44 | console.error(err); 45 | } 46 | // Technically there is a small window of time between when the UUID is 47 | // registered as an external application and when the service is ready to 48 | // receive commands. This edge-case will be best handled in the future 49 | // with the availability of plugins and services from the fin API 50 | function isServiceRunning() { 51 | return new Promise((resolve, reject) => { 52 | console.log("Performing check to see if Excel .NET Exe is running: " + excelServiceUuid); 53 | fin.desktop.System.getAllExternalApplications(extApps => { 54 | var excelServiceIndex = extApps.findIndex(extApp => extApp.uuid === excelServiceUuid); 55 | if (excelServiceIndex >= 0) { 56 | console.log("Excel .NET Exe uuid found in list of external applications."); 57 | resolve(true); 58 | } 59 | else { 60 | console.log("Excel .NET Exe uuid found in list of external applications."); 61 | resolve(false); 62 | } 63 | }); 64 | }); 65 | } 66 | function getAppAssetInfo() { 67 | return new Promise((resolve, reject) => { 68 | console.log("Getting app asset info for alias: " + excelAssetAlias); 69 | fin.desktop.System.getAppAssetInfo({ alias: excelAssetAlias }, resolve, reject); 70 | }); 71 | } 72 | function deploySharedAssets() { 73 | return new Promise((resolve, reject) => { 74 | console.log("Deploying Shared Assets."); 75 | fin.desktop.Application.getCurrent().getManifest(manifest => { 76 | let arguments = `-d "${installFolder}" -c ${manifest.runtime.version}`; 77 | console.log("Manifest retrieved: " + JSON.stringify(manifest)); 78 | console.log(`Launching external process. Alias: ${excelAssetAlias} target: ${servicePath} arguments: ${arguments}`); 79 | fin.desktop.System.launchExternalProcess({ 80 | alias: excelAssetAlias, 81 | target: servicePath, 82 | arguments: arguments, 83 | listener: result => { 84 | console.log(`Asset Deployment completed! Exit Code: ${result.exitCode}`); 85 | resolve(); 86 | } 87 | }, () => console.log('Deploying Shared Assets. Launch External Process executed.'), err => reject(err)); 88 | }); 89 | }); 90 | } 91 | function tryInstallAddIn() { 92 | return new Promise((resolve, reject) => { 93 | let path = `${installFolder}\\${servicePath}`; 94 | let arguments = `-i "${installFolder}"`; 95 | console.log(`Installing Excel Addin. Path: ${path} arguments: ${arguments}`); 96 | fin.desktop.System.launchExternalProcess({ 97 | path: path, 98 | arguments: arguments, 99 | listener: result => { 100 | if (result.exitCode === 0) { 101 | console.log('Add-In Installed'); 102 | } 103 | else { 104 | console.warn(`Installation failed. Exit code: ${result.exitCode}`); 105 | } 106 | resolve(); 107 | } 108 | }, () => console.log('Installing Add-In. Launch External Process executed.'), err => reject(err)); 109 | }); 110 | } 111 | function startExcelService() { 112 | return new Promise((resolve, reject) => { 113 | console.log("Starting Excel .NET Exe Service"); 114 | console.log(`Subscribing to: ${excelServiceEventTopic}`); 115 | let onMessageReceived; 116 | let connected = false; 117 | fin.desktop.InterApplicationBus.subscribe('*', excelServiceEventTopic, onMessageReceived = () => { 118 | console.log("Received message from .NET Exe Service on topic: " + excelServiceEventTopic + ". Unsubscribing now that we have received notification."); 119 | if (connected) { 120 | console.log("We have already recieved a message from the exe indicating connected. Resolve promise."); 121 | resolve(); 122 | return; 123 | } 124 | connected = true; 125 | fin.desktop.InterApplicationBus.unsubscribe('*', excelServiceEventTopic, onMessageReceived, () => { 126 | console.log("Unsubscribed from topic: " + excelServiceEventTopic); 127 | }, err => { 128 | console.log("Error while trying to unsubscribe from " + excelServiceEventTopic + " Error: ", err); 129 | }); 130 | // The channel provider should eventually move into the .NET app 131 | // but for now it only being used for signalling and providing provider version 132 | console.log("Creating Channel for client script to connect to: " + excelServiceUuid + " and providing a getVersion function."); 133 | fin.desktop.InterApplicationBus.Channel.create(excelServiceUuid).then(channel => { 134 | channel.register('getVersion', () => { 135 | return { 136 | providerVersion, buildVersion 137 | }; 138 | }); 139 | }); 140 | resolve(); 141 | }); 142 | console.log("Getting system details to get port."); 143 | chrome.desktop.getDetails(function (details) { 144 | let target = `${installFolder}\\${servicePath}`; 145 | let arguments = '-p ' + details.port; 146 | console.log(`Details retrieved. Launching external process. Target: ${target} Arguments: ${arguments} UUID: ${excelServiceUuid}`); 147 | fin.desktop.System.launchExternalProcess({ 148 | target: target, 149 | arguments: arguments, 150 | uuid: excelServiceUuid, 151 | }, process => { 152 | console.log('Service Launched: ' + process.uuid); 153 | }, error => { 154 | reject('Error starting Excel service'); 155 | }); 156 | }); 157 | }); 158 | } 159 | })); 160 | //# sourceMappingURL=provider.js.map -------------------------------------------------------------------------------- /client/src/ExcelRtd.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ExcelRtd = void 0; 13 | const EventEmitter_1 = require("./EventEmitter"); 14 | class ExcelRtd extends EventEmitter_1.EventEmitter { 15 | constructor(providerName, logger, heartbeatIntervalInMilliseconds = 10000) { 16 | super(); 17 | this.heartbeatIntervalInMilliseconds = heartbeatIntervalInMilliseconds; 18 | this.listeners = {}; 19 | this.connectedTopics = {}; 20 | this.connectedKey = 'connected'; 21 | this.disconnectedKey = 'disconnected'; 22 | this.loggerName = "ExcelRtd"; 23 | this.initialized = false; 24 | this.disposed = false; 25 | var minimumDefaultHeartbeat = 10000; 26 | if (this.heartbeatIntervalInMilliseconds < minimumDefaultHeartbeat) { 27 | logger.warn(`heartbeatIntervalInMilliseconds cannot be less than ${minimumDefaultHeartbeat}. Setting heartbeatIntervalInMilliseconds to ${minimumDefaultHeartbeat}.`); 28 | this.heartbeatIntervalInMilliseconds = minimumDefaultHeartbeat; 29 | } 30 | this.providerName = providerName; 31 | this.logger = logger; 32 | logger.debug(this.loggerName + ": instance created for provider: " + providerName); 33 | } 34 | static create(providerName, logger, heartbeatIntervalInMilliseconds = 10000) { 35 | return __awaiter(this, void 0, void 0, function* () { 36 | logger.debug("ExcelRtd: create called to create provider: " + providerName); 37 | const instance = new ExcelRtd(providerName, logger, heartbeatIntervalInMilliseconds); 38 | yield instance.init(); 39 | if (!instance.isInitialized) { 40 | return undefined; 41 | } 42 | return instance; 43 | }); 44 | } 45 | init() { 46 | return __awaiter(this, void 0, void 0, function* () { 47 | if (this.isInitialized) { 48 | return; 49 | } 50 | this.logger.debug(this.loggerName + ": Initialise called for provider: " + this.providerName); 51 | try { 52 | // A channel is created to ensure it is a singleton so you don't have two apps pushing updates over each other or two windows within the same app 53 | this.provider = yield fin.InterApplicationBus.Channel.create(`excelRtd/${this.providerName}`); 54 | } 55 | catch (err) { 56 | this.logger.warn(this.loggerName + `: The excelRtd/${this.providerName} channel already exists. You can only have one instance of a connection for a provider to avoid confusion. It may be you have multiple instances or another window or application has created a provider with the same name.`, err); 57 | return; 58 | } 59 | this.logger.debug(this.loggerName + `: Subscribing to messages to this provider (${this.providerName}) from excel.`); 60 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/pong/${this.providerName}`, this.onSubscribe.bind(this)); 61 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/ping-request/${this.providerName}`, this.ping.bind(this)); 62 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/unsubscribed/${this.providerName}`, this.onUnsubscribe.bind(this)); 63 | yield this.ping(); 64 | this.establishHeartbeat(); 65 | this.logger.debug(this.loggerName + `: initialisation for provider (${this.providerName}) finished.`); 66 | this.initialized = true; 67 | }); 68 | } 69 | get isDisposed() { 70 | return this.disposed; 71 | } 72 | get isInitialized() { 73 | return this.initialized; 74 | } 75 | setValue(topic, value) { 76 | this.logger.trace(this.loggerName + `: Publishing on rtdTopic: ${topic} and provider: ${this.providerName} value: ${JSON.stringify(value)}`); 77 | fin.InterApplicationBus.publish(`excelRtd/data/${this.providerName}/${topic}`, value); 78 | } 79 | dispose() { 80 | return __awaiter(this, void 0, void 0, function* () { 81 | if (!this.disposed) { 82 | this.logger.debug(this.loggerName + `: dispose called. Will send message to clear values for this provider (${this.providerName}).`); 83 | if (this.heartbeatToken) { 84 | clearInterval(this.heartbeatToken); 85 | } 86 | this.clear(); 87 | if (this.provider !== undefined) { 88 | try { 89 | yield this.provider.destroy(); 90 | } 91 | catch (err) { 92 | // without a catch the rest of the initialisation would be broken 93 | this.logger.warn(this.loggerName + `: The excelRtd/${this.providerName} channel could not be destroyed during cleanup.`, err); 94 | } 95 | } 96 | this.logger.debug(this.loggerName + `: UnSubscribing to messages to this provider (${this.providerName}) from excel.`); 97 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/pong/${this.providerName}`, this.onSubscribe.bind(this)); 98 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/ping-request/${this.providerName}`, this.ping.bind(this)); 99 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/unsubscribed/${this.providerName}`, this.onUnsubscribe.bind(this)); 100 | this.disposed = true; 101 | this.initialized = false; 102 | } 103 | else { 104 | this.logger.debug(this.loggerName + `: This provider (${this.providerName}) has already been disposed.`); 105 | } 106 | }); 107 | } 108 | // Overriding 109 | addEventListener(type, listener) { 110 | this.logger.debug(this.loggerName + `: Event listener add requested for type ${type} received.`); 111 | if (super.hasEventListener(type, listener)) { 112 | this.logger.debug(this.loggerName + `: Event listener add requested for type ${type} received.`); 113 | return; 114 | } 115 | let connectedTopicIds = Object.keys(this.connectedTopics); 116 | let topics = this.connectedTopics; 117 | if (connectedTopicIds.length > 0) { 118 | // need to simulate async action as by default this method would return and then a listener would be called 119 | setTimeout(() => { 120 | connectedTopicIds.forEach(id => { 121 | this.logger.debug(this.loggerName + `: Raising synthetic event as the event listener was added after the event for connected for rtdTopic: ${id}.`); 122 | listener(topics[id]); 123 | }); 124 | }, 0); 125 | } 126 | super.addEventListener(type, listener); 127 | } 128 | dispatchEvent(evtOrTypeArg, data) { 129 | var event; 130 | if (typeof evtOrTypeArg == "string" && data !== undefined) { 131 | this.logger.debug(this.loggerName + `: dispatch event called for type ${evtOrTypeArg} and data: ${JSON.stringify(data)}`); 132 | event = Object.assign({ 133 | target: this.toObject(), 134 | type: evtOrTypeArg, 135 | defaultPrevented: false 136 | }, data); 137 | if (data.topic !== undefined) { 138 | if (evtOrTypeArg === this.connectedKey) { 139 | this.connectedTopics[data.topic] = event; 140 | this.logger.debug(this.loggerName + `: Saving connected event for rtdTopic: ${data.topic}.`); 141 | } 142 | else if (evtOrTypeArg === this.disconnectedKey) { 143 | this.logger.debug(this.loggerName + `: Disconnected event for rtdTopic: ${data.topic} received.`); 144 | if (this.connectedTopics[data.topic] !== undefined) { 145 | // we have removed the topic so clear it from the connected list for late subscribers 146 | this.logger.debug(this.loggerName + `: Clearing saved connected event for rtdTopic: ${data.topic}.`); 147 | delete this.connectedTopics[data.topic]; 148 | } 149 | } 150 | } 151 | this.logger.debug(this.loggerName + `: Dispatching event.`); 152 | return super.dispatchEvent(event, data); 153 | } 154 | event = evtOrTypeArg; 155 | return super.dispatchEvent(event); 156 | } 157 | toObject() { 158 | return this; 159 | } 160 | ping(topic) { 161 | return __awaiter(this, void 0, void 0, function* () { 162 | if (topic !== undefined) { 163 | this.pingPath = `excelRtd/ping/${this.providerName}/${topic}`; 164 | } 165 | else { 166 | this.pingPath = `excelRtd/ping/${this.providerName}`; 167 | } 168 | this.logger.debug(this.loggerName + `: Publishing ping message for this provider (${this.providerName}) to excel on topic: ${this.pingPath}.`); 169 | yield fin.InterApplicationBus.publish(`${this.pingPath}`, true); 170 | }); 171 | } 172 | establishHeartbeat() { 173 | this.heartbeatPath = `excelRtd/heartbeat/${this.providerName}`; 174 | this.heartbeatToken = setInterval(() => { 175 | this.logger.debug(`Heartbeating for ${this.heartbeatPath}.`); 176 | fin.InterApplicationBus.publish(`${this.heartbeatPath}`, this.heartbeatIntervalInMilliseconds); 177 | }, this.heartbeatIntervalInMilliseconds); 178 | } 179 | onSubscribe(topic) { 180 | this.logger.debug(this.loggerName + `: Subscription for rtdTopic ${topic} found. Dispatching connected event for rtdTopic.`); 181 | this.dispatchEvent(this.connectedKey, { topic }); 182 | } 183 | onUnsubscribe(topic) { 184 | this.logger.debug(this.loggerName + `: Unsubscribe for rtdTopic ${topic}. Dispatching disconnected event for rtdTopic.`); 185 | this.dispatchEvent(this.disconnectedKey, { topic }); 186 | } 187 | clear() { 188 | let path = `excelRtd/clear/${this.providerName}`; 189 | this.logger.debug(this.loggerName + `: Clear called. Publishing to excel on topic: ${path} `); 190 | fin.InterApplicationBus.publish(`excelRtd/clear/${this.providerName}`, true); 191 | } 192 | } 193 | exports.ExcelRtd = ExcelRtd; 194 | //# sourceMappingURL=ExcelRtd.js.map -------------------------------------------------------------------------------- /provider/provider.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 0); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ([ 70 | /* 0 */ 71 | /***/ (function(module, exports) { 72 | 73 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 74 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 75 | return new (P || (P = Promise))(function (resolve, reject) { 76 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 77 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 78 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 79 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 80 | }); 81 | }; 82 | // This script determines if the .NET OpenFin.ExcelService is running and if the 83 | // XLL Add-In has been installed. If not, it will perform the deployment, registration, 84 | // and start the service process 85 | fin.desktop.main(() => __awaiter(this, void 0, void 0, function* () { 86 | const providerVersion = "4.1.2"; 87 | const buildVersion = "4.1.2.0"; 88 | const excelAssetAlias = 'excel-api-addin'; 89 | const excelServiceUuid = '886834D1-4651-4872-996C-7B2578E953B9'; 90 | const installFolder = '%localappdata%\\OpenFin\\shared\\assets\\excel-api-addin'; 91 | const servicePath = 'OpenFin.ExcelService.exe'; 92 | const addInPath = 'OpenFin.ExcelApi-AddIn.xll'; 93 | const excelServiceEventTopic = 'excelServiceEvent'; 94 | try { 95 | console.log("Starting up excel provider: " + providerVersion + " build: " + buildVersion); 96 | let serviceIsRunning = yield isServiceRunning(); 97 | let assetInfo = yield getAppAssetInfo(); 98 | console.log("Provider Configured Asset Info: " + JSON.stringify(assetInfo)); 99 | if (serviceIsRunning) { 100 | console.log('Service Already Running: Skipping Deployment and Registration'); 101 | return; 102 | } 103 | if (assetInfo.version === localStorage.installedAssetVersion && !assetInfo.forceDownload) { 104 | console.log('Current Add-In version previously installed: Skipping Deployment and Registration'); 105 | } 106 | else { 107 | yield deploySharedAssets(); 108 | yield tryInstallAddIn(); 109 | console.log("Updating locally stored version number to: " + assetInfo.version); 110 | localStorage.installedAssetVersion = assetInfo.version; 111 | } 112 | yield startExcelService(); 113 | console.log('Excel Service Started'); 114 | } 115 | catch (err) { 116 | console.error(err); 117 | } 118 | // Technically there is a small window of time between when the UUID is 119 | // registered as an external application and when the service is ready to 120 | // receive commands. This edge-case will be best handled in the future 121 | // with the availability of plugins and services from the fin API 122 | function isServiceRunning() { 123 | return new Promise((resolve, reject) => { 124 | console.log("Performing check to see if Excel .NET Exe is running: " + excelServiceUuid); 125 | fin.desktop.System.getAllExternalApplications(extApps => { 126 | var excelServiceIndex = extApps.findIndex(extApp => extApp.uuid === excelServiceUuid); 127 | if (excelServiceIndex >= 0) { 128 | console.log("Excel .NET Exe uuid found in list of external applications."); 129 | resolve(true); 130 | } 131 | else { 132 | console.log("Excel .NET Exe uuid found in list of external applications."); 133 | resolve(false); 134 | } 135 | }); 136 | }); 137 | } 138 | function getAppAssetInfo() { 139 | return new Promise((resolve, reject) => { 140 | console.log("Getting app asset info for alias: " + excelAssetAlias); 141 | fin.desktop.System.getAppAssetInfo({ alias: excelAssetAlias }, resolve, reject); 142 | }); 143 | } 144 | function deploySharedAssets() { 145 | return new Promise((resolve, reject) => { 146 | console.log("Deploying Shared Assets."); 147 | fin.desktop.Application.getCurrent().getManifest(manifest => { 148 | let arguments = `-d "${installFolder}" -c ${manifest.runtime.version}`; 149 | console.log("Manifest retrieved: " + JSON.stringify(manifest)); 150 | console.log(`Launching external process. Alias: ${excelAssetAlias} target: ${servicePath} arguments: ${arguments}`); 151 | fin.desktop.System.launchExternalProcess({ 152 | alias: excelAssetAlias, 153 | target: servicePath, 154 | arguments: arguments, 155 | listener: result => { 156 | console.log(`Asset Deployment completed! Exit Code: ${result.exitCode}`); 157 | resolve(); 158 | } 159 | }, () => console.log('Deploying Shared Assets. Launch External Process executed.'), err => reject(err)); 160 | }); 161 | }); 162 | } 163 | function tryInstallAddIn() { 164 | return new Promise((resolve, reject) => { 165 | let path = `${installFolder}\\${servicePath}`; 166 | let arguments = `-i "${installFolder}"`; 167 | console.log(`Installing Excel Addin. Path: ${path} arguments: ${arguments}`); 168 | fin.desktop.System.launchExternalProcess({ 169 | path: path, 170 | arguments: arguments, 171 | listener: result => { 172 | if (result.exitCode === 0) { 173 | console.log('Add-In Installed'); 174 | } 175 | else { 176 | console.warn(`Installation failed. Exit code: ${result.exitCode}`); 177 | } 178 | resolve(); 179 | } 180 | }, () => console.log('Installing Add-In. Launch External Process executed.'), err => reject(err)); 181 | }); 182 | } 183 | function startExcelService() { 184 | return new Promise((resolve, reject) => { 185 | console.log("Starting Excel .NET Exe Service"); 186 | console.log(`Subscribing to: ${excelServiceEventTopic}`); 187 | let onMessageReceived; 188 | let connected = false; 189 | fin.desktop.InterApplicationBus.subscribe('*', excelServiceEventTopic, onMessageReceived = () => { 190 | console.log("Received message from .NET Exe Service on topic: " + excelServiceEventTopic + ". Unsubscribing now that we have received notification."); 191 | if (connected) { 192 | console.log("We have already recieved a message from the exe indicating connected. Resolve promise."); 193 | resolve(); 194 | return; 195 | } 196 | connected = true; 197 | fin.desktop.InterApplicationBus.unsubscribe('*', excelServiceEventTopic, onMessageReceived, () => { 198 | console.log("Unsubscribed from topic: " + excelServiceEventTopic); 199 | }, err => { 200 | console.log("Error while trying to unsubscribe from " + excelServiceEventTopic + " Error: ", err); 201 | }); 202 | // The channel provider should eventually move into the .NET app 203 | // but for now it only being used for signalling and providing provider version 204 | console.log("Creating Channel for client script to connect to: " + excelServiceUuid + " and providing a getVersion function."); 205 | fin.desktop.InterApplicationBus.Channel.create(excelServiceUuid).then(channel => { 206 | channel.register('getVersion', () => { 207 | return { 208 | providerVersion, buildVersion 209 | }; 210 | }); 211 | }); 212 | resolve(); 213 | }); 214 | console.log("Getting system details to get port."); 215 | chrome.desktop.getDetails(function (details) { 216 | let target = `${installFolder}\\${servicePath}`; 217 | let arguments = '-p ' + details.port; 218 | console.log(`Details retrieved. Launching external process. Target: ${target} Arguments: ${arguments} UUID: ${excelServiceUuid}`); 219 | fin.desktop.System.launchExternalProcess({ 220 | target: target, 221 | arguments: arguments, 222 | uuid: excelServiceUuid, 223 | }, process => { 224 | console.log('Service Launched: ' + process.uuid); 225 | }, error => { 226 | reject('Error starting Excel service'); 227 | }); 228 | }); 229 | }); 230 | } 231 | })); 232 | //# sourceMappingURL=provider.js.map 233 | 234 | /***/ }) 235 | /******/ ]); -------------------------------------------------------------------------------- /client/src/ExcelApplication.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ExcelApplication = void 0; 13 | const RpcDispatcher_1 = require("./RpcDispatcher"); 14 | const ExcelWorkbook_1 = require("./ExcelWorkbook"); 15 | const ExcelWorksheet_1 = require("./ExcelWorksheet"); 16 | class ExcelApplication extends RpcDispatcher_1.RpcDispatcher { 17 | constructor(connectionUuid, logger) { 18 | super(logger); 19 | this.workbooks = {}; 20 | this.version = { clientVersion: "[EXCEL_CLIENT_VERSION]", buildVersion: "[EXCEL_BUILD_VERSION]" }; 21 | this.loggerName = "ExcelApplication"; 22 | this.processExcelEvent = (data, uuid) => { 23 | var eventType = data.event; 24 | this.logger.debug(this.loggerName + `: ExcelApplication.processExcelEvent received from ${uuid} with data ${JSON.stringify(data)}.`); 25 | var workbook = this.workbooks[data.workbookName]; 26 | var worksheets = workbook && workbook.worksheets; 27 | var worksheet = worksheets && worksheets[data.sheetName]; 28 | switch (eventType) { 29 | case "connected": 30 | this.logger.debug(this.loggerName + ": Received Excel Connected Event."); 31 | this.connected = true; 32 | this.dispatchEvent(eventType); 33 | break; 34 | case "sheetChanged": 35 | this.logger.debug(this.loggerName + ": Received Sheet Changed Event."); 36 | if (worksheet) { 37 | this.logger.debug(this.loggerName + ": Worksheet Exists, dispatching event."); 38 | worksheet.dispatchEvent(eventType, { data: data }); 39 | } 40 | break; 41 | case "sheetRenamed": 42 | var oldWorksheetName = data.oldSheetName; 43 | var newWorksheetName = data.sheetName; 44 | worksheet = worksheets && worksheets[oldWorksheetName]; 45 | this.logger.debug(this.loggerName + ": Sheet renamed: Old name:" + oldWorksheetName + " New name: " + newWorksheetName); 46 | if (worksheet) { 47 | delete worksheets[oldWorksheetName]; 48 | worksheet.worksheetName = newWorksheetName; 49 | worksheets[worksheet.worksheetName] = worksheet; 50 | workbook.dispatchEvent(eventType, { worksheet: worksheet.toObject(), oldWorksheetName: oldWorksheetName }); 51 | } 52 | break; 53 | case "selectionChanged": 54 | this.logger.debug(this.loggerName + ": Selection Changed."); 55 | if (worksheet) { 56 | worksheet.dispatchEvent(eventType, { data: data }); 57 | } 58 | break; 59 | case "sheetActivated": 60 | case "sheetDeactivated": 61 | this.logger.debug(this.loggerName + ": Sheet Deactivated"); 62 | if (worksheet) { 63 | worksheet.dispatchEvent(eventType); 64 | } 65 | break; 66 | case "workbookDeactivated": 67 | case "workbookActivated": 68 | if (workbook) { 69 | workbook.dispatchEvent(eventType); 70 | } 71 | break; 72 | case "sheetAdded": 73 | var newWorksheet = worksheet || new ExcelWorksheet_1.ExcelWorksheet(data.sheetName, workbook); 74 | worksheets[newWorksheet.worksheetName] = newWorksheet; 75 | workbook.dispatchEvent(eventType, { worksheet: newWorksheet.toObject() }); 76 | break; 77 | case "sheetRemoved": 78 | delete workbook.worksheets[worksheet.worksheetName]; 79 | worksheet.dispatchEvent(eventType); 80 | workbook.dispatchEvent(eventType, { worksheet: worksheet.toObject() }); 81 | break; 82 | case "workbookAdded": 83 | case "workbookOpened": 84 | var newWorkbook = workbook || new ExcelWorkbook_1.ExcelWorkbook(this, data.workbookName); 85 | this.workbooks[newWorkbook.workbookName] = newWorkbook; 86 | this.dispatchEvent(eventType, { workbook: newWorkbook.toObject() }); 87 | break; 88 | case "workbookClosed": 89 | delete this.workbooks[workbook.workbookName]; 90 | workbook.dispatchEvent(eventType); 91 | this.dispatchEvent(eventType, { workbook: workbook.toObject() }); 92 | break; 93 | case "workbookSaved": 94 | var oldWorkbookName = data.oldWorkbookName; 95 | var newWorkbookName = data.workbookName; 96 | workbook = this.workbooks[oldWorkbookName]; 97 | if (workbook) { 98 | delete this.workbooks[oldWorkbookName]; 99 | workbook.workbookName = newWorkbookName; 100 | this.workbooks[workbook.workbookName] = workbook; 101 | workbook.refreshObject(); 102 | this.dispatchEvent(eventType, { workbook: workbook.toObject(), oldWorkbookName: oldWorkbookName }); 103 | } 104 | break; 105 | case "afterCalculation": 106 | default: 107 | this.dispatchEvent(eventType); 108 | break; 109 | } 110 | }; 111 | this.processExcelResult = (result) => { 112 | var callbackData = {}; 113 | var executor = RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 114 | delete RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 115 | this.logger.debug(this.loggerName + `: Received an Excel result with messageId ${result.messageId}.`); 116 | //TODO: Somehow received a result not in the callback map 117 | if (!executor) { 118 | this.logger.debug(this.loggerName + `: Received an Excel result for messageId ${result.messageId} that doesnt have an associated promise executor.`); 119 | return; 120 | } 121 | if (result.error) { 122 | this.logger.debug(this.loggerName + `: Received an Excel result with error ${result.error}.`); 123 | executor.reject(result.error); 124 | return; 125 | } 126 | var workbook = this.workbooks[result.target.workbookName]; 127 | var worksheets = workbook && workbook.worksheets; 128 | var worksheet = worksheets && worksheets[result.target.sheetName]; 129 | var resultData = result.data; 130 | switch (result.action) { 131 | case "getWorkbooks": 132 | var workbookNames = resultData; 133 | var oldworkbooks = this.workbooks; 134 | this.workbooks = {}; 135 | workbookNames.forEach(workbookName => { 136 | this.workbooks[workbookName] = oldworkbooks[workbookName] || new ExcelWorkbook_1.ExcelWorkbook(this, workbookName); 137 | }); 138 | callbackData = workbookNames.map(workbookName => this.workbooks[workbookName].toObject()); 139 | break; 140 | case "getWorksheets": 141 | var worksheetNames = resultData; 142 | var oldworksheets = worksheets; 143 | workbook.worksheets = {}; 144 | worksheetNames.forEach(worksheetName => { 145 | workbook.worksheets[worksheetName] = oldworksheets[worksheetName] || new ExcelWorksheet_1.ExcelWorksheet(worksheetName, workbook); 146 | }); 147 | callbackData = worksheetNames.map(worksheetName => workbook.worksheets[worksheetName].toObject()); 148 | break; 149 | case "addWorkbook": 150 | case "openWorkbook": 151 | var newWorkbookName = resultData; 152 | var newWorkbook = this.workbooks[newWorkbookName] || new ExcelWorkbook_1.ExcelWorkbook(this, newWorkbookName); 153 | this.workbooks[newWorkbook.workbookName] = newWorkbook; 154 | callbackData = newWorkbook.toObject(); 155 | break; 156 | case "addSheet": 157 | var newWorksheetName = resultData; 158 | var newWorksheet = workbook[newWorkbookName] || new ExcelWorksheet_1.ExcelWorksheet(newWorksheetName, workbook); 159 | worksheets[newWorksheet.worksheetName] = newWorksheet; 160 | callbackData = newWorksheet.toObject(); 161 | break; 162 | default: 163 | callbackData = resultData; 164 | break; 165 | } 166 | this.logger.debug(this.loggerName + `: Calling resolver for Excel message ${result.messageId} with data ${callbackData}.`); 167 | executor.resolve(callbackData); 168 | }; 169 | this.connectionUuid = connectionUuid; 170 | this.connected = false; 171 | } 172 | init() { 173 | return __awaiter(this, void 0, void 0, function* () { 174 | this.logger.info(this.loggerName + ": Init called."); 175 | if (!this.initialized) { 176 | this.logger.info(this.loggerName + ": Not initialised...Initialising."); 177 | yield this.subscribeToExcelMessages(); 178 | yield this.monitorDisconnect(); 179 | this.initialized = true; 180 | this.logger.info(this.loggerName + ": initialised."); 181 | } 182 | return; 183 | }); 184 | } 185 | release() { 186 | return __awaiter(this, void 0, void 0, function* () { 187 | this.logger.info(this.loggerName + ": Release called."); 188 | if (this.initialized) { 189 | this.logger.info(this.loggerName + ": Calling unsubscribe as we are currently intialised."); 190 | yield this.unsubscribeToExcelMessages(); 191 | //TODO: Provide external means to stop monitoring disconnect 192 | } 193 | return; 194 | }); 195 | } 196 | subscribeToExcelMessages() { 197 | return Promise.all([ 198 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(this.connectionUuid, "excelEvent", this.processExcelEvent, resolve)), 199 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(this.connectionUuid, "excelResult", this.processExcelResult, resolve)) 200 | ]); 201 | } 202 | unsubscribeToExcelMessages() { 203 | return Promise.all([ 204 | new Promise(resolve => fin.desktop.InterApplicationBus.unsubscribe(this.connectionUuid, "excelEvent", this.processExcelEvent, resolve)), 205 | new Promise(resolve => fin.desktop.InterApplicationBus.unsubscribe(this.connectionUuid, "excelResult", this.processExcelResult, resolve)) 206 | ]); 207 | } 208 | monitorDisconnect() { 209 | return new Promise((resolve, reject) => { 210 | var excelApplicationConnection = fin.desktop.ExternalApplication.wrap(this.connectionUuid); 211 | var onDisconnect; 212 | excelApplicationConnection.addEventListener('disconnected', onDisconnect = () => { 213 | excelApplicationConnection.removeEventListener('disconnected', onDisconnect); 214 | this.connected = false; 215 | this.dispatchEvent('disconnected'); 216 | }, resolve, reject); 217 | }); 218 | } 219 | run(callback) { 220 | var runPromise = this.connected ? Promise.resolve() : new Promise(resolve => { 221 | var connectedCallback = () => { 222 | this.removeEventListener('connected', connectedCallback); 223 | resolve(); 224 | }; 225 | if (this.connectionUuid !== undefined) { 226 | this.addEventListener('connected', connectedCallback); 227 | } 228 | fin.desktop.System.launchExternalProcess({ 229 | target: 'excel', 230 | uuid: this.connectionUuid 231 | }); 232 | }); 233 | return this.applyCallbackToPromise(runPromise, callback); 234 | } 235 | getWorkbooks(callback) { 236 | return this.invokeExcelCall("getWorkbooks", null, callback); 237 | } 238 | getWorkbookByName(name) { 239 | return this.workbooks[name]; 240 | } 241 | addWorkbook(callback) { 242 | return this.invokeExcelCall("addWorkbook", null, callback); 243 | } 244 | openWorkbook(path, callback) { 245 | return this.invokeExcelCall("openWorkbook", { path: path }, callback); 246 | } 247 | getConnectionStatus(callback) { 248 | return this.applyCallbackToPromise(Promise.resolve(this.connected), callback); 249 | } 250 | getCalculationMode(callback) { 251 | return this.invokeExcelCall("getCalculationMode", null, callback); 252 | } 253 | calculateAll(callback) { 254 | return this.invokeExcelCall("calculateFull", null, callback); 255 | } 256 | toObject() { 257 | return this.objectInstance || (this.objectInstance = { 258 | connectionUuid: this.connectionUuid, 259 | version: this.version, 260 | addEventListener: this.addEventListener.bind(this), 261 | removeEventListener: this.removeEventListener.bind(this), 262 | addWorkbook: this.addWorkbook.bind(this), 263 | calculateAll: this.calculateAll.bind(this), 264 | getCalculationMode: this.getCalculationMode.bind(this), 265 | getConnectionStatus: this.getConnectionStatus.bind(this), 266 | getWorkbookByName: name => this.getWorkbookByName(name).toObject(), 267 | getWorkbooks: this.getWorkbooks.bind(this), 268 | openWorkbook: this.openWorkbook.bind(this), 269 | run: this.run.bind(this) 270 | }); 271 | } 272 | } 273 | exports.ExcelApplication = ExcelApplication; 274 | ExcelApplication.defaultInstance = undefined; 275 | //# sourceMappingURL=ExcelApplication.js.map -------------------------------------------------------------------------------- /client/src/ExcelApi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ExcelService = void 0; 13 | const RpcDispatcher_1 = require("./RpcDispatcher"); 14 | const ExcelApplication_1 = require("./ExcelApplication"); 15 | const ExcelRtd_1 = require("./ExcelRtd"); 16 | const DefaultLogger_1 = require("./DefaultLogger"); 17 | const NoOpLogger_1 = require("./NoOpLogger"); 18 | const excelServiceUuid = "886834D1-4651-4872-996C-7B2578E953B9"; 19 | class ExcelService extends RpcDispatcher_1.RpcDispatcher { 20 | constructor() { 21 | super(new NoOpLogger_1.NoOpLogger()); 22 | this.defaultApplicationUuid = undefined; 23 | this.defaultApplicationObj = undefined; 24 | this.logger = new NoOpLogger_1.NoOpLogger(); 25 | this.loggerName = "ExcelService"; 26 | this.applications = {}; 27 | this.version = { 28 | buildVersion: "0.0.0.0", "providerVersion": "0.0.0" 29 | }; 30 | this.processExcelServiceEvent = (data) => __awaiter(this, void 0, void 0, function* () { 31 | var eventType = data.event; 32 | this.logger.debug(this.loggerName + ": Received event for data..."); 33 | this.logger.debug(JSON.stringify(data)); 34 | const mainChannelCreated = yield this.mainChannelCreated; 35 | this.logger.debug(this.loggerName + `: Main Channel created... ${mainChannelCreated}`); 36 | var eventData; 37 | switch (data.event) { 38 | case "started": 39 | break; 40 | case "registrationRollCall": 41 | if (this.initialized) { 42 | this.logger.debug(this.loggerName + ": Initialized, about to register window instance."); 43 | this.registerWindowInstance(); 44 | } 45 | else { 46 | this.logger.debug(this.loggerName + ": NOT initialized. Window will not be registered."); 47 | } 48 | break; 49 | case "excelConnected": 50 | yield this.processExcelConnectedEvent(data); 51 | eventData = { connectionUuid: data.uuid }; 52 | break; 53 | case "excelDisconnected": 54 | yield this.processExcelDisconnectedEvent(data); 55 | eventData = { connectionUuid: data.uuid }; 56 | break; 57 | } 58 | this.dispatchEvent(eventType, eventData); 59 | }); 60 | this.processExcelServiceResult = (result) => __awaiter(this, void 0, void 0, function* () { 61 | var executor = RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 62 | delete RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 63 | this.logger.debug(this.loggerName + `: Received an ExcelService result with messageId ${result.messageId}.`); 64 | //TODO: Somehow received a result not in the callback map 65 | if (!executor) { 66 | this.logger.debug(this.loggerName + `: Received an ExcelService result for messageId ${result.messageId} that doesnt have an associated promise executor.`); 67 | return; 68 | } 69 | if (result.error) { 70 | this.logger.debug(this.loggerName + `: Received a result with error ${result.error}.`); 71 | executor.reject(result.error); 72 | return; 73 | } 74 | // Internal processing 75 | switch (result.action) { 76 | case "getExcelInstances": 77 | yield this.processGetExcelInstancesResult(result.data); 78 | break; 79 | } 80 | this.logger.debug(this.loggerName + `: Calling resolver for message ${result.messageId} with data ${JSON.stringify(result.data)}.`); 81 | executor.resolve(result.data); 82 | }); 83 | this.registerWindowInstance = (callback) => { 84 | return this.invokeServiceCall("registerOpenfinWindow", { domain: document.domain }, callback); 85 | }; 86 | this.connectionUuid = excelServiceUuid; 87 | this.setMainChanelCreated(); 88 | } 89 | setMainChanelCreated() { 90 | this.mainChannelCreated = new Promise((resolve, reject) => { 91 | this.mainChannelResolve = resolve; 92 | this.mainChannelReject = reject; 93 | }); 94 | } 95 | init(logger) { 96 | return __awaiter(this, void 0, void 0, function* () { 97 | if (!this.initialized) { 98 | if (logger !== undefined) { 99 | if (typeof logger === "boolean") { 100 | if (logger) { 101 | let defaultLogger = new DefaultLogger_1.DefaultLogger("Excel Adapter"); 102 | this.logger = defaultLogger; 103 | } 104 | } 105 | else { 106 | let defaultLogger = new DefaultLogger_1.DefaultLogger(logger.name || "Excel Adapter"); 107 | this.logger = Object.assign({}, logger); 108 | if (this.logger.name === undefined) { 109 | this.logger.name === defaultLogger.name; 110 | } 111 | if (this.logger.trace === undefined) { 112 | this.logger.trace = defaultLogger.trace; 113 | } 114 | if (this.logger.debug === undefined) { 115 | this.logger.debug = defaultLogger.debug; 116 | } 117 | if (this.logger.info === undefined) { 118 | this.logger.info = defaultLogger.info; 119 | } 120 | if (this.logger.warn === undefined) { 121 | this.logger.warn = defaultLogger.warn; 122 | } 123 | if (this.logger.error === undefined) { 124 | this.logger.error = defaultLogger.error; 125 | } 126 | if (this.logger.fatal === undefined) { 127 | this.logger.fatal = defaultLogger.fatal; 128 | } 129 | } 130 | } 131 | this.logger.info(this.loggerName + ": Initialised called."); 132 | this.logger.debug(this.loggerName + ": Subscribing to Service Messages."); 133 | yield this.subscribeToServiceMessages(); 134 | this.logger.debug(this.loggerName + ": Ensuring monitor is not conencted before connecting to channel."); 135 | yield this.monitorDisconnect(); 136 | try { 137 | this.logger.debug(this.loggerName + ": Connecting to channel: " + excelServiceUuid); 138 | let providerChannel = yield fin.desktop.InterApplicationBus.Channel.connect(excelServiceUuid); 139 | this.logger.debug(this.loggerName + ": Channel connected: " + excelServiceUuid); 140 | this.logger.debug(this.loggerName + ": Flagging that mainChannelIs connected: " + excelServiceUuid); 141 | this.logger.debug(this.loggerName + ": Setting service provider version by requesting it from channel."); 142 | this.version = yield providerChannel.dispatch('getVersion'); 143 | this.logger.debug(this.loggerName + `: Service provider version set to: ${JSON.stringify(this.version)}.`); 144 | } 145 | catch (err) { 146 | let errorMessage; 147 | if (err !== undefined && err.message !== undefined) { 148 | errorMessage = "Error: " + err.message; 149 | } 150 | this.mainChannelReject(`${this.loggerName}: Error connecting or fetching version to/from provider. The version of the provider is likely older than the script version. ${errorMessage}`); 151 | this.logger.warn(this.loggerName + ": Error connecting or fetching version to/from provider. The version of the provider is likely older than the script version.", errorMessage); 152 | } 153 | this.initialized = true; 154 | this.mainChannelResolve(true); 155 | yield this.registerWindowInstance(); 156 | yield this.getExcelInstances(); 157 | } 158 | return; 159 | }); 160 | } 161 | subscribeToServiceMessages() { 162 | return Promise.all([ 163 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(excelServiceUuid, "excelServiceEvent", this.processExcelServiceEvent, resolve)), 164 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(excelServiceUuid, "excelServiceCallResult", this.processExcelServiceResult, resolve)) 165 | ]); 166 | } 167 | monitorDisconnect() { 168 | return new Promise((resolve, reject) => { 169 | var excelServiceConnection = fin.desktop.ExternalApplication.wrap(excelServiceUuid); 170 | var onDisconnect; 171 | excelServiceConnection.addEventListener("disconnected", onDisconnect = () => { 172 | excelServiceConnection.removeEventListener("disconnected", onDisconnect); 173 | this.dispatchEvent("stopped"); 174 | }, resolve, reject); 175 | }); 176 | } 177 | configureDefaultApplication() { 178 | return __awaiter(this, void 0, void 0, function* () { 179 | this.logger.debug(this.loggerName + ": Configuring Default Excel Application."); 180 | var defaultAppObjUuid = this.defaultApplicationObj && this.defaultApplicationObj.connectionUuid; 181 | var defaultAppEntry = this.applications[defaultAppObjUuid]; 182 | var defaultAppObjConnected = defaultAppEntry ? defaultAppEntry.connected : false; 183 | if (defaultAppObjConnected) { 184 | this.logger.debug(this.loggerName + ": Already connected to Default Excel Application: " + defaultAppObjUuid); 185 | return; 186 | } 187 | else { 188 | this.logger.debug(this.loggerName + ": Default Excel Application: " + defaultAppObjUuid + " not connected."); 189 | } 190 | this.logger.debug(this.loggerName + ": As Default Excel Application not connected checking for existing connected instance."); 191 | var connectedAppUuid = Object.keys(this.applications).find(appUuid => this.applications[appUuid].connected); 192 | if (connectedAppUuid) { 193 | this.logger.debug(this.loggerName + ": Found connected Excel Application: " + connectedAppUuid + " setting it as default instance."); 194 | delete this.applications[defaultAppObjUuid]; 195 | this.defaultApplicationObj = this.applications[connectedAppUuid].toObject(); 196 | return; 197 | } 198 | if (defaultAppEntry === undefined) { 199 | var disconnectedAppUuid = fin.desktop.getUuid(); 200 | this.logger.debug(this.loggerName + ": No default Excel Application. Creating one with id: " + disconnectedAppUuid + " and setting it as default instance."); 201 | var disconnectedApp = new ExcelApplication_1.ExcelApplication(disconnectedAppUuid, this.logger); 202 | yield disconnectedApp.init(); 203 | this.applications[disconnectedAppUuid] = disconnectedApp; 204 | this.defaultApplicationObj = disconnectedApp.toObject(); 205 | this.logger.debug(this.loggerName + ": Default Excel Application with id: " + disconnectedAppUuid + " set as default instance."); 206 | } 207 | }); 208 | } 209 | // Internal Event Handlers 210 | processExcelConnectedEvent(data) { 211 | return __awaiter(this, void 0, void 0, function* () { 212 | var applicationInstance = this.applications[data.uuid] || new ExcelApplication_1.ExcelApplication(data.uuid, this.logger); 213 | yield applicationInstance.init(); 214 | this.applications[data.uuid] = applicationInstance; 215 | // Synthetically raise connected event 216 | applicationInstance.processExcelEvent({ event: "connected" }, data.uuid); 217 | yield this.configureDefaultApplication(); 218 | return; 219 | }); 220 | } 221 | processExcelDisconnectedEvent(data) { 222 | return __awaiter(this, void 0, void 0, function* () { 223 | var applicationInstance = this.applications[data.uuid]; 224 | if (applicationInstance === undefined) { 225 | return; 226 | } 227 | delete this.applications[data.uuid]; 228 | yield this.configureDefaultApplication(); 229 | yield applicationInstance.release(); 230 | }); 231 | } 232 | // Internal API Handlers 233 | processGetExcelInstancesResult(connectionUuids) { 234 | return __awaiter(this, void 0, void 0, function* () { 235 | var existingInstances = this.applications; 236 | this.applications = {}; 237 | yield Promise.all(connectionUuids.map((connectionUuid) => __awaiter(this, void 0, void 0, function* () { 238 | var existingInstance = existingInstances[connectionUuid]; 239 | if (existingInstance === undefined) { 240 | // Assume that since the ExcelService is aware of the instance it, 241 | // is connected and to simulate the the connection event 242 | yield this.processExcelServiceEvent({ event: "excelConnected", uuid: connectionUuid }); 243 | } 244 | else { 245 | this.applications[connectionUuid] = existingInstance; 246 | } 247 | return; 248 | }))); 249 | yield this.configureDefaultApplication(); 250 | }); 251 | } 252 | // API Calls 253 | install(callback) { 254 | return this.invokeServiceCall("install", null, callback); 255 | } 256 | getInstallationStatus(callback) { 257 | return this.invokeServiceCall("getInstallationStatus", null, callback); 258 | } 259 | getExcelInstances(callback) { 260 | return this.invokeServiceCall("getExcelInstances", null, callback); 261 | } 262 | createRtd(providerName, heartbeatIntervalInMilliseconds = 10000) { 263 | return ExcelRtd_1.ExcelRtd.create(providerName, this.logger, heartbeatIntervalInMilliseconds); 264 | } 265 | toObject() { 266 | return {}; 267 | } 268 | } 269 | exports.ExcelService = ExcelService; 270 | ExcelService.instance = new ExcelService(); 271 | //# sourceMappingURL=ExcelApi.js.map -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by haseebriaz on 14/05/15. 3 | */ 4 | 5 | // fin.desktop.Excel API Injected via preload script 6 | 7 | fin.desktop.main(function () { 8 | // Initialization and startup logic for Excel is at the very bottom 9 | 10 | var view = {}; 11 | [].slice.call(document.querySelectorAll('[id]')).forEach(element => view[element.id] = element); 12 | 13 | var displayContainers = new Map([ 14 | [view.noConnectionContainer, { windowHeight: 195 }], 15 | [view.noWorkbooksContainer, { windowHeight: 195 }], 16 | [view.workbooksContainer, { windowHeight: 830 }] 17 | ]); 18 | 19 | var rowLength = 27; 20 | var columnLength = 12; 21 | 22 | var excelInstance; 23 | var currentWorksheet = null; 24 | var currentWorkbook = null; 25 | var currentCell = null; 26 | 27 | // Initialization 28 | 29 | function initializeTable() { 30 | 31 | for (var i = 0; i <= rowLength; i++) { 32 | var isHeaderRow = (i === 0); 33 | var rowClass = isHeaderRow ? "cellHeader" : "cell"; 34 | 35 | var row = createRow(i, columnLength, rowClass, isHeaderRow); 36 | var rowTarget = isHeaderRow ? view.worksheetHeader : view.worksheetBody; 37 | rowTarget.appendChild(row); 38 | } 39 | 40 | function createRow(rowNumber, columnCount, rowClassName, isHeaderRow) { 41 | var row = document.createElement("tr"); 42 | 43 | for (var i = 0; i <= columnCount; i++) { 44 | var isHeaderCell = (i === 0); 45 | var cellClass = isHeaderCell ? "rowNumber" : rowClassName; 46 | var editable = !(isHeaderRow || isHeaderCell); 47 | 48 | var cellContent = 49 | (isHeaderCell && !isHeaderRow) ? rowNumber.toString() : 50 | (!isHeaderCell && isHeaderRow) ? String.fromCharCode(64 + i) : // Only support one letter-columns for now 51 | undefined; 52 | 53 | var cell = createCell(cellClass, cellContent, editable); 54 | row.appendChild(cell); 55 | } 56 | 57 | return row; 58 | } 59 | 60 | function createCell(cellClassName, cellContent, editable) { 61 | 62 | var cell = document.createElement("td"); 63 | cell.className = cellClassName; 64 | 65 | if (cellContent !== undefined) { 66 | cell.innerText = cellContent; 67 | } 68 | 69 | if (editable) { 70 | 71 | cell.contentEditable = true; 72 | 73 | cell.addEventListener("keydown", onDataChange); 74 | cell.addEventListener("blur", onDataChange); 75 | cell.addEventListener("mousedown", onCellClicked); 76 | } 77 | 78 | return cell; 79 | } 80 | } 81 | 82 | function initializeUIEvents() { 83 | view.newWorkbookTab.addEventListener("click", function () { 84 | fin.desktop.Excel.addWorkbook(); 85 | }); 86 | 87 | view.openWorkbookTab.addEventListener("click", function () { 88 | view.dialogOverlay.style.visibility = "visible"; 89 | }); 90 | 91 | view.newWorksheetButton.addEventListener("click", function () { 92 | currentWorkbook.addWorksheet(); 93 | }); 94 | 95 | view.launchExcelLink.addEventListener("click", function () { 96 | connectToExcel(); 97 | }); 98 | 99 | view.newWorkbookLink.addEventListener("click", function () { 100 | fin.desktop.Excel.addWorkbook(); 101 | }); 102 | 103 | view.dialogOverlay.addEventListener("click", function (e) { 104 | if (e.target === view.dialogOverlay) { 105 | view.dialogOverlay.style.visibility = "hidden"; 106 | } else { 107 | e.stopPropagation(); 108 | } 109 | }); 110 | 111 | view.openWorkbookButton.addEventListener("click", function (e) { 112 | view.dialogOverlay.style.visibility = "hidden"; 113 | fin.desktop.Excel.openWorkbook(view.openWorkbookPath.value); 114 | }); 115 | 116 | window.addEventListener("keydown", function (event) { 117 | 118 | switch (event.keyCode) { 119 | 120 | case 78: // N 121 | if (event.ctrlKey) fin.desktop.Excel.addWorkbook(); 122 | break; 123 | case 37: // LEFT 124 | selectAdjacentCell('left'); 125 | break; 126 | case 38: // UP 127 | selectAdjacentCell('above'); 128 | break; 129 | case 39: // RIGHT 130 | selectAdjacentCell('right'); 131 | break; 132 | case 40: //DOWN 133 | selectAdjacentCell('below'); 134 | break; 135 | } 136 | }); 137 | 138 | } 139 | 140 | function initializeExcelEvents() { 141 | fin.desktop.ExcelService.addEventListener("excelConnected", onExcelConnected); 142 | fin.desktop.ExcelService.addEventListener("excelDisconnected", onExcelDisconnected); 143 | } 144 | 145 | // UI Functions 146 | 147 | function setDisplayContainer(containerToDisplay) { 148 | if (!displayContainers.has(containerToDisplay)) { 149 | return; 150 | } 151 | 152 | // Element is already showing 153 | if (containerToDisplay.style.display === "block") { 154 | return; 155 | } 156 | 157 | // Reset the display containers to their CSS definition (none) 158 | for (var container of displayContainers.keys()) { 159 | container.style.display = null; 160 | } 161 | 162 | containerToDisplay.style.display = "block"; 163 | 164 | var windowHeight = displayContainers.get(containerToDisplay).windowHeight; 165 | 166 | fin.desktop.Window.getCurrent().animate({ 167 | size: { 168 | height: windowHeight, 169 | duration: 500 170 | } 171 | }); 172 | } 173 | 174 | function setStatusLabel(text) { 175 | view.connectionStatus.innerText = text; 176 | } 177 | 178 | function addWorkbookTab(name) { 179 | var button = getWorkbookTab(name); 180 | button.addEventListener("click", onWorkbookTabClicked); 181 | view.workbookTabs.insertBefore(button, view.newWorkbookTab); 182 | } 183 | 184 | function getWorkbookTab(name) { 185 | var elementId = 'workbook-'.concat(name); 186 | var element = document.getElementById(elementId) || document.createElement('button'); 187 | 188 | element.id = elementId; 189 | element.className = 'workbookTab'; 190 | element.innerHTML = name; 191 | 192 | return element; 193 | } 194 | 195 | function selectWorkbook(workbook) { 196 | if (currentWorkbook) { 197 | 198 | var tab = getWorkbookTab(currentWorkbook.name); 199 | if (tab) tab.className = "workbookTab"; 200 | } 201 | 202 | getWorkbookTab(workbook.name).className = "workbookTabSelected"; 203 | currentWorkbook = workbook; 204 | currentWorkbook.getWorksheets(updateSheets); 205 | } 206 | 207 | function addWorksheetTab(worksheet) { 208 | var sheetsTabHolder = view.worksheetTabs; 209 | var button = getWorksheetTab(worksheet.name); 210 | button.addEventListener("click", onSheetButtonClicked); 211 | sheetsTabHolder.insertBefore(button, view.newWorksheetButton); 212 | 213 | worksheet.addEventListener("sheetChanged", onSheetChanged); 214 | worksheet.addEventListener("selectionChanged", onSelectionChanged); 215 | worksheet.addEventListener("sheetActivated", onSheetActivated); 216 | } 217 | 218 | function getWorksheetTab(name) { 219 | var elementId = 'worksheet-'.concat(name); 220 | var element = document.getElementById(elementId) || document.createElement('button'); 221 | 222 | element.id = elementId; 223 | element.className = 'tab'; 224 | element.innerText = name; 225 | 226 | return element; 227 | } 228 | 229 | function selectWorksheet(sheet) { 230 | 231 | if (currentWorksheet === sheet) { 232 | return; 233 | } 234 | 235 | if (currentWorksheet) { 236 | var tab = getWorksheetTab(currentWorksheet.name); 237 | if (tab) tab.className = "tab"; 238 | } 239 | getWorksheetTab(sheet.name).className = "tabSelected"; 240 | currentWorksheet = sheet; 241 | currentWorksheet.getCells("A1", columnLength, rowLength, updateData); 242 | } 243 | 244 | function updateSheets(worksheets) { 245 | 246 | var sheetsTabHolder = view.worksheetTabs; 247 | while (sheetsTabHolder.firstChild) { 248 | 249 | sheetsTabHolder.removeChild(sheetsTabHolder.firstChild); 250 | } 251 | 252 | sheetsTabHolder.appendChild(view.newWorksheetButton); 253 | for (var i = 0; i < worksheets.length; i++) { 254 | 255 | addWorksheetTab(worksheets[i]); 256 | } 257 | 258 | selectWorksheet(worksheets[0]); 259 | } 260 | 261 | function getAddress(td) { 262 | 263 | var column = td.cellIndex; 264 | var row = td.parentElement.rowIndex; 265 | var offset = view.worksheetHeader.children[0].children[column].innerText.toString() + row; 266 | return { column: column, row: row, offset: offset }; 267 | } 268 | 269 | function selectCell(cell, preventDefault) { 270 | 271 | if (currentCell) { 272 | 273 | currentCell.className = "cell"; 274 | updateCellNumberClass(currentCell, "rowNumber", "cellHeader"); 275 | } 276 | 277 | currentCell = cell; 278 | currentCell.className = "cellSelected"; 279 | view.formulaInput.innerText = "Formula: " + cell.title; 280 | cell.focus(); 281 | 282 | updateCellNumberClass(cell, "rowNumberSelected", "cellHeaderSelected"); 283 | 284 | var address = getAddress(currentCell); 285 | 286 | if (!preventDefault) { 287 | currentWorksheet.activateCell(address.offset); 288 | } 289 | } 290 | 291 | function selectAdjacentCell(direction) { 292 | if (!currentCell) return; 293 | var info = getAddress(currentCell); 294 | 295 | var cell; 296 | 297 | switch (direction) { 298 | case 'above': 299 | if (info.row <= 1) return; 300 | cell = view.worksheetBody.childNodes[info.row - 2].childNodes[info.column]; 301 | break; 302 | case 'below': 303 | if (info.row >= rowLength) return; 304 | cell = view.worksheetBody.childNodes[info.row].childNodes[info.column]; 305 | break; 306 | case 'left': 307 | if (info.column <= 1) return; 308 | cell = view.worksheetBody.childNodes[info.row - 1].childNodes[info.column - 1]; 309 | break; 310 | case 'right': 311 | if (info.column >= columnLength) return; 312 | cell = view.worksheetBody.childNodes[info.row - 1].childNodes[info.column + 1]; 313 | break; 314 | } 315 | 316 | if (cell) { 317 | selectCell(cell); 318 | } 319 | } 320 | 321 | function updateData(data) { 322 | 323 | var row = null; 324 | var currentData = null; 325 | 326 | for (var i = 0; i < data.length; i++) { 327 | 328 | row = view.worksheetBody.childNodes[i]; 329 | 330 | if (!row) { 331 | continue; 332 | } 333 | 334 | for (var j = 1; j < row.childNodes.length; j++) { 335 | 336 | currentData = data[i][j - 1]; 337 | updateCell(row.childNodes[j], currentData.value, currentData.formula); 338 | } 339 | } 340 | } 341 | 342 | function updateCell(cell, value, formula) { 343 | cell.innerText = value ? value : ""; 344 | cell.title = formula ? formula : ""; 345 | } 346 | 347 | function updateCellNumberClass(cell, className, headerClassName) { 348 | var row = cell.parentNode; 349 | var columnIndex = Array.prototype.indexOf.call(row.childNodes, cell); 350 | var rowIndex = Array.prototype.indexOf.call(row.parentNode.childNodes, cell.parentNode); 351 | view.worksheetBody.childNodes[rowIndex].childNodes[0].className = className; 352 | view.worksheetHeader.children[0].children[columnIndex].className = headerClassName; 353 | } 354 | 355 | // UI Event Handlers 356 | 357 | function onCellClicked(event) { 358 | selectCell(event.target); 359 | } 360 | 361 | function onSheetButtonClicked(event) { 362 | var sheet = currentWorkbook.getWorksheetByName(event.target.innerText); 363 | if (currentWorksheet === sheet) return; 364 | sheet.activate(); 365 | } 366 | 367 | function onWorkbookTabClicked(event) { 368 | var workbook = fin.desktop.Excel.getWorkbookByName(event.target.innerText); 369 | workbook.activate(); 370 | } 371 | 372 | function onDataChange(event) { 373 | 374 | if (event.keyCode === 13 || event.type === "blur") { 375 | 376 | var update = getAddress(event.target); 377 | update.value = event.target.innerText; 378 | 379 | currentWorksheet.setCells([[update.value]], update.offset); 380 | if (event.type === "keydown") { 381 | 382 | selectAdjacentCell('below'); 383 | event.preventDefault(); 384 | } 385 | } 386 | } 387 | 388 | // Excel Helper Functions 389 | 390 | function checkConnectionStatus() { 391 | fin.desktop.Excel.getConnectionStatus(connected => { 392 | if (connected) { 393 | console.log('Already connected to Excel, synthetically raising event.') 394 | onExcelConnected(fin.desktop.Excel); 395 | } else { 396 | setStatusLabel("Excel not connected"); 397 | setDisplayContainer(view.noConnectionContainer); 398 | } 399 | }); 400 | } 401 | 402 | function connectToExcel() { 403 | console.log('connectToExcel'); 404 | setStatusLabel("Connecting..."); 405 | 406 | return fin.desktop.Excel.run(); 407 | } 408 | 409 | // Excel Event Handlers 410 | 411 | function onExcelConnected(data) { 412 | if (excelInstance) { 413 | return; 414 | } 415 | 416 | console.log("Excel Connected: " + data.connectionUuid); 417 | setStatusLabel("Connected to Excel"); 418 | 419 | // Grab a snapshot of the current instance, it can change! 420 | excelInstance = fin.desktop.Excel; 421 | 422 | excelInstance.addEventListener("workbookAdded", onWorkbookAdded); 423 | excelInstance.addEventListener("workbookOpened", onWorkbookAdded); 424 | excelInstance.addEventListener("workbookClosed", onWorkbookRemoved); 425 | excelInstance.addEventListener("workbookSaved", onWorkbookSaved); 426 | 427 | fin.desktop.Excel.getWorkbooks(workbooks => { 428 | for (var i = 0; i < workbooks.length; i++) { 429 | addWorkbookTab(workbooks[i].name); 430 | workbooks[i].addEventListener("workbookActivated", onWorkbookActivated); 431 | workbooks[i].addEventListener("sheetAdded", onWorksheetAdded); 432 | workbooks[i].addEventListener("sheetRemoved", onWorksheetRemoved); 433 | workbooks[i].addEventListener("sheetRenamed", onWorksheetRenamed); 434 | } 435 | 436 | if (workbooks.length) { 437 | selectWorkbook(workbooks[0]); 438 | setDisplayContainer(view.workbooksContainer); 439 | } 440 | else { 441 | setDisplayContainer(view.noWorkbooksContainer); 442 | } 443 | }); 444 | } 445 | 446 | function onExcelDisconnected(data) { 447 | console.log("Excel Disconnected: " + data.connectionUuid); 448 | 449 | if (data.connectionUuid !== excelInstance.connectionUuid) { 450 | return; 451 | } 452 | 453 | excelInstance.removeEventListener("workbookAdded", onWorkbookAdded); 454 | excelInstance.removeEventListener("workbookOpened", onWorkbookAdded); 455 | excelInstance.removeEventListener("workbookClosed", onWorkbookRemoved); 456 | excelInstance.removeEventListener("workbookSaved", onWorkbookSaved); 457 | 458 | excelInstance = undefined; 459 | 460 | checkConnectionStatus(); 461 | } 462 | 463 | function onWorkbookAdded(event) { 464 | var workbook = event.workbook; 465 | 466 | workbook.addEventListener("workbookActivated", onWorkbookActivated); 467 | workbook.addEventListener("sheetAdded", onWorksheetAdded); 468 | workbook.addEventListener("sheetRemoved", onWorksheetRemoved); 469 | workbook.addEventListener("sheetRenamed", onWorksheetRenamed); 470 | 471 | addWorkbookTab(workbook.name); 472 | 473 | setDisplayContainer(view.workbooksContainer); 474 | } 475 | 476 | function onWorkbookRemoved(event) { 477 | currentWorkbook = null; 478 | var workbook = event.workbook; 479 | workbook.removeEventListener("workbookActivated", onWorkbookActivated); 480 | workbook.removeEventListener("sheetAdded", onWorksheetAdded); 481 | workbook.removeEventListener("sheetRemoved", onWorksheetRemoved); 482 | workbook.removeEventListener("sheetRenamed", onWorksheetRenamed); 483 | 484 | view.workbookTabs.removeChild(getWorkbookTab(workbook.name)); 485 | 486 | if (view.workbookTabs.children.length < 3) { 487 | setDisplayContainer(view.noWorkbooksContainer); 488 | } 489 | } 490 | 491 | function onWorkbookActivated(event) { 492 | selectWorkbook(event.target); 493 | } 494 | 495 | function onWorkbookSaved(event) { 496 | var workbook = event.workbook; 497 | var oldWorkbookName = event.oldWorkbookName; 498 | 499 | var button = getWorkbookTab(oldWorkbookName); 500 | 501 | button.id = workbook.name; 502 | button.innerText = workbook.name; 503 | } 504 | 505 | function onWorksheetAdded(event) { 506 | addWorksheetTab(event.worksheet); 507 | } 508 | 509 | function onWorksheetRemoved(event) { 510 | var worksheet = event.worksheet; 511 | 512 | if (worksheet.workbook === currentWorkbook) { 513 | worksheet.removeEventListener("sheetChanged", onSheetChanged); 514 | worksheet.removeEventListener("selectionChanged", onSelectionChanged); 515 | worksheet.removeEventListener("sheetActivated", onSheetActivated); 516 | view.worksheetTabs.removeChild(getWorksheetTab(worksheet.name)); 517 | currentWorksheet = null; 518 | } 519 | } 520 | 521 | function onSheetActivated(event) { 522 | selectWorksheet(event.target); 523 | } 524 | 525 | function onWorksheetRenamed(event) { 526 | var worksheet = event.worksheet; 527 | var oldWorksheetName = event.oldWorksheetName; 528 | 529 | var button = getWorksheetTab(oldWorksheetName); 530 | button.id = worksheet.name; 531 | button.innerText = worksheet.name; 532 | } 533 | 534 | function onSelectionChanged(event) { 535 | var cell = view.worksheetBody.children[event.data.row - 1].children[event.data.column]; 536 | selectCell(cell, true); 537 | } 538 | 539 | function onSheetChanged(event) { 540 | var cell = view.worksheetBody.children[event.data.row - 1].children[event.data.column]; 541 | updateCell(cell, event.data.value, event.data.formula); 542 | } 543 | 544 | // Main App Start 545 | 546 | initializeTable(); 547 | initializeUIEvents(); 548 | initializeExcelEvents(); 549 | 550 | fin.desktop.ExcelService.init() 551 | .then(checkConnectionStatus) 552 | .catch(err => console.error(err)); 553 | 554 | fin.desktop.System.getEnvironmentVariable("userprofile", profilePath => { 555 | view.openWorkbookPath.value = profilePath + "\\Documents\\"; 556 | }); 557 | }); -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # OpenFin Service API Documentation 2 | 3 | The Excel API is composition-based object model. Where Excel is the top most level that has workbooks that have worksheets and worksheets have cells. 4 | To use the API, you will need to include `ExcelAPI.js` in your project and it will extend Openfin API with Excel API included. 5 | Once included, you will be able to use following API calls. 6 | 7 | ## fin.desktop.ExcelService 8 | 9 | Represents the helper service which manages OpenFin connections to running instances of Excel. 10 | 11 | ### Properties 12 | 13 | ```javascript 14 | connected: Boolean // indicates that OpenFin is connected to the helper service 15 | initialized: Boolean // indicates that the current window is subscribed to Excel service events 16 | ``` 17 | 18 | ### Functions 19 | 20 | ```javascript 21 | /* 22 | init(); 23 | Returns a promise which resolves when the Excel helper service is running and initialized. 24 | */ 25 | 26 | await fin.desktop.ExcelService.init(); 27 | ``` 28 | 29 | ## fin.desktop.Excel: 30 | 31 | Represents a single instance of an Excel application. 32 | 33 | ### Functions 34 | 35 | ```javascript 36 | 37 | /* 38 | getWorkbooks(); 39 | Returns a promise which resolves the currently opened workbooks from Excel. 40 | */ 41 | 42 | var workbooks = await fin.desktop.Excel.getWorkbooks(); 43 | 44 | /* 45 | addWorkbook(); 46 | Asynchronously creates a new workbook in Excel and returns a promise which resolves the newly added workbook. 47 | */ 48 | 49 | var workbook = await fin.desktop.Excel.addWorkbook(); 50 | 51 | /* 52 | openWorkbook(path); 53 | Asynchronously opens workbook from the specified path and returns a promise which resolves the opened workbook. 54 | */ 55 | 56 | var workbook = await fin.desktop.Excel.openWorkbook(path); 57 | 58 | /* 59 | getWorkbookByName(name); 60 | Synchronously returns workbook object that represents the workbook with supplied name. 61 | 62 | */ 63 | 64 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 65 | 66 | /* 67 | getConnectionStatus(); 68 | Returns a promise which resolves the connection status of the current Excel application instance. 69 | */ 70 | 71 | var isConnected = await fin.desktop.Excel.getConnectionStatus(); 72 | 73 | /* 74 | getCalculationMode(); 75 | Returns a promise which resolves the calculation mode information. 76 | */ 77 | 78 | var info = await fin.desktop.Excel.getCalculationMode(); 79 | 80 | /* 81 | calculateAll(); 82 | Asynchronously forces calculation on all sheets 83 | */ 84 | 85 | await fin.desktop.Excel.calculateAll(); 86 | 87 | /* 88 | addEventListener(type, listener); 89 | Adds event handler to handle events from Excel 90 | */ 91 | 92 | fin.desktop.Excel.addEventListener("workbookAdded", function(event){...}) 93 | 94 | /* 95 | removeEventListener(type, listener); 96 | removes an attached event handler from Excel 97 | */ 98 | 99 | removeEventListener("workbookAdded", handler); 100 | ``` 101 | 102 | ### Events 103 | 104 | ```javascript 105 | {type: "connected"}; 106 | // is fired when excel connects to Openfin. 107 | //Example: 108 | fin.desktop.Excel.addEventListener("connected", function(){ console.log("Connected to Excel"); }) 109 | 110 | {type: "workbookAdded", workbook: ExcelWorkbook}; 111 | //is fired when a new workbook is added in excel (this includes adding workbooks using API). 112 | //Example: 113 | fin.desktop.Excel.addEventListener("workbookAdded", 114 | function(event){ 115 | console.log("New workbook added; Name:", event.workbook.name); 116 | }); 117 | 118 | {type: "workbookClosed", workbook: ExcelWorkbook}; 119 | //is fired when a workbook is closed. 120 | //Example: 121 | fin.desktop.Excel.addEventListener("workbookClosed", 122 | function(event){ 123 | console.log("Workbook closed; Name:", event.workbook.name); 124 | }); 125 | 126 | {type: "afterCalculation"}; 127 | //is fired when calculation is complete on any sheet. 128 | //Example: 129 | fin.desktop.Excel.addEventListener("afterCalculation", 130 | function(event){ 131 | console.log("calculation is complete."; 132 | }); 133 | ``` 134 | 135 | ## fin.desktop.ExcelWorkbook: 136 | 137 | Represents an Excel workbook. 138 | 139 | Note: New workbooks are not supposed to be created using new or `Object.create()`. 140 | Workbook objects can only be retrieved using API calls like `fin.desktop.Excel.getWorkbooks()`, `fin.desktop.Excel.getWorkbookByName()`, and `fin.desktop.Excel.addWorkbook()`, etc. 141 | 142 | ### Properties 143 | 144 | ``` 145 | name: String // name of the workbook that the object represents. 146 | ``` 147 | 148 | ### Functions 149 | 150 | ```javascript 151 | /* 152 | getWorksheets(); 153 | Returns a promise which resolves an array of worksheets in the current workbook. 154 | */ 155 | 156 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 157 | var worksheets = await workbook.getWorksheets(); 158 | 159 | /* 160 | getWorksheetByName(name); 161 | Synchronously returns worksheet object that represents the worksheet with supplied name. 162 | */ 163 | 164 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 165 | var sheet = workbook.getWorksheetByName("sheet1"); 166 | 167 | /* 168 | addWorksheet(); 169 | Asynchronously creates a new worksheet in the workbook and returns a promise which resolves the newly added worksheet. 170 | */ 171 | 172 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 173 | var worksheet = await workbook.addWorksheet(); 174 | 175 | /* 176 | activate(); 177 | Returns a promise which resolves when the workbook is activated 178 | */ 179 | 180 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 181 | await workbook.activate(); 182 | 183 | /* 184 | save(); 185 | Asynchronously saves changes to the current workbook and returns a promise which resolves when the operation is complete. 186 | */ 187 | 188 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 189 | await workbook.save(); 190 | 191 | /* 192 | close(); 193 | Asynchronously closes the current workbook and returns a promise which resolves when the operation is complete. 194 | */ 195 | 196 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 197 | await workbook.close(); 198 | ``` 199 | 200 | ### Events 201 | 202 | ```javascript 203 | {type: "sheetAdded", target: ExcelWorkbook, worksheet: ExcelWorksheet}; 204 | //fired when a new sheet is added to the workbook 205 | //Example: 206 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 207 | workbook.addEventListener("sheetAdded", 208 | function(event){ 209 | console.log("New sheet", event.worksheet.name, "was added to the workbook", event.worksheet.workbook.name) 210 | }); 211 | 212 | {type: "sheetRemoved", target: ExcelWorkbook, worksheet: ExcelWorksheet}; 213 | //fired when a sheet is closed/removed 214 | //Example: 215 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 216 | workbook.addEventListener("sheetRemoved", 217 | function(event){ 218 | console.log("Sheet", event.worksheet.name, "was removed from workbook", event.worksheet.workbook.name) 219 | }); 220 | 221 | {type: "workbookActivated", target: ExcelWorkbook}; 222 | //fired when a workbook is activated/focused 223 | //Example: 224 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 225 | workbook.addEventListener("workbookActivated", 226 | function(event){ 227 | console.log("Workbook", event.target.name, "was activated"); 228 | }); 229 | 230 | {type: "workbookDeactivated", target: ExcelWorkbook}; 231 | //fired when a workbook is deactivated/blurred 232 | //Example: 233 | var workbook = fin.desktop.Excel.getWorkbookByName("Workbook1"); 234 | workbook.addEventListener("workbookDeactivated", 235 | function(event){ 236 | console.log("Workbook", event.target.name, "was deactivated"); 237 | }); 238 | 239 | ``` 240 | 241 | ## fin.desktop.ExcelWorksheet: 242 | 243 | Represents a worksheet in Excel. 244 | Note: New sheets are not supposed to be created using `new` or `Object.create()`. 245 | new sheets can be created only using workbook.addWorksheet() or existing sheet objects can be retrieved using `workbook.getWorksheets()` and `workbook.getWorksheetByName();` 246 | 247 | ### Properties 248 | 249 | ``` 250 | name: String // name of the worksheet 251 | workbook: fin.desktop.ExcelWorkbook // workbook object that worksheet belongs to. 252 | ``` 253 | 254 | ### Functions 255 | 256 | ```javascript 257 | /* 258 | setCells(values, offset); 259 | Asynchronously populates the cells with the values starting from the provided cell reference and returns a promise which resolves when the operation is complete. 260 | */ 261 | 262 | var worksheet = await workbook.addWorksheet(); 263 | await worksheet.setCells([["a", "b", "c"], [1, 2, 3]], "A1"); 264 | 265 | /* 266 | setFilter(start, offsetWidth, offsetHeight, field, criteria1[, operator, criteria2, visibleDropDown]); 267 | Asynchronously sets a filter on selected range in a worksheet and returns a promise which resolves when the operation is complete. 268 | 269 | arguments: 270 | start: starting address of the range. e.g "A1" 271 | offsetWidth: width of the range. 272 | offsetHeight: height of the range. 273 | field: integer representing the field number. starts with 1. 274 | criteria1: The criteria (a string; for example, "101"). Use "=" to find blank fields, or use "<>" to find nonblank fields. If this argument is omitted, the criteria is All. If Operator is xlTop10Items, Criteria1 specifies the number of items (for example, "10"). 275 | operator: Optional. Can be one of the following: 276 | and 277 | bottom10items 278 | bottom10percent 279 | or 280 | top10items 281 | top10percent 282 | criteria2: Optional. The second criteria (a string). Used with Criteria1 and Operator to construct compound criteria. 283 | visibleDropDown: Optional. true to display the AutoFilter drop-down arrow for the filtered field; false to hide the AutoFilter drop-down arrow for the filtered field. true by default. 284 | */ 285 | 286 | var worksheet = workbook.getSheetByName("sheet1"); 287 | await worksheet.setCells([["Column1", "Column2"], ["TRUE", "1"], ["TRUE", "2"],["FALSE", ""]], "A1"); 288 | await worksheet.setFilter("A1", 2, 4, 1, "TRUE"); 289 | 290 | /* 291 | getCells(start, offsetWidth, offsetHeight); 292 | Returns a promise which resolves a two dimensional array of cell values starting at the specified cell reference and the specified width and length. 293 | */ 294 | 295 | var sheet = workbook.getSheetByName("sheet1"); 296 | var cells = await sheet.getCells("A1", 3, 2); // cells: [[{value: --, formula: --}, ...], ...] 297 | 298 | /* 299 | activate(); 300 | Asynchronously activates the worksheet and returns a promise which resolves when the operation is complete. 301 | */ 302 | 303 | var sheet = workbook.getSheetByName("sheet1"); 304 | await sheet.activate(); 305 | 306 | /* 307 | activateCell(cellReference); 308 | Asynchronously selects specified cell reference and returns a promise which resolves when the operation is complete. 309 | */ 310 | 311 | var sheet = workbook.getSheetByName("sheet1"); 312 | await sheet.activateCell("A1"); 313 | 314 | /* 315 | clearAllCells(); 316 | Asynchronously clears all the cell values and formatting in the worksheet and returns a promise which resolves when the operation is complete. 317 | */ 318 | 319 | var sheet = workbook.getSheetByName("sheet1"); 320 | await sheet.clearAllCells(); 321 | 322 | /* 323 | clearAllCellContents(); 324 | Asynchronously clears all the cell values in the worksheet and returns a promise which resolves when the operation is complete. 325 | */ 326 | 327 | var sheet = workbook.getSheetByName("sheet1"); 328 | await sheet.clearAllCellContents(); 329 | 330 | /* 331 | clearAllCellFormats(); 332 | Asynchronously clears all the cell formatting in the worksheet and returns a promise which resolves when the operation is complete. 333 | */ 334 | 335 | var sheet = workbook.getSheetByName("sheet1"); 336 | await sheet.clearAllCellFormats(); 337 | 338 | /* 339 | clearRange(); 340 | Asynchronously clears all the cell values and formatting in the specified range and returns a promise which resolves when the operation is complete. 341 | */ 342 | 343 | var sheet = workbook.getSheetByName("sheet1"); 344 | await sheet.clearRange(); 345 | 346 | /* 347 | clearRangeContents(); 348 | Asynchronously clears all the cell values in the specified range and returns a promise which resolves when the operation is complete. 349 | */ 350 | 351 | var sheet = workbook.getSheetByName("sheet1"); 352 | await sheet.clearRangeContents(); 353 | 354 | /* 355 | clearRangeFormats(); 356 | Asynchronously clears all the cell formatting in the specified range and returns a promise which resolves when the operation is complete. 357 | */ 358 | 359 | var sheet = workbook.getSheetByName("sheet1"); 360 | await sheet.clearRangeFormats(); 361 | 362 | /* 363 | setCellName(cellAddress, cellName); 364 | Asynchronously sets a name for the cell which can be referenced to get values or in formulas and returns a promise which resolves when the operation is complete. 365 | */ 366 | 367 | var sheet = workbook.getSheetByName("sheet1"); 368 | await sheet.setCellName("A1", "TheCellName"); 369 | 370 | /* 371 | calculate(); 372 | Asynchronously forces calculation on the sheet and returns a promise which resolves when the operation is complete. 373 | */ 374 | 375 | var sheet = workbook.getSheetByName("sheet1"); 376 | await sheet.calculate(); 377 | 378 | /* 379 | getCellByName(name); 380 | Returns a promise which resolves the cell info of the cell with the name provided. 381 | */ 382 | 383 | var sheet = workbook.getSheetByName("sheet1"); 384 | var cellInfo = await sheet.getCellByName("TheCellName"); 385 | 386 | /* 387 | protect(); 388 | Asynchronously makes all cells in the sheet read only, except the ones marked as locked:false and returns a promise which resolves when the operation is complete. 389 | */ 390 | 391 | var sheet = workbook.getSheetByName("sheet1"); 392 | await sheet.protect(); 393 | 394 | /* 395 | formatRange(rangeCode, format); 396 | Asynchronously formats the specified range and returns a promise which resolves when the operation is complete. 397 | */ 398 | 399 | var sheet = workbook.getSheetByName("sheet1"); 400 | await sheet.formatRange("A1:E:10", { 401 | border: {color:"0,0,0,1", style: "continuous"}, //dash, dashDot, dashDotDot, dot, double, none, slantDashDot 402 | border-right: {color:"0,0,0,1", style: "continuous"}, 403 | border-left: {color:"0,0,0,1", style: "continuous"}, 404 | border-top: {color:"0,0,0,1", style: "continuous"}, 405 | border-bottom: {color:"0,0,0,1", style: "continuous"}, 406 | horizontalLines: {color:"255,255,255,1", style: "none"}, // horizontal lines between cells 407 | verticalLines: {color:"255,255,255,1", style: "none"}, // vertical lines between cell rows 408 | font: {color: "100,100,100,1", size: 12, bold: true, italic: true, name: "Verdana"}, 409 | mergeCells: true, // merges the given range into one big cell 410 | shrinkToFit: true, // the text will shrink to fit the cell 411 | locked: false // specifies if the cell is readonly or not in protect mode, default is true 412 | }); 413 | ``` 414 | 415 | ### Events 416 | 417 | ```javascript 418 | {type: "sheetChanged", target: ExcelWorksheet, data: {column: int, row: int, formula: String, sheetName: String, value:String}}; 419 | //fired when any cell value in the sheet has changed. 420 | //Example: 421 | var sheet = workbook.getSheetByName("sheet1"); 422 | sheet.addEventListener("sheetChanged", 423 | function(event){ 424 | console.log("sheet values were changed. column:", event.data.column, "row:", event.data.row, "value:", event.data.value, "formula", event.data.formula); 425 | }); 426 | 427 | {type: "selectionChanged", target: ExcelWorksheet, data: {column: int, row: int, value: String}}; 428 | //fired when a selection on the sheet has changed. 429 | //Example: 430 | var sheet = workbook.getSheetByName("sheet1"); 431 | sheet.addEventListener("selectionChanged", 432 | function(event){ 433 | console.log("sheet selection was changed. column:", event.data.column, "row:", event.data.row, "value:", event.data.value); 434 | }); 435 | 436 | {type: "sheetActivated", target: ExcelWorksheet}; 437 | //fired when the sheet gets into focus. 438 | //Example: 439 | var sheet = workbook.getSheetByName("sheet1"); 440 | sheet.addEventListener("sheetActivated", 441 | function(event){ 442 | console.log("sheet activated. Sheet", event.target.name, "Workbook:", event.target.workbook.name); 443 | }); 444 | 445 | {type: "sheetDeactivated", target: ExcelWorksheet}; 446 | //fired when the sheet gets out of focus due to a different sheet getting in focus. 447 | //Example: 448 | var sheet = workbook.getSheetByName("sheet1"); 449 | sheet.addEventListener("sheetDeactivated", 450 | function(event){ 451 | console.log("sheet deactivated. Sheet", event.target.name, "Workbook:", event.target.workbook.name); 452 | }); 453 | 454 | {type: "sheetRenamed", target: ExcelWorksheet}; 455 | //fired when the sheet is renamed. 456 | //Example: 457 | var sheet = workbook.getSheetByName("sheet1"); 458 | sheet.addEventListener("sheetRenamed", 459 | function(event){ 460 | console.log("sheet", event.data.sheetName, "was renamed to: ", event.data.newName 461 | ); 462 | }); 463 | 464 | ``` 465 | 466 | ### Excel RTD Functionality (New in 3.1.1) 467 | 468 | You can create an RTD Connection in JavaScript: 469 | 470 | ```javascript 471 | const rtd = await fin.desktop.ExcelService.createRtd('Provider1'); 472 | 473 | // you can then listen to when an excel sheet connects by adding the following 474 | // example to a cell: =OpenFinRTD("Provider1", "Topic1") 475 | rtd.addEventListener('connected', console.dir); 476 | 477 | // the connected event will included the topic id that has been assigned 478 | 479 | // Your application can listen to the disconnected event 480 | // The event will have topic: "Topic1", type: "disconnected" and your application can react. 481 | rtd.addEventListener('disconnected', console.dir); 482 | 483 | // if the webapp refreshes or is closed and loses connection then the excel sheet wipes away the value with #N/A to ensure no stale data is shown. 484 | 485 | // You can push values to excel cells as follows 486 | rtd.setValue('Topic1', 'Hello'); 487 | rtd.setValue('Topic1', true); 488 | rtd.setValue('Topic1', 123.55); 489 | 490 | // with this functionality you can listen to multiple cells (each with a specific topic) and return a single value or apply updates on an interval as the application value changes. 491 | 492 | ``` 493 | 494 | ### Excel RTD Functionality (New in 4.0.0) 495 | 496 | From version 4.0 you can dispose the rtd instance that you created. You should call dispose if your application is closing or if you no longer need the functionality. 497 | 498 | ```javascript 499 | const rtd = await fin.desktop.ExcelService.createRtd('Provider1'); 500 | 501 | // Will stop listening to excel and will tell excel to clear all values in the connected topics 502 | // When the excel provider service closes we will clear the cells as well but that won't happen if another application 503 | // is still using the excel service so you should always try and tidy up and dispose when your application is about to close. 504 | await rtd.dispose(); 505 | 506 | ``` 507 | 508 | ### Excel Self Hosting 509 | 510 | You may wish to control the version of the excel service you are using and self host it. 511 | 512 | This would involve having your own server to host the assets. 513 | 514 | An example can be seen in our demo by looking at the **dev-app.json** file within the /demo folder. 515 | 516 | #### Scenario: You are hosting the assets in a versioned excel folder on your server https://yourserver/excel/x.x.x/ (x.x.x represents the version you are using throughout this example) 517 | 518 | The excel setup is made up of the following: 519 | 520 | 1. Your app references a service through it's manifest (and this is where you can specify your own service manifest url): 521 | 522 | ```javascript 523 | "services": [ 524 | { 525 | "name": "excel", 526 | "manifestUrl": "https://yourserver/excel/x.x.x/provider/app.json" 527 | } 528 | ] 529 | ``` 530 | 2. Your app references adds a script tag (or bundles) the excel client script e.g. 531 | 532 | ```javascript 533 | 534 | ``` 535 | 536 | 3. The provider folder that is hosted by you has an updated app.json file to reflect your host: 537 | 538 | ```javascript 539 | { 540 | "licenseKey": "Your License Key", 541 | "startup_app": { 542 | "name": "Excel-Service-Manager", 543 | "url": "https://yourserver/excel/x.x.x/provider/provider.html", 544 | "uuid": "excel-service-manager", 545 | "permissions": { 546 | "System": { 547 | "launchExternalProcess": true 548 | } 549 | } 550 | }, 551 | "runtime": { 552 | "arguments": "", 553 | "version": "14.78.48.16" 554 | }, 555 | "appAssets": [ 556 | { 557 | "src": "https://yourserver/excel/x.x.x/provider/add-in.zip", 558 | "alias": "excel-api-addin", 559 | "version": "x.x.x" 560 | } 561 | ] 562 | } 563 | ``` 564 | 565 | #### Why do this? 566 | 567 | This is to control the version of the scripts/service you are using (rather than pointing to the latest version). 568 | 569 | #### Anything to be aware of? 570 | 571 | The excel service acts as a singleton so the first version that is spun up is the active version (for scenarios where you have multiple apps using excel). 572 | 573 | #### What if I want more features/control over my excel integration? 574 | 575 | OpenFin have a .Net Adapter that allows you to launch OpenFin applications/windows as well as communicate on the OpenFin Message Bus. This in combination with ExcelDNA will give you the two pieces you need to create your own custom experience. 576 | 577 | * .NET Adapter: https://developers.openfin.co/docs/net-api 578 | * Excel DNA: https://excel-dna.net/ 579 | -------------------------------------------------------------------------------- /client/fin.desktop.Excel.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 9); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ([ 70 | /* 0 */ 71 | /***/ (function(module, exports, __webpack_require__) { 72 | 73 | "use strict"; 74 | 75 | Object.defineProperty(exports, "__esModule", { value: true }); 76 | exports.RpcDispatcher = void 0; 77 | const EventEmitter_1 = __webpack_require__(1); 78 | const NoOpLogger_1 = __webpack_require__(2); 79 | class RpcDispatcher extends EventEmitter_1.EventEmitter { 80 | constructor(logger) { 81 | super(); 82 | this.logger = new NoOpLogger_1.NoOpLogger(); 83 | if (logger !== undefined) { 84 | this.logger = logger; 85 | } 86 | } 87 | getDefaultMessage() { 88 | return {}; 89 | } 90 | invokeExcelCall(functionName, data, callback) { 91 | return this.invokeRemoteCall('excelCall', functionName, data, callback); 92 | } 93 | invokeServiceCall(functionName, data, callback) { 94 | return this.invokeRemoteCall('excelServiceCall', functionName, data, callback); 95 | } 96 | invokeRemoteCall(topic, functionName, data, callback) { 97 | var message = this.getDefaultMessage(); 98 | var args = data || {}; 99 | var invoker = this; 100 | Object.assign(message, { 101 | messageId: RpcDispatcher.messageId, 102 | target: { 103 | connectionUuid: this.connectionUuid, 104 | workbookName: invoker.workbookName || (invoker.workbook && invoker.workbook.workbookName) || args.workbookName || args.workbook, 105 | worksheetName: invoker.worksheetName || args.worksheetName || args.worksheet, 106 | rangeCode: args.rangeCode 107 | }, 108 | action: functionName, 109 | data: data 110 | }); 111 | var executor; 112 | var promise = new Promise((resolve, reject) => { 113 | executor = { 114 | resolve, 115 | reject 116 | }; 117 | }); 118 | // Legacy Callback-style API 119 | promise = this.applyCallbackToPromise(promise, callback); 120 | var currentMessageId = RpcDispatcher.messageId; 121 | RpcDispatcher.messageId++; 122 | if (this.connectionUuid !== undefined) { 123 | RpcDispatcher.promiseExecutors[currentMessageId] = executor; 124 | fin.desktop.InterApplicationBus.send(this.connectionUuid, topic, message, ack => { 125 | // TODO: log once we support configurable logging. 126 | }, nak => { 127 | delete RpcDispatcher.promiseExecutors[currentMessageId]; 128 | executor.reject(new Error(nak)); 129 | }); 130 | } 131 | else { 132 | executor.reject(new Error('The target UUID of the remote call is undefined.')); 133 | } 134 | return promise; 135 | } 136 | applyCallbackToPromise(promise, callback) { 137 | if (callback) { 138 | promise 139 | .then(result => { 140 | callback(result); 141 | return result; 142 | }).catch(err => { 143 | this.logger.error("unable to apply callback to promise", err); 144 | }); 145 | promise = undefined; 146 | } 147 | return promise; 148 | } 149 | } 150 | exports.RpcDispatcher = RpcDispatcher; 151 | RpcDispatcher.messageId = 1; 152 | RpcDispatcher.promiseExecutors = {}; 153 | //# sourceMappingURL=RpcDispatcher.js.map 154 | 155 | /***/ }), 156 | /* 1 */ 157 | /***/ (function(module, exports, __webpack_require__) { 158 | 159 | "use strict"; 160 | 161 | Object.defineProperty(exports, "__esModule", { value: true }); 162 | exports.EventEmitter = void 0; 163 | class EventEmitter { 164 | constructor() { 165 | this.listeners = {}; 166 | } 167 | addEventListener(type, listener) { 168 | if (this.hasEventListener(type, listener)) { 169 | return; 170 | } 171 | if (!this.listeners[type]) { 172 | this.listeners[type] = []; 173 | } 174 | this.listeners[type].push(listener); 175 | } 176 | removeEventListener(type, listener) { 177 | if (!this.hasEventListener(type, listener)) { 178 | return; 179 | } 180 | var callbacksOfType = this.listeners[type]; 181 | callbacksOfType.splice(callbacksOfType.indexOf(listener), 1); 182 | } 183 | hasEventListener(type, listener) { 184 | if (!this.listeners[type]) { 185 | return false; 186 | } 187 | if (!listener) { 188 | return true; 189 | } 190 | return (this.listeners[type].indexOf(listener) >= 0); 191 | } 192 | dispatchEvent(evtOrTypeArg, data) { 193 | var event; 194 | if (typeof evtOrTypeArg == "string") { 195 | event = Object.assign({ 196 | target: this.toObject(), 197 | type: evtOrTypeArg, 198 | defaultPrevented: false 199 | }, data); 200 | } 201 | else { 202 | event = evtOrTypeArg; 203 | } 204 | var callbacks = this.listeners[event.type] || []; 205 | callbacks.forEach(callback => callback(event)); 206 | return event.defaultPrevented; 207 | } 208 | } 209 | exports.EventEmitter = EventEmitter; 210 | //# sourceMappingURL=EventEmitter.js.map 211 | 212 | /***/ }), 213 | /* 2 */ 214 | /***/ (function(module, exports, __webpack_require__) { 215 | 216 | "use strict"; 217 | 218 | Object.defineProperty(exports, "__esModule", { value: true }); 219 | exports.NoOpLogger = void 0; 220 | class NoOpLogger { 221 | constructor() { 222 | } 223 | trace(message, ...args) { 224 | } 225 | debug(message, ...args) { 226 | } 227 | info(message, ...args) { 228 | } 229 | warn(message, ...args) { 230 | } 231 | error(message, error, ...args) { 232 | } 233 | fatal(message, error, ...args) { 234 | } 235 | } 236 | exports.NoOpLogger = NoOpLogger; 237 | //# sourceMappingURL=NoOpLogger.js.map 238 | 239 | /***/ }), 240 | /* 3 */ 241 | /***/ (function(module, exports, __webpack_require__) { 242 | 243 | "use strict"; 244 | 245 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 246 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 247 | return new (P || (P = Promise))(function (resolve, reject) { 248 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 249 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 250 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 251 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 252 | }); 253 | }; 254 | Object.defineProperty(exports, "__esModule", { value: true }); 255 | exports.ExcelService = void 0; 256 | const RpcDispatcher_1 = __webpack_require__(0); 257 | const ExcelApplication_1 = __webpack_require__(5); 258 | const ExcelRtd_1 = __webpack_require__(6); 259 | const DefaultLogger_1 = __webpack_require__(4); 260 | const NoOpLogger_1 = __webpack_require__(2); 261 | const excelServiceUuid = "886834D1-4651-4872-996C-7B2578E953B9"; 262 | class ExcelService extends RpcDispatcher_1.RpcDispatcher { 263 | constructor() { 264 | super(new NoOpLogger_1.NoOpLogger()); 265 | this.defaultApplicationUuid = undefined; 266 | this.defaultApplicationObj = undefined; 267 | this.logger = new NoOpLogger_1.NoOpLogger(); 268 | this.loggerName = "ExcelService"; 269 | this.applications = {}; 270 | this.version = { 271 | buildVersion: "0.0.0.0", "providerVersion": "0.0.0" 272 | }; 273 | this.processExcelServiceEvent = (data) => __awaiter(this, void 0, void 0, function* () { 274 | var eventType = data.event; 275 | this.logger.debug(this.loggerName + ": Received event for data..."); 276 | this.logger.debug(JSON.stringify(data)); 277 | const mainChannelCreated = yield this.mainChannelCreated; 278 | this.logger.debug(this.loggerName + `: Main Channel created... ${mainChannelCreated}`); 279 | var eventData; 280 | switch (data.event) { 281 | case "started": 282 | break; 283 | case "registrationRollCall": 284 | if (this.initialized) { 285 | this.logger.debug(this.loggerName + ": Initialized, about to register window instance."); 286 | this.registerWindowInstance(); 287 | } 288 | else { 289 | this.logger.debug(this.loggerName + ": NOT initialized. Window will not be registered."); 290 | } 291 | break; 292 | case "excelConnected": 293 | yield this.processExcelConnectedEvent(data); 294 | eventData = { connectionUuid: data.uuid }; 295 | break; 296 | case "excelDisconnected": 297 | yield this.processExcelDisconnectedEvent(data); 298 | eventData = { connectionUuid: data.uuid }; 299 | break; 300 | } 301 | this.dispatchEvent(eventType, eventData); 302 | }); 303 | this.processExcelServiceResult = (result) => __awaiter(this, void 0, void 0, function* () { 304 | var executor = RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 305 | delete RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 306 | this.logger.debug(this.loggerName + `: Received an ExcelService result with messageId ${result.messageId}.`); 307 | //TODO: Somehow received a result not in the callback map 308 | if (!executor) { 309 | this.logger.debug(this.loggerName + `: Received an ExcelService result for messageId ${result.messageId} that doesnt have an associated promise executor.`); 310 | return; 311 | } 312 | if (result.error) { 313 | this.logger.debug(this.loggerName + `: Received a result with error ${result.error}.`); 314 | executor.reject(result.error); 315 | return; 316 | } 317 | // Internal processing 318 | switch (result.action) { 319 | case "getExcelInstances": 320 | yield this.processGetExcelInstancesResult(result.data); 321 | break; 322 | } 323 | this.logger.debug(this.loggerName + `: Calling resolver for message ${result.messageId} with data ${JSON.stringify(result.data)}.`); 324 | executor.resolve(result.data); 325 | }); 326 | this.registerWindowInstance = (callback) => { 327 | return this.invokeServiceCall("registerOpenfinWindow", { domain: document.domain }, callback); 328 | }; 329 | this.connectionUuid = excelServiceUuid; 330 | this.setMainChanelCreated(); 331 | } 332 | setMainChanelCreated() { 333 | this.mainChannelCreated = new Promise((resolve, reject) => { 334 | this.mainChannelResolve = resolve; 335 | this.mainChannelReject = reject; 336 | }); 337 | } 338 | init(logger) { 339 | return __awaiter(this, void 0, void 0, function* () { 340 | if (!this.initialized) { 341 | if (logger !== undefined) { 342 | if (typeof logger === "boolean") { 343 | if (logger) { 344 | let defaultLogger = new DefaultLogger_1.DefaultLogger("Excel Adapter"); 345 | this.logger = defaultLogger; 346 | } 347 | } 348 | else { 349 | let defaultLogger = new DefaultLogger_1.DefaultLogger(logger.name || "Excel Adapter"); 350 | this.logger = Object.assign({}, logger); 351 | if (this.logger.name === undefined) { 352 | this.logger.name === defaultLogger.name; 353 | } 354 | if (this.logger.trace === undefined) { 355 | this.logger.trace = defaultLogger.trace; 356 | } 357 | if (this.logger.debug === undefined) { 358 | this.logger.debug = defaultLogger.debug; 359 | } 360 | if (this.logger.info === undefined) { 361 | this.logger.info = defaultLogger.info; 362 | } 363 | if (this.logger.warn === undefined) { 364 | this.logger.warn = defaultLogger.warn; 365 | } 366 | if (this.logger.error === undefined) { 367 | this.logger.error = defaultLogger.error; 368 | } 369 | if (this.logger.fatal === undefined) { 370 | this.logger.fatal = defaultLogger.fatal; 371 | } 372 | } 373 | } 374 | this.logger.info(this.loggerName + ": Initialised called."); 375 | this.logger.debug(this.loggerName + ": Subscribing to Service Messages."); 376 | yield this.subscribeToServiceMessages(); 377 | this.logger.debug(this.loggerName + ": Ensuring monitor is not conencted before connecting to channel."); 378 | yield this.monitorDisconnect(); 379 | try { 380 | this.logger.debug(this.loggerName + ": Connecting to channel: " + excelServiceUuid); 381 | let providerChannel = yield fin.desktop.InterApplicationBus.Channel.connect(excelServiceUuid); 382 | this.logger.debug(this.loggerName + ": Channel connected: " + excelServiceUuid); 383 | this.logger.debug(this.loggerName + ": Flagging that mainChannelIs connected: " + excelServiceUuid); 384 | this.logger.debug(this.loggerName + ": Setting service provider version by requesting it from channel."); 385 | this.version = yield providerChannel.dispatch('getVersion'); 386 | this.logger.debug(this.loggerName + `: Service provider version set to: ${JSON.stringify(this.version)}.`); 387 | } 388 | catch (err) { 389 | let errorMessage; 390 | if (err !== undefined && err.message !== undefined) { 391 | errorMessage = "Error: " + err.message; 392 | } 393 | this.mainChannelReject(`${this.loggerName}: Error connecting or fetching version to/from provider. The version of the provider is likely older than the script version. ${errorMessage}`); 394 | this.logger.warn(this.loggerName + ": Error connecting or fetching version to/from provider. The version of the provider is likely older than the script version.", errorMessage); 395 | } 396 | this.initialized = true; 397 | this.mainChannelResolve(true); 398 | yield this.registerWindowInstance(); 399 | yield this.getExcelInstances(); 400 | } 401 | return; 402 | }); 403 | } 404 | subscribeToServiceMessages() { 405 | return Promise.all([ 406 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(excelServiceUuid, "excelServiceEvent", this.processExcelServiceEvent, resolve)), 407 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(excelServiceUuid, "excelServiceCallResult", this.processExcelServiceResult, resolve)) 408 | ]); 409 | } 410 | monitorDisconnect() { 411 | return new Promise((resolve, reject) => { 412 | var excelServiceConnection = fin.desktop.ExternalApplication.wrap(excelServiceUuid); 413 | var onDisconnect; 414 | excelServiceConnection.addEventListener("disconnected", onDisconnect = () => { 415 | excelServiceConnection.removeEventListener("disconnected", onDisconnect); 416 | this.dispatchEvent("stopped"); 417 | }, resolve, reject); 418 | }); 419 | } 420 | configureDefaultApplication() { 421 | return __awaiter(this, void 0, void 0, function* () { 422 | this.logger.debug(this.loggerName + ": Configuring Default Excel Application."); 423 | var defaultAppObjUuid = this.defaultApplicationObj && this.defaultApplicationObj.connectionUuid; 424 | var defaultAppEntry = this.applications[defaultAppObjUuid]; 425 | var defaultAppObjConnected = defaultAppEntry ? defaultAppEntry.connected : false; 426 | if (defaultAppObjConnected) { 427 | this.logger.debug(this.loggerName + ": Already connected to Default Excel Application: " + defaultAppObjUuid); 428 | return; 429 | } 430 | else { 431 | this.logger.debug(this.loggerName + ": Default Excel Application: " + defaultAppObjUuid + " not connected."); 432 | } 433 | this.logger.debug(this.loggerName + ": As Default Excel Application not connected checking for existing connected instance."); 434 | var connectedAppUuid = Object.keys(this.applications).find(appUuid => this.applications[appUuid].connected); 435 | if (connectedAppUuid) { 436 | this.logger.debug(this.loggerName + ": Found connected Excel Application: " + connectedAppUuid + " setting it as default instance."); 437 | delete this.applications[defaultAppObjUuid]; 438 | this.defaultApplicationObj = this.applications[connectedAppUuid].toObject(); 439 | return; 440 | } 441 | if (defaultAppEntry === undefined) { 442 | var disconnectedAppUuid = fin.desktop.getUuid(); 443 | this.logger.debug(this.loggerName + ": No default Excel Application. Creating one with id: " + disconnectedAppUuid + " and setting it as default instance."); 444 | var disconnectedApp = new ExcelApplication_1.ExcelApplication(disconnectedAppUuid, this.logger); 445 | yield disconnectedApp.init(); 446 | this.applications[disconnectedAppUuid] = disconnectedApp; 447 | this.defaultApplicationObj = disconnectedApp.toObject(); 448 | this.logger.debug(this.loggerName + ": Default Excel Application with id: " + disconnectedAppUuid + " set as default instance."); 449 | } 450 | }); 451 | } 452 | // Internal Event Handlers 453 | processExcelConnectedEvent(data) { 454 | return __awaiter(this, void 0, void 0, function* () { 455 | var applicationInstance = this.applications[data.uuid] || new ExcelApplication_1.ExcelApplication(data.uuid, this.logger); 456 | yield applicationInstance.init(); 457 | this.applications[data.uuid] = applicationInstance; 458 | // Synthetically raise connected event 459 | applicationInstance.processExcelEvent({ event: "connected" }, data.uuid); 460 | yield this.configureDefaultApplication(); 461 | return; 462 | }); 463 | } 464 | processExcelDisconnectedEvent(data) { 465 | return __awaiter(this, void 0, void 0, function* () { 466 | var applicationInstance = this.applications[data.uuid]; 467 | if (applicationInstance === undefined) { 468 | return; 469 | } 470 | delete this.applications[data.uuid]; 471 | yield this.configureDefaultApplication(); 472 | yield applicationInstance.release(); 473 | }); 474 | } 475 | // Internal API Handlers 476 | processGetExcelInstancesResult(connectionUuids) { 477 | return __awaiter(this, void 0, void 0, function* () { 478 | var existingInstances = this.applications; 479 | this.applications = {}; 480 | yield Promise.all(connectionUuids.map((connectionUuid) => __awaiter(this, void 0, void 0, function* () { 481 | var existingInstance = existingInstances[connectionUuid]; 482 | if (existingInstance === undefined) { 483 | // Assume that since the ExcelService is aware of the instance it, 484 | // is connected and to simulate the the connection event 485 | yield this.processExcelServiceEvent({ event: "excelConnected", uuid: connectionUuid }); 486 | } 487 | else { 488 | this.applications[connectionUuid] = existingInstance; 489 | } 490 | return; 491 | }))); 492 | yield this.configureDefaultApplication(); 493 | }); 494 | } 495 | // API Calls 496 | install(callback) { 497 | return this.invokeServiceCall("install", null, callback); 498 | } 499 | getInstallationStatus(callback) { 500 | return this.invokeServiceCall("getInstallationStatus", null, callback); 501 | } 502 | getExcelInstances(callback) { 503 | return this.invokeServiceCall("getExcelInstances", null, callback); 504 | } 505 | createRtd(providerName, heartbeatIntervalInMilliseconds = 10000) { 506 | return ExcelRtd_1.ExcelRtd.create(providerName, this.logger, heartbeatIntervalInMilliseconds); 507 | } 508 | toObject() { 509 | return {}; 510 | } 511 | } 512 | exports.ExcelService = ExcelService; 513 | ExcelService.instance = new ExcelService(); 514 | //# sourceMappingURL=ExcelApi.js.map 515 | 516 | /***/ }), 517 | /* 4 */ 518 | /***/ (function(module, exports, __webpack_require__) { 519 | 520 | "use strict"; 521 | 522 | Object.defineProperty(exports, "__esModule", { value: true }); 523 | exports.DefaultLogger = void 0; 524 | class DefaultLogger { 525 | constructor(name) { 526 | this.name = name || "logger"; 527 | } 528 | trace(message, ...args) { 529 | console.log(this.name + ": " + message, ...args); 530 | } 531 | debug(message, ...args) { 532 | console.log(this.name + ": " + message, ...args); 533 | } 534 | info(message, ...args) { 535 | console.info(this.name + ": " + message, ...args); 536 | } 537 | warn(message, ...args) { 538 | console.warn(this.name + ": " + message, ...args); 539 | } 540 | error(message, error, ...args) { 541 | console.error(this.name + ": " + message, error, ...args); 542 | } 543 | fatal(message, error, ...args) { 544 | console.error(this.name + ": " + message, error, ...args); 545 | } 546 | } 547 | exports.DefaultLogger = DefaultLogger; 548 | //# sourceMappingURL=DefaultLogger.js.map 549 | 550 | /***/ }), 551 | /* 5 */ 552 | /***/ (function(module, exports, __webpack_require__) { 553 | 554 | "use strict"; 555 | 556 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 557 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 558 | return new (P || (P = Promise))(function (resolve, reject) { 559 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 560 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 561 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 562 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 563 | }); 564 | }; 565 | Object.defineProperty(exports, "__esModule", { value: true }); 566 | exports.ExcelApplication = void 0; 567 | const RpcDispatcher_1 = __webpack_require__(0); 568 | const ExcelWorkbook_1 = __webpack_require__(7); 569 | const ExcelWorksheet_1 = __webpack_require__(8); 570 | class ExcelApplication extends RpcDispatcher_1.RpcDispatcher { 571 | constructor(connectionUuid, logger) { 572 | super(logger); 573 | this.workbooks = {}; 574 | this.version = { clientVersion: "4.1.2", buildVersion: "4.1.2.0" }; 575 | this.loggerName = "ExcelApplication"; 576 | this.processExcelEvent = (data, uuid) => { 577 | var eventType = data.event; 578 | this.logger.debug(this.loggerName + `: ExcelApplication.processExcelEvent received from ${uuid} with data ${JSON.stringify(data)}.`); 579 | var workbook = this.workbooks[data.workbookName]; 580 | var worksheets = workbook && workbook.worksheets; 581 | var worksheet = worksheets && worksheets[data.sheetName]; 582 | switch (eventType) { 583 | case "connected": 584 | this.logger.debug(this.loggerName + ": Received Excel Connected Event."); 585 | this.connected = true; 586 | this.dispatchEvent(eventType); 587 | break; 588 | case "sheetChanged": 589 | this.logger.debug(this.loggerName + ": Received Sheet Changed Event."); 590 | if (worksheet) { 591 | this.logger.debug(this.loggerName + ": Worksheet Exists, dispatching event."); 592 | worksheet.dispatchEvent(eventType, { data: data }); 593 | } 594 | break; 595 | case "sheetRenamed": 596 | var oldWorksheetName = data.oldSheetName; 597 | var newWorksheetName = data.sheetName; 598 | worksheet = worksheets && worksheets[oldWorksheetName]; 599 | this.logger.debug(this.loggerName + ": Sheet renamed: Old name:" + oldWorksheetName + " New name: " + newWorksheetName); 600 | if (worksheet) { 601 | delete worksheets[oldWorksheetName]; 602 | worksheet.worksheetName = newWorksheetName; 603 | worksheets[worksheet.worksheetName] = worksheet; 604 | workbook.dispatchEvent(eventType, { worksheet: worksheet.toObject(), oldWorksheetName: oldWorksheetName }); 605 | } 606 | break; 607 | case "selectionChanged": 608 | this.logger.debug(this.loggerName + ": Selection Changed."); 609 | if (worksheet) { 610 | worksheet.dispatchEvent(eventType, { data: data }); 611 | } 612 | break; 613 | case "sheetActivated": 614 | case "sheetDeactivated": 615 | this.logger.debug(this.loggerName + ": Sheet Deactivated"); 616 | if (worksheet) { 617 | worksheet.dispatchEvent(eventType); 618 | } 619 | break; 620 | case "workbookDeactivated": 621 | case "workbookActivated": 622 | if (workbook) { 623 | workbook.dispatchEvent(eventType); 624 | } 625 | break; 626 | case "sheetAdded": 627 | var newWorksheet = worksheet || new ExcelWorksheet_1.ExcelWorksheet(data.sheetName, workbook); 628 | worksheets[newWorksheet.worksheetName] = newWorksheet; 629 | workbook.dispatchEvent(eventType, { worksheet: newWorksheet.toObject() }); 630 | break; 631 | case "sheetRemoved": 632 | delete workbook.worksheets[worksheet.worksheetName]; 633 | worksheet.dispatchEvent(eventType); 634 | workbook.dispatchEvent(eventType, { worksheet: worksheet.toObject() }); 635 | break; 636 | case "workbookAdded": 637 | case "workbookOpened": 638 | var newWorkbook = workbook || new ExcelWorkbook_1.ExcelWorkbook(this, data.workbookName); 639 | this.workbooks[newWorkbook.workbookName] = newWorkbook; 640 | this.dispatchEvent(eventType, { workbook: newWorkbook.toObject() }); 641 | break; 642 | case "workbookClosed": 643 | delete this.workbooks[workbook.workbookName]; 644 | workbook.dispatchEvent(eventType); 645 | this.dispatchEvent(eventType, { workbook: workbook.toObject() }); 646 | break; 647 | case "workbookSaved": 648 | var oldWorkbookName = data.oldWorkbookName; 649 | var newWorkbookName = data.workbookName; 650 | workbook = this.workbooks[oldWorkbookName]; 651 | if (workbook) { 652 | delete this.workbooks[oldWorkbookName]; 653 | workbook.workbookName = newWorkbookName; 654 | this.workbooks[workbook.workbookName] = workbook; 655 | workbook.refreshObject(); 656 | this.dispatchEvent(eventType, { workbook: workbook.toObject(), oldWorkbookName: oldWorkbookName }); 657 | } 658 | break; 659 | case "afterCalculation": 660 | default: 661 | this.dispatchEvent(eventType); 662 | break; 663 | } 664 | }; 665 | this.processExcelResult = (result) => { 666 | var callbackData = {}; 667 | var executor = RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 668 | delete RpcDispatcher_1.RpcDispatcher.promiseExecutors[result.messageId]; 669 | this.logger.debug(this.loggerName + `: Received an Excel result with messageId ${result.messageId}.`); 670 | //TODO: Somehow received a result not in the callback map 671 | if (!executor) { 672 | this.logger.debug(this.loggerName + `: Received an Excel result for messageId ${result.messageId} that doesnt have an associated promise executor.`); 673 | return; 674 | } 675 | if (result.error) { 676 | this.logger.debug(this.loggerName + `: Received an Excel result with error ${result.error}.`); 677 | executor.reject(result.error); 678 | return; 679 | } 680 | var workbook = this.workbooks[result.target.workbookName]; 681 | var worksheets = workbook && workbook.worksheets; 682 | var worksheet = worksheets && worksheets[result.target.sheetName]; 683 | var resultData = result.data; 684 | switch (result.action) { 685 | case "getWorkbooks": 686 | var workbookNames = resultData; 687 | var oldworkbooks = this.workbooks; 688 | this.workbooks = {}; 689 | workbookNames.forEach(workbookName => { 690 | this.workbooks[workbookName] = oldworkbooks[workbookName] || new ExcelWorkbook_1.ExcelWorkbook(this, workbookName); 691 | }); 692 | callbackData = workbookNames.map(workbookName => this.workbooks[workbookName].toObject()); 693 | break; 694 | case "getWorksheets": 695 | var worksheetNames = resultData; 696 | var oldworksheets = worksheets; 697 | workbook.worksheets = {}; 698 | worksheetNames.forEach(worksheetName => { 699 | workbook.worksheets[worksheetName] = oldworksheets[worksheetName] || new ExcelWorksheet_1.ExcelWorksheet(worksheetName, workbook); 700 | }); 701 | callbackData = worksheetNames.map(worksheetName => workbook.worksheets[worksheetName].toObject()); 702 | break; 703 | case "addWorkbook": 704 | case "openWorkbook": 705 | var newWorkbookName = resultData; 706 | var newWorkbook = this.workbooks[newWorkbookName] || new ExcelWorkbook_1.ExcelWorkbook(this, newWorkbookName); 707 | this.workbooks[newWorkbook.workbookName] = newWorkbook; 708 | callbackData = newWorkbook.toObject(); 709 | break; 710 | case "addSheet": 711 | var newWorksheetName = resultData; 712 | var newWorksheet = workbook[newWorkbookName] || new ExcelWorksheet_1.ExcelWorksheet(newWorksheetName, workbook); 713 | worksheets[newWorksheet.worksheetName] = newWorksheet; 714 | callbackData = newWorksheet.toObject(); 715 | break; 716 | default: 717 | callbackData = resultData; 718 | break; 719 | } 720 | this.logger.debug(this.loggerName + `: Calling resolver for Excel message ${result.messageId} with data ${callbackData}.`); 721 | executor.resolve(callbackData); 722 | }; 723 | this.connectionUuid = connectionUuid; 724 | this.connected = false; 725 | } 726 | init() { 727 | return __awaiter(this, void 0, void 0, function* () { 728 | this.logger.info(this.loggerName + ": Init called."); 729 | if (!this.initialized) { 730 | this.logger.info(this.loggerName + ": Not initialised...Initialising."); 731 | yield this.subscribeToExcelMessages(); 732 | yield this.monitorDisconnect(); 733 | this.initialized = true; 734 | this.logger.info(this.loggerName + ": initialised."); 735 | } 736 | return; 737 | }); 738 | } 739 | release() { 740 | return __awaiter(this, void 0, void 0, function* () { 741 | this.logger.info(this.loggerName + ": Release called."); 742 | if (this.initialized) { 743 | this.logger.info(this.loggerName + ": Calling unsubscribe as we are currently intialised."); 744 | yield this.unsubscribeToExcelMessages(); 745 | //TODO: Provide external means to stop monitoring disconnect 746 | } 747 | return; 748 | }); 749 | } 750 | subscribeToExcelMessages() { 751 | return Promise.all([ 752 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(this.connectionUuid, "excelEvent", this.processExcelEvent, resolve)), 753 | new Promise(resolve => fin.desktop.InterApplicationBus.subscribe(this.connectionUuid, "excelResult", this.processExcelResult, resolve)) 754 | ]); 755 | } 756 | unsubscribeToExcelMessages() { 757 | return Promise.all([ 758 | new Promise(resolve => fin.desktop.InterApplicationBus.unsubscribe(this.connectionUuid, "excelEvent", this.processExcelEvent, resolve)), 759 | new Promise(resolve => fin.desktop.InterApplicationBus.unsubscribe(this.connectionUuid, "excelResult", this.processExcelResult, resolve)) 760 | ]); 761 | } 762 | monitorDisconnect() { 763 | return new Promise((resolve, reject) => { 764 | var excelApplicationConnection = fin.desktop.ExternalApplication.wrap(this.connectionUuid); 765 | var onDisconnect; 766 | excelApplicationConnection.addEventListener('disconnected', onDisconnect = () => { 767 | excelApplicationConnection.removeEventListener('disconnected', onDisconnect); 768 | this.connected = false; 769 | this.dispatchEvent('disconnected'); 770 | }, resolve, reject); 771 | }); 772 | } 773 | run(callback) { 774 | var runPromise = this.connected ? Promise.resolve() : new Promise(resolve => { 775 | var connectedCallback = () => { 776 | this.removeEventListener('connected', connectedCallback); 777 | resolve(); 778 | }; 779 | if (this.connectionUuid !== undefined) { 780 | this.addEventListener('connected', connectedCallback); 781 | } 782 | fin.desktop.System.launchExternalProcess({ 783 | target: 'excel', 784 | uuid: this.connectionUuid 785 | }); 786 | }); 787 | return this.applyCallbackToPromise(runPromise, callback); 788 | } 789 | getWorkbooks(callback) { 790 | return this.invokeExcelCall("getWorkbooks", null, callback); 791 | } 792 | getWorkbookByName(name) { 793 | return this.workbooks[name]; 794 | } 795 | addWorkbook(callback) { 796 | return this.invokeExcelCall("addWorkbook", null, callback); 797 | } 798 | openWorkbook(path, callback) { 799 | return this.invokeExcelCall("openWorkbook", { path: path }, callback); 800 | } 801 | getConnectionStatus(callback) { 802 | return this.applyCallbackToPromise(Promise.resolve(this.connected), callback); 803 | } 804 | getCalculationMode(callback) { 805 | return this.invokeExcelCall("getCalculationMode", null, callback); 806 | } 807 | calculateAll(callback) { 808 | return this.invokeExcelCall("calculateFull", null, callback); 809 | } 810 | toObject() { 811 | return this.objectInstance || (this.objectInstance = { 812 | connectionUuid: this.connectionUuid, 813 | version: this.version, 814 | addEventListener: this.addEventListener.bind(this), 815 | removeEventListener: this.removeEventListener.bind(this), 816 | addWorkbook: this.addWorkbook.bind(this), 817 | calculateAll: this.calculateAll.bind(this), 818 | getCalculationMode: this.getCalculationMode.bind(this), 819 | getConnectionStatus: this.getConnectionStatus.bind(this), 820 | getWorkbookByName: name => this.getWorkbookByName(name).toObject(), 821 | getWorkbooks: this.getWorkbooks.bind(this), 822 | openWorkbook: this.openWorkbook.bind(this), 823 | run: this.run.bind(this) 824 | }); 825 | } 826 | } 827 | exports.ExcelApplication = ExcelApplication; 828 | ExcelApplication.defaultInstance = undefined; 829 | //# sourceMappingURL=ExcelApplication.js.map 830 | 831 | /***/ }), 832 | /* 6 */ 833 | /***/ (function(module, exports, __webpack_require__) { 834 | 835 | "use strict"; 836 | 837 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 838 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 839 | return new (P || (P = Promise))(function (resolve, reject) { 840 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 841 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 842 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 843 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 844 | }); 845 | }; 846 | Object.defineProperty(exports, "__esModule", { value: true }); 847 | exports.ExcelRtd = void 0; 848 | const EventEmitter_1 = __webpack_require__(1); 849 | class ExcelRtd extends EventEmitter_1.EventEmitter { 850 | constructor(providerName, logger, heartbeatIntervalInMilliseconds = 10000) { 851 | super(); 852 | this.heartbeatIntervalInMilliseconds = heartbeatIntervalInMilliseconds; 853 | this.listeners = {}; 854 | this.connectedTopics = {}; 855 | this.connectedKey = 'connected'; 856 | this.disconnectedKey = 'disconnected'; 857 | this.loggerName = "ExcelRtd"; 858 | this.initialized = false; 859 | this.disposed = false; 860 | var minimumDefaultHeartbeat = 10000; 861 | if (this.heartbeatIntervalInMilliseconds < minimumDefaultHeartbeat) { 862 | logger.warn(`heartbeatIntervalInMilliseconds cannot be less than ${minimumDefaultHeartbeat}. Setting heartbeatIntervalInMilliseconds to ${minimumDefaultHeartbeat}.`); 863 | this.heartbeatIntervalInMilliseconds = minimumDefaultHeartbeat; 864 | } 865 | this.providerName = providerName; 866 | this.logger = logger; 867 | logger.debug(this.loggerName + ": instance created for provider: " + providerName); 868 | } 869 | static create(providerName, logger, heartbeatIntervalInMilliseconds = 10000) { 870 | return __awaiter(this, void 0, void 0, function* () { 871 | logger.debug("ExcelRtd: create called to create provider: " + providerName); 872 | const instance = new ExcelRtd(providerName, logger, heartbeatIntervalInMilliseconds); 873 | yield instance.init(); 874 | if (!instance.isInitialized) { 875 | return undefined; 876 | } 877 | return instance; 878 | }); 879 | } 880 | init() { 881 | return __awaiter(this, void 0, void 0, function* () { 882 | if (this.isInitialized) { 883 | return; 884 | } 885 | this.logger.debug(this.loggerName + ": Initialise called for provider: " + this.providerName); 886 | try { 887 | // A channel is created to ensure it is a singleton so you don't have two apps pushing updates over each other or two windows within the same app 888 | this.provider = yield fin.InterApplicationBus.Channel.create(`excelRtd/${this.providerName}`); 889 | } 890 | catch (err) { 891 | this.logger.warn(this.loggerName + `: The excelRtd/${this.providerName} channel already exists. You can only have one instance of a connection for a provider to avoid confusion. It may be you have multiple instances or another window or application has created a provider with the same name.`, err); 892 | return; 893 | } 894 | this.logger.debug(this.loggerName + `: Subscribing to messages to this provider (${this.providerName}) from excel.`); 895 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/pong/${this.providerName}`, this.onSubscribe.bind(this)); 896 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/ping-request/${this.providerName}`, this.ping.bind(this)); 897 | yield fin.InterApplicationBus.subscribe({ uuid: '*' }, `excelRtd/unsubscribed/${this.providerName}`, this.onUnsubscribe.bind(this)); 898 | yield this.ping(); 899 | this.establishHeartbeat(); 900 | this.logger.debug(this.loggerName + `: initialisation for provider (${this.providerName}) finished.`); 901 | this.initialized = true; 902 | }); 903 | } 904 | get isDisposed() { 905 | return this.disposed; 906 | } 907 | get isInitialized() { 908 | return this.initialized; 909 | } 910 | setValue(topic, value) { 911 | this.logger.trace(this.loggerName + `: Publishing on rtdTopic: ${topic} and provider: ${this.providerName} value: ${JSON.stringify(value)}`); 912 | fin.InterApplicationBus.publish(`excelRtd/data/${this.providerName}/${topic}`, value); 913 | } 914 | dispose() { 915 | return __awaiter(this, void 0, void 0, function* () { 916 | if (!this.disposed) { 917 | this.logger.debug(this.loggerName + `: dispose called. Will send message to clear values for this provider (${this.providerName}).`); 918 | if (this.heartbeatToken) { 919 | clearInterval(this.heartbeatToken); 920 | } 921 | this.clear(); 922 | if (this.provider !== undefined) { 923 | try { 924 | yield this.provider.destroy(); 925 | } 926 | catch (err) { 927 | // without a catch the rest of the initialisation would be broken 928 | this.logger.warn(this.loggerName + `: The excelRtd/${this.providerName} channel could not be destroyed during cleanup.`, err); 929 | } 930 | } 931 | this.logger.debug(this.loggerName + `: UnSubscribing to messages to this provider (${this.providerName}) from excel.`); 932 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/pong/${this.providerName}`, this.onSubscribe.bind(this)); 933 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/ping-request/${this.providerName}`, this.ping.bind(this)); 934 | yield fin.InterApplicationBus.unsubscribe({ uuid: '*' }, `excelRtd/unsubscribed/${this.providerName}`, this.onUnsubscribe.bind(this)); 935 | this.disposed = true; 936 | this.initialized = false; 937 | } 938 | else { 939 | this.logger.debug(this.loggerName + `: This provider (${this.providerName}) has already been disposed.`); 940 | } 941 | }); 942 | } 943 | // Overriding 944 | addEventListener(type, listener) { 945 | this.logger.debug(this.loggerName + `: Event listener add requested for type ${type} received.`); 946 | if (super.hasEventListener(type, listener)) { 947 | this.logger.debug(this.loggerName + `: Event listener add requested for type ${type} received.`); 948 | return; 949 | } 950 | let connectedTopicIds = Object.keys(this.connectedTopics); 951 | let topics = this.connectedTopics; 952 | if (connectedTopicIds.length > 0) { 953 | // need to simulate async action as by default this method would return and then a listener would be called 954 | setTimeout(() => { 955 | connectedTopicIds.forEach(id => { 956 | this.logger.debug(this.loggerName + `: Raising synthetic event as the event listener was added after the event for connected for rtdTopic: ${id}.`); 957 | listener(topics[id]); 958 | }); 959 | }, 0); 960 | } 961 | super.addEventListener(type, listener); 962 | } 963 | dispatchEvent(evtOrTypeArg, data) { 964 | var event; 965 | if (typeof evtOrTypeArg == "string" && data !== undefined) { 966 | this.logger.debug(this.loggerName + `: dispatch event called for type ${evtOrTypeArg} and data: ${JSON.stringify(data)}`); 967 | event = Object.assign({ 968 | target: this.toObject(), 969 | type: evtOrTypeArg, 970 | defaultPrevented: false 971 | }, data); 972 | if (data.topic !== undefined) { 973 | if (evtOrTypeArg === this.connectedKey) { 974 | this.connectedTopics[data.topic] = event; 975 | this.logger.debug(this.loggerName + `: Saving connected event for rtdTopic: ${data.topic}.`); 976 | } 977 | else if (evtOrTypeArg === this.disconnectedKey) { 978 | this.logger.debug(this.loggerName + `: Disconnected event for rtdTopic: ${data.topic} received.`); 979 | if (this.connectedTopics[data.topic] !== undefined) { 980 | // we have removed the topic so clear it from the connected list for late subscribers 981 | this.logger.debug(this.loggerName + `: Clearing saved connected event for rtdTopic: ${data.topic}.`); 982 | delete this.connectedTopics[data.topic]; 983 | } 984 | } 985 | } 986 | this.logger.debug(this.loggerName + `: Dispatching event.`); 987 | return super.dispatchEvent(event, data); 988 | } 989 | event = evtOrTypeArg; 990 | return super.dispatchEvent(event); 991 | } 992 | toObject() { 993 | return this; 994 | } 995 | ping(topic) { 996 | return __awaiter(this, void 0, void 0, function* () { 997 | if (topic !== undefined) { 998 | this.pingPath = `excelRtd/ping/${this.providerName}/${topic}`; 999 | } 1000 | else { 1001 | this.pingPath = `excelRtd/ping/${this.providerName}`; 1002 | } 1003 | this.logger.debug(this.loggerName + `: Publishing ping message for this provider (${this.providerName}) to excel on topic: ${this.pingPath}.`); 1004 | yield fin.InterApplicationBus.publish(`${this.pingPath}`, true); 1005 | }); 1006 | } 1007 | establishHeartbeat() { 1008 | this.heartbeatPath = `excelRtd/heartbeat/${this.providerName}`; 1009 | this.heartbeatToken = setInterval(() => { 1010 | this.logger.debug(`Heartbeating for ${this.heartbeatPath}.`); 1011 | fin.InterApplicationBus.publish(`${this.heartbeatPath}`, this.heartbeatIntervalInMilliseconds); 1012 | }, this.heartbeatIntervalInMilliseconds); 1013 | } 1014 | onSubscribe(topic) { 1015 | this.logger.debug(this.loggerName + `: Subscription for rtdTopic ${topic} found. Dispatching connected event for rtdTopic.`); 1016 | this.dispatchEvent(this.connectedKey, { topic }); 1017 | } 1018 | onUnsubscribe(topic) { 1019 | this.logger.debug(this.loggerName + `: Unsubscribe for rtdTopic ${topic}. Dispatching disconnected event for rtdTopic.`); 1020 | this.dispatchEvent(this.disconnectedKey, { topic }); 1021 | } 1022 | clear() { 1023 | let path = `excelRtd/clear/${this.providerName}`; 1024 | this.logger.debug(this.loggerName + `: Clear called. Publishing to excel on topic: ${path} `); 1025 | fin.InterApplicationBus.publish(`excelRtd/clear/${this.providerName}`, true); 1026 | } 1027 | } 1028 | exports.ExcelRtd = ExcelRtd; 1029 | //# sourceMappingURL=ExcelRtd.js.map 1030 | 1031 | /***/ }), 1032 | /* 7 */ 1033 | /***/ (function(module, exports, __webpack_require__) { 1034 | 1035 | "use strict"; 1036 | 1037 | Object.defineProperty(exports, "__esModule", { value: true }); 1038 | exports.ExcelWorkbook = void 0; 1039 | const RpcDispatcher_1 = __webpack_require__(0); 1040 | class ExcelWorkbook extends RpcDispatcher_1.RpcDispatcher { 1041 | constructor(application, name) { 1042 | super(application.logger); 1043 | this.worksheets = {}; 1044 | this.connectionUuid = application.connectionUuid; 1045 | this.application = application; 1046 | this.workbookName = name; 1047 | } 1048 | getDefaultMessage() { 1049 | return { 1050 | workbook: this.workbookName 1051 | }; 1052 | } 1053 | getWorksheets(callback) { 1054 | return this.invokeExcelCall("getWorksheets", null, callback); 1055 | } 1056 | getWorksheetByName(name) { 1057 | return this.worksheets[name]; 1058 | } 1059 | addWorksheet(callback) { 1060 | return this.invokeExcelCall("addSheet", null, callback); 1061 | } 1062 | activate() { 1063 | return this.invokeExcelCall("activateWorkbook"); 1064 | } 1065 | save() { 1066 | return this.invokeExcelCall("saveWorkbook"); 1067 | } 1068 | close() { 1069 | return this.invokeExcelCall("closeWorkbook"); 1070 | } 1071 | refreshObject() { 1072 | this.objectInstance = null; 1073 | this.toObject(); 1074 | } 1075 | toObject() { 1076 | return this.objectInstance || (this.objectInstance = { 1077 | addEventListener: this.addEventListener.bind(this), 1078 | removeEventListener: this.removeEventListener.bind(this), 1079 | name: this.workbookName, 1080 | activate: this.activate.bind(this), 1081 | addWorksheet: this.addWorksheet.bind(this), 1082 | close: this.close.bind(this), 1083 | getWorksheetByName: name => this.getWorksheetByName(name).toObject(), 1084 | getWorksheets: this.getWorksheets.bind(this), 1085 | save: this.save.bind(this) 1086 | }); 1087 | } 1088 | } 1089 | exports.ExcelWorkbook = ExcelWorkbook; 1090 | //# sourceMappingURL=ExcelWorkbook.js.map 1091 | 1092 | /***/ }), 1093 | /* 8 */ 1094 | /***/ (function(module, exports, __webpack_require__) { 1095 | 1096 | "use strict"; 1097 | 1098 | Object.defineProperty(exports, "__esModule", { value: true }); 1099 | exports.ExcelWorksheet = void 0; 1100 | const RpcDispatcher_1 = __webpack_require__(0); 1101 | class ExcelWorksheet extends RpcDispatcher_1.RpcDispatcher { 1102 | constructor(name, workbook) { 1103 | super(workbook.logger); 1104 | this.connectionUuid = workbook.connectionUuid; 1105 | this.workbook = workbook; 1106 | this.worksheetName = name; 1107 | } 1108 | getDefaultMessage() { 1109 | return { 1110 | workbook: this.workbook.workbookName, 1111 | worksheet: this.worksheetName 1112 | }; 1113 | } 1114 | setCells(values, offset) { 1115 | if (!offset) 1116 | offset = "A1"; 1117 | return this.invokeExcelCall("setCells", { offset: offset, values: values }); 1118 | } 1119 | getCells(start, offsetWidth, offsetHeight, callback) { 1120 | return this.invokeExcelCall("getCells", { start: start, offsetWidth: offsetWidth, offsetHeight: offsetHeight }, callback); 1121 | } 1122 | getRow(start, width, callback) { 1123 | return this.invokeExcelCall("getCellsRow", { start: start, offsetWidth: width }, callback); 1124 | } 1125 | getColumn(start, offsetHeight, callback) { 1126 | return this.invokeExcelCall("getCellsColumn", { start: start, offsetHeight: offsetHeight }, callback); 1127 | } 1128 | activate() { 1129 | return this.invokeExcelCall("activateSheet"); 1130 | } 1131 | activateCell(cellAddress) { 1132 | return this.invokeExcelCall("activateCell", { address: cellAddress }); 1133 | } 1134 | addButton(name, caption, cellAddress) { 1135 | return this.invokeExcelCall("addButton", { address: cellAddress, buttonName: name, buttonCaption: caption }); 1136 | } 1137 | setFilter(start, offsetWidth, offsetHeight, field, criteria1, op, criteria2, visibleDropDown) { 1138 | return this.invokeExcelCall("setFilter", { 1139 | start: start, 1140 | offsetWidth: offsetWidth, 1141 | offsetHeight: offsetHeight, 1142 | field: field, 1143 | criteria1: criteria1, 1144 | op: op, 1145 | criteria2: criteria2, 1146 | visibleDropDown: visibleDropDown 1147 | }); 1148 | } 1149 | formatRange(rangeCode, format, callback) { 1150 | return this.invokeExcelCall("formatRange", { rangeCode: rangeCode, format: format }, callback); 1151 | } 1152 | clearRange(rangeCode, callback) { 1153 | return this.invokeExcelCall("clearRange", { rangeCode: rangeCode }, callback); 1154 | } 1155 | clearRangeContents(rangeCode, callback) { 1156 | return this.invokeExcelCall("clearRangeContents", { rangeCode: rangeCode }, callback); 1157 | } 1158 | clearRangeFormats(rangeCode, callback) { 1159 | return this.invokeExcelCall("clearRangeFormats", { rangeCode: rangeCode }, callback); 1160 | } 1161 | clearAllCells(callback) { 1162 | return this.invokeExcelCall("clearAllCells", null, callback); 1163 | } 1164 | clearAllCellContents(callback) { 1165 | return this.invokeExcelCall("clearAllCellContents", null, callback); 1166 | } 1167 | clearAllCellFormats(callback) { 1168 | return this.invokeExcelCall("clearAllCellFormats", null, callback); 1169 | } 1170 | setCellName(cellAddress, cellName) { 1171 | return this.invokeExcelCall("setCellName", { address: cellAddress, cellName: cellName }); 1172 | } 1173 | calculate() { 1174 | return this.invokeExcelCall("calculateSheet"); 1175 | } 1176 | getCellByName(cellName, callback) { 1177 | return this.invokeExcelCall("getCellByName", { cellName: cellName }, callback); 1178 | } 1179 | protect(password) { 1180 | return this.invokeExcelCall("protectSheet", { password: password ? password : null }); 1181 | } 1182 | renameSheet(name) { 1183 | return this.invokeExcelCall("renameSheet", { worksheetName: name }); 1184 | } 1185 | toObject() { 1186 | return this.objectInstance || (this.objectInstance = { 1187 | addEventListener: this.addEventListener.bind(this), 1188 | removeEventListener: this.removeEventListener.bind(this), 1189 | name: this.worksheetName, 1190 | activate: this.activate.bind(this), 1191 | activateCell: this.activateCell.bind(this), 1192 | addButton: this.addButton.bind(this), 1193 | calculate: this.calculate.bind(this), 1194 | clearAllCellContents: this.clearAllCellContents.bind(this), 1195 | clearAllCellFormats: this.clearAllCellFormats.bind(this), 1196 | clearAllCells: this.clearAllCells.bind(this), 1197 | clearRange: this.clearRange.bind(this), 1198 | clearRangeContents: this.clearRangeContents.bind(this), 1199 | clearRangeFormats: this.clearRangeFormats.bind(this), 1200 | formatRange: this.formatRange.bind(this), 1201 | getCellByName: this.getCellByName.bind(this), 1202 | getCells: this.getCells.bind(this), 1203 | getColumn: this.getColumn.bind(this), 1204 | getRow: this.getRow.bind(this), 1205 | protect: this.protect.bind(this), 1206 | renameSheet: this.renameSheet.bind(this), 1207 | setCellName: this.setCellName.bind(this), 1208 | setCells: this.setCells.bind(this), 1209 | setFilter: this.setFilter.bind(this) 1210 | }); 1211 | } 1212 | } 1213 | exports.ExcelWorksheet = ExcelWorksheet; 1214 | //# sourceMappingURL=ExcelWorksheet.js.map 1215 | 1216 | /***/ }), 1217 | /* 9 */ 1218 | /***/ (function(module, exports, __webpack_require__) { 1219 | 1220 | "use strict"; 1221 | 1222 | Object.defineProperty(exports, "__esModule", { value: true }); 1223 | // This is the entry point of the Plugin script 1224 | const ExcelApi_1 = __webpack_require__(3); 1225 | window.fin.desktop.ExcelService = ExcelApi_1.ExcelService.instance; 1226 | Object.defineProperty(window.fin.desktop, 'Excel', { 1227 | get() { return ExcelApi_1.ExcelService.instance.defaultApplicationObj; } 1228 | }); 1229 | //# sourceMappingURL=index.js.map 1230 | 1231 | /***/ }) 1232 | /******/ ]); --------------------------------------------------------------------------------