Detect click outside element? - javascript

Similar to this question, but taking it a step further. I would like to detect clicks outside of a set of items, which I am handling in the following way:
$('#menu div').live('click', function() {
// Close other open menu items, if any.
// Toggle the clicked menu item.
$('body').one('click', function(event) {
// Hide the menu item.
event.stopPropagation();
});
});
This works like a charm, unfortunately, when another menu item is open and a
second is clicked, it requires two clicks to open the second item. The first
click hides the first menu item that was open, the second shows the second menu
item.
The "correct" behavior works in the following way:
Clicking a menu item opens it.
Clicking the same menu item (or it's children) closes it.
Clicking another menu item closes the first, opens the second.
Clicking away from (open) menu items closes them.
I have tried the following in place of the above $('body').one() order to ignore clicks on menu items with little success:
// Captures click on menu items in spite of the not.
$('*').not('#menu *').one('click', function() { // Hide menu }
$('*:not(#menu)').one('click', function() { // Hide menu }
As always, thanks for any help!

Just move the body click handler outside and do something like this:
$('body').bind('click', function(e) {
if($(e.target).closest('#menu').length == 0) {
// click happened outside of menu, hide any visible menu items
}
});
It was incorrectly pointed out in the comments that e.target does not work in IE; this is not true as jQuery's Event object fixes these inconsistencies where necessary (IE, Safari).

I wrote this a long time ago, before the glory days of jQuery...
function clickedOutsideElement(elemId) {
var theElem = getEventTarget(window.event);
while(theElem != null) {
if(theElem.id == elemId)
return false;
theElem = theElem.offsetParent;
}
return true;
}
function getEventTarget(evt) {
var targ = (evt.target) ? evt.target : evt.srcElement;
if(targ != null) {
if(targ.nodeType == 3)
targ = targ.parentNode;
}
return targ;
}
document.onclick = function() {
if(clickedOutsideElement('divTest'))
alert('Outside the element!');
else
alert('Inside the element!');
}

Related

testing for click event with jasmine?

I am required to write a Jasmine.js test to test a menu icon for what will happen when it is clicked, (the menu bar slides in when it is clicked for the first time, and out when it is clicked for the second time which it does, but my test fails to demonstrate it)
I came up with this idea but the spec-runner shows (expected false to be true). any help on what could be the problem?
describe('The menu', function () {
/* TODO: Write a test that ensures the menu changes
* visibility when the menu icon is clicked. This test
* should have two expectations: does the menu display when
* clicked and does it hide when clicked again.
*/
it('the menu changes visibility when the menu icon is clicked', function () {
var menuIconClicked, menuChangesWhenClicked = false,
menuChangesWhenClickedAgain = false;
$(".menu-icon-link").click(function(){
var $this = $(this);
$(this).data('clicked', true);
if($(this).data('clicked')) {
menuIconClicked=true // if menu icon is clicked, set the menuIconClicked value to true
if (menuIconClicked && $('body').hasClass(null)) {
menuChangesWhenClicked=true;
}
}
if($this.data('clicked') && menuIconClicked===true) {
menuIconClicked=false // if menu icon is clicked when it already has been clicked aka menuIconClicked===true
if (!menuIconClicked && $('body').hasClass('menu-hidden')) {
menuChangesWhenClickedAgain=true;
}
}
});
expect(menuChangesWhenClicked).toBe(true);
expect(menuChangesWhenClickedAgain).toBe(true);
});
});
It looks like you are already using Jasmine and JQuery, so I would suggest using also the jasmine-jquery.js library to help you with tracking states?
This was a good reference: Testing That A DOM Event Was Fired
To get the code below to work, just include jasmine-jquery.js in your project folder, link the <\script> via your index.html's <\head> and your set. Hope this helps.
describe('The menu', function() {
// Add a spyOnEvent
let spyEvent, menu;
beforeEach(function() {
// I assumed your menu icon has a unique ID of 'menuIconID'
// so I passed onto a spy listener.
spyEvent = spyOnEvent('#menuIconID', 'click');
});
it('the menu changes visibility when the menu icon is clicked', function() {
// Click once
$("#menuIconID").trigger("click");
expect('click').toHaveBeenTriggeredOn('#menuIconID');
expect(spyEvent).toHaveBeenTriggered();
menu = $('body').attr('class'); // assign the new class
expect(menu).toBe('');
// Click again
$("#menuIconID").trigger("click");
expect('click').toHaveBeenTriggeredOn('#menuIconID');
expect(spyEvent).toHaveBeenTriggered();
menu = $('body').attr('class'); // update the new class
expect(menu).toBe('menu-hidden');
});
});

Logic for showing elements and hiding them on body click

I have some piece of code.
This code on button click open menu.
When i click on button again, menu is hidden (i remove .show class, show class has display:block rule, so i toggle visibility of this item by clicking on button).
In next line, i have event, which check what element is clicked. If i "click" outside" of menu, menu become hidden, beacuse i remove .show class.
And now i have a problem, it looks like first part of code dont work anymore (button.on('click')) - i mean, work, but second part of code is also executed, and this logic is now broken.
Have you got any idea for workaround?
Thanks
var menu = $('.main-menu');
var button = $('.burger');
button.on('click',function() {
if (menu.hasClass('show')) {
menu.removeClass('show');
$(this).removeClass('opened');
} else {
menu.addClass('show');
$(this).addClass('opened');
}
});
$(document).bind( "mouseup touchend", function(e){
var container = menu;
if (!container.is(e.target)
&& container.has(e.target).length === 0) {
container.removeClass('show');
button.removeClass('opened');
}
});
maybe use jQuery toggle() method ? For example:
button.on('click',function() {
menu.toggle();
});
You need to bind an outer click event only when the button click event has been triggered, and remove the outer click event when the outer click event has been triggered:
var menu = $('.main-menu');
var button = $('.burger');
button.on('click',function() {
if (menu.hasClass('show')) {
menu.removeClass('show');
$(this).removeClass('opened');
} else {
menu.addClass('show');
$(this).addClass('opened');
}
var butbindfunc = function(e){
var container = menu;
container.removeClass('show');
button.removeClass('opened');
$(this).unbind("mouseup touchend", butbindfunc);
};
$(document).not(button).bind( "mouseup touchend", butbindfunc);
});
Note, that I have removed your condition in the document binding callback, and simple excluded it from the select set.

How to make what the menu will automatically closes?

Need to do so that would be the menu closes when you click to another area of ​​the screen, please help, i'm use mootools functions
toggleMenu: function() {
if (menuOpened) {
$('home-menu').setStyle('-webkit-transform', 'translateX(-50px)');
menuOpened = false;
}
else {
$('home-menu').setStyle('-webkit-transform', 'translateX(0px)');
menuOpened = true;
}
},
If I understand you right, you want to close the menu if there is a click outside the menu.
Suggestion (assuming the menu is open):
window.addEvent('click', function (e) {
// this line under will give true if the click is outside the menu
if (e.target.id != 'home-menu' && !e.target.getParent('#home-menu')) myFunctionSpace.toggleMenu();
});
Example: http://jsbin.com/dicuquwe/1/

X-Editable: stop propagation on "click to edit"

I have an editable element inside a div which itself is clickable. Whenever I click the x-editable anchor element, the click bubbles up the DOM and triggers a click on the parent div. How can I prevent that? I know it's possible to stop this with jQuery's stopPropagation() but where would I call this method?
Here's the JSFiddle with the problem: http://jsfiddle.net/4RZvV/ . To replicate click on the editable values and you'll see that the containing div will catch a click event. This also happens when I click anywhere on the x-editable popup and I'd like to prevent that as well.
EDIT after lightswitch05 answer
I have multiple dynamic DIVs which should be selectable so I couldn't use a global variable. I added an attribute to the .editable-click anchors which get's changed instead.
editable-active is used to know if the popup is open or not
editable-activateable is used instead to know if that .editable-click anchor should be treated like it is
$(document).on('shown', "a.editable-click[editable-activateable]", function(e, reason) {
return $(this).attr("editable-active", true);
});
$(document).on('hidden', "a.editable-click[editable-activateable]", function(e, reason) {
return $(this).removeAttr("editable-active");
});
The check is pretty much like you've described it
$(document).on("click", ".version", function() {
$this = $(this)
// Check that the xeditable popup is not open
if($this.find("a[editable-active]").length === 0) { // means that editable popup is not open so we can do the stuff
// ... do stuff ...
}
})
For the click on the links, simply catch the click event and stop it:
$("a.editable-click").click(function(e){
e.stopPropagation();
});
The clicks within X-editable are a bit trickier. One way is to save a flag on weather the X-editable window is open or not, and only take action if X-editable is closed
var editableActive = false;
$("a.editable-click").on('shown', function(e, reason) {
editableActive = true;
});
$("a.editable-click").on('hidden', function(e, reason) {
editableActive = false;
});
$("div.version").click(function(e) {
var $this;
$this = $(this);
if(editableActive === false){
if ($this.hasClass("selected")) {
$(this).removeClass("selected");
} else {
$(this).addClass("selected");
}
}
});
Fixed Fiddle
It's not pretty, but we solved this problem with something like:
$('.some-class').click(function(event) {
if(event.target.tagName === "A" || event.target.tagName === "INPUT" || event.target.tagName === "BUTTON"){
return;
}
We're still looking for a solution that doesn't require a specific list of tagNames that are okay to click on.

two onClick() events happening simultaneously?

What I am expecting from my code is this:
When clicking a button, a menu of options appears at the pointer position. Any following click, whether on a menu item or elsewhere in the browser, should close the menu. Clicking on a menu item closes the menu, but not clicking anywhere else. When I uncomment $(document.body).one('click', function() {menu.remove()} the menu never appears in the first place, and I suspect that I somehow have it arranged so that the click to bring up the menu actually closes the menu as well. Here is the code:
render : function() {
this.$el.html(this.template(this.model.toJSON()));
var that = this;
if (this.model.attributes.memberType != 'OWNER') {
this.$('.memberTypeSelector').button({
icons : {
secondary : "ui-icon-triangle-1-s"
}
}).click(function(event) {
that.showPermissions(that.model, event, that);
});
...
},
showPermissions : function(member, event, view) {
var levels = ['ADMIN', 'CONTRIBUTOR', 'VIEWER'];
var menu = $('<ul>');
$.each(levels, function() {
if(member.attributes.memberType !== this) {
var item = $('<li>').appendTo(menu);
$('<a>').attr('href', '#').text(this).appendTo(item).click(function() {
menu.remove();
view.changePermission(member, this.text, view);
});
}
});
menu.menu().css({
position : 'absolute',
left : event.clientX,
top : event.clientY
});
$(document.body).append(menu);
/*$(document.body).one('click', function() {
menu.remove();
});*/
}
Thanks in advance for your help.
If you delay binding to the document by 10ms, that should be enough time for the event to propagate to the body so that it doesn't immediately close the menu, then the next click on the menu will result in the body click handler triggering.
setTimeout(function(){
$(document.body).one('click', function() {menu.remove();});
},10)
you can't use stop propagation or anything similar because that would also stop the 2nd click on the menu.
While delaying the second event binding will probably work 99.999% of the time, I can't help but feel that nagging 'what if' for the one time that something lags and it doesn't work.
This question provides a more satisfactory (at least to me) solution

Categories

Resources