zoukankan      html  css  js  c++  java
  • 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 年间,我们看过太多的开源项目轰轰烈烈的来,平平淡淡的去,那些曾经熟悉的身影,曾经陪伴我们的代码,都已经不复存在。其实很多时候,开源项目不是被新技术淘汰,而是被开源作者所丢弃,不免让人扼腕叹息。

    每个存在都有存在的价值,时间总会让之前的东西看起来不再那么新奇好玩,但是还有那么一帮曾经关注的网友,一直在使用的用户,只有不断的更新,才不会让关心你的人失望。

    任何事物的存在价值是无限的!

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 贪吃的大嘴
    Java实现 蓝桥杯VIP 算法提高 贪吃的大嘴
    Java实现 蓝桥杯VIP 算法提高 贪吃的大嘴
    Java实现 蓝桥杯VIP 算法提高 贪吃的大嘴
    Java实现 蓝桥杯VIP 算法提高 士兵排队问题
    Java实现 蓝桥杯VIP 算法提高 士兵排队问题
    Java实现 蓝桥杯VIP 算法提高 士兵排队问题
    Java实现 蓝桥杯VIP 算法提高 士兵排队问题
    Java实现 蓝桥杯VIP 算法提高 数字黑洞
    Minifilter微过滤框架:框架介绍以及驱动层和应用层的通讯
  • 原文地址:https://www.cnblogs.com/sanshi/p/5976809.html
Copyright © 2011-2022 走看看