src/cm/media/js/lib/yui/yui3.0.0/examples/node-focusmanager/assets/menubutton.js
author Yves-Marie Haussonne <ymh.work+github@gmail.com>
Fri, 09 May 2014 18:35:26 +0200
changeset 656 a84519031134
parent 0 40c8f766c9b8
permissions -rw-r--r--
add link to "privacy policy" in the header test

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);
	
});