Coolite (現更名為 Ext.NET) 的 TabPanel 是建構多標籤介面的核心容器。本文整理三種建立分頁的方式:靜態宣告、伺服器端動態建立 (AjaxEvent)、客戶端動態建立 (JavaScript API),並示範如何將其他容器 (如 TreePanel) 整合為分頁內容。
<ext:TabPanel ID="tabPanel" runat="server" Width="800" Height="500">
<Tabs>
<ext:Tab runat="server" Title="首頁" Closable="false">
<Body>
<p>歡迎使用系統</p>
</Body>
</ext:Tab>
<ext:Tab runat="server" Title="報表" Closable="true">
<AutoLoad Mode="IFrame" Url="Report.aspx" NoCache="true" />
</ext:Tab>
</Tabs>
</ext:TabPanel>
適用於固定、少量的分頁。`AutoLoad.Mode="IFrame"` 可隔離 CSS/JS,適合載入完整頁面。
關鍵點:一般 PostBack 會導致動態建立的控制項消失,必須使用 Coolite 的 AjaxEvent 機制 (非同步回發),並在回發後重新加入控制項樹。
[DirectMethod] // 或 AjaxEvent 處理函式
protected void CreateTab_Click(object sender, DirectEventArgs e)
{
var tab = new Tab("動態新增分頁 " + DateTime.Now.ToString("HH:mm:ss"))
{
ID = "tab_" + Guid.NewGuid().ToString("N").Substring(0, 8),
Closable = true,
AutoLoad = new AutoLoadConfig
{
Url = "https://www.example.com",
Mode = LoadMode.IFrame,
NoCache = true,
ShowMask = true,
MaskMsg = "載入中..."
}
};
tabPanel.Tabs.Add(tab);
tabPanel.ActiveTab = tab; // 切換到新分頁
}
<ext:Button ID="btnCreateTab" runat="server" Text="新增分頁 (Server)">
<DirectEvents>
<Click OnEvent="CreateTab_Click" />
</DirectEvents>
</ext:Button>
純前端操作,無 PostBack,效能最好,支援即時互斥檢查 (避免重複分頁)。
<script type="text/javascript">
function addTab(tabPanelId, tabId, url, title) {
var tabPanel = Ext.getCmp(tabPanelId);
if (!tabPanel) return;
// 檢查是否已存在
var existing = tabPanel.getComponent(tabId);
if (existing) {
tabPanel.setActiveTab(existing);
return;
}
// 建立新分頁
var newTab = tabPanel.add({
id: tabId,
title: title || url,
closable: true,
autoLoad: {
showMask: true,
url: url,
mode: 'iframe',
maskMsg: '正在載入...'
}
});
tabPanel.setActiveTab(newTab);
}
</script>
<ext:Button ID="btnClient" runat="server" Text="新增分頁 (Client)">
<Listeners>
<Click Handler="addTab('tabPanel', 'tab_google', 'https://www.google.com', 'Google');" />
</Listeners>
</ext:Button>
Ext.NET 允許將任何 `Component` 動態加入 `TabPanel`,實現「分頁即容器」的彈性佈局。
<ext:TreePanel ID="treePanel" runat="server" Title="導航樹" RootVisible="false">
<Root>
<ext:Node Text="根節點" Expanded="true">
<Nodes>
<ext:Node Text="子節點 1" leaf="true" />
<ext:Node Text="子節點 2" leaf="true" />
</Nodes>
</ext:Node>
</Root>
</ext:TreePanel>
<ext:Button ID="btnAddTreeAsTab" runat="server" Text="將樹加為分頁">
<Listeners>
<Click Handler="
var tabPanel = #{tabPanel};
var tree = #{treePanel};
var tabId = 'tab_tree';
// 移除樹原本的容器 (若已渲染在別處)
// tree.ownerCt?.remove(tree, false);
var tab = tabPanel.getComponent(tabId);
if (!tab) {
tab = tabPanel.add({
id: tabId,
title: '樹狀導航',
closable: true,
layout: 'fit',
items: [tree] // 直接將元件實例加入
});
}
tabPanel.setActiveTab(tab);
" />
</Listeners>
</ext:Button>
// 儲存使用者開啟的分頁狀態到 Cookie/LocalStorage
function saveTabState(tabPanel) {
var tabs = tabPanel.items.map(t => ({ id: t.id, title: t.title, url: t.autoLoad?.url }));
localStorage.setItem('myTabState', JSON.stringify(tabs));
}
// 頁面載入時還原
function restoreTabState(tabPanel) {
var state = JSON.parse(localStorage.getItem('myTabState') || '[]');
state.forEach(t => addTab(tabPanel.id, t.id, t.url, t.title));
if (state.length) tabPanel.setActiveTab(state[state.length-1].id);
}
// 父頁面監聽
window.addEventListener('message', function(e) {
if (e.origin !== 'https://trusted-child.com') return;
if (e.data.type === 'resize') {
var iframe = document.getElementById(e.data.tabId + '_iframe');
if (iframe) iframe.style.height = e.data.height + 'px';
}
});
// 子頁面發送
parent.postMessage({ type: 'resize', tabId: 'tab_1', height: document.body.scrollHeight }, '*');
// 僅在分頁被啟用時才載入內容
autoLoad: {
url: 'HeavyReport.aspx',
mode: 'iframe',
noCache: true,
// Ext.NET 會自動處理:非活動分頁不載入
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 動態分頁 PostBack 後消失 | 一般 PostBack 不會重建動態控制項 | 改用 DirectEvent/AjaxEvent 或客戶端建立 |
| IFrame 內頁面無法存取父頁面 JS | 同源策略限制 | 同域用 parent.xxx,跨域用 postMessage |
| 分頁標題過長導致版面破裂 | CSS 未處理長文字 | 加 CSS `.x-tab-strip-text { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-width: 150px; }` |
| 大量分頁記憶體洩漏 | 未銷毀 IFrame/Component | 關閉分頁時呼叫 `tab.destroy()`,或設 `autoDestroy: true` |
掌握這三種模式與進階技巧,即可駕馭 Ext.NET TabPanel 建構企業級多標籤應用介面。