AppBox v6.0中實現子頁面和父頁面的復雜交互


前言

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 年間,我們看過太多的開源項目轟轟烈烈的來,平平淡淡的去,那些曾經熟悉的身影,曾經陪伴我們的代碼,都已經不復存在。其實很多時候,開源項目不是被新技術淘汰,而是被開源作者所丟棄,不免讓人扼腕嘆息。

每個存在都有存在的價值,時間總會讓之前的東西看起來不再那么新奇好玩,但是還有那么一幫曾經關注的網友,一直在使用的用戶,只有不斷的更新,才不會讓關心你的人失望。

 

任何事物的存在價值是無限的!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM