ASP.NET WebControl 渲染流程為 OnLoad → Render → RenderContents。SkyTreeViewControl 在此管線中精心編排三段 HTML 輸出,分別承擔動態載入基礎設施、XSLT 模板與初始化腳本、節點互動邏輯三大職責,並透過 RegisterStartupScript 確保腳本在 </form> 前正確輸出。本文逐段拆解這三段 HTML 的生成邏輯、關鍵 JavaScript 函數與資料流向。
if (this.DynamicLoadChildNodes)
{
// 輸出第一段 HTML 區塊
}
<!-- XML 資料島:用於非同步載入子節點 XML 定義 -->
<xml id='SkyTreeViewControlTempXML'
onreadystatechange='SkyTreeViewControlDynamicLoadChildNodes()'></xml>
<script language=javascript>
// 當前正在動態載入的根節點 ID
var SkyTreeViewControlDyanmicRootNodeID;
// 動態載入子節點主流程
function SkyTreeViewControlDynamicLoadChildNodes() {
var xml = document.getElementById('SkyTreeViewControlTempXML');
if (!xml) return;
if (xml.readyState != 'complete') return; // 尚未載入完成
// 取得父節點文字元素
var rootNode = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_text');
if (!rootNode) return;
var html = '';
var errorFlag = false;
// 取得 XSLT 模板資料島
var xsl = document.getElementById('SkyTreeViewControlXSLT');
if (!xsl) { html = '缺失 XSLT 資料'; errorFlag = true; }
else if (xml.XMLDocument.parseError.errorCode != 0) {
html = '錯誤: ' + xml.XMLDocument.parseError.reason + '[' + xml.XMLDocument.parseError.url + ']';
errorFlag = true;
}
else if (!xml.XMLDocument.documentElement) {
html = '未找到根節點'; errorFlag = true;
}
else {
// 關鍵:將父節點 ID 寫入 XML 文件,供 XSLT 模板讀取
xml.XMLDocument.documentElement.setAttribute('RootID', SkyTreeViewControlDyanmicRootNodeID);
// 執行 XSLT 轉換 → 產生子節點 HTML
html = xml.XMLDocument.transformNode(xsl.XMLDocument);
}
// 移除 Loading 提示
var loading = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_Loading');
if (loading) {
if (errorFlag) { loading.innerHTML = '<br/>' + html; return; }
loading.insertAdjacentHTML('afterEnd', html);
loading.parentNode.removeChild(loading);
}
else {
rootNode.insertAdjacentHTML('afterEnd', html);
}
// 更新展開/收合圖示狀態
var expend = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_expend');
var icon = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_icon');
var text = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_text');
var table = document.getElementById(SkyTreeViewControlDyanmicRootNodeID + '_table');
if (expend && icon && text && table) {
// 交換 src 與 SrcBack 實現圖示切換
var tmp = icon.src; icon.src = icon.getAttribute('SrcBack'); icon.setAttribute('SrcBack', tmp);
tmp = expend.src; expend.src = expend.getAttribute('SrcBack'); expend.setAttribute('SrcBack', tmp);
}
SkyTreeViewControlDyanmicRootNodeID = null; // 重置
}
</script>
| 技術點 | 實作細節 | 優勢 |
|---|---|---|
| XML Data Island | <xml id=...> + onreadystatechange | IE 原生非同步 XML 載入,無需 XMLHttpRequest 物件 |
| 狀態機驅動 | readyState == 'complete' 才執行轉換 | 避免部分載入導致轉換失敗 |
| RootID 回寫 | setAttribute('RootID', ...) | 讓 XSLT 模板知曉父節點 ID,生成正確的子節點 ID 前綴 |
| transformNode() | MSXML 核心方法,C++ 實作極快 | 客戶端 XSLT 轉換效能遠超 JavaScript 字串拼接 |
| insertAdjacentHTML | DOM 範圍插入,不破壞現有事件綁定 | 比 innerHTML 更精確、效能更好 |
| SrcBack 雙緩衝 | 交換 src/SrcBack 實現圖示切換 | 無需預載入圖片,零延遲切換 |
if (!this.GenerateAtServer || this.DynamicLoadChildNodes)
{
// 輸出第二段 HTML 區塊
}
<!-- XSLT 模板資料島:從嵌入資源 SkyTreeViewControl.xslt 讀取 -->
<xml id='SkyTreeViewControlXSLT'>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" version="1.0">
... 完整 XSLT 內容 ...
</xsl:stylesheet>
</xml>
<script language=javascript>
// 樹狀列表初始化函數
function RefreshSkyTreeViewControl(id) {
var xml = document.getElementById(id + '_xml'); // 節點資料 XML 資料島
var xsl = document.getElementById('SkyTreeViewControlXSLT'); // XSLT 模板
var container = document.getElementById(id + '_container'); // 顯示容器
if (!container) { alert('未找到目標'); return; }
if (!xml) { container.innerText = '缺失 XML 資料'; return; }
if (!xsl) { container.innerText = '缺失 XSLT 資料'; return; }
// 執行 XSLT 轉換並填充容器
var html = xml.XMLDocument.transformNode(xsl.XMLDocument);
container.innerHTML = html;
}
</script>
private static string _cachedXslt = null;
private string ReadXSLTString()
{
if (_cachedXslt == null)
{
foreach (string name in GetType().Assembly.GetManifestResourceNames())
{
if (name.EndsWith(".SkyTreeViewControl.xslt"))
{
using (Stream stream = GetType().Assembly.GetManifestResourceStream(name))
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
_cachedXslt = reader.ReadToEnd();
}
return _cachedXslt;
}
}
throw new Exception("未找到嵌入資源 SkyTreeViewControl.xslt");
}
return _cachedXslt;
}
<script language=javascript>
// 當前高亮節點
var SkyTreeViewContrlCurrentTreeNode;
// 展開/收合/高亮統一入口
function SkyTreeViewContrlExpendNodeByID(strID, bolSelect) {
var expend = document.getElementById(strID + '_expend');
var icon = document.getElementById(strID + '_icon');
var text = document.getElementById(strID + '_text');
var table = document.getElementById(strID + '_table');
if (!text) return;
// 高亮處理
if (bolSelect) {
if (SkyTreeViewContrlCurrentTreeNode != text && SkyTreeViewContrlCurrentTreeNode) {
SkyTreeViewContrlCurrentTreeNode.className = 'SkyTreeViewControl_TreeNode';
}
SkyTreeViewContrlCurrentTreeNode = text;
text.className = 'SkyTreeViewControl_SelectedNode';
}
// 展開/收合處理
if (expend && icon && table) {
// 交換圖示
var tmp = icon.src; icon.src = icon.getAttribute('SrcBack'); icon.setAttribute('SrcBack', tmp);
tmp = expend.src; expend.src = expend.getAttribute('SrcBack'); expend.setAttribute('SrcBack', tmp);
// 切換子節點表格顯示/隱藏
table.style.display = (table.style.display != 'none') ? 'none' : '';
}
// 動態載入觸發
var dynamicLoad = false;
if (!table) { // 無子節點表格 → 可能需動態載入
var xmlSource = text.getAttribute('XMLSource');
if (xmlSource && xmlSource.length > 0) {
text.removeAttribute('XMLSource'); // 防止重複觸發
var xmlIsland = document.getElementById('SkyTreeViewControlTempXML');
if (xmlIsland) {
SkyTreeViewControlDyanmicRootNodeID = strID;
// 顯示 Loading
var loading = document.getElementById(strID + '_Loading');
if (loading) loading.style.display = '';
// 觸發 XML 載入 → onreadystatechange → 第一段腳本執行
xmlIsland.src = xmlSource;
dynamicLoad = true;
}
}
}
if (!dynamicLoad) {
// text.scrollIntoView(false); // 可選:滾動到可視區
}
}
</script>
StringWriter strWriter = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(strWriter);
if (this.IndentXML && !this.GenerateAtServer)
{
xmlWriter.Indentation = 3;
xmlWriter.IndentChar = ' ';
xmlWriter.Formatting = Formatting.Indented;
}
// XML 序列化:SkyTreeNodeList → XML
XmlSerializer serializer = new XmlSerializer(typeof(SkyTreeNodeList));
serializer.Serialize(xmlWriter, this.Nodes);
xmlWriter.Close();
string xml = strWriter.ToString(); // 完整節點 XML
if (this.GenerateAtServer)
{
// 載入 XSLT 模板
string xslt = ReadXSLTString();
XmlDocument xsltDoc = new XmlDocument(); xsltDoc.LoadXml(xslt);
XmlDocument nodeDoc = new XmlDocument(); nodeDoc.LoadXml(xml);
// XSLT 編譯轉換 (NET 2.0+)
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xsltDoc);
StringWriter output = new StringWriter();
transform.Transform(nodeDoc, null, output);
// 直接寫入回應流
writer.Write(output.ToString());
return;
}
// 輸出容器與 XML 資料島
writer.Write($"<span id='{ClientID}_container'>正在載入...</span>");
writer.Write($"<xml id='{ClientID}_xml'>{xml}</xml>");
// 註冊啟動腳本:頁面載入完成後自動初始化
Page.ClientScript.RegisterStartupScript(
GetType(),
"SkyTreeViewControl_Init_" + ClientID,
$"");
protected override object SaveViewState()
{
ViewState["nodes"] = _nodes; // SkyTreeNodeList 需 [Serializable]
return base.SaveViewState();
}
protected override void LoadViewState(object savedState)
{
_nodes = ViewState["nodes"] as SkyTreeNodeList;
base.LoadViewState(savedState);
}
關鍵:SkyTreeNode 與 SkyTreeNodeList 必須標記 [Serializable()],否則 ViewState 序列化失敗。
僅 IE 支援。Firefox/Chrome 需改用 XMLHttpRequest + XSLTProcessor:
// 現代跨瀏覽器實作概念
fetch(xmlSource)
.then(r => r.text())
.then(xmlText => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
const xsltDoc = parser.parseFromString(xsltText, "text/xml");
const processor = new XSLTProcessor();
processor.importStylesheet(xsltDoc);
const resultDoc = processor.transformToDocument(xmlDoc);
container.innerHTML = new XMLSerializer().serializeToString(resultDoc);
});
三段式 HTML 輸出架構實現了關注點分離:
RefreshSkyTreeViewControl,支援客戶端渲染SkyTreeViewContrlExpendNodeByID,統一處理展開/收合/高亮/動態觸發伺服器端透過 GenerateAtServer 旗標決定是否執行 XSLT 轉換,大數據量走客戶端渲染 (傳輸 XML),小數據量走伺服器端渲染 (傳輸 HTML),完美平衡頻寬與 CPU。這種雙模式渲染 + 按需載入 + 純資料傳輸的設計,是現代前端框架 (SSR/CSR 混合、Suspense、React Server Components) 的早期雛形。