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

XP 风格 JavaScript 开始菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (21投票s)

2011 年 8 月 19 日

CPOL

7分钟阅读

viewsIcon

40212

downloadIcon

982

多级、可滚动、浏览器兼容的 XP 风格 JavaScript 开始菜单和上下文菜单

引言

正如标题所示,本文介绍的是多级、可滚动、浏览器兼容的 XP 风格 JavaScript 开始菜单以及上下文菜单。

背景

开始菜单的需求源于一个项目需求,该项目需要以一种有条理的方式显示大量的应用程序链接。因此,我们找到了一个解决方案,在项目中实现类似开始菜单的功能,该功能可以轻松处理所有应用程序链接。

因此,我开始寻找一个可以塑造成 XP 风格开始菜单的 JavaScript 菜单 API。我发现了很多选项,例如 - 基于列表的菜单、基于树的菜单、基于 CSS 的菜单等,但没有一个适合我的需求。大多数免费提供的菜单都满足了多级菜单的要求,但没有一个是可滚动的。

最后,我决定准备自己的开始菜单 API,这就是结果。

startmenu_screenshot.png

Using the Code

首先,您需要执行以下步骤

  1. 在您的 HTML 页面的 HEAD 部分包含以下 CSS 和 JS 文件
    <LINK REL="stylesheet" TYPE="text/css" HREF="base.css" />
    <LINK REL="stylesheet" TYPE="text/css" HREF="theme.css" />
    <SCRIPT TYPE="text/javascript" SRC="Utility.js"></SCRIPT>
    <SCRIPT TYPE="text/javascript" SRC="StartMenu.js"></SCRIPT>

    Utility.js 文件是一个多用途文件,其中包含操作 DOM 对象所需的常用函数。

    StartMenu.js 是包含整体功能的核心文件。

    两个 CSS 文件,即 - base.csstheme.css,用于设置菜单的样式。

  2. body 部分,创建一个具有唯一 ID 的容器来容纳要创建的所有菜单项。
    <DIV ID='menuContainer'></DIV>
  3. 要创建菜单项,您可以添加一个 JavaScript 文件,也可以直接将代码放在 <script> 开/闭标签内。为了代码的可读性,我在提供的示例中添加了一个 JavaScript 文件。无论哪种方式,您都需要在您的文件中添加以下代码
    //Define a unique global variable name that depicts the StartMenu object.
    var menu = null;
    
    // Define a function that will be invoked at the time of body onload
    function bindOnLoad(e) {
    	 // function to calculate the dimensions of the screen
    	_screenDimensions();
    	 // re-calculating the dimensions on window resize
    	_addEvent(window, function(e) { _screenDimensions(); }, "resize");
    	 // disabling the text selection 
    	// (you can remove this line as per requirement)
    	_disableSelection(document.body);
    	// function which will create the menuitems as required
    	createStartMenuItems(e);
    }
    
    function createStartMenuItems(e) {
    	// Create StartMenu object with passing the unique ID 
    	// for the container created in step-2 
    	menu = new StartMenu('menuContainer'); 
    	
    	//Example API -1 for adding the menu item
    
    	//startmenuObject.add(isSeparator, menuBarId, menuId, menuText, 
    	//menuOnClick, menuTooltip, subMenuBarId, menuBarCSS, meta)
    	menu.add(false,'MenuBar01','MenuItem01','Menu Item 01',function(evt) 
    		{alert('Hi ! \n' + this.toString()); } ,
    		'Description of Menu Item 01','MenuBar02','MainMenu',
    		'any extra information about the menu item goes here. 
    		You can access this information directly in your binded function');
    	menu.add(false,'MenuBar01','MenuItem02','Menu Item 02');
    	menu.add(true,'MenuBar01'); // adding a separator between the menu items
    	menu.add(false,'MenuBar01','MenuItem03','Menu Item 03');
    
    	menu.add(false,'MenuBar02','MenuItem04','Menu Item 04 Javascript call',
    		'javascript:alert("you can call function this way also")');
    	menu.add(false,'MenuBar02','MenuItem05',
    	'Menu Item 05 Open codeproject.com','url:https://codeproject.org.cn/');
    
    	menu.add(false,'MenuBar02','MenuItem06','Menu Item 06');
    	menu.add(false,'MenuBar02','MenuItem07','Menu Item 07');
    
    	// -------------------------------------------
    	//        More menuitems goes here
    	// -------------------------------------------
    
    	//Example API -2 for adding the menu item
    	var item = new MenuItemHashmap();
    	item.menuBarId = 'MenuBar02';
    	item.menuText = 'Menu item created using map';
    	item.menuOnClick = function(e) { alert('called from map.\n\n' + 
    				this.toString());}
    	item.meta = 'extra information on this item goes here';
    
    	menu.addMap(item);
    	item.clear();
    
    	item.menuBarId = 'MenuBar02';
    	item.menuText = 'SubMenu item created using map';
    	item.menuOnClick = function(e) { alert('called from next map.\n\n' + 
    				this.toString());}
    	item.meta = 'extra information on this sub item goes here';
    	menu.addMap(item);
    
    	//startmenuObject.init(menuBarId, eventObject[, isContextMenu])
    	menu.init("MenuBar01", e, false); // menuBarId is the first menuBar 
    				// to be displayed on click of start button
    }
  4. body 部分添加 onload 事件。还添加开始菜单按钮或链接。使用第 3 步中创建的全局 StartMenu 对象为开始菜单按钮添加 onclick 事件
    <BODY onload="bindOnLoad(event)">
    
    <DIV ID="MenuButton" CLASS="MenuButton" ONCLICK="menu.show(null, event)">
    Start</DIV>

就是这样。开始菜单已准备就绪。

创建上下文菜单

重复上面所示的第 2 步和第 3 步来创建上下文菜单。开始菜单上下文菜单之间的唯一区别在于 API 调用 - menuObj.init()。此方法中第三个参数的值决定了它是否为上下文菜单。请参阅下面的 API 描述以及附加的 sample3.htmsample3.js 文件中的代码以获取更多详细信息。

理解 API

使用以下代码创建 StartMenu 对象

var menuObj = new StartMenu(containerDivId); 

添加菜单项可以通过两种方式完成

(A) 添加

menuObj.add(isSeparator, menuBarId, menuId, menuText, menuOnClick, 
	menuTooltip, subMenuBarId, menuBarCSS, meta); 

参数含义

  • isSeparator: (类型布尔值) 设置为 true 时,菜单项被视为分隔符,不允许进行任何操作。默认为 false
  • menuBarId: 此特定菜单项所属的分组或菜单栏。这是一个必填参数。
  • menuId: 用于标识当前菜单项的唯一 ID。如果您不需要,可以将其留空。
  • menuText: 要在开始菜单上显示的文本。
  • menuOnClick: 这可以是 JavaScript 函数对象,也可以是带有前缀 "javascript:" 或 "url:" 的 string
  • menuTooltip: 在菜单项上显示的鼠标悬停文本或工具提示。
  • subMenuBarId: 提供将作为此菜单项子项显示的菜单栏的 ID。
  • menuBarCSS: 可选的菜单栏 CSS,用于格式化任何特定的菜单栏(而非菜单项)。有关演示,请参阅源文件中提供的 Sample2.htm 文件。
  • meta: 任何可选的特定于菜单项的详细信息,需要存储起来,以便在处理项目点击事件时使用。

当 JavaScript 函数绑定到菜单项时,您可以使用 this 对象访问所有这些属性。例如,this.meta, this.id 等。

(B) MenuItemHashmap

首先,像下面这样创建一个 MenuItemHashmap 对象

var itemObj = new MenuItemHashmap();

然后,设置项目的属性。这些属性与上面方法中的参数相同(如 (A) 部分所示)。您可以选择添加什么和排除什么,其余的将设置为默认值,如下所示

itemObj.menuBarId = 'MenuBar02';
itemObj.menuText = 'Menu item created using map';
itemObj.menuOnClick = function(e) 
	{ alert('called from map.\n\n' + this.toString());}

下一步是将此映射添加到先前创建的 StartMenu 对象中,如下所示

menuObj.addMap(itemObj); 

您可以通过使用以下 API 将此对象重置为默认值来重用相同的项目对象来添加更多菜单项

itemObj.clear();

使用任何一个 API 添加完所有菜单项后,接下来需要像下面这样初始化 StartMenu 对象

menuObj.init(menuBarId, eventObject, isContextMenu);

参数含义

  • menuBarId: 顾名思义,是使用上述 API 创建的菜单栏的 ID。点击开始菜单按钮时,此 ID 应该是第一个显示的菜单栏。
  • eventObject: 这是事件对象。
  • isContextMenu: 它决定相关的菜单对象是用于创建开始菜单还是上下文菜单。如果设置为 true,则相关的菜单对象将被视为上下文菜单。默认为 falsenull。当设置为 true 时,menuBarId 中的值将是上下文菜单激活时要显示的第一个菜单栏的 ID。

完成所有这些步骤后,最后一步是将菜单绑定到任何链接、按钮、图像或文本。这可以通过在点击 DOM 对象或鼠标悬停在其上时调用以下 API 来完成(如上面第 4 步所示)。

menuObj.show(null, event); 

关注点

我用于设计这段代码的基本概念是使用 DIV SPAN 标签。有一个主 DIV 标签用于处理菜单项的滚动。下一个 DIV 标签用于组合所有菜单项,这些菜单项是使用 SPAN 标签构建的。因此,整体结构将如下所示

  • DIV
    • DIV
      • SPAN

因此,您可以操作外部 DIV 标签并应用任何所需的格式。Sample1.htm 是一个简单的开始菜单,而 Sample2.htm 文件展示了如何应用用户定义的格式。

我与此一起设计的另一件事是 Utility.js 文件,其中包含各种常用的函数,用于处理 DOM 对象。以下是可用的函数集

  • function _gs(obj): **获取样式**的缩写
    • 获取指定对象的当前样式。它返回一个包含当前高度、宽度、顶部、左侧位置、边框宽度以及填充的映射。
  • function _gol(obj): **获取左偏移量**的缩写
    • 以像素为单位获取指定对象距离屏幕左侧的距离。
  • function _got(obj): 获取顶部偏移量的缩写
    • 以像素为单位获取指定对象距离屏幕顶部的距离。
  • function _ge(id): **按 ID 获取元素**的缩写
    • 获取指定 ID 的 DOM 对象。
  • function _addEvent(target, functionref, eventType)
    • 将任何事件绑定到指定的 target,并在事件发生时调用引用的函数。
  • function _getEvent(type, e)
    • 根据指定的类型获取发生事件的 Source 或 Destination。如果类型是 "Src",它将返回生成事件的源 DOM 对象。如果类型是 "Dest",它将返回事件将结束的目标 DOM 对象。
  • function _processStyle(el, tc, action)
    • 检查、添加或删除特定 DOM 对象的指定 CSS 类。在这里,"el" 是 DOM 对象,"tc" 是 CSS 类名,"action" 可以是 - "check"、"add" 或 "remove"。
  • function _screenDimensions()
    • 计算当前屏幕的尺寸(高度和宽度),并将值存储在两个全局变量 - _dh _dw (表示文档高度和文档宽度)中。
  • function _disableSelection(target)
    • 禁用指定 DOM 对象的文本选择。

注意

包中还包含另一个 JavaScript 文件 - MessageBox.js。这是我设计的另一个实用程序。它已在 Sample2.htm 文件中使用。有关此实用程序及其用法的更多详细信息,请在此处 查看

升级说明

代码升级(2.0 版本)向后兼容。如果有人已将 1.0 版本集成到他们的代码中,他们只需替换 StartMenu.js 文件即可升级到 2.0 版本。

2.0 版本中的功能

  • 添加了多级、可滚动、跨浏览器上下文菜单
  • 与前一版本向后兼容
  • 使用相同的 API 创建上下文菜单

结论

希望大家都会发现这篇文章非常有帮助。

历史

  • 2011 年 8 月 19 日:初始发布
  • 2011 年 8 月 23 日:添加了注释
  • 2011 年 9 月 15 日:2.0 版本,添加了多级、可滚动上下文菜单
© . All rights reserved.