Hide popup div when clicking outside the div.

I've attached the page I am currently working on. The items are context sensitive menus, and I have everything working apart from closing the menu when a user clicks outside of one.

I've been looking into event targeting etc and have written the findClick function but I'm stuggling as it doesn't work :(

I would be grateful if someone could look over what I have done and show me what I have done wrong?

Many thanks for your time.

Kindest regards

Chris
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<script src="getByClass.js" language="javascript" type="text/javascript"></script>
<script type="text/javascript">

/**	sticky tools
	ensures tools accordion remains on page when it would normally scroll off the top
	
	Info: csM is our Resolve Position Handler.
	
	Requires:
		library.js - browserDetect and windowOffsetY functions.
**/

var csM = {

	addEvent: function(elm, evType, fn, useCapture) {
		// addEvent cross-browser event handling for IE5+, NS6 and Mozilla
		// By Scott Andrew
		if (elm.addEventListener) {
			elm.addEventListener(evType, fn, useCapture);
			return true;
		} else if (elm.attachEvent) {
			var r = elm.attachEvent('on' + evType, fn);
			return r;
		} else {
			elm['on' + evType] = fn;
		}
	},

	init: function() {

		if (!document.getElementById) {
			return;
		}

		csM.contextMenuContainer = getElementsByClassName("contextMenuContainer");
		csM.contextMenuLabel = getElementsByClassName("contextMenuLabel");
		csM.contextMenuPanel = getElementsByClassName("contextMenuPanel");
		
		while (csM.cmlCnt<csM.contextMenuLabel.length)
		{

			csM.addEvent(csM.contextMenuLabel[csM.cmlCnt], "click", new Function("csM.showHideMenu(" + csM.cmlCnt + ")"), false);
			csM.cmlCnt++;
			
		}
	},

	showHideMenu: function(cmpCnt) {

		var con = csM.contextMenuContainer[cmpCnt];		
	
		if (con.className == 'contextMenuContainerOpen') {
	
			con.className = 'contextMenuContainer';

		} else {

			con.className = 'contextMenuContainerOpen';
			csM.findClick(e,cmpCnt);

		}
	},

    findClick: function(e,cmpCnt){
      
	  	var con = csM.contextMenuContainer[cmpCnt];
	  	var evt = (e)?e:event;
        var theElem = (evt.srcElement)?evt.srcElement:evt.target;

        while(theElem!=null){
        	if(theElem.className = 'contextMenuContainerOpen') {
          		return true;
        	}
	
          	theElem = theElem.offsetParent;
        }
        con.className = 'contextMenuContainer';
        return true;

	},

	contextMenuLabel: null, //seperate with commas
	contextMenuPanel: null,
	cmlCnt: 0
}

csM.addEvent(window, "load", csM.init, false);

</script>
<style>

body {
	background: #ffffff;
	font: normal 12px Arial, Helvetica, sans-serif;	
}
a {
	color: #1279A2;
}






.contextMenuContainer,
.contextMenuContainerOpen {
	float: right;
	clear: right;
	position: relative;
	z-index: 1;
}
.contextMenuLabel {
	cursor: pointer;	
	padding: 1px;
	display: block;
	position: relative;
	top: 1px;
	z-index: 3;
	color: #1279A2;
}
.contextMenuLabel span {
	display: block;
	background: url(images/context_menu_label_arrow.png) no-repeat right center;
	padding: 4px 20px 4px 4px;
}
.contextMenuLabel:hover,
.contextMenuContainerOpen .contextMenuLabel {
	border: solid 1px #868686;
	padding: 0; /* Always 1 less than .contextMenuLabel */
	background: url(images/context_menu_label_bg.png) repeat-x left bottom;
}
.contextMenuPanel {
	display: none;
	background: url(images/context_menu_bg.gif) repeat-y #ffffff;
	margin: 0;
	padding: 0;
	border: solid 1px #868686;
	list-style: none;
	min-width: 250px;
	position: absolute;
	right: 0;
	z-index: 2;
}
.contextMenuContainerOpen {
	z-index: 999;
}
.contextMenuContainerOpen .contextMenuLabel {
	border-bottom: none;
	z-index: 1001;
}
.contextMenuContainerOpen .contextMenuPanel {
	display: block;
	z-index: 1000;
}
.contextMenuPanel a {
	padding: 4px 4px 4px 30px;
	display: block;
	margin: 1px;
	text-decoration:none;
}
.contextMenuPanel a:hover {
	padding: 3px 3px 3px 29px;
	border: solid 1px #d2b47a;
	background: #ffe6a0;
}
.contextMenuPanel a.recent {
	background: url(images/context_menu_icon_recent.gif) no-repeat 5px center;
}
.contextMenuPanel a.recent:hover {
	background: #ffe6a0 url(images/context_menu_icon_recent.gif) no-repeat 5px center;
}
</style>
</head>
<body>

<div class="contextMenuContainer">
	<span class="contextMenuLabel"><span>Options</span></span>
	<ul class="contextMenuPanel">
		<li><a href="" class="recent">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
	</ul>
</div>

<div class="contextMenuContainer">
	<span class="contextMenuLabel"><span>Options</span></span>
	<ul class="contextMenuPanel">
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
	</ul>
</div>

<div class="contextMenuContainer">
	<span class="contextMenuLabel"><span>Options</span></span>
	<ul class="contextMenuPanel">
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
		<li><a href="">Test</a></li>
	</ul>
</div>

</body>
</html>

Open in new window

LVL 2
chrissp26Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

gamarrojgqCommented:
Hi,

You can add a handler for onMouseLeave  event to call you function to show/hide the DIV where the menu is
0
chrissp26Author Commented:
Hi thanks for that, but that's not the functionality I'm after, the menu should stay open either until the label is clicked a 2nd time or the user clicks outside the panel.
Thanks for the suggestion.
Chris


0
hieloCommented:
what is in getByClass.js
0
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

chrissp26Author Commented:
Sorry should have included that.
That gets a list of all the objects on a page with a particular classname and returns an array.

/*
	Developed by Robert Nyman, http://www.robertnyman.com
	Code/licensing: http://code.google.com/p/getelementsbyclassname/
*/
var getElementsByClassName = function (className, tag, elm){
	if (document.getElementsByClassName) {
		getElementsByClassName = function (className, tag, elm) {
			elm = elm || document;
			var elements = elm.getElementsByClassName(className),
				nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
				returnElements = [],
				current;
			for(var i=0, il=elements.length; i<il; i+=1){
				current = elements[i];
				if(!nodeName || nodeName.test(current.nodeName)) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	else if (document.evaluate) {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = "",
				xhtmlNamespace = "http://www.w3.org/1999/xhtml",
				namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
				returnElements = [],
				elements,
				node;
			for(var j=0, jl=classes.length; j<jl; j+=1){
				classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
			}
			try	{
				elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
			}
			catch (e) {
				elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
			}
			while ((node = elements.iterateNext())) {
				returnElements.push(node);
			}
			return returnElements;
		};
	}
	else {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = [],
				elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
				current,
				returnElements = [],
				match;
			for(var k=0, kl=classes.length; k<kl; k+=1){
				classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
			}
			for(var l=0, ll=elements.length; l<ll; l+=1){
				current = elements[l];
				match = false;
				for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
					match = classesToCheck[m].test(current.className);
					if (!match) {
						break;
					}
				}
				if (match) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	return getElementsByClassName(className, tag, elm);
};

Open in new window

0
hieloCommented:
What are YOU calling a "popup"? I am not seeing any "popup"!
0
chrissp26Author Commented:
I'm using the getElementsByClassName function to find all objects on a page with a specific class: contextMenuLabel
It then loops through the array which is returned by the getElementsByClassName function and applys an event listener click event to the label.
That event listener triggers the event showHideMenu which changes the parent class of the label which in turn either shows or hides the related panel.
What I need it to do now is when someone clicks outside of the panel the menu disappears.

Try the attached code sample instead as this demonstrates the context menu. without any errors.


<script type="text/javascript">

/**	sticky tools
	ensures tools accordion remains on page when it would normally scroll off the top
	
	Info: csM is our Resolve Position Handler.
	
	Requires:
		library.js - browserDetect and windowOffsetY functions.
**/

var csM = {

	addEvent: function(elm, evType, fn, useCapture) {
		// addEvent cross-browser event handling for IE5+, NS6 and Mozilla
		// By Scott Andrew
		if (elm.addEventListener) {
			elm.addEventListener(evType, fn, useCapture);
			return true;
		} else if (elm.attachEvent) {
			var r = elm.attachEvent('on' + evType, fn);
			return r;
		} else {
			elm['on' + evType] = fn;
		}
	},

	init: function() {

		if (!document.getElementById) {
			return;
		}

		csM.contextMenuContainer = getElementsByClassName("contextMenuContainer");
		csM.contextMenuLabel = getElementsByClassName("contextMenuLabel");
		csM.contextMenuPanel = getElementsByClassName("contextMenuPanel");
		
		while (csM.cmlCnt<csM.contextMenuLabel.length)
		{

			csM.addEvent(csM.contextMenuLabel[csM.cmlCnt], "click", new Function("csM.showHideMenu(" + csM.cmlCnt + ")"), false);
			csM.cmlCnt++;
			
		}
	},

	showHideMenu: function(cmpCnt) {

		var con = csM.contextMenuContainer[cmpCnt];		
	
		if (con.className == 'contextMenuContainerOpen') {
	
			con.className = 'contextMenuContainer';

		} else {

			con.className = 'contextMenuContainerOpen';
			//csM.findClick(cmpCnt);

		}
	},

//    findClick: function(e,cmpCnt){
//      
//	  	var con = csM.contextMenuContainer[cmpCnt];
//	  	var evt = (e)?e:event;
//        var theElem = (evt.srcElement)?evt.srcElement:evt.target;
//
//        while(theElem!=null){
//        	if(theElem.className = 'contextMenuContainerOpen') {
//          		return true;
//        	}
//	
//          	theElem = theElem.offsetParent;
//        }
//        con.className = 'contextMenuContainer';
//        return true;
//
//	},

	contextMenuLabel: null, //seperate with commas
	contextMenuPanel: null,
	cmlCnt: 0
}

csM.addEvent(window, "load", csM.init, false);

</script>

Open in new window

0
hieloCommented:
you will need to attach an event listener to an element outside of the area you are showing/hiding. For example, to the <HTML> tag.

Then when anything outside the container is clicked, you get all element with the "contextMenuContainerOpen" class name and then invoke csM.showHideMenu() passing a reference to that element.

Currently showHideMenu expects only a number. Modify it so that if it sees a number then it will behave as it is currently doing, but if it is NOT a number, then treat it as a node reference. That way when you do:
var openList = getElementsByClassName("contextMenuOpen");

for( var i=0; i < openList.length; ++i) csM.showHideMenu( openList[i] );

will close the item(s) in question. This is what I am doing in the private function handleClick

Below is the updated code. However, you will need to stop event bubbling so that when you click on the options it does NOT trigger the handleClick function attached to HTML. Read about it here if you are not familiar with the subject:
http://www.quirksmode.org/js/events_order.html
<script type="text/javascript">

/**	sticky tools
	ensures tools accordion remains on page when it would normally scroll off the top
	
	Info: csM is our Resolve Position Handler.
	
	Requires:
		library.js - browserDetect and windowOffsetY functions.
**/

var csM = {

	addEvent: function(elm, evType, fn, useCapture) {
		// addEvent cross-browser event handling for IE5+, NS6 and Mozilla
		// By Scott Andrew
		if (elm.addEventListener) {
			elm.addEventListener(evType, fn, useCapture);
			return true;
		} else if (elm.attachEvent) {
			var r = elm.attachEvent('on' + evType, fn);
			return r;
		} else {
			elm['on' + evType] = fn;
		}
	},

	init: function() {

		if (!document.getElementById) {
			return;
		}

		function handleClick(event){
			var l = getElementsByClassName("contextMenuContainerOpen");
			for( var i=0; i < l.length; ++i)
			{
				csM.showHideMenu(l[i])
			}
		}	

		var containers = document.getElementsByTagName("html");
		var i=-1, limit=containers.length;
		while(++i<limit)
		{
			containers[i].onclick=handleClick;
		}
		csM.contextMenuContainer = getElementsByClassName("contextMenuContainer");
		csM.contextMenuLabel = getElementsByClassName("contextMenuLabel");
		csM.contextMenuPanel = getElementsByClassName("contextMenuPanel");
		
		while (csM.cmlCnt<csM.contextMenuLabel.length)
		{

			//csM.addEvent(csM.contextMenuLabel[csM.cmlCnt], "click", new Function("csM.showHideMenu(" + csM.cmlCnt + ")"), false);
			csM.addEvent(csM.contextMenuLabel[csM.cmlCnt], "click", new Function("e","csM.showHideMenu(" + csM.cmlCnt + ");if (!e) var e = window.event;e.cancelBubble = true;if (e.stopPropagation) e.stopPropagation();"),false);
			csM.cmlCnt++;
			
		}
	},
	hideOpenMenu:function(except){
		var l = getElementsByClassName("contextMenuContainerOpen");
		for( var i=0; i < l.length; ++i)
		{
			if(l[i]!=except)
				l[i].className='contextMenuContainer';
		}
	}
	,showHideMenu: function(cmpCnt) {
		var con=cmpCnt;
		if( typeof(cmpCnt)=="number" )
			con = csM.contextMenuContainer[cmpCnt];		
	
		if( getElementsByClassName("contextMenuContainerOpen").length )
		{
			this.hideOpenMenu(con);
		}

		if (con.className == 'contextMenuContainerOpen') {
	
			con.className = 'contextMenuContainer';

		} else {

			con.className = 'contextMenuContainerOpen';
			//csM.findClick(cmpCnt);

		}
	},

//    findClick: function(e,cmpCnt){
//      
//	  	var con = csM.contextMenuContainer[cmpCnt];
//	  	var evt = (e)?e:event;
//        var theElem = (evt.srcElement)?evt.srcElement:evt.target;
//
//        while(theElem!=null){
//        	if(theElem.className = 'contextMenuContainerOpen') {
//          		return true;
//        	}
//	
//          	theElem = theElem.offsetParent;
//        }
//        con.className = 'contextMenuContainer';
//        return true;
//
//	},

	contextMenuLabel: null, //seperate with commas
	contextMenuPanel: null,
	cmlCnt: 0
}

csM.addEvent(window, "load", csM.init, false);

</script>

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
chrissp26Author Commented:
Amazing. I've got so much to learn!

Many thanks for the excellent response.

Kindest regards
Chris
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Languages and Standards

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.