Here's a pen with the full html: https://codepen.io/froggomad/pen/WLdzoB
I'm writing 2 functions - one to show hidden content, and one to hide it. I'm wanting the show() function to execute on the parent div and the hide() function to execute on the div with the selector .click-text.
However, I'm switching text on .click-text from show to hide so I don't want the hide function to remain on the text at all times. I also want it obvious that its interactive text when changing to a hide function, so I make it a link.
That's all well, but when attempting to set the onclick Attr of the parent back to the show() function, nothing in the hide block executes at all.
If I remove the line setting the parent's onclick Attr, the script executes as expected. If I set another element's onclick Attr, the script executes as expected.
However, with that line in there, nothing happens and there's no output in the console to indicate an error. I even set an alert with the type of element and classname to ensure I'm targeting the right element.
Get closest parent of element matching selector:
var getClosest = function (element, selector) {
for ( ; element && element !== document; element = element.parentNode ) {
if ( element.matches(selector) ) return element;
}
return null;
}
Show Hidden Element ul.service-category-menu
function show(elem) {
var menu = elem.querySelector("ul.service-category-menu"),
click = elem.querySelector(".click-text"),
parent = getClosest(elem, '.service-category');
;
if (menu.style.display === "none" || menu.style.display === "") {
menu.style.display = "block";
click.innerHTML = "<a href=\"#\">Click to Hide<\/a>";
click.setAttribute('onclick','hide(this);');
elem.setAttribute('onclick', 'null');
}
}
Hide Element
function hide(elem) {
var parent = getClosest(elem, '.service-category'),
menu = parent.querySelector("ul.service-category-menu"),
click = parent.querySelector(".click-text")
;
alert(parent + "\n" + parent.className);
//Outputs div element with expected class name (class name is unique on each div)
if (menu.style.display === "block") {
menu.style.display = "none";
click.innerHTML = "Click to Show";
click.setAttribute('onclick', 'null');
//the above lines don't execute when the following line is in place. There's no error in console.
parent.setAttribute('onclick','show(this)');
}
}
First off, I must confess that I'm against using onclick attributes. If you're not using a framework such as VueJS or React, I think HTML and JS should remain separated for better control and maintainability.
You can use addEventListener, removeEventListener, and e.stopPropagation() to avoid triggering multiple event handlers.
Events have two phases:
Event capture: the event spreads from the document all the way down to the target element.
To catch an event during this phase, do:
elm.addEventListener('click', myFunc, true);
Event bubbling: the event bounces back from the target to the document.
To catch an event during this phase, do:
elm.addEventListener('click', myFunc, false); /* or just omit the 3rd param */
Using e.stopPropagation() allows you to break that chain.
// When the DOM is ready
window.addEventListener("DOMContentLoaded", init);
function init() {
// Get all categories
var $categories = document.querySelectorAll(".service-category");
// For each of them
Array.from($categories).forEach(function($category) {
// Add an event listener for clicks
$category.addEventListener("click", show);
});
}
function getClosest(element, selector) {
for (; element && element !== document; element = element.parentNode) {
if (element.matches(selector)) return element;
}
return null;
}
function show(e) {
var $menu = this.querySelector("ul.service-category-menu"),
$click = this.querySelector(".click-text");
if (["none", ""].includes($menu.style.display)) {
$menu.style.display = "block";
$click.innerHTML = 'Click to Hide';
$click.addEventListener("click", hide);
// Remove the `show` event listener
this.removeEventListener("click", show);
}
e.stopPropagation();
}
function hide(e) {
var $parent = getClosest(this, ".service-category"),
$menu = $parent.querySelector("ul.service-category-menu"),
$click = $parent.querySelector(".click-text");
if (!["none", ""].includes($menu.style.display)) {
$menu.style.display = "none";
$click.innerHTML = "Click to Show";
$click.removeEventListener("click", hide);
$parent.addEventListener("click", show);
}
e.stopPropagation();
}
.service-category{display:inline-block;border:3px solid #ccc;margin:1%;font-weight:700;font-size:3.5vw;cursor:pointer;background-color:#fff;z-index:3;background-position:center;background-size:cover;color:#000}.click-text{text-align:right;font-size:1.25vw;font-style:italic;font-weight:700;padding-right:1%}.service-category:hover .click-text{color:#b22222}.service-category-menu{display:none;margin-left:8%;margin-right:8%;margin-top:1%;background-color:#fff;font-weight:700;font-size:1.6vw;border-radius:10px}
<div class="service-category web-back" id="web-back">
<div class="row-overlay">
Web <br /> Development
<div class="click-text">Click to Show</div>
<ul class="service-category-menu web">
<li>
Some text...
</li>
</ul>
</div>
</div>
<div class="service-category web-front" id="web-front">
<div class="row-overlay">
Web <br /> Design
<div class="click-text">Click to Show</div>
<ul class="service-category-menu web">
<li>
Some text...
</li>
</ul>
</div>
</div>
It is executed, it's just after you click that Click to Hide, the event continues to parent and the event handler of the parent executed. Thus, what exactly happen is (with that line), after hide() called, you inadvertently called show().
In javascript it's usually called bubbles (when you click the children, the click handler of parent will also be executed after click handler of children complete).
So the solution, you can add this line at the end of the hide() function
event.stopPropagation();
To stop the event from continuing to the parent
Setting event.stopPropagation as mentioned in the other answer will potentially fix your issue. Alternatively, you can change the last line of your hide function to window.setTimeout(e => parent.setAttribute('onclick','show(this)'), 0).
What's happening right now is:
You click
it executes your hide function, and during that function it binds a click event to the parent
The click propagates to the parent and executes the newly bound function, re-showing the content.
By using setTimeout(fn, 0), you're making sure the click event completes before the function is bound to the parent.
Related
How to define on child element, not to trigger an event,
like on .mouseclick = 'no-event' or : .mousedown = 'no-event'
with natural Javascript command?
So, it's parent will get the the first event before bubbling-up,
the parent element event will get e. target = e.currentTarget
I need to do it on some specific child elements, and some not.
On parent element the 'capture', does not give the correct behaviour
as mentioned above.
All other solutions I follow on the web, are giving many 'tricky' solutions
that don't work correctly.
var parentEl = document.getElementById('parent');
var childEl = document.getElementById('child');
parentEl.addEventListener('click', function(event) {
}, true)
childEl.addEventListener('click', function(event) {
}, true)
If you pass the true on eventListener it stops the event from bubbling.
You could give the children a custom class which determines it should be clickable or not.
Inside the callback function for it's parent container check which class the target is using and act accordingly.
Here's an example:
document.getElementById("container").addEventListener('click', function(e) {
if (e.target.className == "clickable") {
console.log("I should do something!");
} else {
console.log("I should NOT do something!");
}
});
<div id="container">
<div class="clickable">
this is clickable
</div>
<div>
this is not clickable
</div>
</div>
Sorry the title maybe a bit bogus. but here its, imagine I have 3 divs like this :
<div id="1" class="clickable">
<div id="2" class="some random thing">
<div id="3" class="clickable">
</div>
</div>
</div>
now imagine I have
$('.clickable').on('click',function(){blahblah});
I want them both to be clickable but not at the same time.
When I click the inside clickable class div (where the id is 3) both the the inner one and the parent one will trigger the blahblah. I know I can use something like
$('.clickable').on('click',function(e){e.stopPropagation(); blahblah});
but the problem is even if I do that, clicking the middle child (the one with the id of 2) will trigger the blah blah on the parent as well.
Is there anyway to stop that? For example if this div and only this div not parent not child, only this div has the class of clickable, be clickable.
Thank you very much.
event.stopPropogation stops the event from bubbling up the event chain, but this conflicts when you click on the middle div. I am posting the javascript code that you can refer to. Inside the click event listener, the conditional if block checks whether the event was triggered in that particular div element, hence stopping the event bubbling.
document.getElementById("outer").addEventListener('click', function(event) {
if (event.target !== this) {
return;
}
alert("You clicked outer div!");
});
document.getElementById("middle").addEventListener('click', function(event) {
if (event.target !== this) {
return;
}
alert("You clicked middle div!");
});
document.getElementById("inner").addEventListener('click', function(event) {
alert("You clicked inner div!");
});
You can also refer to this fiddle : https://jsfiddle.net/9fskuunr/3/
You might want to do something like this
$(".clickable").click(function(e){
e.stopPropagation();
var id = $(e.target).attr('id');
alert(id + ' is clicked');
}).children(':not(.clickable)').click(function(e) {
return false;
});
See the JSFiddle here: https://jsfiddle.net/3h4yvfv4/1/
One approach you might want to consider is to use event delegation. That way you only assign one event handler, rather than multiple event handlers for every .clickable element, which may give you a performance benefit depending on how many .clickable elements you have on a given page.
The code using event delegation looks something like this:
$(document).on('click', function(e) {
var target = $(e.target),
isClickable,
closestClickable,
isNestedInClickable;
isClickable = target.hasClass('clickable');
if (isClickable) {
handleClick(e);
} else {
closestClickable = target.closest('.clickable');
isNestedInClickable = !!closestClickable;
}
if (isClickable || isNestedInClickable) {
e.stopPropagation();
}
});
function handleClick(e) {
console.log(e.target.id + ' clicked');
}
JSFiddle: https://jsfiddle.net/sytkvgng/2/
I want to make link on parent node active in tree view. So far I do this:
<li>A - Referensi Spasial <!--this is parent node-->
<ul>
<li>Jaring Kerangka Referensi Geodesi</li>
<li>Model Geoid
<ul>
<li>AB01010010</li>
<li>AB01010020</li>
</ul>
</li>
<li>Stasiun Pasang Surut</li>
</ul>
</li>
When I click the parent node, it just expand the children nodes. What I want is when I click it, it open the link I set on <a></a>
Here is my screenshot of my tree view:
And this is the javascript code:
$.fn.extend({
treed: function (o) {
var openedClass = 'glyphicon-minus-sign';
var closedClass = 'glyphicon-plus-sign';
if (typeof o != 'undefined'){
if (typeof o.openedClass != 'undefined'){
openedClass = o.openedClass;
}
if (typeof o.closedClass != 'undefined'){
closedClass = o.closedClass;
}
};
//initialize each of the top levels
var tree = $(this);
tree.addClass("tree");
tree.find('li').has("ul").each(function () {
var branch = $(this); //li with children ul
branch.prepend("<i class='indicator glyphicon " + closedClass + "'></i>");
branch.addClass('branch');
branch.on('click', function (e) {
if (this == e.target) {
var icon = $(this).children('i:first');
icon.toggleClass(openedClass + " " + closedClass);
$(this).children().children().toggle();
}
})
branch.children().children().toggle();
});
//fire event from the dynamically added icon
tree.find('.branch .indicator').each(function(){
$(this).on('click', function () {
$(this).closest('li').click();
});
});
//fire event to open branch if the li contains an anchor instead of text
tree.find('.branch>a').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
//fire event to open branch if the li contains a button instead of text
tree.find('.branch>button').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
}
});
//Initialization of treeviews
$('#tree1').treed();
So, how can I do that thing? Can anyone help me? Thanks
If my understanding is correct, you are asking why your links seem to have no effect at all, and clicking on them just expands the tree as if it were normal text?
It seems to me that this is simply due to the code that attaches events on those links, i.e. the block below comment "fire event to open branch if the li contains an anchor instead of text".
The $(this).closest('li').click(); instruction generates a new click event on the parent "li" item.
The e.preventDefault(); instruction prevents the link from receiving the "click" event, therefore it does not redirect the page / scroll to anchor.
So the result is as if the "click" had "jumped" your link and be passed to the parent "li", therefore not redirecting but expanding the tree.
You could simply remove that block to restore the links normal behaviour. However, the "click" event would still bubble to the parent "li" element, and expand the tree. Not an issue if the pages is redirected, but it is noticeable if the link goes to a local anchor (same page).
To prevent this (but still let the link do its normal job), keep the block but replace the 2 inner instructions by e.stopPropagation();. On the contrary of preventDefault(), it lets the current event happening, but it stops the event bubbling (parent elements do not receive it).
Now I am not sure about the reason for that block. It seems that it was more intended for anchors (which use the same "a" tag but with "name" attribute instead of "href"). But there would be no reason to prevent the "click" event on an anchor?
I have this piece of code:
<div id="mydiv" onclick="ajax_call()">
Mylink
</div>
I'd like ajax_call() to be called only when clicking on empty space inside div but not on "Mylink". Is it possible without any external javascript framework?
Moreover I have this piece of css:
div#mydiv:hover{
background-color: blue;
}
Is it possible to disable the :hover stylesheet when the cursor is placed over "Mylink" (in order to suggest that clicking on "Mylink" won't trigger ajax_call() but will take to myurl)?
Attach the function at child element with click event, After clicked on child element it's handler stops the immediate propagation, As a result ajax_call() will not be invoked.
HTML
<div id="mydiv" onclick="ajax_call()">
Mylink
</div>
javaScript
function ajax_call(){
alert("empt space");
}
//this function stops the propagation and not triggered above
//function when clicked on child element.
function notCall(event) {
event.stopImmediatePropagation();
return false;
}
DEMO
I'm not sure what you want but if I keep my imagination may be that this work, lucky !
$("div#mydiv a").hover(function(){
$(this).parent().css("background-color","transparent")
})
Sure, what you need is the event target || scrElement
Like
function ajax_call() {
var target = event.target ? event.target : event.srcElement;
if(target.id==="mydiv") { alert("good to go"); }
}
See : http://jsbin.com/qujuxufo/1/edit
Edit/Update ( missed the second part ) - Started to answer this before the q was closed - but might as well as it now ..
For the second part of the question - it is not possible to set parent elements in CSS ( it flows top to bottom ) - for that some more JS is needed.
See http://jsbin.com/cileqipi/1/edit
CSS
#mydiv:hover { background-color:green; color:#fff}
#mydiv.anchorhover, #mydiv.anchorhover:hover { background-color:white;}
Then JS
var _mydiv = document.getElementById("mydiv");
var _mydiv_anchors = _mydiv.getElementsByTagName("a");
function toggleClass() {
var addClass=true, cls="anchorhover";
if((_mydiv.className).indexOf(cls) >= 0){ addClass=false; }
if(addClass) {
_mydiv.className=_mydiv.className+=' '+cls;
} else {
/* remove */
_mydiv.className=_mydiv.className.replace(new RegExp('(\\s|^)'+cls+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, '');
}
}
for(var i=0, len=_mydiv_anchors.length; i<len; ++i) {
_mydiv_anchors[i].onmouseover = toggleClass;
_mydiv_anchors[i].onmouseout = toggleClass;
}
^ That feels like quite a trip compared to how simple jquery abstracts it .. >
$("#mydiv a").hover(function() {
$(this).parent().addClass("anchorhover");
}, function() {
$(this).parent().removeClass("anchorhover");
});
Either way, the principle is : to add a style class to the parent element on mouseover and remove it on mouseout
How to get the ID of an element passed as (e)?
window.addEventListener('load', function(){
var tags = document.getElementsByClassName("tag");
for (i=0; i<tags.length; i++){
tags[i].addEventListener('mousedown', function(e){ tagClick(e) }, false);
}
}, false);
function tagClick(e){
/* here I'm gonna need the event to cancel the bubble and the ID to work with it*/
alert('The id of the element you clicked: ' + [?object].id);
[?object].className='newClass';
e.stopPropagation();
e.cancelBubble = true;
}
I need to get the element/object inside tagClick so I can change its properties
html:
<div class="tag">
<img src="/images/tags/sample.jpg"/>
<label class="tagLabel">Sample</label>
</div>
See, the element with the event attached is the div, but ig gives me the image object instead when using e.srcElement.
When you bind an event listener with addEventListener, it's called with this referring to the element you bound the event on. So this.id will be the id of the element (if it has one).
alert('The id of the element you clicked: ' + this.id);
But you're breaking that with this line:
tags[i].addEventListener('mousedown', function(e){ tagClick(e) }, false);
...because you're putting an extra function in the middle, then calling tagClick without setting this. There's no need for that extra function, change that to:
tags[i].addEventListener('mousedown', tagClick, false);
...so this doesn't get messed up. Or alternately if you prefer to have the extra function, ensure this is maintained using Function#call:
tags[i].addEventListener('mousedown', function(e){ tagClick.call(this, e) }, false);
...but there's no reason to do that with the tagClick function shown.
The (standard) event object also has the properties target (which may not be the element you bound the event on, it may well be a descendant) and currentTarget (which will be the element you bound the event on). But this is convenient and reliable if you use addEventListener (or even attachEvent, on IE).
You can get the target of the event with e.target.
However keep in mind that some browsers consider text nodes to be a target, so try something like this:
var t = e.target;
while(t && !t.id) t = t.parentNode;
if( t) {
alert("You clicked element #"+t.id);
}
This will find the first element that actually has an ID.
Happy New Year!
EDIT: On second thought, if it's the "tag" element itself you want to refer to, just use this. In an event handler, this refers to the element that actually has the handler. Although in this case you'll need to change your handler to ('mousedown', tagClick, false)
Or better still:
document.body.addEventListener("mousedown",function(e) {
var t = e.target;
while(t && t.nodeName != "TAG") { // note, must be uppercase
t = t.parentNode;
}
if( t) {
alert("You clicked on #"+t.id);
}
},false);
Fewer event handlers is always better.
document.getElementById("body").addEventListener("mousedown", function(e){
console.log(e.target.id);
});
enjoy.