I'm trying to make a very simple (it looks like) thing: clone already existring html-element, modify it's attribute and append in on the page. And it doesn't work, and I have no idea why.
Here is a simple demo:
Hello World
Change color
// js
var banner = $("#banner-message")
var button = $("button")
// handle click and add class
button.on("click", function(){
var row = $('#banner-message').find('.test:first').clone(true);
row.removeData('event-num');
//row.data('event-num', 500);
$('#banner-message').find('.test:last').after(row);
})
Here is a jsfiddle demo: https://jsfiddle.net/jn1wm9da/
It neither deletes nor overrides (actually I want to delete it) already existing attribute. What I'm doing wrong?
jQuery's .data() method stores information in a jQuery object that is connected to the element, and doesn't effect data-* attributes. Data attributes are just attributes, and you should use .attr(), and .removeAttr() to add/change or remove them.
// find elements
var banner = $("#banner-message")
var button = $("button")
// handle click and add class
button.on("click", function() {
var row = $('#banner-message').find('.test:first').clone(true);
row.removeAttr('data-event-num');
row.attr('data-event-num', 500);
$('#banner-message').find('.test:last').after(row);
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#banner-message {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
text-align: center;
transition: all 0.2s;
margin: 0 auto;
width: 300px;
}
button {
background: #0084ff;
border: none;
border-radius: 5px;
padding: 8px 14px;
font-size: 15px;
color: #fff;
}
#banner-message.alt {
background: #0084ff;
color: #fff;
margin-top: 40px;
width: 200px;
}
#banner-message.alt button {
background: #fff;
color: #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="banner-message">
<p>Hello World</p>
<button class="test" data-event-num='0'>Change color</button>
</div>
From the jQuery Documentation for removeData
Note that .removeData() will only remove data from jQuery's internal .data() cache, and any corresponding data- attributes on the element will not be removed.
Try using
row.removeAttr('data-event-num');
instead
Use row.attr('data-event-num', null); to remove the data attribute
// find elements
var banner = $("#banner-message")
var button = $("button")
// handle click and add class
button.on("click", function(){
var row = $('#banner-message').find('.test:first').clone(true);
//row.removeData('event-num');
row.attr('data-event-num', null);
$('#banner-message').find('.test:last').after(row);
})
Related
I have this basic HTML that represents a button, I want to attach a click event, but this button is appendend dinamically to the container through a an ajax function, so because this is not present initialy in the DOM, I'm attaching the event to the container in this way
document.querySelector('#container').addEventListener('click', (e) => {
var t = e.target.classList;
if( t.contains('button-outer') ) console.log(e.target);
});
#container {
width: 100%;
height: 100%;
position: relative;
text-align: center;
}
.button-outer {
padding: 15px;
background-color: orange;
display: inline-block;
margin: 0 auto;
text-align: center;
}
.button-inner{
font-weight: bold;
cursor: pointer;
font-size: 75px;
}
<div id="container">
<div class="button-outer">
<div class="button-inner">BUTTON</div>
</div>
</div>
This works, but obviously only when I'm clicking on on the padding part of the outer div. To fix this I have to change the if statement in a way that it will contains the inner part too, like this:
document.querySelector('#container').addEventListener('click', (e) => {
var t = e.target.classList;
if( t.contains('button-outer') || t.contains('button-inner')) console.log(e.target);
});
I think that this is a little uncovenient, because sometimes the inner part could have other classes, or could be an icon, a link, it is a little difficoult to create specific statements each time, so question is:
How can I propagate the event starting from outer to all inner elements when the button is dinamically appended?
You should attach your event handler when the button is created, in your ajax function.
But if you need to do it the way you are doing it, you can use closest(), it will traverse all of the target's parents until it finds your query.
document.querySelector('#container').addEventListener('click', (e) => {
var t = e.target;
if(t.closest('.button-outer')) console.log(e.target);
});
#container {
width: 100%;
height: 100%;
position: relative;
text-align: center;
}
.button-outer {
padding: 15px;
background-color: orange;
display: inline-block;
margin: 0 auto;
text-align: center;
}
.button-inner{
font-weight: bold;
cursor: pointer;
font-size: 75px;
}
<div id="container">
<div class="button-outer">
<div class="button-inner">BUTTON</div>
</div>
</div>
I know there's a lot of similar questions on SO, but none of them addresses this situation.
Consider this code:
//elements
var parent = $("#parent");
var button = $("button");
var version = $('#version');
version.text('jQuery ' + jQuery.fn.jquery);
//dispatcher
var eventDispatcher = {
trigger: function(targetElement, eventName) {
var ev = new jQuery.Event(eventName);
ev.stopPropagation();
jQuery(targetElement).trigger(ev);
}
}
//events
version.bind('custom', function(){
button.text('version custom');
});
parent.bind('custom', function(){
button.text('parent custom');
});
button.bind("click", function(){
eventDispatcher.trigger(version, 'custom');
});
body { background: #333; padding: 20px; font-family: Helvetica; }
#parent { background: #0c0; padding: 20px; font-size: 25px;
text-align: center; margin: 0 auto; width: 300px; }
button { background: #fff; border: none; border-radius: 5px;
padding: 8px 14px; font-size: 15px; color: #fff; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<div id="parent">
<p id="version">jQuery Version</p>
<button>Change color</button>
</div>
The dispatcher must fire only the child element's event.
I can't just put stopPropagation() on the child's event declaration, because it's the dispatcher that decides if the event is going to propagate or not.
Context
I'm upgrading an old project. In jQuery 1.5.1 the dispatcher worked, but on newer versions, it don't.
How to configure an jQuery event not to propagate beyond the target element, before firing the event.
I was wondering that the onclick method that is calling my function doesn't have access to the style value on the first click but it does on the second. I was wondering if for jQuery it would be the same but it seems like it's not.
I created a short code that shows the issue:
HTML:
<p class="flip" onclick="myFunction()">Click to show panel</p>
<div id="panel">
<p>panel</p>
</div>
CSS:
#panel, .flip {
font-size: 16px;
padding: 10px;
text-align: center;
background-color: #4CAF50;
color: white;
border: solid 1px #a6d8a8;
margin: auto;
}
.flip {
cursor: pointer;
}
#panel {
display: none;
}
Script:
function myFunction() {
console.log(document.getElementById("panel").style.display); //on first call returns empty string
console.log($("#panel").css("display")); // on first call returns none
const setPanel = (a) => {document.getElementById("panel").style.display = a;};
const getPanel = document.getElementById("panel").style.display;
(getPanel === "none") ? setPanel("block") : setPanel("none");
}
I am wondering why the behavior is the way it is and is it possible to retrieve the style directly without using jQuery?
jQuery internally uses .getComputedStyle() to determine the effective styles on an element rather than the ones explicitly defined on that element. .style only returns the explicit ones.
Observe:
function myFunction() {
console.log(window.getComputedStyle(document.getElementById("panel")).display); //on first call returns none
console.log($("#panel").css("display")); // on first call returns none
const setPanel = (a) => {document.getElementById("panel").style.display = a;};
const getPanel = window.getComputedStyle(document.getElementById("panel")).display;
(getPanel === "none") ? setPanel("block") : setPanel("none");
}
#panel, .flip {
font-size: 16px;
padding: 10px;
text-align: center;
background-color: #4CAF50;
color: white;
border: solid 1px #a6d8a8;
margin: auto;
}
.flip {
cursor: pointer;
}
#panel {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="flip" onclick="myFunction()">Click to show panel</p>
<div id="panel">
<p>panel</p>
</div>
I am new in JavaScript and still learning various things. Right now i'm stuck with adding and removing li elements from the list. I did the exercise just fine using jQuery, but now have difficulties with the pure JS version of the same task.
The main idea is to add a new li element by clicking on the button, and remove the the element by clicking on the X button right next to it. I have tried using 'this' and other advices mentioned in similar questions here on Stackoverflow, but nothing worked for me. Could you, please, guide me what am I doing wrong?
P.S. the adding function seems to be working in snippet, but console logs error: cannot read property 'addeventlistener' of null.
//declaring the variables
var btn = document.getElementsByClassName('btn');
var list = document.getElementById('list');
var add = document.getElementById('add');
//adding a new element to the list
add.addEventListener('click', function(){
var newElement = document.createElement('LI');
list.appendChild(newElement);
newElement.innerHTML= "I am a new element<button class='btn'>X</button>";
});
//removing the clicked element
btn.addEventListener('click', function(){
list.parentNode.removeChild(this);
});
ul li {
decoration: none;
display: block;
margin-top: 1em;
text-align: center;
font-family: 'Avant Garde', Avantgarde, 'Century Gothic', CenturyGothic, AppleGothic, sans-serif;
font-size: 18px;
}
#add {
background-color: black;
color: white;
border: none;
width: 280px;
font-color: white;
border-radius: 8px;
font-size: 16px;
padding: 15px;
outline: none;
text-align: center;
margin: 20px auto;
display: block;
}
#add:hover {
background-color: #28364d;
color: white;
border: none;
outline: none;
}
#add:active {
position: relative;
bottom: 2px;
}
.btn{
margin-left: 10px;
border-radius: 10px;
background-color: #000;
color: white;
border: none;
outline: none;
font-size: 14px;
font-family: sans-serif;
}
.btn:active {
position: relative;
bottom: 2px;
}
<div>
<ul id="list">
<li class="element">I am a new element<button class="btn">X</button></li>
<li class="element">I am a new element<button class="btn">X</button></li>
<li class="element">I am a new element<button class="btn">X</button></li>
<li class="element">I am a new element<button class="btn">X</button></li>
<li class="element">I am a new element<button class="btn">X</button></li>
</ul>
<button id="add">Add an element to the list</button>
</div>
This returns a non-live collection:
var btn = document.getElementsByClassName('btn');
Which means that it will only contain the objects which exist at the point of the method call. You need to call getElementsByClassName() after the creation of the new li elements, and attach the EventListeners on the invidual buttons. Just remember not to put an EventListener twice on the buttons.
A nicer solution
Better yet: do not use getElementsByClassName(), just attach the event handler directly in the function, in which you create the new button. That way, you don't have to worry about pre-existing event handlers:
add.addEventListener('click', function(){
var newElement = document.createElement('LI');
list.appendChild(newElement);
newElement.innerHTML= "I am a new element<button class='btn'>X</button>";
newElement.addEventListener('click', function () {
this.parentNode.removeChild(this);
});
});
In your case, btn is a node list of elements, not an element, so you can't attach an event to an array. You need to iterate through them:
for(i=0; i < btn.length; i++) {
btn[i].addEventListener('click', function(){
list.parentNode.removeChild(this);
});
}
I am learning too and this was a good little challenge as a newcomer. Thanks to rlemon for pointing us in the right direction with that article link. I learnt something here.
In case you were interested what I arrived at:
https://jsfiddle.net/nyxhj0tg/1/
JS:
var list = document.getElementById('list');
var add = document.getElementById('add');
//adding a new element to the list
add.addEventListener('click', function(){
var newElement = document.createElement('LI');
list.appendChild(newElement);
newElement.innerHTML= "I am a new element<button class='btn'>X</button>";
});
list.addEventListener('click', function(e){
if(e.target && e.target.nodeName == "BUTTON") {
console.log("Button ", e, " was clicked!");
e.target.parentNode.remove();
}
});
I'm trying to remove class 'active' when you click on the checkbox the 2nd time, the same way Pinterest does it for Twitter/Facebook checkboxes when a user adds a pin:
Adding 'active' class on click is easy. However, I couldn't figure how to remove it once it was added. I tried this, but it didn't work:
$(".add_link_twitter.active").click(function(e) {
$(this).removeClass(activePostTwitter);
});
I have two questions:
How to remove the 'active' css class on the 2nd click on the
checkbox?
How to disable '.add_link_twitter:hover' when the Twitter
checkbox is selected?
Thanks in advance!
Here's the jQuery:
var postTwitter = ".add_link_twitter";
var activePostTwitter = "active";
$(postTwitter).click(function(e) {
$(this).addClass(activePostTwitter);
});
Here's the html:
<label class="add_link_twitter">
<input type="checkbox" name="publish_to_twitter" class="publish_to_twitter"><span>Share on Twitter</span>
</label>
Here's the css:
.add_link_twitter{
position:absolute;
left:15px;
bottom:16px;
color: #a19486;
border: 2px solid transparent;
border-color: #F0EDE8;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
cursor: pointer;
padding: 7px;
padding-top: 5px;
padding-bottom: 5px;
}
.active {
border-color: #468BD0;
color: #468BD0;
background-color: whiteSmoke;
}
.add_link_twitter:hover
{
color: #A19486;
border: 2px solid transparent;
border-color: #C2B1A2;
border-radius: 4px;
background-color: white;
padding: 7px;
padding-top: 5px;
padding-bottom: 5px;
}
Instead of
$(postTwitter).click(function(e) {
$(this).addClass(activePostTwitter);
});
use
$(postTwitter).click(function(e) {
$(this).toggleClass(activePostTwitter);
});
EDIT:
The event triggers twice per click, probably because of event propagation. To work around this, assign the handler to the input and have it change the class of its parent:
$(postTwitter + " input").click(function(e) {
$(this).parent().toggleClass(activePostTwitter);
});
Confirm jsfiddle: http://jsfiddle.net/bpfqB/
This should work for both your questions:
$(function() {
"use strict";
var $postTwitter = $("label.add_link_twitter");
$postTwitter.find("input:checkbox").click(function() {
$(this).parent().toggleClass("active");
if($("input.publish_to_twitter").is(":checked")) {
$(this).parent().removeClass("hover");
}
});
$postTwitter.hover(
function() {
if($("input.publish_to_twitter").is(":checked")) {
return;
}
$(this).addClass("hover");
},
function() {
$(this).removeClass("hover");
});
});
You need to make some changes to your CSS though, you have to do the hovering with jQuery (skip the CSS hover).
DEMO