Removing attributes of an element on clicking outside with Vanilla JS - javascript

How can I remove an attribute of an element on click outside or on another div of same type? Here's my code:
HTML:
<div id="container">
<div data-something></div>
<div data-something></div>
</div>
JavaScript:
var elements = document.querySelectorAll("[data-something]");
Array.prototype.forEach.call(elements, function(element) {
// Adding
element.onclick = function() {
this.setAttribute("data-adding", "");
};
// Removing -- Example
element.onclickoutside = function() {
this.removeAttribute("data-adding");
};
});

I would probably use a click handler on the document, and then remove the attribute from any element that had it that wasn't in the bubbling path.
document.addEventListener("click", function(e) {
Array.prototype.forEach.call(document.querySelectorAll("*[data-adding][data-something]"), function(element) {
var node, found = false;
for (node = e.target; !found && node; node = node.parentNode) {
if (node === element) {
found = true;
}
}
if (!found) {
element.removeAttribute("data-adding");
}
});
}, false);
...or something along those lines.
Live Example:
document.addEventListener("click", function(e) {
Array.prototype.forEach.call(document.querySelectorAll("*[data-adding]"), function(element) {
var node, found = false;
for (node = e.target; !found && node; node = node.parentNode) {
if (node === element) {
found = true;
}
}
if (!found) {
element.removeAttribute("data-adding");
}
});
}, false);
*[data-adding] {
color: red;
}
<div data-adding data-something>One</div>
<div data-adding data-something>Two</div>

You can use Node.contains() inside a global click event handler to check if a click is outside an element, and handle the event appropriately:
box = document.getElementById('box');
lastEvent = document.getElementById('event');
box.addEventListener('click', function(event) {
// click inside box
// (do stuff here...)
lastEvent.textContent = 'Inside';
});
window.addEventListener('click', function(event) {
if (!box.contains(event.target)) {
// click outside box
// (do stuff here...)
lastEvent.textContent = 'Outside';
}
});
#box {
width: 200px;
height: 50px;
background-color: #ffaaaa;
}
<div id="box">Click inside or outside me</div>
<div>Last event: <span id="event">(none)</span>
</div>

Related

How to click one id from a list of ids?

I have a list of ids, when one of them is clicked I want to give it the attribute .className="open.
So far what I've done is to put all ids in a list and try to loop through them.
const memberB = document.querySelectorAll('#memberA, #memberAA, #memberAAA ');
for (var i = 0; i < memberB.length; i++) {
memberB[i].onclick = function(){
alert(memberB[i])
if(memberB[i].className=="open"){
memberB[i].className="";
}
else{
memberB[i].className="open";
}
}
What did I do wrong, I try to alert to see if I get the element that i clicked, all i get is 'undefined'.
you can use forEach to loop the NodeList which use querySelectorAll method, and use addEventListener to watch click event happen on all the elements you selected. Finally, use Element.classList.toggle method to toggle the class open or close
there is an example of toggle its background color after click
const members = document.querySelectorAll('.member');
members.forEach(member => {
member.addEventListener('click', e => {
e.target.classList.toggle('hight-light');
});
});
.member {
background-color: gray;
}
.hight-light {
background-color: green;
}
<div class="container">
<div class="member">1</div>
<div class="member hight-light">2</div>
<div class="member">3</div>
<div class="member">4</div>
</div>
I have a code snippet I like to keep around to do these kind of things in a single event listener
window.addEvent = (event_type, target, callback) => {
document.addEventListener(event_type, function (event) {
// If the event doesn't have a target
// Or the target doesn't look like a DOM element (no matches method
// Bail from the listener
if (event.target && typeof (event.target.matches) === 'function') {
if (!event.target.matches(target)) {
// If the element triggering the event is contained in the selector
// Copy the event and trigger it on the right target (keep original in case)
if (event.target.closest(target)) {
const new_event = new CustomEvent(event.type, event);
new_event.data = { originalTarget: event.target };
event.target.closest(target).dispatchEvent(new_event);
}
} else {
callback(event);
}
}
});
};
then in your case I'd do this
window.addEvent('click', '#memberA,#memberAA,#memberAAA', (event) => {
event.target.classList.toggle('open');
});
The script runs befor the DOM elements load.
You can put the script as a function inside an $(document).ready such that it runs after all the elements have been loaded.
$(document).ready(
function () {
const memberB = document.querySelectorAll('#memberA, #memberAA, #memberAAA ');
for (let i = 0; i < memberB.length; i++) {
memberB[i].onclick = function () {
//alert(memberB[i])
if (memberB[i].className === "open") {
memberB[i].className = "";
} else {
memberB[i].className = "open";
}
alert(memberB[i].className)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="memberA">A</button>
<button id="memberAA">AA</button>
<button id="memberAAA">AAA</button>
Let me know if this works!

why preventdefault/stopPropagation not working in javascript?

I have a demo application where preventdefault/stopPropagation .Not sure where I am doing wrong .Using Jquery it is working fine.Follow the below steps to reproduce the bug
Run the application and click on button.
when I put jQuery code it works perfectly . it only show 'jquery:: 1' on console not showing
'jquery:: 2' as expected because we used e.preventDefault();e.stopPropagation();
jQuery(document).on('click', '.bclink[href*="bcid="]', function(e){
e.preventDefault();
e.stopPropagation();
console.log('jquery:: 1')
})
jQuery(document).on('click', '.clickvideo', function(e){
// detect .clickvideo-overlay parent to prevent spawning of additional w10 lightboxes
console.log('jquery:: 2')
});
but same code when I used in javascript and click button it console both JS:::1 and JS:::2.why prevent default not works
document.addEventListener('click', function(e) {
// loop parent nodes from the target to the delegation node
function handler(e){
e.preventDefault();
e.stopPropagation();
console.log("JS:::1")
}
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.bclink[href*="bcid="]')) {
handler.call(target, e);
break;
}
}
}, false)
document.addEventListener('click', function(e) {
// loop parent nodes from the target to the delegation node
function handler(e){
e.preventDefault();
e.stopPropagation();
console.log("JS::: 2")
}
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.clickvideo')) {
handler.call(target, e);
break;
}
}
}, false)
})
here is my code
https://jsbin.com/riwazorine/edit?html,css,js,output
Expected output : it only show "JS:::1" as I used preventdefault and stopPropagation()
In the JS, both event listeners are attached to the same element - the document. stopPropagation only stops event propagation to other elements (ancestors or descendants), but not to the same elements.
You need stopImmediatePropagation, which stops other event handlers on the same element from running.
document.addEventListener('click', function(e) {
// loop parent nodes from the target to the delegation node
function handler(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log("JS:::1")
}
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.bclink[href*="bcid="]')) {
handler.call(target, e);
break;
}
}
}, false)
document.addEventListener('click', function(e) {
// loop parent nodes from the target to the delegation node
function handler(e) {
e.preventDefault();
e.stopPropagation();
console.log("JS::: 2")
}
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.clickvideo')) {
handler.call(target, e);
break;
}
}
}, false)
.bcthumbnail {
width: 100px;
height: 100px;
border: 1px solid
}
<div class="bcthumbnail clickvideo bcimgadded" data-bcid="6086922094001"><button class="vjs-big-play-button"></button>button</div>
Also, rather than your for loo, you can use .closest instead, it's a whole lot cleaner.
document.addEventListener('click', (e) => {
if (e.target.closest('.bclink[href*="bcid="]')) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log("JS:::1")
}
})
document.addEventListener('click', function(e) {
if (e.target.closest('.clickvideo')) {
console.log("JS::: 2")
}
})
.bcthumbnail {
width: 100px;
height: 100px;
border: 1px solid
}
<div class="bcthumbnail clickvideo bcimgadded" data-bcid="6086922094001"><button class="vjs-big-play-button"></button>button</div>

Issues with using classList to hide and show elements

I need to implement a feature where a div shows up when another div is clicked and should hide if the div is clicked again.
Here is the fiddle with a small portion of the code : https://jsfiddle.net/6dgL6zqb/1/
var portfolio = document.getElementById('portfolio');
EventUtil.addHandler(portfolio, 'click', function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id) {
case 'kk':
console.log('kk clicked');
var idName = target.id + 'Details',
doc = document,
currentProject = doc.getElementById(idName),
otherProjects = doc.getElementsByClassName('projectDetails');
console.log(currentProject);
for (var i = otherProjects.length - 1; i >= 0; i--) {
var projectClassList = otherProjects.item(i).classList;
if (projectClassList.contains('showMe')) {
projectClassList.remove('showMe');
projectClassList.add('hideMe');
}
};
var currentProjectClassList = currentProject.classList;
console.log(currentProjectClassList);
if (!currentProjectClassList.contains('showMe')) {
currentProjectClassList.remove('hideMe');
currentProjectClassList.add('showMe');
} else {
currentProjectClassList.remove('showMe');
currentProjectClassList.add('hideMe');
}
break;
}
});
I am using an EventUtil handler to handle events, along-with event delegation.
So, when I click on Section 1 with the id 'kk', another div with id 'kkDetails' displays. But, I expect the div with id 'kkDetails' to disappear when I click on the div with id 'kk'. How can I get that to happen?
As far as I can see, the currentProjectClassList object is not updating as I expect. Although, I don't understand why.
PS: I am new at programming, so please bear with any ignorance.
There problem that you are having is this section:
otherProjects = doc.getElementsByClassName('projectDetails');
for (var i = otherProjects.length - 1; i >= 0; i--) {
var projectClassList = otherProjects.item(i).classList;
if (projectClassList.contains('showMe')) {
projectClassList.remove('showMe');
projectClassList.add('hideMe');
}
};
You are actually hiding your element and then later showing it again, as an example modify this section to be something like:
otherProjects = doc.getElementsByClassName('projectDetails');
for (var i = otherProjects.length - 1; i >= 0; i--) {
if(otherProjects.item(i).id !== idName){
var projectClassList = otherProjects.item(i).classList;
if (projectClassList.contains('showMe')) {
projectClassList.remove('showMe');
projectClassList.add('hideMe');
}
}
};
In this section you are swapping the visible state of your selected div:
if (!currentProjectClassList.contains('showMe')) {
currentProjectClassList.remove('hideMe');
currentProjectClassList.add('showMe');
} else {
currentProjectClassList.remove('showMe');
currentProjectClassList.add('hideMe');
}
however in the previous section of code because your selected element also has the class projectDetails it gets its showMe removed and hideMe added. So when it gets to section of code above this if(!currentProjectClassList.contains('showMe')) will always be true.
I would use a single class to toggle visibility. It is a lot more manageable. Also I would link click element with the toggled element via an attribute of some kind (instead of using a switch statement in javascript).
EventUtil.addHandler(portfolio, 'click', function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var divId = target.getAttribute('href');
if (divId) {
event.preventDefault();
Array
.from(document.querySelectorAll('.projectDetails:not(.hideMe)'))
.forEach(function(a) {
a.classList.add('hideMe')
});
document.querySelector(divId).classList.remove('hideMe')
}
});
// Cross-browser event handler
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
var portfolio = document.getElementById('portfolio');
EventUtil.addHandler(portfolio, 'click', function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var divId = target.getAttribute('href');
if (divId) {
event.preventDefault();
Array
.from(document.querySelectorAll('.projectDetails:not(.hideMe)'))
.forEach(function(a) {
a.classList.add('hideMe')
});
document.querySelector(divId).classList.remove('hideMe')
}
});
.projectDetails {
display: block;
}
.projectDetails.hideMe {
display: none;
}
<body>
<div class="container">
<section id="portfolio">
<div class="row">
<div class="col-sm-4">
Section 1
</div>
<div class="col-sm-4">
Section 2
</div>
<div class="col-sm-4">
Section 3
</div>
</div>
<div class="row">
<div id="kkDetails" class="hideMe projectDetails">
<p>Text</p>
</div>
<div id="arthDetails" class="hideMe projectDetails">
<p>Text 2</p>
</div>
<div id="bobyPinzDetails" class="hideMe projectDetails">
<p>Text 3</p>
</div>
</div>
</section>
</body>

Delegated click event on anchors

I am trying to target anchor tags and trigger an ajax request. Using jQuery this is very easy:
$(document.body).on('click', "a", function (event) {
'use strict';
if ($(this).is('.a-btn')) {
event.preventDefault();
} else if ($(this).is('.no-sp')) {
//
} else {
address = $(this).attr("href")
event.preventDefault();
App.Ajax.Page(address + '/');
}
});
However using native javascript, I would imagine using event.target would do the trick.
But this does not work, because the event always targets whatever element is inside the anchor tag:
App.Ajax.Navigate = function () {
'use strict';
document.body.addEventListener('click', function (e) {
e.preventDefault();
console.log(e.currentTarget);
if (e.target.tagName === 'a') {
var element, link;
element = e.target;
link = element.href;
if (App.HTML.hasClass(element, 'a-btn')) {
e.preventDefault();
} else if (App.HTML.hasClass(element, 'no-sp')) {
return;
} else {
e.preventDefault();
App.Ajax.Page(link);
}
}
}, true);
window.onpopstate = function (event) {
App.Page.type = event.state.type;
App.Page.Replace(event.state.content, event.state.type, App.Empty, false);
};
};
I want to use native javascript to do what jquery does in the first code snippet, is it possible?
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
https://developer.mozilla.org/ru/docs/Web/API/Element/closest - 2 polifills here
event.target.closest("a")
Don't forget about polifill for most browsers.
(function(e){
if (!e.matches) {
e.matches = e.mozMatchesSelector || e.msMatchesSelector || e.oMatchesSelector || e.webkitMatchesSelector;
}
if (!e.closest) {
e.closest = function (css) {
for (var node = this; node; node = node.parentElement) {
if (node.matches(css)) {
return node;
}
}
return null;
}
}
})(Element.prototype);

Check whether a div or one of its children were clicked

I have a div as follows
<div class="parent">
<!--several child divs now-->
</div>
Now, I have registered a click handler on body using AngularJS as follows:
HTML:
<body ng-click="registerClick($event)">
</body>
Controller:
$scope.registerClick(e) {
//Here check if the parent div or one of its children were clicked
}
How, can use $event in my handler to determine if the div with class 'parent' or one of its children were clicked?
Change it to this:
$scope.registerClick(e) {
if (e.target.classList.contains('parent')){
// The .parent div is clicked
} else if (e.target.parentNode.classList.contains('parent')){
// Some child of the .parent div is clicked
} else {
var elem = e.target;
while(elem.tagName != 'body' || !elem.classList.contains('parent')){
elem = elem.parentNode;
}
if (elem.classList.contains('parent')){
console.log('DIV .parent')
} else {
console.log('Body tag reached. No .parent element found');
}
}
}
e.target is the clicked element. Use it to determine which element was clicked. This is a clean JavaScript solution. You wont even need the $scope.registerClick(e) { part if can attach the event like so:
someDiv.onclick = function(){/* Same code as above. */}
if you use the latter approach, 'this' points to the div, so you can that the validations a little bit.
You can do something like
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.registerClick = function($event) {
//if has jQuery
if ($($event.target).closest('.parent').length) {
console.log('clicked parent');
$scope.jQuery = true;
} else {
$scope.jQuery = false;
}
var isParent = false;
if (angular.isFunction($event.target.closest)) {
isParent = !!$event.target.closest('.parent');
} else {
var tmp = $event.target;
do {
isParent = tmp.classList.contains('parent');
tmp = tmp.parentNode;
} while (tmp && tmp.classList && !isParent);
}
$scope.native = isParent;
};
})
.parent {
border: 1px solid grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="my-app">
<div ng-controller="AppController" ng-click="registerClick($event)">
<div>outside</div>
<div class="parent">
parent
<div>div</div>
<div>div</div>
</div>
<div>
jQuery: {{jQuery}}
</div>
<div>
native: {{native}}
</div>
</div>
</div>

Categories