diff -r 000000000000 -r 40c8f766c9b8 src/cm/media/js/lib/yui/yui3.0.0/examples/node-focusmanager/node-focusmanager-3.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui3.0.0/examples/node-focusmanager/node-focusmanager-3.html Mon Nov 23 15:14:29 2009 +0100 @@ -0,0 +1,919 @@ + + + + + YUI Library Examples: Focus Manager Node Plugin: Accessible Menu Button + + + + + + + + + + + + + +
+
+
+

+ + YUI 3.x Home - + +

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

YUI Library Examples: Focus Manager Node Plugin: Accessible Menu Button

+
+
+ + +
+
+
+
+ +

Focus Manager Node Plugin: Accessible Menu Button

+ +
+
+

+This example illustrates how to use the Focus Manager Node Plugin, +Event's delegation support and +mouseenter event, along with +the Overlay widget and Node's support for the +WAI-ARIA Roles and States to +create an accessible menu button. +

+ +
+
+ + + + + Move To + + + + + + +
+
+
+ +

Setting Up the HTML

+

+For a menu button, start with an <a> element whose +href attribute is set to the id of an <div> +that wraps a list of <input> elements. +Therefore, without JavaScript and CSS, the menu button is simply an in-page +link to a set of additional buttons. +

+
  1. <a id="button-1" href="#menu-1"><span><span>Move To</span></span></a>
  2. <div id="menu-1">
  3. <ul>
  4. <li><input type="button" name="button-1" value="Inbox"></li>
  5. <li><input type="button" name="button-2" value="Archive"></li>
  6. <li><input type="button" name="button-3" value="Trash"></li>
  7. </ul>
  8. </div>
<a id="button-1" href="#menu-1"><span><span>Move To</span></span></a>
+<div id="menu-1">
+    <ul>
+        <li><input type="button" name="button-1" value="Inbox"></li>
+        <li><input type="button" name="button-2" value="Archive"></li>
+        <li><input type="button" name="button-3" value="Trash"></li>
+    </ul>
+</div>
+ +

Progressive Enhancement

+

+The markup above will be transformed using both CSS and JavaScript. To account +for the scenario where the user has CSS enabled in their browser but JavaScript +is disabled, the CSS used to style the menu button will be loaded via JavaScript +using the YUI instance's built-in Loader. +Additionally, a small block of JavaScript will be placed in the +<head> — used to temporarily hide the markup +while the JavaScript and CSS are in the process of loading to prevent the user +from seeing a flash unstyled content. +

+
  1. YUI({
  2. base:"../../build/",
  3. timeout: 10000,
  4. modules: {
  5. "menubuttoncss": {
  6. type: "css",
  7. fullpath: "assets/menubutton.css"
  8. },
  9. "menubuttonjs": {
  10. type: "js",
  11. fullpath: "assets/menubutton.js",
  12. requires: ["menubuttoncss", "node-focusmanager", "node-event-simulate", "overlay"]
  13. }
  14. }
  15.  
  16. }).use("menubuttonjs", function(Y, result) {
  17.  
  18. // The callback supplied to use() will be executed regardless of
  19. // whether the operation was successful or not. The second parameter
  20. // is a result object that has the status of the operation. We can
  21. // use this to try to recover from failures or timeouts.
  22.  
  23. if (!result.success) {
  24.  
  25. Y.log("Load failure: " + result.msg, "warn", "Example");
  26.  
  27. // Show the menu button HTML if loading the JavaScript resources
  28. // failed, that way the original unskinned menu button
  29. // will be visible so that the user can interact with the menu
  30. // either way.
  31.  
  32. document.documentElement.className = "";
  33.  
  34. }
  35.  
  36. });
YUI({
+    base:"../../build/",
+    timeout: 10000,
+    modules: {
+        "menubuttoncss": {
+            type: "css",
+            fullpath: "assets/menubutton.css"
+        },
+        "menubuttonjs": {
+            type: "js",
+            fullpath: "assets/menubutton.js",
+            requires: ["menubuttoncss", "node-focusmanager", "node-event-simulate", "overlay"]
+        }
+    }
+ 
+}).use("menubuttonjs", function(Y, result) {
+ 
+    //  The callback supplied to use() will be executed regardless of
+    //  whether the operation was successful or not.  The second parameter
+    //  is a result object that has the status of the operation.  We can
+    //  use this to try to recover from failures or timeouts.
+ 
+    if (!result.success) {
+ 
+        Y.log("Load failure: " + result.msg, "warn", "Example");
+ 
+        //  Show the menu button HTML if loading the JavaScript resources
+        //  failed, that way the original unskinned menu button
+        //  will be visible so that the user can interact with the menu
+        //  either way.
+ 
+        document.documentElement.className = "";
+ 
+    }
+ 
+});
+ +

ARIA Support

+

+Through the use of CSS and JavaScript the HTML for the menu button can be +transformed into something that looks and behaves like a desktop menu button, +but users of screen readers won't perceive it as an atomic widget, but rather +simply as a set of HTML elements. However, through the application +of the +WAI-ARIA Roles and States, it is +possible to improve the semantics of the markup such that users of screen +readers perceive it as a menu button control. +

+ + +

Keyboard Functionality

+

+The keyboard functionality for the button's menu will be provided by the Focus +Manager Node Plugin. The Focus Manager's +descendants +attribute is set to a value of "input", so that only one menuitem in the +button's menu is in the browser's default tab flow. This allows users +navigating via the keyboard to use the tab key to quickly move into and out of +the menu. Once the menu has focus, the user can move focus among each menuitem +using the up and down arrows keys, as defined by the value of the +keys +attribute. The +focusClass +attribute is used to apply a class of yui-menuitem-active to +the parent <li> of each +<input> when it is focused, making it easy to style the +menuitem's focused state in each of the +A-Grade browsers. + +

  1. YUI().use("*", function (Y) {
  2.  
  3. var menuButton = Y.one("#button-1"),
  4. menu;
  5.  
  6.  
  7. var initMenu = function () {
  8.  
  9. menu = new Y.Overlay({
  10. contentBox: "#menu-1",
  11. visible: false,
  12. tabIndex: null
  13. });
  14.  
  15. menu.render();
  16.  
  17.  
  18. Y.one("#menu-1").setStyle("display", "");
  19.  
  20. var boundingBox = menu.get("boundingBox"),
  21. contentBox = menu.get("contentBox");
  22.  
  23. boundingBox.addClass("yui-buttonmenu");
  24. contentBox.addClass("yui-buttonmenu-content");
  25.  
  26.  
  27. // Append a decorator element to the bounding box to render the shadow.
  28.  
  29. boundingBox.append('<div class="yui-menu-shadow"></div>');
  30.  
  31.  
  32. // Apply the ARIA roles, states and properties to the menu.
  33.  
  34. boundingBox.setAttrs({
  35. role: "menu",
  36. "aria-labelledby": menuButton.get("id")
  37. });
  38.  
  39. boundingBox.all("input").set("role", "menuitem");
  40.  
  41.  
  42. // For NVDA: Add the role of "presentation" to each LI
  43. // element to prevent NVDA from announcing the
  44. // "listitem" role.
  45.  
  46. boundingBox.all("div,ul,li").set("role", "presentation");
  47.  
  48.  
  49. // Use the FocusManager Node Plugin to manage the focusability
  50. // of each menuitem in the menu.
  51.  
  52. contentBox.plug(Y.Plugin.NodeFocusManager, {
  53.  
  54. descendants: "input",
  55. keys: { next: "down:40", // Down arrow
  56. previous: "down:38" }, // Up arrow
  57. focusClass: {
  58. className: "yui-menuitem-active",
  59. fn: function (node) {
  60. return node.get("parentNode");
  61. }
  62. },
  63. circular: true
  64.  
  65. });
  66.  
  67.  
  68. // Subscribe to the change event for the "focused" attribute
  69. // to listen for when the menu initially gains focus, and
  70. // when the menu has lost focus completely.
  71.  
  72. contentBox.focusManager.after("focusedChange", function (event) {
  73.  
  74. if (!event.newVal) { // The menu has lost focus
  75.  
  76. // Set the "activeDescendant" attribute to 0 when the
  77. // menu is hidden so that the user can tab from the
  78. // button to the first item in the menu the next time
  79. // the menu is made visible.
  80.  
  81. this.set("activeDescendant", 0);
  82.  
  83. }
  84.  
  85. });
  86.  
  87.  
  88. // Hide the button's menu if the user presses the escape key
  89. // while focused either on the button or its menu.
  90.  
  91. Y.on("key", function () {
  92.  
  93. menu.hide();
  94. menuButton.focus();
  95.  
  96. }, [menuButton, boundingBox] ,"down:27");
  97.  
  98.  
  99. if (Y.UA.ie === 6) {
  100.  
  101. // Set the width and height of the menu's bounding box -
  102. // this is necessary for IE 6 so that the CSS for the
  103. // shadow element can simply set the shadow's width and
  104. // height to 100% to ensure that dimensions of the shadow
  105. // are always sync'd to the that of its parent menu.
  106.  
  107. menu.on("visibleChange", function (event) {
  108.  
  109. if (event.newVal) {
  110.  
  111. boundingBox.setStyles({ height: "", width: "" });
  112.  
  113. boundingBox.setStyles({
  114. height: (boundingBox.get("offsetHeight") + "px"),
  115. width: (boundingBox.get("offsetWidth") + "px") });
  116.  
  117. }
  118.  
  119. });
  120.  
  121. }
  122.  
  123.  
  124. menu.after("visibleChange", function (event) {
  125.  
  126. var bVisible = event.newVal;
  127.  
  128. // Focus the first item when the menu is made visible
  129. // to allow users to navigate the menu via the keyboard
  130.  
  131. if (bVisible) {
  132.  
  133. // Need to set focus via a timer for Webkit and Opera
  134. Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus);
  135.  
  136. }
  137.  
  138. boundingBox.set("aria-hidden", (!bVisible));
  139.  
  140. });
  141.  
  142.  
  143. // Hide the menu when one of menu items is clicked.
  144.  
  145. boundingBox.delegate("click", function (event) {
  146.  
  147. alert("You clicked " + this.query("input").get("value"));
  148.  
  149. contentBox.focusManager.blur();
  150. menu.hide();
  151.  
  152. }, "li");
  153.  
  154.  
  155. // Focus each menuitem as the user moves the mouse over
  156. // the menu.
  157.  
  158. boundingBox.delegate("mouseenter", function (event) {
  159.  
  160. var focusManager = contentBox.focusManager;
  161.  
  162. if (focusManager.get("focused")) {
  163. focusManager.focus(this.query("input"));
  164. }
  165.  
  166. }, "li");
  167.  
  168.  
  169. // Hide the menu if the user clicks outside of it or if the
  170. // user doesn't click on the button
  171.  
  172. boundingBox.get("ownerDocument").on("mousedown", function (event) {
  173.  
  174. var oTarget = event.target;
  175.  
  176. if (!oTarget.compareTo(menuButton) &&
  177. !menuButton.contains(oTarget) &&
  178. !oTarget.compareTo(boundingBox) &&
  179. !boundingBox.contains(oTarget)) {
  180.  
  181. menu.hide();
  182.  
  183. }
  184.  
  185. });
  186.  
  187. };
  188.  
  189.  
  190. menuButton.addClass("yui-menubutton");
  191.  
  192.  
  193. // Hide the list until it is transformed into a menu
  194.  
  195. Y.one("#menu-1").setStyle("display", "none");
  196.  
  197.  
  198. // Remove the "yui-loading" class from the documentElement
  199. // now that the necessary YUI dependencies are loaded and the
  200. // menu button has been skinned.
  201.  
  202. menuButton.get("ownerDocument").get("documentElement").removeClass("yui-loading");
  203.  
  204.  
  205. // Apply the ARIA roles, states and properties to the anchor.
  206.  
  207. menuButton.setAttrs({
  208. role: "button",
  209. "aria-haspopup": true
  210. });
  211.  
  212.  
  213. // Remove the "href" attribute from the anchor element to
  214. // prevent JAWS and NVDA from reading the value of the "href"
  215. // attribute when the anchor is focused.
  216.  
  217. if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) {
  218.  
  219. menuButton.removeAttribute("href");
  220.  
  221. // Since the anchor's "href" attribute has been removed, the
  222. // element will not fire the click event in Firefox when the
  223. // user presses the enter key. To fix this, dispatch the
  224. // "click" event to the anchor when the user presses the
  225. // enter key.
  226.  
  227. Y.on("key", function (event) {
  228.  
  229. menuButton.simulate("click");
  230.  
  231. }, menuButton, "down:13");
  232.  
  233. }
  234.  
  235.  
  236. // Set the "tabIndex" attribute of the anchor element to 0 to
  237. // place it in the browser's default tab flow. This is
  238. // necessary since 1) anchor elements are not in the default
  239. // tab flow in Opera and 2) removing the "href" attribute
  240. // prevents the anchor from firing its "click" event
  241. // in Firefox.
  242.  
  243. menuButton.set("tabIndex", 0);
  244.  
  245.  
  246. var showMenu = function (event) {
  247.  
  248. // For performance: Defer the creation of the menu until
  249. // the first time the button is clicked.
  250.  
  251. if (!menu) {
  252. initMenu();
  253. }
  254.  
  255.  
  256. if (!menu.get("visible")) {
  257.  
  258. menu.set("align", {
  259. node: menuButton,
  260. points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL]
  261. });
  262.  
  263. menu.show();
  264.  
  265. }
  266.  
  267. // Prevent the anchor element from being focused
  268. // when the users mouses down on it.
  269. event.preventDefault();
  270.  
  271. };
  272.  
  273.  
  274. // Bind both a "mousedown" and "click" event listener to
  275. // ensure the button's menu can be invoked using both the
  276. // mouse and the keyboard.
  277.  
  278. menuButton.on("mousedown", showMenu);
  279. menuButton.on("click", showMenu);
  280.  
  281. });
YUI().use("*", function (Y) {
+ 
+    var menuButton = Y.one("#button-1"),
+        menu;
+ 
+ 
+    var initMenu = function () {
+ 
+        menu = new Y.Overlay({
+            contentBox: "#menu-1",
+            visible: false,
+            tabIndex: null
+        });
+ 
+        menu.render();
+ 
+ 
+        Y.one("#menu-1").setStyle("display", "");
+ 
+        var boundingBox = menu.get("boundingBox"),
+            contentBox = menu.get("contentBox");
+ 
+        boundingBox.addClass("yui-buttonmenu");
+        contentBox.addClass("yui-buttonmenu-content");
+ 
+ 
+        // Append a decorator element to the bounding box to render the shadow.
+ 
+        boundingBox.append('<div class="yui-menu-shadow"></div>');
+ 
+ 
+        //  Apply the ARIA roles, states and properties to the menu.
+ 
+        boundingBox.setAttrs({
+            role: "menu",
+            "aria-labelledby": menuButton.get("id")
+        });
+ 
+        boundingBox.all("input").set("role", "menuitem");
+ 
+ 
+        //  For NVDA: Add the role of "presentation" to each LI
+        //  element to prevent NVDA from announcing the
+        //  "listitem" role.
+ 
+        boundingBox.all("div,ul,li").set("role", "presentation");
+ 
+ 
+        //  Use the FocusManager Node Plugin to manage the focusability
+        //  of each menuitem in the menu.
+ 
+        contentBox.plug(Y.Plugin.NodeFocusManager, {
+ 
+                descendants: "input",
+                keys: { next: "down:40", // Down arrow
+                        previous: "down:38" },  // Up arrow
+                focusClass: {
+                    className: "yui-menuitem-active",
+                    fn: function (node) {
+                        return node.get("parentNode");
+                    }
+                },
+                circular: true
+ 
+            });
+ 
+ 
+        //  Subscribe to the change event for the "focused" attribute
+        //  to listen for when the menu initially gains focus, and
+        //  when the menu has lost focus completely.
+ 
+        contentBox.focusManager.after("focusedChange", function (event) {
+ 
+            if (!event.newVal) {    // The menu has lost focus
+ 
+                //  Set the "activeDescendant" attribute to 0 when the
+                //  menu is hidden so that the user can tab from the
+                //  button to the first item in the menu the next time
+                //  the menu is made visible.
+ 
+                this.set("activeDescendant", 0);
+ 
+            }
+ 
+        });
+ 
+ 
+        //  Hide the button's menu if the user presses the escape key
+        //  while focused either on the button or its menu.
+ 
+        Y.on("key", function () {
+ 
+            menu.hide();
+            menuButton.focus();
+ 
+        }, [menuButton, boundingBox] ,"down:27");
+ 
+ 
+        if (Y.UA.ie === 6) {
+ 
+            //  Set the width and height of the menu's bounding box -
+            //  this is necessary for IE 6 so that the CSS for the
+            //  shadow element can simply set the shadow's width and
+            //  height to 100% to ensure that dimensions of the shadow
+            //  are always sync'd to the that of its parent menu.
+ 
+            menu.on("visibleChange", function (event) {
+ 
+                if (event.newVal) {
+ 
+                    boundingBox.setStyles({ height: "", width: "" });
+ 
+                    boundingBox.setStyles({
+                        height: (boundingBox.get("offsetHeight") + "px"),
+                        width: (boundingBox.get("offsetWidth") + "px") });
+ 
+                }
+ 
+            });
+ 
+        }
+ 
+ 
+        menu.after("visibleChange", function (event) {
+ 
+            var bVisible = event.newVal;
+ 
+            //  Focus the first item when the menu is made visible
+            //  to allow users to navigate the menu via the keyboard
+ 
+            if (bVisible) {
+ 
+                //  Need to set focus via a timer for Webkit and Opera
+                Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus);
+ 
+            }
+ 
+            boundingBox.set("aria-hidden", (!bVisible));
+ 
+        });
+ 
+ 
+        //  Hide the menu when one of menu items is clicked.
+ 
+        boundingBox.delegate("click", function (event) {
+ 
+            alert("You clicked " + this.query("input").get("value"));
+ 
+            contentBox.focusManager.blur();
+            menu.hide();
+ 
+        }, "li");
+ 
+ 
+        //  Focus each menuitem as the user moves the mouse over
+        //  the menu.
+ 
+        boundingBox.delegate("mouseenter", function (event) {
+ 
+            var focusManager = contentBox.focusManager;
+ 
+            if (focusManager.get("focused")) {
+                focusManager.focus(this.query("input"));
+            }
+ 
+        }, "li");
+ 
+ 
+        //  Hide the menu if the user clicks outside of it or if the
+        //  user doesn't click on the button
+ 
+        boundingBox.get("ownerDocument").on("mousedown", function (event) {
+ 
+            var oTarget = event.target;
+ 
+            if (!oTarget.compareTo(menuButton) &&
+                !menuButton.contains(oTarget) &&
+                !oTarget.compareTo(boundingBox) &&
+                !boundingBox.contains(oTarget)) {
+ 
+                menu.hide();
+ 
+            }
+ 
+        });
+ 
+    };
+ 
+ 
+    menuButton.addClass("yui-menubutton");
+ 
+ 
+    //  Hide the list until it is transformed into a menu
+ 
+    Y.one("#menu-1").setStyle("display", "none");
+ 
+ 
+    //  Remove the "yui-loading" class from the documentElement
+    //  now that the necessary YUI dependencies are loaded and the
+    //  menu button has been skinned.
+ 
+    menuButton.get("ownerDocument").get("documentElement").removeClass("yui-loading");
+ 
+ 
+    //  Apply the ARIA roles, states and properties to the anchor.
+ 
+    menuButton.setAttrs({
+        role: "button",
+        "aria-haspopup": true
+    });
+ 
+ 
+    //  Remove the "href" attribute from the anchor element to
+    //  prevent JAWS and NVDA from reading the value of the "href"
+    //  attribute when the anchor is focused.
+ 
+    if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) {
+ 
+        menuButton.removeAttribute("href");
+ 
+        //  Since the anchor's "href" attribute has been removed, the
+        //  element will not fire the click event in Firefox when the
+        //  user presses the enter key.  To fix this, dispatch the
+        //  "click" event to the anchor when the user presses the
+        //  enter key.
+ 
+        Y.on("key", function (event) {
+ 
+            menuButton.simulate("click");
+ 
+        }, menuButton, "down:13");
+ 
+    }
+ 
+ 
+    //  Set the "tabIndex" attribute of the anchor element to 0 to
+    //  place it in the browser's default tab flow.  This is
+    //  necessary since 1) anchor elements are not in the default
+    //  tab flow in Opera and 2) removing the "href" attribute
+    //  prevents the anchor from firing its "click" event
+    //  in Firefox.
+ 
+    menuButton.set("tabIndex", 0);
+ 
+ 
+    var showMenu = function (event) {
+ 
+        //  For performance: Defer the creation of the menu until
+        //  the first time the button is clicked.
+ 
+        if (!menu) {
+            initMenu();
+        }
+ 
+ 
+        if (!menu.get("visible")) {
+ 
+            menu.set("align", {
+                node: menuButton,
+                points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL]
+            });
+ 
+            menu.show();
+ 
+        }
+ 
+        //  Prevent the anchor element from being focused
+        //  when the users mouses down on it.
+        event.preventDefault();
+ 
+    };
+ 
+ 
+    //  Bind both a "mousedown" and "click" event listener to
+    //  ensure the button's menu can be invoked using both the
+    //  mouse and the keyboard.
+ 
+    menuButton.on("mousedown", showMenu);
+    menuButton.on("click", showMenu);
+ 
+});
+ +
+ +
+
+ + + +
+ +
+

Copyright © 2009 Yahoo! Inc. All rights reserved.

+

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

+
+
+ + + + + +