65.9K
CodeProject 正在变化。 阅读更多。
Home

另一个下拉菜单 WebControl

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (10投票s)

2005年3月17日

CPOL

9分钟阅读

viewsIcon

178453

downloadIcon

4988

使用 JavaScript、WebControl 和 XSL & XML 进行 UI 导航。

Yet__Another_Drop_Down_Me.gif

引言

本文将介绍一个简洁酷炫的 Web 控件,用于实现具有各种客户端行为和功能的钻取式菜单,并且还将介绍一些服务器端非常实用和方便的选项。下一节将简要解释对这类 UI 组件的需求,并提出一些关于何时应该使用以及何时不应该使用这类控件的观点。

只要 Web 存在,开发人员就希望在他们的 Web 应用程序中实现丰富的客户端 UI。像层次树、钻取式菜单等 UI 组件对我们以及有时对我们的客户都具有吸引力。我们都习惯于在 WinForms 中看到它们,也希望在 Web 应用程序中看到它们——为什么不呢?如果问的话……

即使是我这样的非 UI 专家也能提出几个“为什么不”的答案。

  • Web 应用程序是无状态的,有时这个事实本身就使得实现需要跨客户端和服务器往返维护状态的丰富客户端变得更加困难。
  • 丰富的 UI 意味着大量的客户端脚本来实现与最终用户的 UI 交互,这些脚本有时难以调试,需要额外的技能。
  • 大量的脚本最终会影响页面加载时间。
  • 在设计 Web 应用程序时,我们的客户反馈和 UI 专家建议保持 UI 简洁(这条黄金法则适用于一切)。例如,显示简单的平面链接而不是钻取式菜单——让用户看到他们的选项,不要将它们隐藏在“隐藏的地方”——就像在钻取式菜单中一样。简洁的 UI 将是更用户友好的 UI。我们希望我们的客户发现我们的应用程序易于使用并使用它们。最终,我们将能够销售我们的未来版本,每个人都会很高兴。

曾经有过其他问题,如 IE 兼容性、网络连接缓慢以及缺少必要的脚本语言功能集——但谢天谢地,这些问题都过去了。

好的,在我开始深入讲解钻取菜单的细节之前,我想指出我的版本是基于 Dan Wahlin 最初编写的伟大的 XML & XSL & JS 代码,并且我得到了他添加我的改进的许可(我不是唯一一个)。你可以在 Dan 的 XML 网站上找到原始代码,强烈推荐给任何对 .NET 特别是 ASP.NET 的 XML 感兴趣的人。

钻取菜单的主要功能列表

  1. 可以配置菜单文件的位置——CSS、JS、XSL 和 XML。我们的选项是
    1. 在我们的 Web 应用程序的 Web 文件夹中。
    2. aspnet_client Web 文件夹中,它通常位于 wwwroot 文件夹下。
    3. 在不位于端口 80 而是其他端口的站点中。
  2. 支持权限检查——两个级别的权限行为。
    1. 低级别限制——HTML 视觉上禁用用户不允许激活的菜单项。
    2. 严格限制——从客户端省略用户不应该知道的任何项目(不会成为客户端渲染 HTML 的一部分)。
  3. Webcontrol 服务器事件。
    • OnMenuItemClick
    • OnPrePermissionCheck
    • OnPreRender

      最后两个事件允许通过订阅这些事件来更改渲染结果,并添加自定义渲染逻辑。

  4. 不同级别的缓存。
    1. 应用程序级别(当不需要权限检查时)——渲染的 HTML 菜单将保存在 Cache 对象中。
    2. 用户会话缓存——强制或不强制权限检查。
    3. 无缓存模式。
  5. RTL/LTR 支持。

    所有文件都可以位于特定语言的文件夹中。

  6. 客户端 onClick 事件处理程序增强。
    1. 您可以将点击的菜单项链接在新窗口(对话框或常规窗口)、同一页面或 IFrame 中打开。
    2. 开发人员可以附加自定义客户端函数,这些函数将在菜单客户端逻辑流发生之前执行。自定义函数可以通过返回“skip”值来覆盖默认行为,这将指示跳过菜单默认函数。插件客户端函数是
      • customOnClick
      • canNavigate——此函数可用于检查当前表单的 isDirty 标志并采取相应措施。
  7. 以下附加的自定义客户端函数可以被附加
    • clientParamsGetFunction

      菜单内部 onclick 函数将调用此函数以获取将在后续步骤中使用/发送的附加参数值。

    • clientWindowGetStyleFuncName

      配置在点击菜单时打开新窗口(对话框或简单窗口)。我们可以附加一个函数来返回窗口显示样式设置(大小、显示工具栏等)。

    • clientWindowAfterCloseEvent

      关闭窗口后,如果定义了此函数,我们将调用它,并将从窗口和其他步骤(请参阅 clientParamsGetFunction)获得的任何参数值发送给它。

  8. 当我们将菜单配置为在 IFrame 中加载页面时,可以在当前页面或 IFrame 页面中执行 PostBack。触发 IFrame 加载页面中 PostBack 的函数签名
    menu_doPostback( gCurrentMenu, key, gDefaultLink, url, args);

    参数用途

    • gCurrentMenu – 未实现。
    • Key – 点击的菜单键。当我们不想强制使用特定链接时可以使用,例如在使用 UIP Application Block 时。
    • gDefaultLink – 我们在菜单 XML 根元素元数据中预定义的全局链接值。使用 MasterPage 机制时,我们可以使用全局链接。
    • url – 我们为菜单项预定义的 URL。
    • Args – 一个字符串分隔值/对参数(&)。
  9. 使用 IFrame 的客户端缓存。

    当配置菜单项使用 IFrame 且不将 Postback 设置为 true 时,我们通过使用 IFrame 实现客户端缓存。当点击菜单项并且 IFrame 模式开启时,我们在客户端浏览器中检查是否已经创建了将保存点击菜单链接页面的 IFrame。如果我们已经有了,我们将其显示设置为可见,否则我们创建它并从服务器加载。

建议使用 IFrame 配置,当你拥有加载时间无法接受的页面,并且在它们加载后可以显示而无需返回服务器时(例如,带有大量 XML & XSL 的页面,可以在客户端进行不同的过滤和显示)。

实际上,还有更多不同的配置选项,也有组合不同选项并获得混合行为的方法。

我下载了包含 Web 控件代码和 6 个简单示例的 ZIP 文件,展示了你可以配置菜单元数据 XML 的不同方式。

菜单全局设置

public struct MenuSettingsType
{
    public string name;
    public bool forcePermission;
    public bool ommitNotAuthMenus;
    public string defaultLink;
    public string customCSSFile;
    public string customJSFile;
    public string customXSLFile;
    public bool recheckPermissionsOnPostBack;
    public bool triggerPostBack;
    public string appFolder;
    public string port;
    public string frameDivHolderID;
    public string customOnClick;
    public bool customOnClickIsInFrame;
    public bool canNavigateIsInFrame;
}

大多数设置是可选的。以下设置可以被特定的菜单项设置覆盖。

public string defaultLink;
public bool triggerPostBack;
public string frameDivHolderID;
public string customOnClick;
public bool customOnClickIsInFrame;
public bool canNavigateIsInFrame;

菜单项元数据

//the name (id) of your menu (mandatory)
public const string EL_NAME= "name";
//comma delimiter roles that can access this menu item content (optional)
public const string EL_PERMISSIONS="permissions";
//check the user permissions for this menu item (optional)
public const string EL_FORCE_PERMISSIONS="forcePermission";
//the display name of this menu item (mandatory)
public const string EL_CAPTION="caption";
//the tooltip (optional)
public const string EL_TOOL_TIP="toolTip";
//the mode the menu item will be display (optional)
public const string EL_DISABLED="disabled";
//the default message for tooltip for not authorized user (optional)
public const string EL_ACCESS_DENIED_MESSAGE="accessDeniedMessage";
//not implemented
public const string EL_VISIBLE="visible";
//is the menu clicked should trigger a postback - true/false (optional)
public const string EL_TRIGGET_POSTBACK="triggerPostBack";
//the target link the menu item point to (optional)
public const string EL_LINK="link";
//the valid value: PostBack, Window, Frame (mandatory)
public const string EL_CLIENT_OPEN_TARGET_TYPE="clientOpenTargetType";
//the DIV where the IFRAMES will be located in IFRAME mode
//(mandatory in IFRAME MODE)
public const string EL_CLIENT_FRAMES_DIV_ID="clientFramesDivHolderName";
//your function name that provide additional parameters
//and will be called after a menu item was clicked and before the menu
//item will start its internal logic (optional)
public const string EL_CLIENT_PARAMS_GET_FN="clientParamsGetFunction";
//dialog or window. default is dialog. (optional)
public const string EL_CLIENT_WINDOW_TYPE="clientWindowType";
//the name of your function that will be triggered after a window will be
//closed when menu item open a window (optional)
public const string EL_CLIENT_AFTER_WIN_CLOSE_FN="clientWindowAfterCloseEvent";
//your function name that will be called before a window will open
//and will be use to get the window preferences string (optional)
public const string EL_CLIENT_GET_WIN_STYLE_FN="clientWindowGetStyleFuncName";
//when working in IFRAME mode and you want to use single IFRAME with
//postback to this IFRAME loading page (see attach sample)
//the clientTargetFrameName value should point to your single IFRAME (optional)
public const string EL_CLIENT_TAGET_FRAME_NAME="clientTargetFrameName";
//your custom on menu item click handler (optional)
public const string EL_CLIENT_CUSTOM_CLICK_FN="customOnClick";
//this settings specify where your custom on click function located (optional)
public const string EL_CLIENT_CUSTOM_CLICK_IN_FRAME="customOnClickIsInFrame";
//this settings specify where canNavigate function located (optional)
public const string EL_CLIENT_CAN_NAV_IN_FRAME="canNavigateIsInFrame";

必需的元素是

//the unique name (id) of the menu
public const string EL_NAME= "name";
//the display name
public const string EL_CAPTION="caption";
//There are three types: PostBack or Window or Frame
public const string EL_CLIENT_OPEN_TARGET_TYPE="clientOpenTargetType";

当你以 frame 模式工作时,你必须提供一个 DIV id 来容纳帧(clientFramesDivHolderName)。你还必须使用这个 DIV 设置你的默认 IFrame

XML 的大小取决于你使用的不同元素。大多数菜单项元数据元素是可选的。它还取决于你需要的行为配置——使用 PostBack 到同一页面,使用 IFrame——带或不带 PostBack 到 IFrame,以及最终使用窗口模式。

Web 控件,服务器端功能

如前所述,有三个服务器端事件你可以订阅并更改最终结果。

public delegate void MenuItemClickedHandler(MenuItemClickedEventArgs e);

public delegate void MenuItemRanderHandler(XmlNode item, out bool skipItem, 
                                           out string xmlAdditionalAttr);

public delegate void MenuItemCheckPermissionHandler(XmlNode menu, 
       string permissions, bool forcePermissions, string caption, 
       string originalToolTip, out bool skip, 
       out bool enable, out string toolTip);

MenuItemClickedEventArgs - 包括以下参数

  • NextMenu - 用户点击的菜单值。
  • CurrentMenu – 当前显示的菜单(未实现)。
  • AdditionalArgs – 用户在自定义客户端函数中添加的参数的名称/值哈希表。

forcePermissions 设置为 true 时,将触发 MenuItemCheckPermissionHandler 事件。菜单内部还有一个权限检查,我已经将其移除并替换为 TODO。这是你可以使用 entLib 相关组件或其他组件添加默认权限检查的地方。

ASP.NET 页面中的菜单元素定义

<?XML:NAMESPACE PREFIX = CC1 /><CC1:DDMENU id=DDMenu1 
dir=ltr MenuName="FramesAndPostBackToFrame"
MenuXMLFileName="FramesAndPostBackToFrame.xml"
MenuXMLRelPath="menu" runat="server">
</CC1:DDMENU>

使用 Frame 模式时,需要添加 DIV 元素,其中包含默认 IFrame 的定义。 DIV id 应在 Menu XML 元数据中定义。Frame ID 应与默认菜单项的 Menu 名称元素相同,该元素也定义在 menu XML 元数据中。

重要

当使用单个 IFrame(默认)并 postback 到 IFrame 加载页面时(就像我在附带示例中所做的那样),你需要使用名为 clientTargetFrameName 的菜单项元素,并将其值设置为默认单个 IFrame 的名称。

附带的示例代码文件

    示例包括 RTL / LTR 示例。

  • MenuRTLWithFrames.aspx – 右到左显示,带有 IFrame 缓存。
  • MenuRTLUsingCustomJSFuncs.aspx – 右到左显示,使用在菜单 XML 文件中配置的自定义客户端插件函数。
  • MenuLTRWithPostbacks.aspx – 左到右显示,展示了如何使用服务器端点击事件 + 为每次客户端菜单点击触发 postback。
  • MenuLTRWithFrames.aspx – 与 # MenuRTLWithFrames.aspx 相同,但为左到右显示。
  • MenuLTRFrameAndPostBackToFrame.aspx – 使用单帧场景,postback 到位于 IFrame 加载页面中的函数。

一些说明

  • 当你为所有菜单项定义全局链接,并且希望位于第一级且没有子项的特定菜单项可点击时——你需要为其定义链接。这是一个已知行为。
  • 演示中包含了一些恼人的 JavaScript 调试警报,以便更容易地跟踪 onclick 函数。
  • 菜单 onclick 功能和客户端的 XML 可以重用于不同的 UI 组件,而不仅仅是 DDMenu
  • 如果你决定使用该菜单,欢迎报告任何错误或提供反馈。
  • 欢迎你添加对控件的增强功能并与我们分享。

摘要

本文主要解释了配置 DDMenu Web 控件的不同方式。我添加到 ZIP 文件中的演示代码简单明了,有助于理解 Web 控件的配置和工作方式。

© . All rights reserved.