如何让Google Sheets侧边栏自动同步工作表切换状态?
问题描述
我在Google Sheets中搭建了一个侧边栏,以列表项形式展示所有工作表名称:点击列表项可激活对应工作表并为该项添加active-nav类,同时通过服务端通信初始化激活状态。但直接切换Google Sheets的工作表标签时,侧边栏无法自动更新激活状态,希望实现无需刷新侧边栏,自动同步工作表切换的状态。
现有代码如下:
index.html
<div class="sidebar"> <ul class="sidebar-nav"> <li class="nav-item"> <a class="nav-btn" data-sheet-name="Sheet 1" id="sheet1" href="#"> <div>Sheet 1</div> </a> </li> <li class="nav-item"> <a class="nav-btn" data-sheet-name="Sheet 2" id="sheet2" href="#"> <div>Sheet 2</div> </a> </li> </ul> </div>
客户端JavaScript
<script> window.onload = function() { const navBtns = document.querySelectorAll('.nav-btn'); const idToFuncMap = new Map([ ['sheet1', google.script.run.goToSheet1], ['sheet2', google.script.run.goToSheet2], ]); navBtns.forEach((el) => { el.addEventListener('click', () => { const func = idToFuncMap.get(el.id); if (func) func(); }); }); navBtns.forEach(function(link) { link.addEventListener("click", function() { navBtns.forEach(function(link) { if (link.classList.contains("active-nav")) { link.classList.remove("active-nav"); } }); this.classList.add("active-nav"); }); }); } function onSuccess(sheetName) { navBtns.forEach(function(navBtn) { if (navBtn.getAttribute("data-sheet-name") === sheetName) { navBtn.classList.add("active-nav"); } else { navBtn.classList.remove("active-nav"); } }); } google.script.run.withSuccessHandler(onSuccess).getActiveSheetName(); </script>
Code.gs
function showNavigatorSidebar() { const ui = SpreadsheetApp.getUi(); const html = HtmlService.createTemplateFromFile("index").evaluate(); ui.showSidebar(html); function getActiveSheetName() { return SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName(); } function activateSheet(sheetName) { const ss = SpreadsheetApp.getActive(); const sheet = ss.getSheetByName(sheetName); sheet.activate(); } // Go to functions // BUTTONS function goToSheet1() { activateSheet('Sheet 1'); } function goToSheet2() { activateSheet('Sheet 2'); } }
解决方案
核心思路是利用Google Sheets的onSelectionChange触发器监听工作表切换事件,通过中间模态对话框跨iframe传递消息,通知侧边栏更新激活状态。
步骤1:重构Code.gs,添加触发器与消息广播
首先将嵌套在showNavigatorSidebar内的函数移到全局作用域(否则触发器无法调用),再添加监听与广播逻辑:
function showNavigatorSidebar() { const ui = SpreadsheetApp.getUi(); const html = HtmlService.createTemplateFromFile("index").evaluate(); ui.showSidebar(html); } function getActiveSheetName() { return SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName(); } function activateSheet(sheetName) { const ss = SpreadsheetApp.getActive(); const sheet = ss.getSheetByName(sheetName); sheet.activate(); } function goToSheet1() { activateSheet('Sheet 1'); } function goToSheet2() { activateSheet('Sheet 2'); } // 监听选区变化(含工作表切换) function onSelectionChange(e) { // 仅当工作表切换时触发更新 if (e.source.getActiveSheet().getName() !== e.oldRange.getSheet().getName()) { broadcastSheetChange(); } } // 向侧边栏广播当前工作表名称 function broadcastSheetChange() { const sheetName = getActiveSheetName(); const ui = SpreadsheetApp.getUi(); // 通过临时模态对话框传递消息,用户无感知 ui.showModalDialog(HtmlService.createHtmlOutput(` <script> window.top.google.script.run.withSuccessHandler(() => window.close()).updateSidebarActiveSheet('${sheetName}'); setTimeout(() => window.close(), 100); </script> `), ""); } // 接收广播并通知侧边栏更新 function updateSidebarActiveSheet(sheetName) { const htmlOutput = HtmlService.createHtmlOutput(` <script> // 定位侧边栏iframe并调用更新函数 const sidebarFrame = window.top.document.querySelector('iframe[title="工作表导航"]'); if (sidebarFrame) { sidebarFrame.contentWindow.updateActiveNav('${sheetName}'); } </script> `); SpreadsheetApp.getUi().showModalDialog(htmlOutput, ""); }
步骤2:修改客户端JavaScript,添加自动更新函数
将navBtns改为全局变量,新增updateActiveNav函数用于接收服务端通知:
<script> let navBtns; // 全局变量,方便跨函数调用 window.onload = function() { navBtns = document.querySelectorAll('.nav-btn'); const idToFuncMap = new Map([ ['sheet1', google.script.run.goToSheet1], ['sheet2', google.script.run.goToSheet2], ]); navBtns.forEach((el) => { el.addEventListener('click', () => { const func = idToFuncMap.get(el.id); if (func) func(); }); }); navBtns.forEach(function(link) { link.addEventListener("click", function() { navBtns.forEach(function(link) { if (link.classList.contains("active-nav")) { link.classList.remove("active-nav"); } }); this.classList.add("active-nav"); }); }); // 初始化激活状态 google.script.run.withSuccessHandler(onSuccess).getActiveSheetName(); } function onSuccess(sheetName) { navBtns.forEach(function(navBtn) { if (navBtn.getAttribute("data-sheet-name") === sheetName) { navBtn.classList.add("active-nav"); } else { navBtn.classList.remove("active-nav"); } }); } // 新增:接收服务端通知,更新侧边栏激活状态 function updateActiveNav(sheetName) { navBtns.forEach(function(navBtn) { if (navBtn.getAttribute("data-sheet-name") === sheetName) { navBtn.classList.add("active-nav"); } else { navBtn.classList.remove("active-nav"); } }); } </script>
步骤3:设置侧边栏标题(可选)
在showNavigatorSidebar中给侧边栏添加标题,方便定位iframe:
function showNavigatorSidebar() { const ui = SpreadsheetApp.getUi(); const html = HtmlService.createTemplateFromFile("index") .evaluate() .setTitle("工作表导航"); // 设置标题,用于后续定位iframe ui.showSidebar(html); }
说明
onSelectionChange是Google Sheets的内置简单触发器,会在用户切换单元格或工作表时触发,我们通过判断新旧选区的工作表差异,精准捕获切换事件。- 由于侧边栏与主Sheet属于不同iframe,直接跨域通信受限,因此用临时模态对话框作为消息中转,对话框会快速自动关闭,用户几乎无感知。
内容的提问来源于stack exchange,提问作者psych0groov3
相关产品推荐
相关产品推荐

