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();
}
});
Related
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'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);
})
I am working on a to-do list. Whenever the user enters a new task, it is displayed as a list item. Within the list item I am attempting to position a button element, so that when the user clicks on it, the item can shift into another list - "completed tasks".
I am having a bit of trouble poistioning the button element within the list to suit my requirements. I am trying to posistion it equivalant to "right-aligned", at the end of the list element, so that the button is after the text has finished of the given to-do task (I hope that makes sense!)
HTML:
<div id="incomplete-tasks">
<h4>INCOMPLETE TASKS</h4>
<ul id="task-to-do">
</ul>
</div>
CSS:
ul {
list-style: none;
}
ul li {
position: relative;
margin: auto;
width: 80%;
padding: 10px;
margin-bottom: 10px;
color: white;
border: 1px solid white;
border-radius: 3px;
background-color: #6363B6;
}
li button {
display: block;
width: auto;
padding: 1%;
position: absolute;
margin-left: 80%;
}
JS:
document.getElementById("add").addEventListener("click", function () {
var taskinput = document.getElementById("task").value;
if (taskinput) {
var tasktext = document.createTextNode(taskinput);
var list = document.createElement("li");
list.appendChild(tasktext);
var button = document.createElement("button");
button.innerHTML = "completed";
list.appendChild(button);
document.getElementById("task-to-do").appendChild(list);
document.getElementById("task").value ="";
} else {
alert("Please enter a task");
}
});
Try using this, It may help you:
li button {
display: block;
width: auto;
padding: 1%;
clear:both;
float:right;
}
Try this float property It may help you.
It doesn't let me to complete slider in my mind. And I also want to note that not use JQuery. Only with JavaScript. I tried many ways, but it didn't take effect.
var ul = document.querySelector('ul');
var dots = [];
for (var i = 0; i < 5; i++) {
var dot = document.createElement('li');
dots.push(dot);
ul.appendChild(dot);
}
dots[2].setAttribute('class', 'active');
li {
width: 20px;
height: 20px;
background-color: lightgrey;
text-decoration: none;
display: inline-block;
border-radius: 100%;
margin-right: 15px;
}
.active {
background-color: grey;
}
<ul></ul>
Here is JSFiddle link: https://jsfiddle.net/heybetov1998/camuyve2/4/
Here's an event handler that will do it for you.
function handler(event) {
for (const li of document.querySelectorAll("li.active")) {
li.classList.remove("active");
}
event.currentTarget.classList.add("active");
}
I'll leave it to you to set up the event handler and figure out about legacy browser support if you wish.
You can hook up an event handler on the dots. Within the event handler, this will refer to the element on which you attached the handler. Then you can remove the class from all the others:
var ul = document.querySelector('ul');
var dots = [];
for (var i = 0; i < 5; i++) {
var dot = document.createElement('li');
dot.addEventListener("click", clickHandler); // ** Hook up the handler
dots.push(dot);
ul.appendChild(dot);
}
dots[2].setAttribute('class', 'active');
// Here's the handler
function clickHandler() {
var dots = document.querySelectorAll("li");
for (var n = 0; n < dots.length; ++n) {
if (dots[n] !== this) {
dots[n].className = "";
}
}
this.className = "active";
}
li {
width: 20px;
height: 20px;
background-color: lightgrey;
text-decoration: none;
display: inline-block;
border-radius: 100%;
margin-right: 15px;
}
.active {
background-color: grey;
}
<ul></ul>
If you need to support obsolete browsers that don't have addEventListener, this answer has a function for doing that.
That's the minimal-changes version. But there are several changes I'd look at making:
Don't use setAttribute to set the class attribute. It fails on very old versions of IE, but more importantly, it completely replaces the classes on the element. Look at classList.
The example above hooks up an event handler to each dot, but if you have a lot of dots, or you add/remove them dynamically, you might be better off using event delegation by hooking up the handler on the container of the dots and then using e.target to determine which dot was clicked.
Use addEventListener to assign a listener to the click event of the li element you're adding.
Within the listener function you can remove the active class for all li elements and then add it only to the one clicked using this to refer to it.
Use the classList property of the element to add/remove the active class from the element.
var ul = document.querySelector('ul');
var dots = [];
function dotClicked() {
dots.forEach(function(li) {
li.classList.remove("active");
});
this.classList.add("active");
}
for (var i = 0; i < 5; i++) {
var dot = document.createElement('li');
dot.addEventListener("click", dotClicked);
dots.push(dot);
ul.appendChild(dot);
}
li {
width: 20px;
height: 20px;
background-color: lightgrey;
text-decoration: none;
display: inline-block;
border-radius: 100%;
margin-right: 15px;
}
.active {
background-color: grey;
}
<ul></ul>
Firstly I don't want to edit the html but use JavaScript/jQuery to achieve this.
One input is selected(checked) by default and in this example 'Express Shipping'. Then all of the html is cloned / copied from within the parent which is < li > that is holding the input. I don't need the < li > copied just the input within it.
This is then placed inside the #dumpinfohere < div >.
I can manage up to this point, however I want it so when I then toggle between both radio buttons, or if I add additional radio buttons then the complete html of that checked input plus it's < li > replaced the #dumpinfohere section.
This is the code I've done to try and achieve this:
JSFiddle. You can view what I've done here.
HTML
<ul id="shipping_method">
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_betrs_shipping_12-1" value="betrs_shipping_12-1" class="shipping_method "> Free Shipping
</li>
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_betrs_shipping_12-2" value="betrs_shipping_12-2" class="shipping_method" checked="checked"> Express Shipping
</li>
</ul>
Javascript
jQuery(document).ready(function($){
var changeShip = function() {
$('#shipping_method input:checked')
.parent()
.clone()
.appendTo(".woocommerce-billing-fields #dumpinfohere");
};
$('#shipping_method input').change(changeShip);
changeShip();
});
CSS
#shipping_method {
float: left; width: 100%;
list-style: none;
padding: 0px;
}
#shipping_method li {
/* display: none; */
float: left; width: 100%;
background: #ccc;
padding: 5px 10px;
}
#shipping_method .red { background: red; }
.woocommerce-billing-fields {
background: #000; color: #fff;
width: 100%; height: 300px;
padding: 5px 10px;
float: left;
}
.woocommerce-billing-fields li { padding: 10px 0px; color: #e024a7; list-style: none; }
.woocommerce-billing-fields li input { display: none; }
However you can see if you toggle between the two radio buttons it doesn't change/replace the text but rather just adds to it?
You just need to add
$("#dumpinfohere").html('');
after
var changeShip = function() {
So your final code would be:
jQuery(document).ready(function($){
var changeShip = function() {
$("#dumpinfohere").html('');
$('#shipping_method input:checked')
.parent()
.clone()
.appendTo(".woocommerce-billing-fields #dumpinfohere");
};
$('#shipping_method input').change(changeShip);
changeShip();
});
Basically, you need to empty the container before appending any new text in it.