Can't remove event listener dynamically - javascript

I am building a game and on a certain condition I need to remove the event listener from a <div> that has just been clicked. I don't want the user to click twice on the same div. I'm trying to use .removeEventListener
Here's my code.
let Divs = document.querySelectorAll(".data")
Divs.forEach((v, k) => {
v.addEventListener("click", (e) => {
clic(e, v, k)
});
});
function clic(e, v, k) {
console.log("CLICK");
Divs[k].removeEventListener("click", (e) => {
clic(e, v, k)
});
}

a game with 9 div! Are you making a tictactoe?:)
little snippet with 9 div, I've added a button in div 6 to remove event for div5
I put the remove in a function, so you can call it with a condition somewhere in your code. You have to pass the name of the div
Array.from(document.querySelectorAll('.grid>div')).forEach(el => {
el.addEventListener('click', divclickadd);
});
function divclickadd(ev) {
console.log(ev.target);
}
document.querySelector('button').addEventListener('click', function(evt) {
evt.stopImmediatePropagation();
console.log(evt.target.className);
divclickremove(evt.target.className);
});
function divclickremove(div) {
const el = document.querySelector('#' + div);
console.log(el);
el.removeEventListener('click', divclickadd);
}
*,
*:before,
*:after {
box-sizing: border-box;
}
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
}
body {
width: 100vw;
height: 100vh;
}
.grid {
display: grid;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 10px;
width: 100%;
height: 100%;
}
.grid>div {
display: flex;
justify-content: center;
align-items: center;
}
<div class="grid">
<div id="div1" style="background-color: red">div 1</div>
<div id="div2" style="background-color: blue">div 2</div>
<div id="div3" style="background-color: green">div 3</div>
<div id="div4" style="background-color: yellow">div 4</div>
<div id="div5" style="background-color: purple">div 5</div>
<div id="div6" style="background-color: brown">div 6
<button class="div5">remove event 5</button>
</div>
<div id="div7" style="background-color: darkolivegreen">div 7</div>
<div id="div8" style="background-color: orangered">div 8</div>
<div id="div9" style="background-color: cadetblue">div 9</div>
</div>

as I said, I put a button to remove event as example to call the function. You can call the function to remove other way. As the function is written at the moment, you just need to pass the name of the id. Regarding what you are saying it you need to remove event listener of a specific clicked button...
let condition = false;
Array.from(document.querySelectorAll('button.game')).forEach(el => {
el.addEventListener('click', divclickadd);
});
function divclickadd(ev) {
ev.stopImmediatePropagation();
console.log(ev.target);
if (condition) {
divclickremove(ev.target);
}
}
document.querySelector('button.condition').addEventListener('click', function(evt) {
evt.stopImmediatePropagation();
condition = true;
console.log(condition);
});
function divclickremove(el) {
console.log(el);
el.removeEventListener('click', divclickadd);
}
button {
display: block;
}
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<br>
<button class="condition">change condition value</button>
if you click on change condition value, next time you click on button event, it'll fire because remove not yet fired. Next next time, it's not remove event has been done.

Related

Show and hide different divs on clicks on different buttons [duplicate]

This question already has answers here:
Multiple show/hide divs with separate toggle
(5 answers)
Closed 8 months ago.
I am trying to show and hide different div's on click on different buttons. For example, when I click button "1", it shows the block with id="block-1". When I click on button "4", the block with id="block-4" shows and previous block #block-1 hides. I tried using different ID's because I don't know any other solution to show blocks with different content inside. Unfortunately, my current code doesn't work properly: it toggles the right class to show the div, but I can't hide the previous div or change the block once the button with number is clicked. On the default state, when the page is loaded, the first block (#block-1) should always be visible. Here's the link to codepen with the result: https://codepen.io/tomavl/pen/vYRLJVY
<div class="filter">
<button class="filter-btn active" id="1">1</button>
<button class="filter-btn" id="2">2</button>
<button class="filter-btn" id="3">3</button>
<button class="filter-btn" id="4">4</button>
</div>
<div class="block">
<div class="block-1 block-card active" id="block-1">Block 1</div>
<div class="block-2 block-card" id="block-2">Block 2</div>
<div class="block-3 block-card" id="block-3">Block 3</div>
<div class="block-4 block-card" id="block-4">Block 4</div>
</div>
background-color: red;
color: white;
}
.block-card {
display: none;
}
.block-card.active {
display: block;
}
var filterBtn = document.querySelectorAll(".filter-btn");
for (var i = 0; i < filterBtn.length; i++) {
filterBtn[i].onclick = function () {
if (this.classList) {
for (var j = 0; j < filterBtn.length; j++) {
filterBtn[j].classList.remove("active");
}
this.classList.add("active");
} else {
this.active += " " + active;
}
};
}
$("#2").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-2").addClass("active");
} else {
$(".block-2").removeClass("active");
}
});
$("#3").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-3").addClass("active");
} else {
$(".block-3").removeClass("active");
}
});
$("#4").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-4").addClass("active");
} else {
$(".block-4").removeClass("active");
}
});
You can achieve what you need with much less code by using common classes to group content by behaviour. You can use data attributes where required to store custom metadata in an element.
In the following example all buttons use the same event handler. The differences come simply from the data attribute on the button used to change the selector. The code just removes the active class from all relevant elements before applying it to the target.
let $blocks = $('.block-card');
$('.filter-btn').on('click', e => {
let $btn = $(e.target).addClass('active');
$btn.siblings().removeClass('active');
let selector = $btn.data('target');
$blocks.removeClass('active').filter(selector).addClass('active');
});
body {
background-color: red;
color: white;
}
.block-card {
display: none;
}
.block-card.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div class="filter">
<button class="filter-btn active" data-target="#block-1">1</button>
<button class="filter-btn" data-target="#block-2">2</button>
<button class="filter-btn" data-target="#block-3">3</button>
<button class="filter-btn" data-target="#block-4">4</button>
</div>
<div class="block">
<div class="block-card active" id="block-1">Block 1</div>
<div class="block-card" id="block-2">Block 2</div>
<div class="block-card" id="block-3">Block 3</div>
<div class="block-card" id="block-4">Block 4</div>
</div>

Scroll to bottom when new message added

I am making a chatbot. I want to scroll to the bottom of the chat box when a new input is given by the user or the Data is sent through API.
It doesn't scroll and scroll just stays in the same position but the data is being added in the chat box
I Have tried the code from other chat bot but it didn't work either
var outputArea = $('#chat-output');
$('#user-input-form').on('submit', function (e) {
e.preventDefault();
var message = $('#user-input').val();
outputArea.append(`
<div class='bot-message'>
<div class='message'>
${message}
</div>
</div>
`);
const req = https.request(options, (res) => {
res.setEncoding('utf8');
res.on('data', (d) => {
const myobj = JSON.parse(d);
if ('narrative' in myobj.conversationalResponse.responses[0]) {
const temp = myobj.conversationalResponse.responses[0].narrative.text;
outputArea.append(`
<div class='user-message'>
<div class='message'>
${temp}
</div>
</div>
`);
} else if ('imageUrl' in myobj.conversationalResponse.responses[0]) {
const img = myobj.conversationalResponse.responses[0].imageUrl;
if ('narrative' in myobj.conversationalResponse.responses[1]) {
const text_r = myobj.conversationalResponse.responses[1].narrative.text;
outputArea.append(`
<div class='user-message'>
<div class ="message">
${text_r}
</div>
</div>
`);
} else {
outputArea.append(`
<div class='user-message'>
<div class='message'>
<img src="" width="300" height="200">
</div>
</div>
`);
}
}
});
});
req.on('error', (error) => {
console.error(error);
});
req.write(data);
req.end();
$('#user-input').val('');
.form-container {
width: 400px;
height: 450px;
padding: 10px;
background-color: white;
overflow: scroll;
position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chat-popup" id="myForm">
<div class="form-container">
<div class="chat-output" id="chat-output">
<div class="user-message">
<div class="message">Hi! I'm Bot, what's up?</div>
</div>
</div>
<div class="chat-input">
<form action="#0" id="user-input-form" autocomplete="off">
<input type="text" id="user-input" class="user-input" placeholder="Talk to the bot.">
</form>
</div>
</br></br>
<button type="button" class="btn cancel" onclick="closeForm()">Close</button>
</div>
</div>
Another interesting method is by using pure CSS, using the flex-direction method, which works by creating a wrapper for the content inside the scrolling element.
I've whipped up a quick demo below (with a button and some JavaScript for adding new items). You can also check out this separate demo-page.
The trick then lies in reversing the content direction using column-reverse in the scroller. Because the items are in another container, they don't get 'flipped' but instead always line up to the bottom. This, in fact, makes the scroller scrolled to the bottom whenever stuff is added.
Added bonus: keeps scroll position
Also, and this is something I really like about the method; whenever the user has started scrolling (up), the scroller will not lose its scroll position when stuff is being added. So, it will only 'stick' tot the bottom if it was already scrolled (by default, or by the user) to the bottom. This makes sure there's no annoying content jumping, offering a better user experience.
Demo
let scrollerContent = document.getElementById('scrollerContent');
document.getElementById('addItems').addEventListener('click', function() {
let newChild = scrollerContent.lastElementChild.cloneNode(true);
newChild.innerHTML = "Item " + (scrollerContent.children.length + 1);
scrollerContent.appendChild(newChild);
});
.scroller {
overflow: auto;
height: 100px;
display: flex;
flex-direction: column-reverse;
}
.scroller .scroller-content .item {
height: 20px;
transform: translateZ(0); /* fixes a bug in Safari iOS where the scroller doesn't update */
}
<div class="scroller">
<div class="scroller-content" id="scrollerContent">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
<div class="item">Item 5</div>
<div class="item">Item 6</div>
<div class="item">Item 7</div>
<div class="item">Item 8</div>
<div class="item">Item 9</div>
<div class="item">Item 10</div>
</div>
</div>
<br/><br/>
<button id="addItems">Add more items</button>
You can use scrollTop to achieve it.
I simply wrote an example for your reference, you will find that there are two ways to implement scrollTop, one is to use the animation wrapper, the other is to use it directly, you can compare the difference between the two methods.
const sendMessage = (selector, isAnimate = true) => {
const text = $(selector).val();
const $container = $('.form-container');
$container.append(`<p>${text}</p>`);
if (isAnimate) {
$container.animate({
scrollTop: $container.prop('scrollHeight')
}, 1000);
} else {
$container.scrollTop($container.prop('scrollHeight'));
}
$(selector).val('');
};
$('button:eq(0)').on('click', function() {
sendMessage('input[type=text]');
});
$('button:eq(1)').on('click', function() {
sendMessage('input[type=text]', false);
});
.form-container {
width: 400px;
height: 100px;
padding: 10px;
background-color: white;
overflow: scroll;
position: relative;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<div class='form-container'>
<p>This is line 1 of text</p>
<p>This is line 2 of text</p>
</div>
<div>
<input type='text' placeholder="Type a message.">
<button type='button'>Use animation</button>
<button type='button'>Don't use animation</button>
</div>
This worked for me:
outputArea[0].scrollTop = 9e9;

My active/disable Functionality no longer works after cloning

I'm using the clone method to duplicate a form. I'm adding and removing the active
class on the buttons but, once I clone the form, the duplicate buttons no longer
function because they share the same class as the original. I want the buttons to still
function regardless how many times I clone it. I used jQuery and JavaScript, and I'm
still new to programming. Can you please give me some ideas as to how to solve this.
Thanks in advance fellow developers.
Here is my HTML Code:
<div class="column-bottom phone">
<p class="para_txt">Phone</p>
<div id="main-wrapper">
<div id="wrapper_1" class="parentClass">
<div class="basic_infor">
<p>Select the nature of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_first_4 " >Private</button>
<button class="func_btns btn_second_4" >Work</button>
</div>
</div>
<div class="basic_infor">
<p>Select the type of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_5">Mobile</button>
<button class="func_btns btn_6 ">Telephone</button>
<button class="func_btns btn_7 ">Fax</button>
<button class="func_btns btn_8">Extension</button>
</div>
</div>
<div class="txt_area">
<input type="textarea" placeholder="+27 85 223 5258">
<span onclick="delete_el();">x</span>
</div>
</div>
</div>
<div class="btn_add">
<button class="repl_btns phone_repl" onclick="duplicate();">Add additional</button>
<p>Display on foreman contact list?</p>
<input type="checkbox" id="input_field" name="Phone_contact">
</div>
</div>
Here is my jQuery and JavaScript Code. I selected the class for the first button and
added a active class to it while removing the active class for the second button. I did
the same for the rest of the buttons.
//private btn
$(".btn_first_4").click(function () {
$(this).addClass("is_active");
$(".btn_second_4").removeClass("is_active");
});
//work btn
$(".btn_second_4").click(function () {
$(this).addClass("is_active");
$(".btn_first_4").removeClass("is_active");
});
//Bottom 5 btns
$(".btn_5").click(function () {
$(this).addClass("is_active");
$(".btn_6,.btn_7,.btn_8").removeClass("is_active");
})
$(".btn_6").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_7,.btn_8").removeClass("is_active");
})
$(".btn_7").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_8").removeClass("is_active");
})
$(".btn_8").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_7").removeClass("is_active");
})
/*
Cloning Functions....
I tried to set the id of my new clone to "wrapper_2", but it only works when i clone it
once. I wanted to change the class attribute this way but I realize it wont work as
well. Please advise. Thanks
*/
function duplicate(){
const wrapper = document.getElementById("wrapper_1");
const clone = wrapper.cloneNode(true);
clone.id = "wrapper_2";
const main_wrapper = document.getElementById("main-wrapper");
main_wrapper.appendChild(clone)
}
function delete_el() {
const del_el = document.getElementById("wrapper_2");
del_el.remove();
}
Problems
If you use .cloneNode() any event handlers bound to the original will not carry over to the clone. Fortunately you are using jQuery which has it's own method .clone(). It has the ability to clone and keep event handlers, $(selector).clone(true) to copy with events and $(selector).clone(true, true) for a deep copy with events.
Note: Using .clone() has the side-effect of producing elements with duplicate id attributes, which are supposed to be unique. Where possible, it is recommended to avoid cloning elements with this attribute or using class attributes as identifiers instead.
.clone()|jQuery API Documentation
Do not clone anything with an id, in fact you are using jQuery so don't use id at all. Convert every id to a class, it might feel like a lot of work but in the long run you'll be thankful you did.
Do not use inline event handlers
<button onclick="lame(this)">DON'T DO THIS</button>
This is especially important if you use jQuery which makes event handling incredibly easy to write and very versatile.
let count = 0;
$('output').val(++count);
$('.remove').hide();
$('.select button').on('click', function() {
const $old = $(this).parent().find('.active');
if (!$old.is(this)) {
$old.removeClass('active');
}
$(this).toggleClass('active');
});
$('.clear').on('click', function() {
$(this).parent().find('input').val('');
});
$('.remove').on('click', function() {
$(this).closest('.fields').remove();
let out = $.makeArray($('output'));
count = out.reduce((sum, cur, idx) => {
cur.value = idx + 1;
sum = idx + 1;
return sum;
}, 0);
});
$('.add').on('click', function() {
const $first = $('.fields').first();
const $copy = $first.clone(true, true);
$copy.insertAfter($('.fields').last());
$copy.find('output').val(++count);
$copy.find('.remove').show();
$copy.find('input').val('');
});
html {
font: 300 2ch/1.2 'Segoe UI'
}
fieldset {
min-width: fit-content
}
.fields {
margin-top: 1rem;
}
output {
font-weight: 900;
}
menu {
display: flex;
align-items: center;
margin: 0.5rem 0 0.25rem;
}
button,
input {
display: inline-block;
font: inherit;
font-size: 100%;
}
button {
cursor: pointer;
border: 1.5px ridge lightgrey;
}
.numbers {
display: flex;
align-items: center;
margin: 1rem 0 0.5rem -40px;
}
.clear {
border: 0;
font-size: 1.25rem;
line-height: 1.25;
}
.right {
justify-content: flex-end;
}
.left {
padding-left: 0;
}
.number-3 {
width: 9rem;
}
.number-1 {
width: 3rem;
}
[class^="number-"] {
font-family: Consolas
}
.clear {
border: 0;
background: transparent;
}
label+label {
margin-left: 6px;
}
button:first-of-type {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button:nth-of-type(2) {
border-radius: 0;
}
button:last-of-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.active {
outline: 2px lightblue solid;
outline-offset: -2px;
}
#foreman {
transform: translate(0, 1.5px)
}
.btn.remove {
display: block;
border-radius: 4px;
float: right;
}
<form id='phone'>
<fieldset class='main'>
<legend>Add Phone Numbers</legend>
<section class='fields'>
<fieldset>
<legend>Phone Number <output value='1'></output></legend>
<button class='btn remove' type='button'>Remove</button>
<label>Phone number is used for:</label>
<menu class='purpose select'>
<button class="btn priv" type='button'>Private</button>
<button class="btn work" type='button'>Work</button>
</menu>
<label>Select the type of phone:</label>
<menu class='type select'>
<button class="btn mob" type='button'>Mobile</button>
<button class="btn tel" type='button'>Telephone</button>
<button class="btn fax" type='button'>Fax</button>
</menu>
<menu class='numbers'>
<form name='numbers'>
<label>Number:&ThickSpace;</label>
<input name='phone' class='number-3' type="tel" placeholder="+27 85 223 5258" required>
<label>&ThickSpace;Ext.&ThickSpace;</label>
<input name='ext' class='number-1' type='number' placeholder='327'>
<button class='btn clear' type='button'>X</button>
</form>
</menu>
</fieldset>
</section>
<fieldset>
<menu class='right'>
<button class='btn cancel' type='button'>Cancel</button>
<button class='btn done'>Done</button>
<button class='btn add' type='button'>Add</button>
</menu>
</fieldset>
<footer>
<menu>
<input id='foreman' name="contact" type="checkbox">
<label for='foreman'>Display on foreman contact list?</label>
</menu>
</footer>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
When load page , JS add event click for elements ( elements were created)
When you clone new elements ( those do not add event click) and event click of you not working on those elements
You are using Jquery then i suggest you code same as below :
$(document).on('click', ".btn_first_4", function () {
$(this).addClass("is_active");
$(".btn_second_4").removeClass("is_active");
});
//work btn
$(document).on('click', ".btn_second_4", function () {
$(this).addClass("is_active");
$(".btn_first_4").removeClass("is_active");
});
//Bottom 5 btns
$(document).on('click', ".btn_5", function () {
$(this).addClass("is_active");
$(".btn_6,.btn_7,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_6", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_7,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_7", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_8", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_7").removeClass("is_active");
})
function duplicate(){
const wrapper = document.getElementById("wrapper_1");
const clone = wrapper.cloneNode(true);
clone.id = "wrapper_2";
const main_wrapper = document.getElementById("main-wrapper");
main_wrapper.appendChild(clone)
}
function delete_el() {
const del_el = document.getElementById("wrapper_2");
del_el.remove();
}
.is_active {
background-color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="column-bottom phone">
<p class="para_txt">Phone</p>
<div id="main-wrapper">
<div id="wrapper_1" class="parentClass">
<div class="basic_infor">
<p>Select the nature of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_first_4 " >Private</button>
<button class="func_btns btn_second_4" >Work</button>
</div>
</div>
<div class="basic_infor">
<p>Select the type of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_5">Mobile</button>
<button class="func_btns btn_6 ">Telephone</button>
<button class="func_btns btn_7 ">Fax</button>
<button class="func_btns btn_8">Extension</button>
</div>
</div>
<div class="txt_area">
<input type="textarea" placeholder="+27 85 223 5258">
<span onclick="delete_el();">x</span>
</div>
</div>
</div>
<div class="btn_add">
<button class="repl_btns phone_repl" onclick="duplicate();">Add additional</button>
<p>Display on foreman contact list?</p>
<input type="checkbox" id="input_field" name="Phone_contact">
</div>
</div>

How do I check whether an element is already bound to an event?

Goal
Avoid unnecessary event bindings.
Sample code
Comment box with a reply button for each individual comment
const btns = document.getElementsByClassName('reply-btn');
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', showCommentContentAsPreview);
}
function showCommentContentAsPreview(e) {
console.log('showCommentContentAsPreview()');
// CHECK IF THIS BUTTON ALREADY BINDED !!!
const previewDiv = document.getElementById('preview');
const commentId = e.target.getAttribute('data-comment-id')
const commentDiv = document.getElementById('comment-' + commentId);
const commentText = commentDiv.querySelector('p').innerText
const closeReplyBtn = previewDiv.querySelector('button');
const previewContent = previewDiv.querySelector('.preview-content');
// set to preview
previewContent.innerText = commentText;
// show reply close button
closeReplyBtn.classList.remove('hidden');
// bind EventListener to "reply close button"
closeReplyBtn.addEventListener('click', closeReply)
function closeReply() {
console.log('bind to btn');
previewContent.innerText = '';
this.removeEventListener('click', closeReply);
closeReplyBtn.classList.add('hidden');
}
}
.hidden {
display: none;
}
.comment {
border-bottom: 1px solid #000;
padding: 5px;
}
.preview {
background-color: #ccc;
padding: 20px;
margin-top: 20px;
}
<div>
<!-- comment list -->
<div id="comment-1" class="comment">
<p>Comment Content 1</p>
<button class="reply-btn" data-comment-id="1">reply</button>
</div>
<div id="comment-2" class="comment">
<p>Comment Content 2</p>
<button class="reply-btn" data-comment-id="2">reply</button>
</div>
</div>
<!-- output -->
<div>
<div id="preview" class="preview">
<div class="preview-content"></div>
<button class="hidden">Close Preview</button>
</div>
</div>
Simulate problem
When you try the example, the following two scenarios occur:
Click reply once and then click "close preview"
Click on reply several times and then on "close preview".
Question
How can I avoid multiple bindings to the same button? I am already thinking about singleton.
Instead of binding a listener to every element in the series, you can bind a single listener once on a common parent of them all, and then use element.matches() to determine if the click target is the one that you want before doing more work. See the following example:
function logTextContent (elm) {
console.log(elm.textContent);
}
function handleClick (ev) {
if (ev.target.matches('.item')) {
logTextContent(ev.target);
}
}
document.querySelector('ul.list').addEventListener('click', handleClick);
<ul class="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
<li class="item">Item 4</li>
<li class="item">Item 5</li>
</ul>
With the helpful hints from #Zephyr and #jsejcksn I have rewritten the code of the above question. Thus I have achieved my goal of avoiding multiple identical bindings to one element.
const container = document.getElementById('comment-container');
const previewDiv = document.getElementById('preview');
const closeReplyBtn = previewDiv.querySelector('button');
const previewContent = previewDiv.querySelector('.preview-content');
container.addEventListener('click', handleClick);
function handleClick(ev) {
if (ev.target.matches('.reply-btn')) {
if (ev.target.getAttribute('listener') !== 'true') {
removeOtherListenerFlags();
ev.target.setAttribute('listener', 'true');
showCommentContentAsPreview(ev);
}
}
if (ev.target.matches('#preview button')) {
previewContent.innerText = '';
closeReplyBtn.classList.add('hidden');
removeOtherListenerFlags();
}
}
function showCommentContentAsPreview(e) {
console.log('showCommentContentAsPreview()');
const commentId = e.target.getAttribute('data-comment-id')
const commentDiv = document.getElementById('comment-' + commentId);
const commentText = commentDiv.querySelector('p').innerText
// set to preview
previewContent.innerText = commentText;
// show reply close button
closeReplyBtn.classList.remove('hidden');
}
function removeOtherListenerFlags() {
const replyBtns = container.querySelectorAll('.reply-btn')
Object.keys(replyBtns).forEach((el) => {
replyBtns[el].removeAttribute('listener');
})
}
.hidden {
display: none;
}
.comment {
border-bottom: 1px solid #000;
padding: 5px;
}
.preview {
background-color: #ccc;
padding: 20px;
margin-top: 20px;
}
<div id="comment-container">
<div id="comment-listing">
<!-- comment list -->
<div id="comment-1" class="comment">
<p>Comment Content 1</p>
<button class="reply-btn" data-comment-id="1">reply 1</button>
</div>
<div id="comment-2" class="comment">
<p>Comment Content 2</p>
<button class="reply-btn" data-comment-id="2">reply 2</button>
</div>
</div>
<!-- output -->
<div>
<div id="preview" class="preview">
<div class="preview-content"></div>
<button class="hidden">Close Preview</button>
</div>
</div>
</div>
Cool and Thanks!

Check if navbar got clicked

I have a navbar and want to close it when clicking outside. The only thing I need to check is the click event of the body.
var navBtnActive = true;
function toggleMenu(){
navBtnActive = !navBtnActive;
$("#navContent").slideToggle();
}
$('body').click(function() {
// if( clicked target is NOT the menu ){
// if(navBtnActive){ // just if the menu is open
// toggleMenu();
// }
// }
});
#navContainer {
position: relative;
display: inline-block;
}
#navContent button {
display: block;
width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navContainer">
<button onclick="toggleMenu()">Menu</button>
<div id="navContent">
<button onclick="toggleMenu()">Slider</button>
<button onclick="toggleMenu()">Calculator</button>
<button onclick="toggleMenu()">Imageupload</button>
<button onclick="toggleMenu()">Settings</button>
<button onclick="toggleMenu()">Search</button>
<button onclick="toggleMenu()">Servercall</button>
</div>
</div>
As you can see below, my $('body').click(function() got the code for closing it. I just want to get a way checking if the clicked object is the menu itself or not. If not, close the menu.
Hi you can try this code
var navBtnActive = true;
function toggleMenu(e) {
navBtnActive = !navBtnActive;
$("#navContent").slideToggle();
}
jQuery(document).on('click', function() {
$("#navContent").slideUp();
});
jQuery('#navContainer').on('click', function(e) {
e.stopPropagation();
});
#navContainer {
position: relative;
display: inline-block;
}
#navContent button {
display: block;
width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navContainer">
<button onclick="toggleMenu()">Menu</button>
<div id="navContent">
<button onclick="toggleMenu()">Slider</button>
<button onclick="toggleMenu()">Calculator</button>
<button onclick="toggleMenu()">Imageupload</button>
<button onclick="toggleMenu()">Settings</button>
<button onclick="toggleMenu()">Search</button>
<button onclick="toggleMenu()">Servercall</button>
</div>
</div>
just check the click targer jQuery event targer
$('body').click(function(e){
var $elem = $(e.target);
if ($elem.attr('id') === 'my_nav_bar_id') {
doSomething();
}
});
Here you go with a solution https://jsfiddle.net/ap7m4xnu/
var navBtnActive = true;
$('#menuToggle').click(function(e){
e.stopPropagation();
navBtnActive = !navBtnActive;
$("#navContent").slideToggle();
})
$('body').not('#menuToggle').click(function(e) {
e.preventDefault();
$("#navContent").slideUp();
});
#navContainer {
position: relative;
display: inline-block;
}
#navContent button {
display: block;
width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navContainer">
<button id="menuToggle">Menu</button>
<div id="navContent">
<button onclick="toggleMenu()">Slider</button>
<button onclick="toggleMenu()">Calculator</button>
<button onclick="toggleMenu()">Imageupload</button>
<button onclick="toggleMenu()">Settings</button>
<button onclick="toggleMenu()">Search</button>
<button onclick="toggleMenu()">Servercall</button>
</div>
</div>
You can use the compareDocumentPosition method. You can read its documentation here: https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
You can test if the clicked element is contained by the menu by doing the following:
var DOCUMENT_POSITION_CONTAINED_BY = 16;
if ((navContainer.compareDocumentPosition(clickedElement) & DOCUMENT_POSITION_CONTAINED_BY) !== 0) {
// clicked element is inside the menu
} else {
// clicked element is outside of the menu
}
In your example it would work like this:
var navBtnActive = true;
function toggleMenu(){
navBtnActive = !navBtnActive;
$("#navContent").slideToggle();
}
$('body').click(function(event) {
var DOCUMENT_POSITION_CONTAINED_BY = 16,
navContainer = document.getElementById('navContainer'),
compareResult = navContainer.compareDocumentPosition(event.target) & DOCUMENT_POSITION_CONTAINED_BY;
if (compareResult === 0) {
// clicked element is outside of the menu
toggleMenu();
}
});
#navContainer {
position: relative;
display: inline-block;
}
#navContent button {
display: block;
width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navContainer">
<button onclick="toggleMenu()">Menu</button>
<div id="navContent">
<button onclick="toggleMenu()">Slider</button>
<button onclick="toggleMenu()">Calculator</button>
<button onclick="toggleMenu()">Imageupload</button>
<button onclick="toggleMenu()">Settings</button>
<button onclick="toggleMenu()">Search</button>
<button onclick="toggleMenu()">Servercall</button>
</div>
</div>
function toggleMenu(){
$("#navContent").slideToggle();
}
$('body').click(function(e) {
if(document.querySelector('body')== e.toElement && $("#navContent").is(':visible')){
$("#navContent").slideToggle();
}
});
#navContainer {
position: relative;
display: inline-block;
}
#navContent button {
display: block;
width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navContainer">
<button onclick="toggleMenu()">Menu</button>
<div id="navContent">
<button onclick="toggleMenu()">Slider</button>
<button onclick="toggleMenu()">Calculator</button>
<button onclick="toggleMenu()">Imageupload</button>
<button onclick="toggleMenu()">Settings</button>
<button onclick="toggleMenu()">Search</button>
<button onclick="toggleMenu()">Servercall</button>
</div>
</div>

Categories

Resources