精讀《Excel JS API》

黃子毅 發表於 2022-01-11
Excel

Excel 現在可利用 js 根據單元格資料生成圖表、表格,或通過 js 擴充自定義函式擴充內建 Excel 表示式。

我們來學習一下 Excel js API 開放是如何設計的,從中學習到一些開放 API 設計經驗。

API 文件:Excel JavaScript API overview

精讀

Excel 將利用 JS API 開放了大量能力,包括使用者能通過介面輕鬆做到的,也包括無法通過介面操作做到的。

為什麼需要開放 JS API

Excel 已經具備了良好的易用性,以及 formula 這個強大的公式。在之前 精讀《Microsoft Power Fx》 提到過,formula 就是 Excel 裡的 Power FX,屬於畫布低程式碼語言,不過在 Excel 裡叫做 “公式” 更合適。

已經具備這麼多能力,為何還需要 JS API 呢?一句話概括就是,在 JS API 內可以使用 formula,即 JS API 是公式能力的超集,它包含了對 Excel 工作簿的增刪改查、資料的限制、RangeAreas 操作、圖表、透視表,甚至可以自定義 formula 函式。

也就是說,JS API 讓 Excel “可程式設計化”,即以開發者視角對 Excel 進行二次擴充,包括對公式進行二次擴充,使 Excel 覆蓋更多場景。

JS API 可以用在哪些地方

從 Excel 流程中最開始的工作薄、工作表環節,到最細節的單元格資料校驗都可通過 JS API 支援,目前看來 Excel JS API 並沒有設定能力邊界,而且還會不斷完善,將 Excel 全生命週期中一切可程式設計的地方開放出來。

首先是對工作薄、工作表的操作,以及對工作表使用者操作的監聽,或者對工作表進行只讀設定。這一類 API 的目的是對 Excel 這個整體進行程式設計操作。

第二步就是對單元格級別進行操作,比如對單元格進行區域選中,獲取選中區域,或者設定單元格屬性、顏色,或者對單元格資料進行校驗。自定義公式也在這個環節,因為單元格的值可以是公式,而公式可以利用 JS API 擴充。

最後一步是擴充行為,即在單元格基礎上引入圖表、透視表擴充。雖然這些功能在 UI 按鈕上也可以操作出來,但 JS API 可以實現 UI 介面配置不出來的邏輯,對於非常複雜的邏輯行為,即便 UI 可以配置出來,可讀性也遠沒有程式碼高。除了表格透視表外、還可以建立一些自定義形狀,基本的幾何圖形、圖片和 SVG 都支援。

JS API 設計

比較有趣的是,Excel 並沒有抽象 “單元格” 物件,即便我們所有人都認為單元格就是 Excel 的代表。

這麼做是出於 API 設計的合理性,因為 Excel 使用 Range 概念表示連續單元格。比如:

Excel.run(function (context) {
    var sheet = context.workbook.worksheets.getActiveWorksheet();

    var headers = [
      ["Product", "Quantity", "Unit Price", "Totals"]
    ];
    var headerRange = sheet.getRange("B2:E2");
    headerRange.values = headers;
    headerRange.format.fill.color = "#4472C4";
    headerRange.format.font.color = "white";

    return context.sync();
});

可以發現,Range 讓 Excel 聚焦在批量單元格 API,即把單元格看做一個範圍,整體 API 都可以圍繞一個範圍去設計。這種設計理念的好處是,把範圍侷限在單格單元格,就可以覆蓋 Cell 概念,而聚焦在多個單元格時,可以很方便的基於二維資料結構建立表格、折線圖等分析圖形,因為二維結構的資料才是結構化資料。

或者可以說,結構化資料是 Excel 最核心的概念,而單元格無法體現結構化。結構化資料的好處是,一張工作表就是一個可以用來分析的資料集,在其之上無論是基於單元格的條件格式,還是建立分析圖表,都是一種資料二次分析行為,這都得益於結構化資料,所以 Excel JS API 必然圍繞結構化資料進行抽象。

再從 API 語法來看,除了工作薄這個級別的 API 採用了 Excel.createWorkbook(); 之外,其他大部分 API 都是以下形式:

Excel.run(function (context) {
    // var sheet = context.workbook.worksheets.getItem("Sample");
    // 對 sheet 操作 ..
    return context.sync();
});

最外層的函式 Excel.run 是注入 context 用的,而且也可以保證執行的時候 Excel context 已經準備好了。而 context.sync() 是同步操作,即使當前對 context 的操作生效。所以 Excel JS API 是命令式的,也不會做類似 MVVM 的雙向繫結,所以在操作過程中資料和 Excel 狀態不會發生變化,直到執行 context.sync()

注意到這點後,就可以理解為什麼要把某些程式碼寫在 context.sync().then 裡了,比如:

Excel.run(function (ctx) {
  var pivotTable = context.workbook.worksheets.getActiveWorksheet().pivotTables.getItem("Farm Sales");

  // Get the totals for each data hierarchy from the layout.
  var range = pivotTable.layout.getDataBodyRange();
  var grandTotalRange = range.getLastRow();
  grandTotalRange.load("address");
  return context.sync().then(function () {
    // Sum the totals from the PivotTable data hierarchies and place them in a new range, outside of the PivotTable.
    var masterTotalRange = context.workbook.worksheets.getActiveWorksheet().getRange("E30");
    masterTotalRange.formulas = [["=SUM(" + grandTotalRange.address + ")"]];
  });
}).catch(errorHandlerFunction);

這個從透視表獲取資料的例子,只有執行 context.sync() 後才能拿到 grandTotalRange.address

總結

微軟還在 Office 套件 Excel、Outlook、Word 中推出了 ScriptLab 功能,就可以在 Excel 的 ScriptLab 裡編寫 Excel JS API。

在 Excel JS API 之上,還有一個 通用 API,定義為跨應用的通用 API,這樣 Excel JS API 就可以把精力聚焦在 Excel 產品本身能力上。

討論地址是:精讀《Excel JS API》· Issue #387 · dt-fe/weekly

如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公眾號

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證