[IBM DW] 使用 Dojo 开发菜单应用

红薯 发布于 2010/10/19 17:59
阅读 1K+
收藏 4

背景介绍

菜单应用是 Web 页面的点睛之笔。当用户在浏览器端右键单击的时候,浏览器会弹出自带的菜单,显示如“查看源代码”、“复制”、“粘贴”等可用菜单栏。通过使用浏览器自带 的菜单,用户可以方便的进行复制、粘贴等操作。然而很多时候,网站开发人员会考虑禁止用户通过浏览器自带的菜单进行以上操作,或者是希望用户使用开发人员 自定义菜单。一个简单的自定义菜单如下图所示:


图 1. 自定义菜单
图 1. 自定义菜单

自定义菜单的使用,可以方便用户快速定位到某个操作,增强了用户界面的交互性,提高用户体验。

Dojo 提供的菜单库,除实现了菜单的基本功能外,还加入对弹出式菜单、图标效果、键盘响应等功能的支持,方便了开发人员的菜单开发过程。本文将首先介绍 Dojo 菜单实现原理,并从创建最简单右键菜单入手,介绍右键菜单的静态和动态两种菜单创建方式,最后举例说明如何开发 Dojo 提供的上下文菜单、下拉式菜单、静态菜单三种菜单。

右键菜单实现原理

在默认状况下,用户在浏览器右键单击时,浏览器会触发 document.oncontextmemu 事件,浏览器会采用默认方式对事件进行处理,弹出浏览器自带的右键菜单。

实现自定义右键菜单的基本原理就是:菜单默认为隐藏;当 document.oncontextmemu 事件触发时,使用 JavaScript 操作菜单节点的 style 属性,显示该菜单;同时使用 JavaScript 侦听鼠标 onclick 事件,当该事件执行时,判断鼠标点击位置是否在菜单区域时,若没有,则通过操作菜单的 style 隐藏该菜单。

Dojo 实现右键菜单的方法也是采用了上面的原理,但 Dojo 封装了底层事件的处理方法,开发人员直接使用 Dojo 提供的简单 API 就能实现复杂的菜单。具体实现方式参见下文。

Dojo 菜单使用

包括右键菜单在内,Dojo 提供了三种类型菜单:上下文菜单(右键菜单和弹出式菜单)、下拉式菜单、静态菜单。由于其他菜单使用和右键菜单使用方式基本相同,本文将从创建一个最简单的右键菜单开始讲解,然后分别介绍上述三种菜单的作用及创建方式。

简单右键菜单示例

在 Dojo 支持的上下文菜单、下拉式菜单、静态菜单三类菜单中,使用最为广泛的是“上下文菜单”中的右键菜单,一个最简单的右键菜单如下图所示:


图 2. 简单右键菜单
图 2. 简单右键菜单

用户在“Please Right-click On Me!”上右键单击,即可看到由 Cut、 Copy、 Paste 纵向三栏构成的右键菜单。可以看到,使用 Dojo 创建的“右键菜单”比较漂亮而且符合用户的使用习惯,下面采用“静态创建”和“动态创建”两种方式实现上述菜单:

静态创建菜单

与 Dojo 静态创建其他 Widget 类似,如果希望一个实体实现菜单的效果,需要在实体的标签里面加上 dojoType=” dijit.Menu” 属性。

静态创建菜单一般需要如下完整的步骤:

  1. 导入所需的 JavaScript 和 CSS 文件后,导入 Dojo 所需要的 dijit.Menu 、dijit.MenuItem 等模块。
  2. 静态创建菜单 Widget 及菜单的各个菜单项 Widget
  3. 将该菜单 Widget 静态绑定到现有的 DOM 节点。


清单 1. 静态创建菜单

				
 <html> 
 <head> 
 <title>Menu Learn</title> 
 <style type="text/css"> 
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/resources/dojo.css"; 
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.4/dijit/themes/tundra/tundra.css"
 </style> 
 
 <script 
  type="text/javascript"
  src="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/dojo.xd.js"
  djConfig="isDebug:true,parseOnLoad:true"> 
 </script> 

 </head> 

 <body class="tundra"> 

 <script language="JavaScript" type="text/javascript"> 
  dojo.require("dijit.Menu"); 
  dojo.require("dijit.MenuItem"); 
  dojo.require("dojo.parser"); 
 </script> 
 
 <!-- 创建菜单 Widget --> 
 <div dojoType="dijit.Menu" id="menu_context_1" contextMenuForWindow="false" 
  style="display: none;"  targetNodeIds="show_context"> 
  <div dojoType="dijit.MenuItem" >Cut</div> 
  <div dojoType="dijit.MenuItem" >Copy</div> 
  <div dojoType="dijit.MenuItem" >Paste</div> 
 </div> 
 
 <!-- 菜单 Widget 显示的节点 --> 
 <div id="show_context">Please Right-click On Me!</div> 

 </body> 
 </html> 

 

dijit.Menu 是 Dojo 中菜单 Widget 的一种,可以理解为是菜单菜单项的容器,一个 dijit.Menu 通常有若干 dijit.MenuItem 组成,每一个 dijit.MenuItem 即为一条菜单项。

dijit.Menu 的 targetNodeIds 属性表示与该 Menu 绑定的目标 DOM 节点,即在该 DOM 节点上右击才会出现右键菜单。contextMenuForWindow 属性表示是否只有在窗体的任何地方右键单击才会打开菜单,如果该值为 true,用户在窗体的任何地方右击都会弹出该菜单,若该值为 false,只有在 targetNodeIds 对应的节点上右击才会弹出菜单。同时,因为右键菜单的在用户右键单击前是不显示的,因此该 Menu Widget 的 style 中 display 属性为 none。

动态创建菜单

在清单 1 中,通过在一些实体的标签里面加上相应的 Dojo 标签属性实现了 Menu Widget 创建。这种静态实现 Menu Widge 的方法简单明了。然而某些情况下,需要根据一些实际情况动态的生成 Menu Widge,或者动态的修改 Menu Widget 的某些属性。下面代码就是动态实现上述简单右键菜单的方法:


清单 2. 动态创建菜单

				
 <html> 
 <head> 
 <title>Menu Learn</title> 
 <style type="text/css"> 
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/resources/dojo.css"; 
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.4/dijit/themes/tundra/tundra.css"
 </style> 
 
 <script 
  type="text/javascript"
  src="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/dojo.xd.js"
  djConfig="isDebug:true,parseOnLoad:true"> 
 </script> 

 </head> 

 <body class="tundra"> 

 <script language="JavaScript" type="text/javascript"> 

  dojo.require("dijit.Menu"); 
  dojo.require("dijit.MenuItem"); 
  dojo.require("dojo.parser");  

  dojo.addOnLoad(function(){ 
   <!-- 动态创建菜单 Widget,初始化时设置其属性 --> 
   var menu = new dijit.Menu({targetNodeIds:["show_context"],id:"menu_context_2"}); 
   
   <!-- 创建菜单项,并添加到菜单中 --> 
   var item1 = new dijit.MenuItem({label:"Cut"}); 
   var item2 = new dijit.MenuItem({label:"Copy"}); 
   var item3 = new dijit.MenuItem({label:"Paste"}); 
   menu.addChild(item1); 
   menu.addChild(item2); 
   menu.addChild(item3); 
   
   <!-- 调用菜单 --> 
   menu.startup(); 
   } 
  )  
 </script> 
 
 <div id="show_context">Please Right-click On Me!</div> 

 </body> 
 </html> 

 

可以看到,与 Dojo 动态创建普通的 Widget 类似,创建 dojo.menu 的过程也可分为三步:

  1. 导入所需的 JavaScript 和 CSS 文件后,导入 Dojo 所需要的 dijit.Menu、dijit.MenuItem 等模块。
  2. 动态创建菜单 Widget,将该菜单 Widget 动态绑定到现有的某个目标 DOM 节点。
  3. 启动菜单。

需要特别注意的是: 在动态创建 dijit.Menu 的时候,dijit.Menu 的 targetNodeIds 属性是一个对象数组,而非特定的对象。

上下文菜单

上下文菜单是最常见的菜单,一般会结合上下文环境使用,该菜单典型的应用是右键菜单和弹出式菜单。上章节设计的菜单即为最简单的右键菜单,而稍微复杂的上下文菜单都会有键盘响应、图标效果显示、自定义快捷键、分隔符、弹出式菜单、禁用菜单项、复选式菜单项等功能:


图 3. 上下文菜单
图 3. 上下文菜单

上图所示:键盘响应指用户可以通过 Dojo 已定义的快捷键对菜单进行操作,如使用“空格键”弹出子菜单;图标效果如上述菜单的“Cut”栏剪刀图标效果所示,而自定义快捷键则如“Cut”栏对应的 “Ctrl + X”快捷键;分隔符的作用如“Paste”栏下面的横线,将不同栏目组分隔开;“Paste”栏底色为灰色,即使点击也不会触发任何时间,即为禁用菜单栏 功能;弹出式菜单则是菜单中最经常用到的功能,用户点击 Popup Menu 时会弹出下一级菜单;而复选菜单的效果则如“Checked”栏所示,用户可以通过点击复选框表示选中该栏或取消选择。

下面的代码实现了上述功能:


清单 3. 上下文菜单

				
 <div dojoType="dijit.Menu" id="menu_context_3" 
  contextMenuForWindow="true" style="display: none;"> 
  
  <!-- 增加图标效果和快捷键 --> 
  <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCut"
   onClick="console.log('nothing will happen,but you can implement it!')" 
   accelKey="Ctrl+X">Cut</div> 
  <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCopy"
   onClick="console.log('nothing will happen,but you can implement it!')" 
   accelKey="Ctrl+C">Copy</div> 
  <!-- 禁用该菜单项 --> 
  <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconPaste"
   onClick="console.log('nothing will happen,but you can implement it!')" 
   disabled="true" accelKey="Ctrl+V">Paste</div> 
  <div dojoType="dijit.MenuSeparator"></div> 
  
  <!-- 弹出子菜单 --> 
  <div dojoType="dijit.PopupMenuItem" id="popupMenuItem"> 
   <span>Popup Menu</span> 
   <div dojoType="dijit.Menu" id="submenu"> 
    <div dojoType="dijit.MenuItem" id="submenu_item1" 
	 onClick="console.log('submenu_item1')">Submenu Item 1</div> 
    <div dojoType="dijit.MenuItem" id="submenu_item2" 
	 onClick="console.log('submenu_item2')">Submenu Item 2</div> 
    <div dojoType="dijit.PopupMenuItem" id="submenu_item3"> 
     <span>Deeper Popup Menu Item</span> 
     <div dojoType="dijit.Menu" id="submenu_item3submenu"> 
      <div dojoType="dijit.MenuItem" 
	  onClick="console.log('
	  submenu_item3submenu_item1!')">Submenu Submenu Item 1</div> 
      <div dojoType="dijit.MenuItem" 
	  onClick="console.log('
	  submenu_item3submenu_item2!')">Submenu Submenu Item 2</div> 
     </div> 
    </div> 
   </div> 
  </div> 
  
  <!-- 弹出其他 Widget --> 
  <div dojoType="dijit.PopupMenuItem"> 
   <span>Different Popup</span> 
   <div dojoType="dijit.ColorPalette"></div> 
  </div> 
  <div dojoType="dijit.MenuSeparator"></div> 
  
  <!-- 复选菜单项 --> 
  <div dojoType="dijit.CheckedMenuItem" checked="true" 
   onChange="if(arguments[0]) console.log('checked'); else console.log('unchecked');"> 
   Checked 
  </div> 
 </div> 

 

下面就各个功能对上述代码进行讲解:

键盘响应

键盘响应的功能是不需要开发人员实现的,Dojo 创建的 Menu 自身就已经具备了键盘响应的功能,Dojo 提供的键盘响应有:


表 1. 键盘响应

功能 快捷键
打开上下文菜单 Windows:shift-f10 或者是在 FireFox 浏览器上右击
Macintosh: ctrl-space
Safari 4 或  Mac: VO+shift+m (VO 一般是指 control+opton 组合键 )
遍历菜单 ↑、↓方向键
弹出子菜单 空格、回车或是→方向键
关闭上下文菜单,或关闭当前子菜单返回上级菜单 Esc 或者←方向键
关闭上下文菜单和所有子菜单 Tab 键

 

图标效果显示

dijit.MenuItem 的 iconClass 属性表示了菜单项而使用的 CSS,当菜单项引入该 CSS 后,该菜单项会添加图标效果。Dojo 提供了如 dijitEditorIconCut、dijitEditorIconCopy、dijitEditorIconPaste 等图标效果的 CSS 类。

自定义快捷键

dijit.MenuItem 的 accelKey 属性表示该菜单项对应的快捷键。需要特别注意的是:尽管菜单项上可以显示该快捷键文本,如上图的第一栏右边显示有“Ctrl+X”,然而当前 Dojo 版本 (1.4) 并没有提供捕捉和执行该快捷键事件的机制,即即使用户键盘输入“Ctrl+X”,也不会触发剪贴事件。

分隔符

dijit.MenuSeparator 表示菜单菜单项之间的线,用于分割各个菜单项。

弹出式菜单

如果想使用弹出式菜单,会需要如下的代码结构:


清单 4. 弹出式菜单

				
 <div dojoType="dijit.PopupMenuItem" id="popupMenuItem"> 
   <span>Popup Menu</span> 
   <div dojoType="dijit.Menu" id="submenu"> 
    <div dojoType="dijit.MenuItem" id="submenu_item1"> 
	 Submenu Item 1 
	 </div> 
 ... 
 </div> 

 

其中,PopupMenuItem 作用类似于 MenuItem,但是它可以显示下一级菜单或者其他 Widget。一般 PopupMenuItem 都会有两个子节点:显示该菜单项内容的静态文本的标签(一般是写在 span 里)和一个需要显示的 Widget,该 Widget 一般是 dijit.Menu,也可以是 dijit.ColorPalette(颜色选择框)等 Widget。

禁用菜单项

dijit.MenuItem 的 disabled 属性表示该菜单项是否可用,该属性默认值为“flase”,表示可用;如果该属性为 true,则该菜单项被禁用,即使点击该菜单项也不会触发点击事件。

复选菜单项

dijit.CheckedMenuItem 表示复选菜单项,其 checked 属性标识了该菜单项是否被选中。checked 属性的默认值为 false,即未被选中,每次用户点击该菜单项,就会触发选中 / 取消选中的事件,菜单项状态就会在“checked”和“unchecked”之间进行切换。用户可以定义 onchange 函数,用于处理选中 / 取消选中该菜单项事件,onchange 函数接受的第一个参数即为 checked 属性的值。

需要说明的是,以上功能并非只有在“上下文菜单”中才有,“下拉式菜单”和“静态菜单栏”都具备相同的功能,使用的方法也一样。

下拉式菜单

下拉式菜单指的是点击某个按钮或者菜单项时,会纵向下拉弹出的菜单。Dojo 提供的下拉式菜单一般会绑定到 dijit.form.ComboButton,dijit.form.DropDownButton 或 dijit.MenuBar Widget 上,点击这些 Widget 或 Widget 的菜单项时,会弹出下拉式菜单。以 dijit.MenuBar 为例:MenuBar Widget 是经常用到的 Widget,它模拟实现了一个典型的菜单条,横向列出若干菜单选项,当点击某个菜单项时,会下拉弹出子菜单或其他 Widget。如下图所示:


图 4. 下拉式菜单
图 4. 下拉式菜单

上述功能可由清单 5 实现:


清单 5. 下拉式菜单

				
 <!-- 下拉式菜单,即菜单条 --> 
 <div id="menubar" dojoType="dijit.MenuBar"> 
  <!-- 菜单条的菜单项 --> 
  <div dojoType="dijit.PopupMenuBarItem" id="file"> 
   <span>File</span> 
   <!-- 菜单项下拉弹出子菜单 --> 
   <div dojoType="dijit.Menu" id="fileMenu"> 
    <div dojoType="dijit.MenuItem" id="new">New</div> 
    <div dojoType="dijit.MenuItem" id="open">Open</div> 
    <div dojoType="dijit.MenuSeparator" id="separator"></div> 
    <div dojoType="dijit.MenuItem" id="save" iconClass="dijitEditorIconSave">Save</div> 
    <div dojoType="dijit.PopupMenuItem" id="saveas"> 
     <span>Save as</span> 
     <div dojoType="dijit.Menu" id="subMenu"> 
      <div dojoType="dijit.MenuItem">*.txt</div> 
      <div dojoType="dijit.MenuItem">*.doc</div> 
     </div> 
    </div> 
   </div> 
  </div> 
  <div dojoType="dijit.PopupMenuBarItem" id="edit"> 
   <span>Edit</span> 
   <div dojoType="dijit.Menu" id="editMenu"> 
    <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCut"
   onClick="console.log('nothing will happen,but you can implement it!')" 
   accelKey="Ctrl+X">Cut</div> 
   <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCopy"
    onClick="console.log('nothing will happen,but you can implement it!')" 
	 accelKey="Ctrl+C">Copy</div> 
   <div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconPaste"
    onClick="console.log('nothing will happen,but you can implement it!')" 
	 disabled="true" accelKey="Ctrl+V">Paste</div> 
   </div> 
  </div> 
  <div dojoType="dijit.PopupMenuBarItem" disabled="true"> 
   <span>Disabled</span> 
   <div dojoType="dijit.Menu"> 
    <div dojoType="dijit.MenuItem">If you see this,there is something wrong!</div> 
   </div> 
  </div> 
  <!-- 不会执行 onclick 事件 --> 
  <div dojoType="dijit.PopupMenuBarItem" 
   onclick="console.log('no submenu,menu donot has any item,handle onclick event?');"> 
   <span>Empty</span> 
   <div dojoType="dijit.Menu"> 
   </div> 
  </div> 
  <!-- 会执行 onclick 事件 --> 
  <div dojoType="dijit.MenuBarItem" 
   onclick="console.log('no submenu,I am MenuBarItem,handle onclick event?'); "> 
   Please! 
  </div> 
 </div> 

 

一个 dijit.MenuBar Widget 由多个 dijit.PopupMenuBarItem 或 dijit.MenuBarItem Widget 组成:

  • 示例的“File”菜单项就是由 dijit.PopupMenuBarItem Widget 实现的,当鼠标点击该菜单项时,菜单项会弹出一个子菜单或其他 Widget。同上下文菜单的 dijit.PopupMenuItem 类似,一个 dijit.PopupMenuBarItem Widget 会包含两个子节点:显示静态文本的标签(一般是写在 span 里)和一个需要显示的 Widget,该 Widget 一般是 digit.Menu Widget。
  • dijit.MenuBarItem 也是菜单条的菜单项,它不支持下拉弹出 digit.Menu 或其他 Widget。与 dijit.PopupMenuBarItem 不同,当在该 dijit.MenuBarItem Widget 单击时,会触发 onclick 函数,这点可以通过“Empty”和“Please!”菜单项得到验证:

当点击“Empty”和“Please!”菜单项时,都不会弹出下拉菜单,两者显示效果看起来一样,但实际触发的事件却不同,观察 firebug 控制台,可以发现:“Empty”菜单栏被单击后,并没有向控制台进行输出,即并没有真正执行 onclick 函数;而“Please!”菜单栏被单击后,则向控制台进行输出。

静态菜单

静态菜单是静态定为到窗体某个位置的菜单,如下图所示:


图 5. 静态菜单

静态菜单与上下文菜单的显示效果是一样的,然而,静态菜单会在网页加载完后固定显示于窗体某个位置,并且不会像上下文菜单一样会因鼠标事件的发生而消失或显示,它的典型应用为作为导航菜单显示在窗体的左侧,用户可以根据其菜单项进行信息的过滤和查找。

实现上述功能菜单的代码为:


清单 6. 静态菜单

				
    <!-- 静态菜单,不需要绑定 DOM 节点,加载时直接显示在页面上 --> 
    <div dojoType="dijit.Menu" id="navMenu"> 
    <!-- 其他用法与上下文菜单相同 --> 
    <div dojoType="dijit.PopupMenuItem"> 
      <span>Africa</span> 
      <div dojoType="dijit.Menu"> 
       <div dojoType="dijit.MenuItem">Egypt</div> 
       <div dojoType="dijit.MenuItem">Kenya</div> 
       <div dojoType="dijit.MenuItem">Sudan</div> 
      </div> 
     </div> 
     <div dojoType="dijit.PopupMenuItem"> 
      <span>Asia</span> 
      <div dojoType="dijit.Menu"> 
       <div dojoType="dijit.MenuItem">China</div> 
       <div dojoType="dijit.MenuItem">India</div> 
       <div dojoType="dijit.MenuItem">Russia</div> 
       <div dojoType="dijit.MenuItem">Mongolia</div> 
      </div> 
     </div> 
     <div dojoType="dijit.PopupMenuItem"> 
      <span>Europe</span> 
      <div dojoType="dijit.Menu"> 
       <div dojoType="dijit.MenuItem">Germany</div> 
       <div dojoType="dijit.MenuItem">France</div> 
       <div dojoType="dijit.MenuItem">Spain</div> 
       <div dojoType="dijit.MenuItem">Italy</div> 
      </div> 
     </div> 
     <div dojoType="dijit.PopupMenuItem"> 
      <span>North America</span> 
      <div dojoType="dijit.Menu"> 
       <div dojoType="dijit.MenuItem">Mexico</div> 
       <div dojoType="dijit.MenuItem">Canada</div> 
       <div dojoType="dijit.MenuItem">United States of America</div> 
      </div> 
     </div> 
     <div dojoType="dijit.PopupMenuItem"> 
      <span>South America</span> 
      <div dojoType="dijit.Menu"> 
       <div dojoType="dijit.MenuItem">Brazil</div> 
       <div dojoType="dijit.MenuItem">Argentina</div> 
      </div> 
     </div> 
     <div dojoType="dijit.MenuSeparator"></div> 
     
     <div dojoType="dijit.PopupMenuItem"> 
      <span>Different Popup</span> 
      <div dojoType="dijit.ColorPalette"></div> 
     </div> 
     <div dojoType="dijit.MenuSeparator"></div> 
     
     <div dojoType="dijit.CheckedMenuItem" checked="true" 
	  onChange="if(arguments[0]) console.log('checked'); 
	  else console.log('unchecked');">Checked</div> 

 

可以看出,静态菜单的实现与上下文菜单的代码几乎是一样的,有区别的地方在于:

  • 静态菜单的 style 中 display 取的是默认值 inline,因为它要求页面加载完就显示给用户。
  • 静态菜单的 contextMenuForWindow 取的是默认值 false,避免用户在浏览器任意位置右键单击后重新加载该菜单。
  • 静态菜单不需要绑定 targetNodeIds,不会因用户在特定节点右键单击后浏览器重新加载该菜单。

小结

综上所述,Dojo 菜单库封装了底层 JavaScript 对右键单击事件的响应,提供了简洁的开发菜单的方法,开发人员使用 Dojo 可以快速开发出一个用户界面良好的菜单。

Dojo 提供 了上下文菜单(即右键菜单和弹出式菜单)、下拉式菜单、静态菜单三种菜单形式,静态、动态两种菜单创建形式,并支持图标效果显示、自定义快捷键、分隔符、 弹出式菜单、禁用菜单项、复选菜单项等多种功能,使得 Web 开发的菜单可以与传统桌面软件菜单相媲美,提高了用户体验。

下载示例代码

加载中
返回顶部
顶部