node-focusmanager-3-source.mustache revision 64e210505dea375c7663112bb34ae4f378a27e29
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright<link rel="stylesheet" href="{{componentAssets}}/menubutton.css">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<div class="yui3-menubutton-loading">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <a id="button-1" href="#menu-1"><span><span>Move To</span></span></a>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <div id="menu-1">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-1" value="Inbox"></li>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-2" value="Archive"></li>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-3" value="Trash"></li>
64e210505dea375c7663112bb34ae4f378a27e29Derek GathrightYUI().use("node-focusmanager", "node-event-simulate", "overlay", function(Y){
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var menuButton = Y.one("#button-1"),
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var initMenu = function () {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright contentBox: "#menu-1",
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright visible: false,
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright tabIndex: null
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright Y.one("#menu-1").setStyle("display", "");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var boundingBox = menu.get("boundingBox"),
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright contentBox = menu.get("contentBox");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.addClass("yui3-buttonmenu");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright contentBox.addClass("yui3-buttonmenu-content");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Append a decorator element to the bounding box to render the shadow.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.append('<div class="yui3-menu-shadow"></div>');
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Apply the ARIA roles, states and properties to the menu.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright role: "menu",
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright "aria-labelledby": menuLabelID
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.all("input").set("role", "menuitem");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // For NVDA: Add the role of "presentation" to each LI
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // element to prevent NVDA from announcing the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // "listitem" role.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.all("div,ul,li").set("role", "presentation");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Use the FocusManager Node Plugin to manage the focusability
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // of each menuitem in the menu.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright contentBox.plug(Y.Plugin.NodeFocusManager, {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright descendants: "input",
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright keys: { next: "down:40", // Down arrow
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright previous: "down:38" }, // Up arrow
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright focusClass: {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright className: "yui3-menuitem-active",
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright fn: function (node) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright return node.get("parentNode");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright circular: true
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Subscribe to the change event for the "focused" attribute
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // to listen for when the menu initially gains focus, and
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // when the menu has lost focus completely.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright contentBox.focusManager.after("focusedChange", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if (!event.newVal) { // The menu has lost focus
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Set the "activeDescendant" attribute to 0 when the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // menu is hidden so that the user can tab from the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // button to the first item in the menu the next time
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // the menu is made visible.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright this.set("activeDescendant", 0);
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Hide the button's menu if the user presses the escape key
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // while focused either on the button or its menu.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright Y.on("key", function () {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright }, [menuButton, boundingBox] ,"down:27");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Set the width and height of the menu's bounding box -
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // this is necessary for IE 6 so that the CSS for the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // shadow element can simply set the shadow's width and
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // height to 100% to ensure that dimensions of the shadow
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // are always sync'd to the that of its parent menu.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menu.on("visibleChange", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.setStyles({ height: "", width: "" });
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright height: (boundingBox.get("offsetHeight") + "px"),
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright width: (boundingBox.get("offsetWidth") + "px") });
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menu.after("visibleChange", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var bVisible = event.newVal;
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Focus the first item when the menu is made visible
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // to allow users to navigate the menu via the keyboard
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if (bVisible) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Need to set focus via a timer for Webkit and Opera
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus);
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.set("aria-hidden", (!bVisible));
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Hide the menu when one of menu items is clicked.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.delegate("click", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright alert("You clicked " + this.one("input").get("value"));
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Focus each menuitem as the user moves the mouse over
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.delegate("mouseenter", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var focusManager = contentBox.focusManager;
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if (focusManager.get("focused")) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Hide the menu if the user clicks outside of it or if the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // user doesn't click on the button
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright boundingBox.get("ownerDocument").on("mousedown", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if (!oTarget.compareTo(menuButton) &&
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright !oTarget.compareTo(boundingBox) &&
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuButton.addClass("yui3-menubutton");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Hide the list until it is transformed into a menu
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright Y.one("#menu-1").setStyle("display", "none");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Remove the "yui3-menubutton-loading" class from the parent container
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // now that the necessary YUI dependencies are loaded and the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // menu button has been skinned.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuButton.ancestor(".yui3-menubutton-loading").removeClass("yui3-menubutton-loading");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Apply the ARIA roles, states and properties to the anchor.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright role: "button",
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright "aria-haspopup": true
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Remove the "href" attribute from the anchor element to
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // prevent JAWS and NVDA from reading the value of the "href"
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // attribute when the anchor is focused.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Since the anchor's "href" attribute has been removed, the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // element will not fire the click event in Firefox when the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // user presses the enter key. To fix this, dispatch the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // "click" event to the anchor when the user presses the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // enter key.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright Y.on("key", function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright }, menuButton, "down:13");
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Set the "tabIndex" attribute of the anchor element to 0 to
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // place it in the browser's default tab flow. This is
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // necessary since 1) anchor elements are not in the default
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // tab flow in Opera and 2) removing the "href" attribute
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // prevents the anchor from firing its "click" event
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // in Firefox.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Since there is some intermediary markup (<span>s) between the anchor element with the role
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // of "button" applied and the text label for the anchor - we need to use the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // "aria-labelledby" attribute to ensure that screen readers announce the text label for the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var menuLabel = menuButton.one("span span"),
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuLabelID = Y.stamp(menuLabel);
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuLabel.set("id", menuLabelID);
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuButton.set("aria-labelledby", menuLabelID);
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright var showMenu = function (event) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // For performance: Defer the creation of the menu until
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // the first time the button is clicked.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright if (!menu.get("visible")) {
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright node: menuButton,
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright points: [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Prevent the anchor element from being focused
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // when the users mouses down on it.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // Bind both a "mousedown" and "click" event listener to
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // ensure the button's menu can be invoked using both the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright // mouse and the keyboard.
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright menuButton.on(["mousedown", "click"], showMenu);