電子試算表已成為現代計算機的重要組成部分。它們允許用戶以表格格式組織、操作和分析數據。
像Google Sheets這樣的應用程式為功能強大、互動式的電子試算表設定了標準。在本博客文章中,我們將帶您逐步了解使用JavaScript構建電子試算表應用程式的過程。我們將聚焦於關鍵的編程概念,探索JavaScript的功能,並包含帶有詳細解釋的代碼片段。
整個源代碼可以在我的Codepen中這裡找到。
什麼是Google電子表格?
Google電子表格是一個基於Web的應用程式,允許用戶在線上創建、編輯和協作電子表格。它提供了公式、數據驗證、圖表和條件格式設定等功能。
我們的項目模擬了Google電子表格的一些核心功能,專注於:
- 可編輯的單元格。
- 公式解析和評估。
- 通過發佈/訂閱模型進行的即時更新。
- 鍵盤導航和選取單元格。
- 單元格之間的動態依賴評估。
此項目的功能
- 可編輯的單元格: 允許用戶在單元格中輸入文本或方程式。
- 公式支持:處理以
=
開頭的公式並評估表達式。 - 即時更新:依賴單元格的更改使用發布/訂閱模型觸發更新。
- 鍵盤導航:啟用使用箭頭鍵在單元格之間移動。
- 動態評估:確保公式即時更新,依賴於其他單元格。
- 錯誤處理:為無效輸入或循環依賴提供有意義的錯誤消息。
- 可擴展設計: 允許輕鬆擴展以添加更多行、列或功能。
應用程序的關鍵組件
1. 模式管理
JavaScript
const Mode = {
EDIT: 'edit',
DEFAULT: 'default'
};
此枚舉定義了兩種模式:
- 編輯:啟用對所選單元格的編輯。
- 默認:允許在不進行編輯的情況下進行導航和交互。
為什麼使用模式?
模式簡化了UI狀態的管理。例如,在默認模式下,鍵盤輸入在單元格之間移動,而在編輯模式下,輸入修改單元格內容。
2. 發布/訂閱類
發布/訂閱模型處理訂閱和即時更新。單元格可以訂閱其他單元格,在依賴關係更改時動態更新。
JavaScript
class PubSub {
constructor() {
this.map = {};
}
get(source) {
let result = [];
let queue = [ (this.map[source] || [])];
while (queue.length) {
let next = queue.shift();
result.push(next.toUpperCase());
if (this.map[next]) queue.unshift(this.map[next]);
}
return result;
}
subscribeAll(sources, destination) {
sources.forEach((source) => {
this.map[source] = this.map[source] || [];
this.map[source].push(destination);
});
}
}
主要功能:
- 動態依賴管理:跟踪單元格之間的依賴關係。
- 更新傳播:當源單元格更改時更新依賴單元格。
- 廣度優先搜索:通過跟踪所有依賴節點來避免無限循環。
示例用法:
JavaScript
let ps = new PubSub();
ps.subscribeAll(['A1'], 'B1');
ps.subscribeAll(['B1'], 'C1');
console.log(ps.get('A1')); // Output: ['B1', 'C1']
3. 創建行和單元格
JavaScript
class Cell {
constructor(cell, row, col) {
cell.id = `${String.fromCharCode(col + 65)}${row}`;
cell.setAttribute('data-eq', '');
cell.setAttribute('data-value', '');
if (row > 0 && col > -1) cell.classList.add('editable');
cell.textContent = col === -1 ? row : '';
}
}
class Row {
constructor(row, r) {
for (let c = -1; c < 13; c++) {
new Cell(row.insertCell(), r, c);
}
}
}
關鍵功能:
- 動態表格生成: 允許以編程方式添加行和列。
- 單元格識別: 基於位置生成ID(例如,A1、B2)。
- 可編輯的單元格: 只有在有效(非標題行/列)的情況下才能編輯單元格。
為什麼使用動態行和單元格?
這種方法使表格的大小具有可伸縮性和靈活性,支持添加行或列而無需更改結構。
4. 互動事件處理
JavaScript
addEventListeners() {
this.table.addEventListener('click', this.onCellClick.bind(this));
this.table.addEventListener('dblclick', this.onCellDoubleClick.bind(this));
window.addEventListener('keydown', this.onKeyDown.bind(this));
}
關鍵功能:
- 點擊事件: 選擇或編輯單元格。
- 雙擊事件: 啟用公式編輯。
- 按鍵按下事件: 支持使用箭頭鍵進行導航。
5. 公式解析和評估
JavaScript
function calcCell(expression) {
if (!expression) return 0;
return expression.split('+').reduce((sum, term) => {
let value = isNaN(term) ? getCellValue(term) : Number(term);
if (value === null) throw new Error(`Invalid cell: ${term}`);
return sum + Number(value);
}, 0);
}
關鍵功能:
- 動態計算: 計算引用其他單元格的公式。
- 遞歸評估: 解析嵌套依賴關係。
- 錯誤處理: 識別無效引用和循環依賴關係。
6. 用戶輸入的錯誤處理
JavaScript
function isValidCell(str) {
let regex = /^[A-Z]{1}[0-9]+$/;
return regex.test(str);
}
關鍵功能:
- 驗證: 確保輸入引用有效的單元格ID。
- 可擴展性: 支持動態擴展表格而不破壞驗證。
JavaScript 主題涵蓋
1. 事件處理
管理互動,如點擊和按鍵操作。
Plain Text
window.addEventListener('keydown', this.onKeyDown.bind(this));
2. DOM 操作
動態創建和修改 DOM 元素。
Plain Text
let cell = document.createElement('td'); cell.appendChild(document.createTextNode('A1'));
3. 遞迴
動態處理依賴。
Plain Text
function calcCell(str) { if (isNaN(str)) { return calcCell(getCellValue(str)); } }
4. 錯誤處理
檢測無效單元格和循環依賴。
Plain Text
if (!isValidCell(p)) throw new Error(`invalid cell ${p}`);
結論
此項目展示了使用JavaScript創建的強大試算表。它利用事件處理、遞迴和發布/訂閱模式,為複雜的Web應用程序奠定基礎。通過添加數據導出、圖表或格式化規則等功能來擴展它。
參考資料
Source:
https://dzone.com/articles/spreadsheet-application-javascript-guide