diff -r 000000000000 -r 40c8f766c9b8 src/cm/media/js/lib/yui/yui3.0.0/examples/node-menunav/node-menunav-8.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui3.0.0/examples/node-menunav/node-menunav-8.html Mon Nov 23 15:14:29 2009 +0100 @@ -0,0 +1,678 @@ + + + + + YUI Library Examples: MenuNav Node Plugin: Adding Submenus On The Fly + + + + + + + + + + + + + + +
+
+
+

+ + YUI 3.x Home - + +

+
+ + +
+ + + +
+
+
+
+

YUI Library Examples: MenuNav Node Plugin: Adding Submenus On The Fly

+
+
+ + +
+
+
+
+ +

MenuNav Node Plugin: Adding Submenus On The Fly

+ +
+
+

+This example demonstrates how to use the IO Utility to +add submenus to a menu built using the MenuNav Node Plugin. +

+ +
+ +
+
+ +

+ + + View example in new window. + + +

+ + +
+
+
+ +

Design Goal

+

+This menu will be created using the +Progressive Enhancement design +pattern, so that the accessibility of the menu can be tailored based on the capabilities of +the user's browser. The goal is to design a menu that satisfies each of the following use cases: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Browser GradeTechnologiesUser Experience
CHTMLThe user is using a browser for which CSS and JavaScript are being withheld.
AHTML + CSSThe user is using an A-Grade browser, but has chosen to disable JavaScript.
AHTML + CSS + JavaScriptThe user is using an A-Grade browser with CSS and JavaScript enabled.
AHTML + CSS + JavaScript + ARIA + The user is using an ARIA-capable, A-Grade browser with CSS and + JavaScript enabled. +
+ +

+The MenuNav Node Plugin helps support most of the these use cases out of the box. By using an +established, semantic, list-based pattern for markup, the core, C-grade experience is easily +cemented using the MenuNav Node Plugin. Using JavaScript, the MenuNav Node Plugin implements +established mouse and keyboard interaction patterns to deliver a user experience that is both +familiar and easy to use, as well as support for the +WAI-ARIA Roles and States, making it easy to satisfy +the last two use cases. The second is the only use case that is not handled out of the box +when using the MenuNav Node Plugin. +

+ +

+One common solution to making a menuing system work when CSS is enabled, but JavaScript is +disabled is to leverage the :hover and :focus pseudo classes to provide +support for both the mouse and the keyboard. However, there are a couple of problems with this +approach: +

+ +
+
Inconsistent Browser Support
+
+ IE 6 only supports the :hover and :focus pseudo classes on + <a> elements. And while IE 7 supports :hover on all + elements, it only supports :focus pseudo class on <a> + elements. This solution won't work if the goal is to provide a consistent user + experience across all of the + A-grade browsers when + JavaScript is disabled. +
+
Poor User Experience
+
+ Even if the :hover and :focus pseudo classes were supported + consistently across all + A-grade browsers, it + would be a solution that would work, but wouldn't work well. Use of + the :focus pseudo class to enable keyboard support for a menu results in an + unfamiliar, potentially cumbersome experience for keyboard users. Having a menu + appear in response to its label simply being focused isn't an established interaction + pattern for menus on the desktop, and implementing that pattern could result in menus that + popup unexpectedly, and as a result, have the potential to get in the user's way. While use of the + :hover pseudo class can be used to show submenus in response to a + mouseover event, it doesn't allow the user to move diagonally from a label to + its corresponding submenu — an established interaction pattern that greatly improves a + menu's usability. +
+
Bloats Code
+
+ Relying on :hover and :focus as an intermediate solution when + JavaScript is disabled adds bloat to a menu's CSS. And relying on these pseudo classes + would also likely mean additional code on the server to detect IE, so that submenu HTML + that is inaccessible to IE users with JavaScript disabled is not delivered to the browser. +
+
+ +

+As the functionality for displaying submenus cannot be implemented in CSS to work +consistently and well in all +A-grade browsers, then that +functionality is better implemented using JavaScript. And if submenus are only accessible if +JavaScript is enabled, then it is best to only add the HTML for submenus via JavaScript. Adding +submenus via JavaScript has the additional advantage of speeding up the initial load time of +a page. +

+ +

Approach

+ +

+The approach for this menu will be to create horizontal top navigation that, when JavaScript is +enabled, is enhanced into split buttons. The content of each submenu is functionality that is +accessible via the page linked from the anchor of each submenu's label. Each submenu is purely +sugar — a faster means of accessing functionality that is accessible via another path. +

+ +

Setting Up the HTML

+ +

+Start by providing the markup for the root horizontal menu, following the pattern outlined in the +Split Button Top Nav example, minus the application of the +yui-splitbuttonnav class to the menu's bounding box, the markup for the submenus, +and the <a href="…" class="yui-menu-toggle"> elements inside each label +that toggle each submenu's display. Include the MenuNav Node Plugin CSS in the +<head> so that menu is styled even if JS is disabled. The following +illustrates what the initial menu markup: +

+ +
  1. <div id="productsandservices" class="yui-menu yui-menu-horizontal">
  2. <div class="yui-menu-content">
  3. <ul>
  4. <li>
  5. <span id="answers-label" class="yui-menu-label">
  6. <a href="http://answers.yahoo.com">Answers</a>
  7. </span>
  8. </li>
  9. <li>
  10. <span id="flickr-label" class="yui-menu-label">
  11. <a href="http://www.flickr.com">Flickr</a>
  12. </span>
  13. </li>
  14. <li>
  15. <span id="mobile-label" class="yui-menu-label">
  16. <a href="http://mobile.yahoo.com">Mobile</a>
  17. </span>
  18. </li>
  19. <li>
  20. <span id="upcoming-label" class="yui-menu-label">
  21. <a href="http://upcoming.yahoo.com/">Upcoming</a>
  22. </span>
  23. </li>
  24. <li>
  25. <span id="forgood-label" class="yui-menu-label">
  26. <a href="http://forgood.yahoo.com/index.html">Yahoo! for Good</a>
  27. </span>
  28. </li>
  29. </ul>
  30. </div>
  31. </div>
<div id="productsandservices" class="yui-menu yui-menu-horizontal">
+    <div class="yui-menu-content">
+        <ul>
+            <li>
+                <span id="answers-label" class="yui-menu-label">
+                    <a href="http://answers.yahoo.com">Answers</a>
+                </span>
+            </li>
+            <li>
+                <span id="flickr-label" class="yui-menu-label">
+                    <a href="http://www.flickr.com">Flickr</a>
+                </span>
+            </li>
+            <li>
+                <span id="mobile-label" class="yui-menu-label">
+                    <a href="http://mobile.yahoo.com">Mobile</a>
+                </span>
+            </li>
+            <li>
+                <span id="upcoming-label" class="yui-menu-label">
+                    <a href="http://upcoming.yahoo.com/">Upcoming</a>
+                </span>
+            </li>
+            <li>
+                <span id="forgood-label" class="yui-menu-label">
+                    <a href="http://forgood.yahoo.com/index.html">Yahoo! for Good</a>
+                </span>
+            </li>
+        </ul>
+    </div>
+</div>
+

Setting Up the script

+

+With the core markup for the menu in place, JavaScript will be responsible for transforming the +simple horizontal menu into top navigation rendered like split buttons. The script will +appended a submenu toggle to each menu label as well as add the yui-splitbuttonnav +class to the menu's bounding box. Each submenu's label will be responsible for creating its +corresponding submenu the first time its display is requested by the user. The content of each +submenu is fetched asynchronously using Y.io. +

+ +
  1. // Call the "use" method, passing in "node-menunav". This will load the
  2. // script and CSS for the MenuNav Node Plugin and all of the required
  3. // dependencies.
  4.  
  5. YUI({base:"../../build/", timeout: 10000}).use("node-menunav", "io", function(Y) {
  6.  
  7. var applyARIA = function (menu) {
  8.  
  9. var oMenuLabel,
  10. oMenuToggle,
  11. sID;
  12.  
  13. menu.set("role", "menu");
  14.  
  15. oMenuLabel = menu.previous();
  16. oMenuToggle = oMenuLabel.one(".yui-menu-toggle");
  17.  
  18. if (oMenuToggle) {
  19. oMenuLabel = oMenuToggle;
  20. }
  21.  
  22. sID = Y.stamp(oMenuLabel);
  23.  
  24. if (!oMenuLabel.get("id")) {
  25. oMenuLabel.set("id", sID);
  26. }
  27.  
  28. menu.set("aria-labelledby", sID);
  29.  
  30. menu.all("ul,li,.yui-menu-content").set("role", "presentation");
  31.  
  32. menu.all(".yui-menuitem-content").set("role", "menuitem");
  33.  
  34. };
  35.  
  36.  
  37. var onIOComplete = function (transactionID, response, submenuNode) {
  38.  
  39. var sHTML = response.responseText;
  40.  
  41. submenuNode.one(".yui-menu-content").set("innerHTML", sHTML);
  42. submenuNode.one("ul").addClass("first-of-type");
  43.  
  44. applyARIA(submenuNode);
  45.  
  46. // Need to set the width of the submenu to "" to clear it, then to nothing
  47. // (or the offsetWidth for IE < 8) so that the width of the submenu is
  48. // rendered correctly, otherwise the width will be rendered at the width
  49. // before the new content for the submenu was loaded.
  50.  
  51. submenuNode.setStyle("width", "");
  52.  
  53. if (Y.UA.ie && Y.UA.ie < 8) {
  54. submenuNode.setStyle("width", (submenuNode.get("offsetWidth") + "px"));
  55. }
  56.  
  57.  
  58. var oAnchor = submenuNode.one("a");
  59.  
  60. if (oAnchor) {
  61. oAnchor.focus();
  62. }
  63.  
  64. };
  65.  
  66.  
  67. var addSubmenu = function (event, submenuIdBase) {
  68.  
  69. var sSubmenuId = submenuIdBase + "-options",
  70. bIsKeyDown = (event.type === "keydown"),
  71. nKeyCode = event.keyCode,
  72. sURI;
  73.  
  74.  
  75. if ((bIsKeyDown && nKeyCode === 40) ||
  76. (event.target.hasClass("yui-menu-toggle") &&
  77. (event.type === "mousedown" || (bIsKeyDown && nKeyCode === 13)))) {
  78.  
  79. // Build the bounding box and content box for the submenu and fill
  80. // the content box with a "Loading..." message so that the user
  81. // knows the submenu's content is in the process of loading.
  82.  
  83. this.get("parentNode").append('<div id="' + sSubmenuId + '" class="yui-menu yui-menu-hidden"><div class="yui-menu-content"><p>Loading&#8230;</p></div></div>');
  84.  
  85.  
  86. // Use Y.io to fetch the content of the submenu
  87.  
  88. sURI = "assets/submenus.php?menu=" + sSubmenuId;
  89.  
  90. Y.io(sURI, { on: { complete: onIOComplete }, arguments: Y.one(("#" + sSubmenuId)) });
  91.  
  92.  
  93. // Detach event listeners so that this code runs only once
  94.  
  95. this.detach("mousedown", addSubmenu);
  96. this.detach("keydown", addSubmenu);
  97.  
  98. }
  99.  
  100. };
  101.  
  102.  
  103. // Retrieve the Node instance representing the root menu
  104. // (<div id="productsandservices">)
  105.  
  106. var menu = Y.one("#productsandservices");
  107.  
  108. menu.addClass("yui-splitbuttonnav");
  109.  
  110.  
  111. var oSubmenuToggles = {
  112. answers: { label: "Answers Options", url: "#answers-options" },
  113. flickr: { label: "Flickr Options", url: "#flickr-options" },
  114. mobile: { label: "Mobile Options", url: "#mobile-options" },
  115. upcoming: { label: "Upcoming Options", url: "#upcoming-options" },
  116. forgood: { label: "Yahoo! for Good Options", url: "#forgood-options" }
  117. },
  118.  
  119. sKey,
  120. oToggleData,
  121. oSubmenuToggle;
  122.  
  123.  
  124. // Add the menu toggle to each menu label
  125.  
  126. menu.all(".yui-menu-label").each(function(node) {
  127.  
  128. sKey = node.get("id").split("-")[0];
  129.  
  130. oToggleData = oSubmenuToggles[sKey];
  131.  
  132. oSubmenuToggle = Y.Node.create('<a class="yui-menu-toggle">' + oToggleData.label + '</a>');
  133.  
  134. // Need to set the "href" attribute via the "set" method as opposed to
  135. // including it in the string passed to "Y.Node.create" to work around a
  136. // bug in IE. The MenuNav Node Plugin code examines the "href" attribute
  137. // of all <A>s in a menu. To do this, the MenuNav Node Plugin retrieves
  138. // the value of the "href" attribute by passing "2" as a second argument
  139. // to the "getAttribute" method. This is necessary for IE in order to get
  140. // the value of the "href" attribute exactly as it was set in script or in
  141. // the source document, as opposed to a fully qualified path. (See
  142. // http://msdn.microsoft.com/en-gb/library/ms536429(VS.85).aspx for
  143. // more info.) However, when the "href" attribute is set inline via the
  144. // string passed to "Y.Node.create", calls to "getAttribute('href', 2)"
  145. // will STILL return a fully qualified URL rather than the value of the
  146. // "href" attribute exactly as it was set in script.
  147.  
  148. oSubmenuToggle.set("href", oToggleData.url);
  149.  
  150.  
  151. // Add a "mousedown" and "keydown" listener to each menu label that
  152. // will build the submenu the first time the users requests it.
  153.  
  154. node.on("mousedown", addSubmenu, node, sKey);
  155. node.on("keydown", addSubmenu, node, sKey);
  156.  
  157. node.appendChild(oSubmenuToggle);
  158.  
  159. });
  160.  
  161.  
  162. // Call the "plug" method passing in a reference to the
  163. // MenuNav Node Plugin.
  164.  
  165. menu.plug(Y.Plugin.NodeMenuNav, { autoSubmenuDisplay: false, mouseOutHideDelay: 0 });
  166.  
  167. });
//  Call the "use" method, passing in "node-menunav".  This will load the
+//  script and CSS for the MenuNav Node Plugin and all of the required
+//  dependencies.
+ 
+YUI({base:"../../build/", timeout: 10000}).use("node-menunav", "io", function(Y) {
+ 
+    var applyARIA = function (menu) {
+ 
+        var oMenuLabel,
+            oMenuToggle,
+            sID;
+ 
+        menu.set("role", "menu");
+ 
+        oMenuLabel = menu.previous();
+        oMenuToggle = oMenuLabel.one(".yui-menu-toggle");
+ 
+        if (oMenuToggle) {
+            oMenuLabel = oMenuToggle;
+        }
+ 
+        sID = Y.stamp(oMenuLabel);
+ 
+        if (!oMenuLabel.get("id")) {
+            oMenuLabel.set("id", sID);
+        }
+ 
+        menu.set("aria-labelledby", sID);
+ 
+        menu.all("ul,li,.yui-menu-content").set("role", "presentation");
+ 
+        menu.all(".yui-menuitem-content").set("role", "menuitem");
+ 
+    };
+ 
+ 
+    var onIOComplete = function (transactionID, response, submenuNode) {
+ 
+        var sHTML = response.responseText;
+ 
+        submenuNode.one(".yui-menu-content").set("innerHTML", sHTML);
+        submenuNode.one("ul").addClass("first-of-type");
+ 
+        applyARIA(submenuNode);
+ 
+        //  Need to set the width of the submenu to "" to clear it, then to nothing
+        //  (or the offsetWidth for IE < 8) so that the width of the submenu is
+        //  rendered correctly, otherwise the width will be rendered at the width
+        //  before the new content for the submenu was loaded.
+ 
+        submenuNode.setStyle("width", "");
+ 
+        if (Y.UA.ie && Y.UA.ie < 8) {
+            submenuNode.setStyle("width", (submenuNode.get("offsetWidth") + "px"));
+        }
+ 
+ 
+        var oAnchor = submenuNode.one("a");
+ 
+        if (oAnchor) {
+            oAnchor.focus();
+        }
+ 
+    };
+ 
+ 
+    var addSubmenu = function (event, submenuIdBase) {
+ 
+        var sSubmenuId = submenuIdBase + "-options",
+            bIsKeyDown = (event.type === "keydown"),
+            nKeyCode = event.keyCode,
+            sURI;
+ 
+ 
+        if ((bIsKeyDown && nKeyCode === 40) ||
+            (event.target.hasClass("yui-menu-toggle") &&
+            (event.type === "mousedown" || (bIsKeyDown && nKeyCode === 13)))) {
+ 
+            //  Build the bounding box and content box for the submenu and fill
+            //  the content box with a "Loading..." message so that the user
+            //  knows the submenu's content is in the process of loading.
+ 
+            this.get("parentNode").append('<div id="' + sSubmenuId + '" class="yui-menu yui-menu-hidden"><div class="yui-menu-content"><p>Loading&#8230;</p></div></div>');
+ 
+ 
+            //  Use Y.io to fetch the content of the submenu
+ 
+            sURI = "assets/submenus.php?menu=" + sSubmenuId;
+ 
+            Y.io(sURI, { on: { complete: onIOComplete }, arguments: Y.one(("#" + sSubmenuId)) });
+ 
+ 
+            //  Detach event listeners so that this code runs only once
+ 
+            this.detach("mousedown", addSubmenu);
+            this.detach("keydown", addSubmenu);
+ 
+        }
+ 
+    };
+ 
+ 
+    //  Retrieve the Node instance representing the root menu
+    //  (<div id="productsandservices">)
+ 
+    var menu = Y.one("#productsandservices");
+ 
+    menu.addClass("yui-splitbuttonnav");
+ 
+ 
+    var oSubmenuToggles = {
+            answers: { label: "Answers Options", url: "#answers-options" },
+            flickr: { label: "Flickr Options", url: "#flickr-options" },
+            mobile: { label: "Mobile Options", url: "#mobile-options" },
+            upcoming: { label: "Upcoming Options", url: "#upcoming-options" },
+            forgood: { label: "Yahoo! for Good Options", url: "#forgood-options" }
+        },
+ 
+        sKey,
+        oToggleData,
+        oSubmenuToggle;
+ 
+ 
+    //  Add the menu toggle to each menu label
+ 
+    menu.all(".yui-menu-label").each(function(node) {
+ 
+        sKey = node.get("id").split("-")[0];
+ 
+        oToggleData = oSubmenuToggles[sKey];
+ 
+        oSubmenuToggle = Y.Node.create('<a class="yui-menu-toggle">' + oToggleData.label + '</a>');
+ 
+        //  Need to set the "href" attribute via the "set" method as opposed to
+        //  including it in the string passed to "Y.Node.create" to work around a
+        //  bug in IE.  The MenuNav Node Plugin code examines the "href" attribute
+        //  of all <A>s in a menu.  To do this, the MenuNav Node Plugin retrieves
+        //  the value of the "href" attribute by passing "2" as a second argument
+        //  to the "getAttribute" method.  This is necessary for IE in order to get
+        //  the value of the "href" attribute exactly as it was set in script or in
+        //  the source document, as opposed to a fully qualified path.  (See
+        //  http://msdn.microsoft.com/en-gb/library/ms536429(VS.85).aspx for
+        //  more info.)  However, when the "href" attribute is set inline via the
+        //  string passed to "Y.Node.create", calls to "getAttribute('href', 2)"
+        //  will STILL return a fully qualified URL rather than the value of the
+        //  "href" attribute exactly as it was set in script.
+ 
+        oSubmenuToggle.set("href", oToggleData.url);
+ 
+ 
+        //  Add a "mousedown" and "keydown" listener to each menu label that
+        //  will build the submenu the first time the users requests it.
+ 
+        node.on("mousedown", addSubmenu, node, sKey);
+        node.on("keydown", addSubmenu, node, sKey);
+ 
+        node.appendChild(oSubmenuToggle);
+ 
+    });
+ 
+ 
+    //  Call the "plug" method passing in a reference to the
+    //  MenuNav Node Plugin.
+ 
+    menu.plug(Y.Plugin.NodeMenuNav, { autoSubmenuDisplay: false, mouseOutHideDelay: 0 });
+ 
+});
Note: In keeping with the +Exceptional Performance +team's recommendation, the script block used to instantiate the menu will be +placed at the bottom of the page. +This not only improves performance, it helps ensure that the DOM subtree of the +element representing the root menu +(<div id="productsandservices">) is ready to be scripted. +

+ +
+ +
+
+ + + +
+ +
+

Copyright © 2009 Yahoo! Inc. All rights reserved.

+

Privacy Policy - + Terms of Service - + Copyright Policy - + Job Openings

+
+
+ + + + + +