Avoid event.target refer to the child instead of parent - javascript

I'm trying to get data attributes of a group of links and buttons, creating a event listener as follow:
// For all major browsers, except IE 8 and earlier
if (document.addEventListener) {
document.addEventListener("click", executeOnClick);
} else if (document.attachEvent) {
// For IE 8 and earlier versions
document.attachEvent("onclick", executeOnClick);
}
This event listener, executes the next function:
function executeOnClick(e){
//////////// Only elements which has "specialClass"
if (hasClass(e.target, 'specialClass')) {
if(e.target !== undefined){
console.info(e.target.getAttribute('data-info'));
e.preventDefault();
}
}
}
But doesn't work when the link or button has other tags inside them. Example:
<a data-info="Lorem ipsum 3!" href="#" class="specialClass">
<div>Link with div inside: <br> "event.target" is "div", not "a"</div>
</a>
I don't know how to get it work when the elements has and has no children. Somebody can help me?
Codepen of my problem: http://codepen.io/tomloprod/pen/gwaVXE
NOTE: I have omitted the definition of the hasClass method because this isn't the problem. Anyway, you can see it on the codepen.

You could use a function which will recursively check the parentNode for the presence of a data-info attribute.
Here is an example.
//////////// This function works well.
function findParentWithData(elem) {
try {
if(elem.getAttribute('data-info'))
return elem;
} catch(e) {
console.log('This was an anchor without data-info attribute.')
return e
}
while(!elem.getAttribute('data-info')) {
return findParentWithData(elem.parentNode);
}
}
function hasClass(event, className) {
if (event.classList) {
return event.classList.contains(className);
}
return new RegExp('(^| )' + className + '( |$)', 'gi').test(event.className);
}
function executeOnClick(e) {
// if click came from body don't do anything
if (e.target === document.body) return;
var result = document.getElementById("result");
result.innerHTML = "";
//////////// Only elements that has "specialClass"
// find parent with data-info
var elem = findParentWithData(e.target)
if (elem instanceof Element && hasClass(elem, 'specialClass')) {
if(elem !== undefined){
result.innerHTML = "Information: " + elem.getAttribute('data-info');
e.preventDefault();
}
}
}
// For all major browsers, except IE 8 and earlier
if (document.addEventListener) {
document.addEventListener("click", executeOnClick);
} else if (document.attachEvent) {
// For IE 8 and earlier versions
document.attachEvent("onclick", executeOnClick);
}
.btn {
opacity:0.8;
border:0;
-webkit-border-radius: 28;
-moz-border-radius: 28;
border-radius: 28px;
font-family: Arial;
color: #ffffff;
font-size: 37px;
padding: 10px 20px 10px 20px;
text-decoration: none;
outline:0;
margin: 0em 0 1em 0;
display: -webkit-inline-box;
}
.btn:hover {
cursor:pointer;
opacity:1;
text-decoration: none;
}
.btn.red{
background:#e74c3c;
}
.btn.green{
background:#2ecc71;
}
<div id="result"></div>
<a data-info="Lorem ipsum!" href="#" class="btn green specialClass">Link: Working well</a>
<button data-info="Lorem ipsum 2!" class="btn green specialClass">Button: Working well too</button>
<a data-info="Lorem ipsum 3!" href="#" class="btn red specialClass">
<div>Link with div inside: <br> Doesn't work</div>
</a>
<a data-info="Lorem ipsum 4!" href="#" class="btn red specialClass">
<ul>
<li>
Link with ul inside:
</li>
<li>
Doesn't work
</li>
</ul>
</a>
Foo

Related

How to get the child of a element with event.target

I'm trying to get the child elements of a div to toggle a class 'active'
JS
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
e.target.getElementsByClassName('tb-drop').classList.toggle('active');
console.log(e.target.getElementsByClassName('tb-drop'))
}
HTML
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
So i'm selecting all the divs with the data-dots attribute and then select the child of that div and add the class active. I tried with e.target.children but didnt work.
Thanks, I'm just trying to learn :)
In order to identify the first child, the easiest option is simply to use Element.querySelector() in place of Element.getElementsByClassName():
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
// Element.querySelector() returns the first - if any -
// element matching the supplied CSS selector (element,
// elements):
e.target.querySelector('.tb-drop').classList.add('active');
}
The problem is, of course, that if no matching element is found by Element.querySelector() then it returns null; which is where your script will raise an error. So, with that in mind, it makes sense to check that the element exists before you try to modify it:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.add('active');
}
}
It's also worth noting that EventTarget.addEventListener() passes the this element into the function, so rather than using:
e.target.querySelector(...)
it's entirely possible to simply write:
this.querySelector(...)
Unless, of course, handleClick() is rewritten as an Arrow function.
Demo:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.add('active');
}
}
div {
display: block;
border: 2px solid #000;
padding: 0.5em;
}
i {
display: inline-block;
height: 1em;
}
::before {
content: attr(class);
}
.active {
color: limegreen;
}
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
Or, if you wish to toggle the 'active' class you could, instead, use toggle() in place of add:
const dots = document.querySelectorAll('[data-dots]');
dots.forEach(dot => dot.addEventListener('click', handleClick));
function handleClick(e) {
let el = e.target.querySelector('.tb-drop');
if (el) {
el.classList.toggle('active');
}
}
div {
display: block;
border: 2px solid #000;
padding: 0.5em;
}
i {
display: inline-block;
height: 1em;
}
::before {
content: attr(class);
}
.active {
color: limegreen;
}
<div class="dots" data-dots>
<i class="fas fa-ellipsis-v dots"></i>
<div class="tb-drop">
<i class="far fa-edit icon-grid"></i>
<i class="fas fa-link icon-grid"></i>
</div>
</div>
References:
Element.querySelector
e.target already is the clicked child of the element that you installed the listener on. You probably want to use e.currentTarget or this instead.
Then you can go using .getElementsByClassName(), .querySelector[All]() or .children from there.
You can also try this code.
var dots = document.querySelectorAll('[data-dots]');
for (var i = 0; i < dots.length; i++) {
dots[i].addEventListener('click', function () {
handleClick(this);
}, false);
}
function handleClick(object) {
var container = object.getElementsByClassName('tb-drop')[0];
if (container != undefined) {
if (container.classList.contains('active')) {
container.classList.remove('active')
}else{
container.classList.add('active')
}
}
}

Add a div below inline-block wrapped row - Part 2

A solution suggested by #musicnothing in an older thread displays a content div below the row of inline divs, this works good when the div.wrapblock is clicked itself.
http://jsfiddle.net/SYJaj/7/
function placeAfter($block) {
$block.after($('#content'));
}
$('.wrapblock').click(function() {
$('#content').css('display','inline-block');
var top = $(this).offset().top;
var $blocks = $(this).nextAll('.wrapblock');
if ($blocks.length == 0) {
placeAfter($(this));
return false;
}
$blocks.each(function(i, j) {
if($(this).offset().top != top) {
placeAfter($(this).prev('.wrapblock'));
return false;
} else if ((i + 1) == $blocks.length) {
placeAfter($(this));
return false;
}
});
});
The issue I'm having.
I need to trigger the same effect, but by adding the click event to a link within the wrapblock itself.
My code is nearly identical.
What I have changed is the click event handle, from $('.wrapblock').click(function() to $('.more').on('click', function() I also needed to add .closest(".wrapblock") for the content div to position itself outside of the wrapblock.
$('.more').on('click', function() {
...
if ($blocks.length == 0) {
placeAfter($(this).closest(".wrapblock"));
return false;
}
Everything can be seen and tested http://jsfiddle.net/7Lt1hnaL/
Would be great if somebody could shed some light on how I can calculate which block it needs to follow with the offset method, thanks in advance.
As you can see in the latest fiddle example, the content div is not displaying below the row of divs.
I also apologise, I wanted to post on the thread in discussion but I only have a minor posting reputation which doesn't let me, thanks.
var $chosen = null;
var $allBlocks = [];
$(function(){
$allBlocks = $('.wrapblock');
})
$(window).on('resize', function() {
if ($chosen != null) {
$('#content').css('display','none');
$('body').append($('#content'));
$chosen.trigger('click');
}
});
$('.more').on('click', function() {
$chosen = $(this);
var position = $chosen.parent('.wrapblock').position();
$('#content').css('display','inline-block');
$allBlocks.filter(function(idx, ele){
return $(ele).position().top == position.top;
})
.last()
.after($('#content'));
});
.wrapblock
{
background: #963a3a;
display: inline-block;
width: 90px;
height: 90px;
color: white;
font-size: 14px;
text-align: left;
padding: 10px;
margin: 10px;
vertical-align:top;
position:relative;
}
#content
{
display:none;
vertical-align:top;
width:100%;
background: #5582c1;
font-size: 12px;
color: white;
padding: 10px;
}
.more {
position:absolute;
bottom:15px;
right:15px;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<div class="wrapblock">1
<span class="more" data-ref="1">more</span>
</div>
<div class="wrapblock">2
<span class="more" data-ref="2">more</span>
</div>
<div class="wrapblock">3
<span class="more" data-ref="3">more</span>
</div>
<div class="wrapblock">4
<span class="more" data-ref="4">more</span>
</div>
<div class="wrapblock">5
<span class="more" data-ref="5">more</span>
</div>
<div class="wrapblock">6
<span class="more" data-ref="6">more</span>
</div>
<div class="wrapblock">7
<span class="more" data-ref="7">more</span>
</div>
<div class="wrapblock">8
<span class="more" data-ref="8">more</span>
</div>
<div class="wrapblock">9
<span class="more" data-ref="9">more</span>
</div>
<div id="content">Some Content</div>
Seems to do what you want. Basically, it just filters down the set of all blocks to the row of the block you clicked on using the assumption that they'll all have the same vertical offset (top), then takes the last one, because jQuery will keep them in document order, so that'll be the last one in the layout row.
Oh, and I updated the fiddle http://jsfiddle.net/7Lt1hnaL/1/

Dynamic <a> not clickable

I have done the following code in php so that I can click on the arrow and a form opens below
echo '<div class="editor" id="'.$par_code.'" style=" background-color: #fdfdfd; padding:14px 25px 30px 20px; font-family: Lucida Console, Monaco, monospace; box-shadow: 0 1px 10px 2px rgba(0,0,0,0.2),0 8px 20px 0 rgba(0,0,0,0.03); border-radius: 3px;">'
.'<img width="50" height="50" style="border-radius:50%" src="images/default.png" alt="Image cannot be displayed"/>'
.'<p class="uname"> '.$uname.'</p> '
.'<p class="time">'.$date.'</p>'
.'<p class="comment-text" style="word-break: break-all;">'.$content.'</p>'
.'<a class="link-reply al" id="reply" name="'.$par_code.'" style="padding-top: 18px; float: right;"><i class="fa fa-reply fa-lg" title="Reply"></i></a>';
My javascript code:
$(document).ready(function() {
$("a#reply").one("click" , function() {
var comCode = $(this).attr("name");
var parent = $(this).parent();
var str1 = "new-reply";
var str2 = "tog";
var res = str1.concat(i);
var tes = str2.concat(i);
// Create a new editor inside the <div id="editor">, setting its value to html
parent.append("<br /><center><form action='index.php' method='post' id='"+tes+"'><input class='iptext2' type='text' name='uname2' id='uname2' placeholder='Your Name' required /><div style='padding-bottom:5px'></div><textarea class='ckeditor' name='editor' placeholder='Your Query' id='"+res+"' required></textarea><input type='hidden' name='code' value='"+comCode+"' /><br/><input type='submit' class='form-submit' id='form-reply' name='new_reply' value='Reply' /></form></center>")
CKEDITOR.replace(res);
/*
var x = document.getElementById("tes");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
*/
i++;
});
})
The following is my css code applied to the anchor tag:
.al {
font-size:11.2px;
text-transform: uppercase;
text-decoration: none;
color:#222;
cursor:pointer;
transition:ease 0.3s all;
}
.al:hover {
color:#0072bc;
}
.link-reply {
color:#767676;
}
Here the arrow icon is displayed but is not clickable
Your code fails, because your <a> elements are created dynamically, whereas the event listener is added only to the elements available when the document has loaded.
In order to get your code to work, you need to use event delegation; that is to add the event listener to a common static ancestor, such as the document or the body, that will in turn delegate it to your target elements.
The methods you can use to achieve this effect in jQuery are on and one, with the latter fitting your case better, if you are trying to attach one-time event listeners.
Code:
$(document).one("click", "a#reply", function() {
// ...
});
Use on for dynamic created events on DOM.
$(document).on("click","a#reply" , function() {
console.log('a#reply => clicked!')
});
Or
$(body).on("click","a#reply" , function() {
console.log('a#reply => clicked!')
});

Create a Tree with dynamic level feature for it's nodes (movable nodes) by jQuery

I want create a Tree with UL and Li tags and using jQuery.
I wand able to change the level of each node by arrow keys and mouse to the Left or Right???
I need movable nodes!
Something like this:
I created this idea with the following codes Here. anyone can use or extend it free... ;)
I know it can be improved and make a better version of it but now you can use it and enjoy....
It's features:
»» Support keyboard arrow keys to move the nodes (Left & Right)
»» Support dragging the nodes by Mouse to Right & Left
»» Save level data of each node in DOM and show it on each node
Thanks from #FabrícioMatté for his answer here. i used some part of his codes to create my jQuery Class.
Result of my codes:
JS & jQuery
/*Code by Ram >> http://stackoverflow.com/users/1474613/ram */
(function ($) {
$.Noder = function (oneOfNodes) {
this.element = '';
oneOfNodes=(oneOfNodes instanceof $) ? oneOfNodes : $(oneOfNodes)
this.baseX=oneOfNodes.position().left;
this.currentX=0;
};
$.Noder.prototype = {
InitEvents: function () {
//`this` references the created instance object inside an instace's method,
//however `this` is set to reference a DOM element inside jQuery event handler functions' scope.
//So we take advantage of JS's lexical scope and assign the `this` reference to
//another variable that we can access inside the jQuery handlers
var that = this;
//I'm using `document` instead of `this` so it will catch arrow keys
//on the whole document and not just when the element is focused.
//Also, Firefox doesn't fire the keypress event for non-printable characters
//so we use a keydown handler
$(document).keydown(function (e) {
var key = e.which;
if (key == 39) {
that.moveRight();
} else if (key == 37) {
that.moveLeft();
}
});},
setElement: function(element){
this.element = (element instanceof $) ? element : $(element);
console.log(this.element);
this.currentX=this.element.position().left;
console.log('currentX: '+this.currentX);
this.element.addClass('active');
},
moveRight: function () {
console.log('bseX: '+this.baseX);
console.log('currentX: '+this.currentX);
var max=(25*2)+this.baseX;
console.log('max: '+max);
if(this.currentX<max)
{
this.element.css("left", '+=' + 25);
this.currentX=this.element.position().left;
setElementLevel(this.element,this.currentX,this.baseX);
console.log('currentX: '+this.currentX);
}
},
moveLeft: function () {
if(this.currentX>this.baseX)
{
this.element.css("left", '-=' + 25);
this.currentX=this.element.position().left;
setElementLevel(this.element,this.currentX,this.baseX);
console.log('currentX: '+this.currentX);
}
}
};
$.Noder.defaultOptions = {
currentX: 0
};
}(jQuery));
function setElementLevel(element,currentX,baseX){
var level=0;
if (currentX==baseX+25)
level=1;
else if(currentX==baseX+25+25)
level=2;
element.data('level', level);
setLevelOnElement(element);
}
function getElementLevel(element){
console.log(element.data('level'));
return element.data('level');
}
function setLevelOnElement(element){
var level = 0;
if(typeof getElementLevel(element) !=='undefined')
level = getElementLevel(element);
console.log('my level: '+level);
var $levelElement=element.find( ".node-level" );
if ($levelElement && $levelElement.length>0)
{
$levelElement=$($levelElement[0]);
console.log($levelElement);
$levelElement.html(level);
}
}
var noder = new $.Noder($("#myTree>.node")[0]);
$("#myTree>.node").on('click',function(){
$("#myTree>.node").each(function(){
$(this).removeClass('active')
});
console.log($(this)[0].id +' clicked')
noder.setElement($(this)[0]);
})
noder.InitEvents();
$(document).ready(function() {
var $dragging = null;
var $myTree=$('ul#myTree');
var $oneOfNodes=null;
var baseX=0;
if($myTree.length>0)
{
console.log($myTree);
$oneOfNodes=$($myTree.children()[0]);
console.log($oneOfNodes);
baseX=$oneOfNodes.position().left;
console.log('baseX >> '+baseX);
console.log($myTree);
var x=0;
$('ul#myTree').find('li').each(function(){
x++;
console.log(x);
setLevelOnElement($(this));
});
}
$(document.body).on("mousemove", function(e) {
if ($dragging) {
var currentX=$dragging.position().left;
if(e.pageX>(baseX+25) && e.pageX<(baseX+(2*25)))
{
$dragging.offset({left: (baseX+25)});
setElementLevel($dragging,currentX,baseX);
}
else if((e.pageX)>(baseX+(2*25)) )
{
$dragging.offset({left: (baseX+(2*25))});
setElementLevel($dragging,currentX,baseX);
}
else if(e.pageX<(baseX+25) )
{
$dragging.offset({left: (baseX)});
setElementLevel($dragging,currentX,baseX);
}
}
});
$(document.body).on("mousedown", function (e) {
var $myTree=$('ul#myTree');
if($(e.target) && $myTree && $myTree.length>0)
{
var $li=$(e.target).parent();
var $ul=$(e.target).parent().parent();
if ( $ul.is($myTree) && $(e.target).hasClass("node-level") )
{
$ul.find('li').each(function(){
$(this).removeClass('active');
});
$li.addClass('active');
$dragging = $($li);
}
}
});
$(document.body).on("mouseup", function (e) {
$dragging = null;
});
});
HTML
<ul id="myTree">
<li class="node" id="node1">
<span class="node-level"></span>
<span class="node-content">Node 1</span>
</li>
<li class="node" id="node2">
<span class="node-level"></span>
<span class="node-content">Node 2</span>
</li>
<li class="node" id="node3">
<span class="node-level"></span>
<span class="node-content">Node 3</span>
</li>
<li class="node" id="node4">
<span class="node-level"></span>
<span class="node-content">Node 4</span>
</li>
<li class="node" id="node5">
<span class="node-level"></span>
<span class="node-content">Node 5</span>
</li>
<li class="node" id="node6">
<span class="node-level"></span>
<span class="node-content">Node 6</span>
</li>
<li class="node" id="node7">
<span class="node-level"></span>
<span class="node-content">Node 7</span>
</li>
</ul>
CSS
#myTree{
margin:20px;
padding:0;
list-style-type:none;
font-size:11px;
}
#myTree>li>.node-level{
padding:6px 10px;
color:#ddd;
background:gray;
position:relative;
cursor:move;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#myTree>li>.node-content{
padding:5px; 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#myTree>.node:hover {
background:#acd;
color:#004;
}
#myTree>.node.active {
border:1px #a66 solid;
background:#fd8;
color:#004;
}
#myTree>li.node {
width:151px;
background:#ddd;
margin-top:2px;
padding:5px 0px 5px 0;
color:#555;
cursor:pointer;
position:relative;
border:1px solid #ccc;
}
html, body {
height:100%;
}
div { width:151px;
background:#ddd;
margin-top:2px;
padding:5px 15px;
color:#555;
cursor:pointer;
position:relative;
border:1px solid #ccc; }
I have faced the same problem. You can get the Level of Node by
$("#treeDiv").tree("getSelectedNode").getLevel();
You can operate using the level.

Highlight current page link using Javascript

I'm having problem to highlight the active menu item in sidebar using Javascript. So I called setPage() in the to highlight current menu item, but nothing happens. Any ideas to solve it?
Btw, here's my code:
HTML:
<nav class="sidebar-nav">
<a class="sidebar-nav-item" href="nav.html">Main page </a>
<a class="sidebar-nav-item" href="page2.html">Dummy page 2</a>
<a class="sidebar-nav-item" href="page3.html">Dummy page 3</a>
<a class="sidebar-nav-item" href="page4.html">Dummy page 4</a>
<script language="text/javascript">setPage()</script>
</nav>
CSS:
* {
margin:0;
padding:0;
}
body {
background:#CCC;
font:140% "Times New Roman", Times, serif, Arial;
line-height:1.5;
font-weight:bold;
}
.sidebar-nav {
border-bottom: 1px solid rgba(255,255,255,.1);
}
.sidebar-nav-item {
display: block;
padding: .5rem 1rem;
border-top: 1px solid rgba(255,255,255,.1);
}
.sidebar-nav-item.active,
a.sidebar-nav-item:hover,
a.sidebar-nav-item:focus {
text-decoration: none;
background-color: rgba(255,255,255,.1);
border-color: transparent;
}
Javascript:
function extractPageName(hrefString) { // This function is
var arr = hrefString.split('/');
return (arr.length < 2) ? hrefString : arr[arr.length - 2].toLowerCase() + arr[arr.length - 1].toLowerCase();
}
function setActiveMenu(arr, crtPage) {
for (var i = 0; i < arr.length; i++) {
if (extractPageName(arr[i].href) == crtPage) {
arr[i].className = "sidebar-nav-item active";
}
}
}
function setPage() {
if (hrefString = document.location.href)
hrefString = document.location.href;
else
hrefString = document.location;
if (document.getElementsByClassName("sidebar-nav") != null)
setActiveMenu(document.getElementsByClassName("sidebar-nav-item"), extractPageName(hrefString));
}
Sorry for my bad English.
It's not language, but type, that's your first mistake (although it might work, it's not correct according to standards.
For setting link active state, get the according anchor (<a>) element and add class active to it. That should work as you've already defined style for active link, I think that should work as it is now.
Your have 2 problems in setPage function:
if (hrefString = document.location.href)
The = symbol means assignment of value, use == or === for comparison. I really recommend ===, because it literally compares 2 objects, instead only the value, as is the case with == comparison.
Second is that hrefString is not defined before this if statement (at least not in code you've provided), so javascript will throw error and stop execution.

Categories