ditto ditto - 7 months ago 12
Javascript Question

Event listener attached to parent element, now how to bubble up clicked target to a specific class name?

I have 1 event listener that is listening to the container element, I want to attach an active class to any of it's children (.container > div.active) when clicked. (So DO NOT attach an active class to .contain > div > ul.active).

My problem is I'm not entirely sure how bubble up the

.target
to reach the
div.diary
?

http://jsbin.com/yereramaxe/edit?html,css,js,output



document.querySelector('.contain').addEventListener('click', diary);

function diary(e) {
if (e.target.className === 'diary') {
//?.classList.add('active');
}
}

* {
font-size:0;
padding:0;
margin:0;
}
.diary {
display:inline-block;
background:red;
width:33.33%;
box-sizing:border-box;
padding:10px;
}
li {
font-size:18px;
}
ul {
list-style-type: none;
}
div > ul > li {
display:inline-block;
width:33.33%;
}
.diary.active {
background:blue;
}

<div class="contain">

<div class="diary">
<ul>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
</ul>
</div>

<div class="diary">
<ul>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
</ul>
</div>

<div class="diary">
<ul>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</li>
</ul>
</div>

</div>




Answer

Since e.target will be where the click actually occurred, you can traverse up the parent hierarchy from that element until you find the corresponding parent diary div and then attach the active class to that parent element.

Here's some code that finds a parent with an appropriate classname:

function hasClass(elem, cls) {
    var str = " " + elem.className + " ";
    var testCls = " " + cls + " ";
    return(str.indexOf(testCls) !== -1) ;
}

function addClass(elem, cls) {
    if (!hasClass(elem, cls)) {
        var oldCls = elem.className;
        if (oldCls) {
            oldCls += " ";
        }
        elem.className = oldCls + cls;
    }
}

function removeClass(elem, cls) {
    var str = " " + elem.className + " ";
    elem.className = str.replace(" " + cls + " ", " ").replace(/^\s+|\s+$/g, "");
}

function findParentByClass(node, cls) {
    while (node && !hasClass(node, cls)) {
        node = node.parentNode;
    }
    return node;    
}

Putting that together into your situation would be like this:

document.querySelector('.contain').addEventListener('click', function(e) {
    var diary = findParentByClass(e.target, "diary");
    // add active class to parent diary div
    if (diary) {
        addClass(diary, "active");
    }
});

I'm guessing that you may also want to mark the other .diary divs as no longer active. If that's the case, then you can do this:

document.querySelector('.contain').addEventListener('click', function(e) {
    var actives = document.querySelectorAll('.contain .active');
    for (var i = 0; i < actives.length; i++) {
        removeClass(actives[i], "active");
    }
    var diary = findParentByClass(e.target, "diary");
    // add active class to parent diary div
    if (diary) {
        addClass(diary, "active");
    }
});

You can, of course, substitute the .classList methods in place if these custom addClass(), hasClass() and removeClass() functions if .classList is compatible with your browser support choices.