前言
1. 你可以通過捐贈獲取 AppBox 的完整源代碼:http://fineui.com/donate/
2. AppBox v3.0於 2013-08 發布,采用了EF CodeFirst開發模式和扁平化的設計理念:http://www.cnblogs.com/sanshi/p/3274122.html
AppBox v3.0中的子頁面向父頁面傳值
AppBox中實現子頁面向父頁面傳值,邏輯代碼比較簡單,完全使用FineUI的內置封裝,沒有引入JavaScript代碼。首先來看下實現效果:
當點擊所屬角色的觸發器輸入框(TriggerBox)時,會在當前頁面彈出一個包含IFrame的窗體控件(Window),在其中選擇需要的數據后關閉。
父頁面代碼和邏輯
在父頁面,我們通過一個 TriggerBox 來記錄選中的文本信息,一個隱藏字段 HiddenField 來記錄選中的值信息:
<f:TriggerBox ID="tbSelectedRole" EnableEdit="false" EnablePostBack="false" TriggerIcon="Search" Label="所屬角色" runat="server"> </f:TriggerBox> <f:HiddenField ID="hfSelectedRole" runat="server"> </f:HiddenField>
點擊 TriggerBox 觸發圖標的客戶端操作是通過服務器端初始化的,這其實是符合FineUI最初的設計目標:盡量減少客戶端腳本,降低代碼復雜度。
private void InitUserRole(User current) { tbSelectedRole.Text = String.Join(",", current.Roles.Select(u => u.Name).ToArray()); hfSelectedRole.Text = String.Join(",", current.Roles.Select(u => u.ID).ToArray()); // 打開編輯角色的窗口 string selectRoleURL = String.Format("./user_select_role.aspx?ids=<script>{0}</script>", hfSelectedRole.GetValueReference()); tbSelectedRole.OnClientTriggerClick = Window1.GetSaveStateReference(hfSelectedRole.ClientID, tbSelectedRole.ClientID) + Window1.GetShowReference(selectRoleURL, "選擇用戶所屬的角色"); }
仔細觀察這段代碼,可以看到FineUI的努力和身影,我們盡量將常用操作提取成公共的方法。
比如這里的:
hfSelectedRole.GetValueReference()
則是返回一段JavaScript腳本,本質上點擊操作是客戶端完成的,因此需要在點擊的時候獲取隱藏輸入框的值,而不是調用InitUserRole初始化的時候!!
再比如這里:
Window1.GetSaveStateReference(hfSelectedRole.ClientID, tbSelectedRole.ClientID)
這是FineUI提供的另一個機制:告訴FineUI子頁面回發數據時,需要將數據保存到父頁面的哪些控件中?
子頁面代碼和邏輯
看完父頁面的代碼,再來看下子頁面怎么返回值。首先,子頁面有一個選擇的按鈕,和一個復選框列表控件:
<f:Button ID="btnSaveClose" ValidateForms="SimpleForm1" Icon="SystemSaveClose" OnClick="btnSaveClose_Click" runat="server" Text="選擇后關閉"> </f:Button> <f:CheckBoxList ID="cblRole" ColumnNumber="4" Label="所屬角色" ShowLabel="false" runat="server"> </f:CheckBoxList>
點擊按鈕會觸發一個服務器端事件:
protected void btnSaveClose_Click(object sender, EventArgs e) { string roleValues = String.Join(",", cblRole.SelectedItemArray.Select(c => c.Value)); string roleTexts = String.Join(",", cblRole.SelectedItemArray.Select(c => c.Text)); PageContext.RegisterStartupScript(ActiveWindow.GetWriteBackValueReference(roleValues, roleTexts) + ActiveWindow.GetHideReference()); }
首先獲取復選框列表中,用戶選擇的文本信息和值信息,然后通過 PageContext.RegisterStartupScript 向子頁面注冊一段腳本。
在內部FineUI其實隱藏了很多復雜的邏輯:
1. 彈出窗體可以在父頁面彈出,可以在父頁面的父頁面彈出,也可以在頂層頁面彈出。
2. 如果在子頁面最快的找到我們所說的父頁面,而不是window.parent!!,這里FineUI封裝了一個ActiveWindow類。
ActiveWindow表示的是當前激活的窗體(也就是我們所說的子頁面IFrame所在的Window控件),用來聯系業務邏輯上的子頁面和父頁面。
ActiveWindow.GetWriteBackValueReference(roleValues, roleTexts)
這段代碼和前面的 GetSaveStateReference 相對應,用來將用戶選擇的值寫入父頁面相應的控件中。
至此,我們沒寫一行JavaScript代碼,實現了子頁面向父頁面傳值這個本來需要JavaScript交互的示例。
AppBox v6.0中的子頁面和父頁面的復雜交互
首先一點聲明,AppBox v6.0雖然和 v3.0版本號變化很大,但是代碼改變並不多,主要是為了跟着 FineUI(開源版)的版本走。
AppBox v6.0中,我們首先想做的一點改變是:為選擇角色的觸發器輸入框增加清空圖標!!
看下最后的實現效果:
之所以放了 5 張圖在這里,是因為這是FineUI(開源版)v6.0.0中內置的 5 種主題。
看似一個簡單的改變,其實一點都不簡單,因為在子頁面傳值這個已有邏輯基礎上,還要進行清空圖標是否可見的邏輯改變:
1. 默認如果所屬角色存在值,則顯示清空圖標;否則不顯示清空圖標
2. 點擊清空圖標時,清空兩個控件的值,然后隱藏清空圖標
3. 從子頁面返回數據時,需要顯示清空圖標
由於存在這些邏輯,FineUI內置的服務器端做法已經滿足不了需求了。因為我們決定自己寫JavaScript代碼來實現。
父頁面代碼和邏輯
首先是將 TriggerBox 改為 TwinTriggerBox 控件,並在客戶端實現兩個觸發圖標的點擊操作:
<f:TwinTriggerBox ID="tbSelectedRole" EnableEdit="false" EnableTrigger1PostBack="false" EnableTrigger2PostBack="false" Trigger1Icon="Clear" Trigger2Icon="Search" ShowTrigger1="false" ShowTrigger2="true" OnClientTrigger1Click="onSelectedRoleTrigger1Click();" OnClientTrigger2Click="onSelectedRoleTrigger2Click();" Label="所屬角色" runat="server"> </f:TwinTriggerBox>
默認是不顯示清空圖標的,所以需要在頁面加載完畢后,進行邏輯判斷:
var tbSelectedRoleClientID = '<%= tbSelectedRole.ClientID %>'; var hfSelectedRoleClientID = '<%= hfSelectedRole.ClientID %>'; function checkSelectedRoleTriggerStatus() { if (F(tbSelectedRoleClientID).getValue()) { F(tbSelectedRoleClientID).showTrigger1(); } else { F(tbSelectedRoleClientID).hideTrigger1(); } } F.ready(function () { checkSelectedRoleTriggerStatus(); });
然后再來看下點擊兩個觸發圖標的操作:
function onSelectedRoleTrigger1Click() { F(tbSelectedRoleClientID).setValue(''); F(hfSelectedRoleClientID).setValue(''); checkSelectedRoleTriggerStatus(); } function onSelectedRoleTrigger2Click() { F('Window1').f_show(F.baseUrl + 'admin/user_select_role.aspx?ids=' + F(hfSelectedRoleClientID).getValue() + '', '選擇用戶所屬的角色'); }
點擊第二個觸發按鈕時,會彈出包含IFrame頁面的Window控件,並向IFrame地址傳入角色值信息。
同時,我們還需要一個函數供子頁面調用(更新用戶所屬角色的兩個控件值):
function updateSelectedRole(roleNames, roleIds) { F(tbSelectedRoleClientID).setValue(roleNames); F(hfSelectedRoleClientID).setValue(roleIds); checkSelectedRoleTriggerStatus(); }
子頁面代碼和邏輯
子頁面點擊選擇按鈕時,所有代碼邏輯在客戶端完成,這樣也減少了一個HTTP回發:
<f:Button ID="btnSaveClose" ValidateForms="SimpleForm1" Icon="SystemSaveClose" EnablePostBack="false" runat="server" Text="選擇后關閉"> <Listeners> <f:Listener Event="click" Handler="onSaveCloseClick" /> </Listeners> </f:Button>
在客戶端腳本,我們需要完成幾個邏輯:
1. 通過JavaScript代碼獲取復選框列表的文本和值信息
2. 獲取對應的業務父頁面(不是window.parent!!)
3. 調用父頁面的 updateSelectedRole 函數
4. 關閉彈出窗體
下面來看下實現代碼,還是很清晰的:
var cblRoleClientID = '<%= cblRole.ClientID %>'; function onSaveCloseClick() { // 數據源 - 復選框列表 var cblRole = F(cblRoleClientID); var roleNames = [], roleIds = []; cblRole.items.each(function (item) { // 是否選中 if (item.getValue()) { roleNames.push(item.boxLabel); roleIds.push(item.inputValue); } }); // 返回當前活動Window對象(瀏覽器窗口對象通過F.getActiveWindow().window獲取) var activeWindow = F.getActiveWindow(); activeWindow.window.updateSelectedRole(roleNames, roleIds); activeWindow.f_hide(); }
獲取復選框列表的值,用到了 extjs 公開的API接口,不難。
而獲取業務父頁面就不那么簡單了,原因前面已經提到了,要特別注意,這個業務父頁面不是window.parent!!!!
FineUI也提供了相應的客戶端接口:
1. F.getActiveWindow() 獲取子頁面所在的Window服務器控件對象(不是JS的window對象)
2. F.getActiveWindow().window 獲取業務父頁面所在的window對象(注意不是Window服務器控件)
理解這兩個接口后,就簡單了,調用父頁面定義的 updateSelectedRole 函數:
F.getActiveWindow().window.updateSelectedRole(roleNames, roleIds);
小結
通過本篇文章的介紹,我們知道了如何不寫一行JavaScript代碼來實現子頁面向父頁面傳值。而對於復雜的交互邏輯,我們也可以手工寫JavaScript代碼來實現。
由於子頁面在作為IFrame放在Window控件中的,而Window控件可以在父頁面彈出、可以在父頁面的父頁面彈出,也可以在頂層頁面彈出,這就讓如何在子頁面中獲取業務父頁面變得撲所迷離(不是window.parent!),幸運的是FineUI對此提供了服務器端支持(ActiveWindow類)和客戶端的支持(F.getActiveWindow函數)。
關於開源和堅持
AppBox作為FineUI(開源版)的一個演示項目,從 2009 年就一直存在了,期間經歷了 v3.0 的大版本更新,其他版本的改動都不多。但是我們一直在更新FineUI(開源版)和AppBox,至今已經有 8 年時間了。
8 年間,我們看過太多的開源項目轟轟烈烈的來,平平淡淡的去,那些曾經熟悉的身影,曾經陪伴我們的代碼,都已經不復存在。其實很多時候,開源項目不是被新技術淘汰,而是被開源作者所丟棄,不免讓人扼腕嘆息。
每個存在都有存在的價值,時間總會讓之前的東西看起來不再那么新奇好玩,但是還有那么一幫曾經關注的網友,一直在使用的用戶,只有不斷的更新,才不會讓關心你的人失望。
任何事物的存在價值是無限的!