I am able to dynamically create fieldsets on button click. Let say first time I click button it creates Definition 1 then Definition 2 then Definition 3 etc.
Each fieldset has X mark to remove the dynamically created fieldset if one was created by accident.
What I am trying to do is for example Definition 2 fieldset was deleted then Definition 3 one should say Definition 2.
What I need to know is when I click X mark in one fieldset, grab the value of the legend from the next fieldset and change it to the value of the one deleted.
Here is what my dynamic call looks like:
if($(".addDef").length > 0){
i++;
}else{
i = 2;
}
$(".definitionBlock").append("<fieldset><legend class='addDef'>Definition #"+ i +"</legend><div class='removeDef'><span>✖</span></div>
</fieldset>");
Thanks!
You can use .text(function(index, text){}) to replace digit portion of .textContent with index of element within collection
var n = 2;
$(".addDefBtn").on("click", function() {
if (!$(".addDef").length) {
n = 2;
}
$(".mvnDefinitionBlock").append("<fieldset class='addDef'><legend class='addDefTitle'>Definition #" + n++ + "</legend><div class='removeDef'><span>✖click</span></div></fieldset>");
});
$('body').on('click', '.removeDef', function() {
$(this).closest('fieldset').remove();
n = 2;
$(".addDef legend").text(function(index, text) {
return text.replace(/\d+/, n++);
});
});
.removeDef {
float: right;
margin-top: -20px;
font-size: 13px;
color: red;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
Add New Definition
<div class="mvnDefinitionBlock"></div>
Related
I'd like to change color of more than one box to purple upon clicking on it. With my current code below, only one box gets colored purple when clicking on it.
I've tried so many different ways to make it work in terms of upon you clicking on any number of boxes, the box should turn purple, but all my attempts have failed.
What am I doing wrong?
function createBoxesDynamically() {
var tileLength = Math.floor(Math.random() * 11);
console.log("tileLength " + tileLength);
var box = new Array(tileLength);
console.log("box" + box);
for (var i = 0; i < box.length; i++) {
box[i] = "box";
}
for (var j = 0; j < box.length; j++) {
var div = document.createElement("div");
div.id = "box";
document.body.appendChild(div);
}
var boxes = document.querySelector("[id^=box]");
boxes.addEventListener("click", function () {
boxes.style.backgroundColor = "purple";
});
}
createBoxesDynamically();
#box {
border: 1px solid;
width: 50px;
height: 50px;
background-color: green;
}
You can't have multiple elements with identical id values, that's why no matter which box you click, the first one is always affected, your .querySelector() call stops looking after finding the first match.
Instead, move the code that sets up the event handler inside the loop where the box is being created and just use this in the click callback to have the callback act upon the box that was clicked. No id necessary. And, because you won't be using ids, you don't need your array or the first loop.
In general, stay away from coding solutions that rely on ids. Yes, they seem precise and easy to use at first, but what you'll find (and you already are) is that they create very brittle solutions that don't scale very well. There are many other ways of referencing and styling elements besides an id.
You should also try to avoid inline styling of elements (setting up styles directly on the style property) as this usually leads to duplication of code and therefore makes the code more difficult to read and maintain. Use CSS classes for as much as you can.
function createBoxesDynamically() {
var tileLength = Math.floor(Math.random() * 11);
console.log("tileLength " + tileLength);
for (var j = 0; j < tileLength; j++) {
var div = document.createElement("div");
div.classList.add("box"); // Add the CSS class to the element
div.addEventListener("click", function () {
this.classList.add("clickColor");;
});
document.body.appendChild(div);
}
}
createBoxesDynamically();
/* Use Classes instead of IDs */
.box {
border: 1px solid;
width: 50px;
height: 50px;
background-color: green;
}
.clickColor { background-color: #800080; }
In this program, I'm able to add inputs with a button but I need to show the length of each input as it changes. I'm able to get the length using an EventListener, but I'm not sure how to change the text value for any newly created buttons.
On line 12, you can see that I'm able to change the value successfully on the first input but I'm using an html variable. If you look at my addCell() function, you'll see that I have an element as a child of each node to keep track of the length of each input. I need to access that element in my change() function so I can set the event.target.value.length to the corresponding nodes child element.
I've tried using this, setting var x = this and I've tried using the event.target properties to find the corresponding node and even innerHTML.
var i = 0;
var count = 1;
var length = 2;
var chars = 0;
document.addEventListener('input', function (evt) {
change(evt);
});
function change(elem) {
var check = document.getElementById("first");
if (event.target == check) {
document.getElementById("len").innerHTML = event.target.value.length;
return;
}
// Here's where I'm stuck
}
function removeCell() {
if (count <= 1) {
alert("Illegal operation, the police have been notified.")
return;
}
var elem = document.getElementById('main');
elem.removeChild(elem.lastChild);
count = count - 1;
length = length - 1;
}
function addCell() {
var node = document.createElement('div');
node.innerHTML += length;
var inp = document.createElement('INPUT');
var size = document.createElement('size');
inp.setAttribute("type", "text");
node.appendChild(inp);
node.appendChild(size);
document.getElementById('main').appendChild(node);
count += 1;
length += 1;
i += 1;
}
#area {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
#texts {
width: 220px;
height: 50px;
border-style: solid;
}
body {
background-color: grey;
}
<div id="area">
<form id="main">
<pre><b> input </b> length</pre>
<span id="list">
1<input type="text" id="first"> <var id="len"></var>
</span>
</form>
<br />
<button onclick="addCell()">Add Cell</button>
<button onclick="removeCell()">Remove Cell</button>
<button onclick="sort()">Sort</button>
</div>
Since I'm able to use alert() to show me the correct length of each newly created input each time it changes, I know there's a way to access the "size" element I created to update it using event.target.value.length
Your problem is that you use a "global" input event listener and your change() function is not programmed to handle multiple input fields because in it you are querying known element ids first and len.
If you want to go with a global listener you have to tell your change() function how to access the new input and corresponding target fields.
An easier way is that you modify your addCell() function and attach an event listener to the input field that you are creating instead of using a global one. Thereby each input field holds its own event listener. Since both the input field and your size element, which displays the length of the input value, are created in the same scope you can use easily write the length to the corresponding size element.
inp.addEventListener('input', function(){
size.innerText = inp.value.length;
});
If you want this to work with your provided HTML you need to remove your first input field and call addCell() manually so that your initial input gets rendered.
Your code should then look like this (note: I set var count = 0; and var length = 1;):
var i = 0;
var count = 0;
var length = 1;
var chars = 0;
function removeCell() {
if (count <= 1) {
alert("Illegal operation, the police have been notified.")
return;
}
var elem = document.getElementById('main');
elem.removeChild(elem.lastChild);
count = count - 1;
length = length - 1;
}
function addCell() {
var node = document.createElement('div');
node.innerHTML += length;
var inp = document.createElement('INPUT');
var size = document.createElement('size');
inp.setAttribute("type", "text");
inp.addEventListener('input', function(){
size.innerText = inp.value.length;
});
node.appendChild(inp);
node.appendChild(size);
document.getElementById('main').appendChild(node);
count += 1;
length += 1;
i += 1;
}
addCell();
#area {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
#texts {
width: 220px;
height: 50px;
border-style: solid;
}
body {
background-color: grey;
}
<div id="area">
<form id="main">
<pre><b> input </b> length</pre>
<span id="list"></span>
</form>
<br />
<button onclick="addCell()">Add Cell</button>
<button onclick="removeCell()">Remove Cell</button>
<button onclick="sort()">Sort</button>
</div>
If HTML layout is planned out and is consistent you can use [name] attribute for form controls and .class or even just the tagName. Use of #id when dealing with multiple tags is difficult and unnecessary. Just in case if you weren't aware of this critical rule: #ids must be unique there cannot be any duplicate #ids on the same page. Having duplicate #ids will break JavaScript/jQuery 90% of the time.
To accessing tags by .class, #id, [name], tagName, etc. use document.querySelector() and document.querySelectorAll() for multiple tags.
To access forms and form controls (input, output, select, etc) by [name] or #id use the HTMLFormElement and HTMLFormControlsCollection APIs.
.innerHTML is destructive as it overwrites everything within a tag. .insertAdjacentHTML() is non-destructive and can place an htmlString in 4 different positions in or around a tag.
Event handlers and event listeners work only on tags that were initially on the page as it was loaded. Any tags dynamically added afterwards cannot be registered to listen/handle events. You must delegate events by registering an ancestor tag that's been on the page since it was loaded. This was done with delRow() since the buttons are dynamically created on each row (changed it because one delete button that removes the last row isn't that useful. ex. 7 rows and you need to delete 4 rows just to get to the third row).
Here's a breakdown of: [...ui.len] ui references all form controls .len is all tags with the [name=len]. The brackets and spread operator converts the collection of len tags to an array.
There's no such thing as <size></size>. So document.createElement('size') is very wrong.
const main = document.forms.main;
main.oninput = count;
main.elements.add.onclick = addRow;
document.querySelector('tbody').onclick = delRow;
function count(e) {
const active = e.target;
const ui = e.currentTarget.elements;
const row = active.closest('tr');
const idx = [...row.parentElement.children].indexOf(row);
const length = [...ui.len][idx];
length.value = active.value.length;
return false;
}
function addRow(e) {
const tbody = document.querySelector('tbody');
let last = tbody.childElementCount+1;
tbody.insertAdjacentHTML('beforeend', `<tr><td data-idx='${last}'><input name='txt' type="text"></td><td><output name='len'>0</output></td><td><button class='del' type='button'>Delete</button></td>`);
return false;
}
function delRow(e) {
if (e.target.matches('.del')) {
const row = e.target.closest('tr');
let rows = [...row.parentElement.children];
let qty = rows.length;
let idx = rows.indexOf(row);
for (let i = idx; i < qty; i++) {
rows[i].querySelector('td').dataset.idx = i;
}
row.remove();
}
return false;
}
body {
background-color: grey;
}
#main {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
tbody tr td:first-of-type::before {
content: attr(data-idx)' ';
}
<form id="main">
<table>
<thead>
<tr>
<th class='txt'>input</th>
<th class='len'>length</th>
<th><button id='add' type='button'>Add</button></th>
</tr>
</thead>
<tbody>
<tr>
<td data-idx='1'><input name='txt' type="text"></td>
<td><output name='len'>0</output></td>
<td><button class='del' type='button'>Delete</button></td>
</tr>
</tbody>
</table>
<!--These are dummy nodes because of the
HTMLFormControlsCollection API ability to use id or name, there
must be at least 2 tags with the same name in order for it to
be considered iterable-->
<input name='txt' type='hidden'>
<input name='len' type='hidden'>
</form>
I have a button that triggers a script on a webpage. One instance works. When I try to add a second button/script, both buttons trigger the second script only. I know (think?) it's because the var I'm defining for the buttons are not unique to their individual scripts, but every way I attempt I break the whole thing.
button {
display: block;
cursor: pointer;
margin-left: 10px;}
button:after {
content: " (off)";
}
button.on:before {
content: "✓ ";
}
button.on:after {
content:" ";
}
.frac span {
-webkit-font-feature-settings: "frac" 1;
font-feature-settings: "frac" 1;
}
.onum span {
-webkit-font-feature-settings: "onum" 1;
font-feature-settings: "onum" 1;
}
Html:
<button name="frac" id="frac">Fractions</button>
<button name="onum" id="onum">Oldstyle Numbers</button>
This text is supposed change OT features when the buttons are pressed.
JS:
<script> var btn = document.getElementById("frac"),
body = document.getElementById("textA"),
activeClass = "frac";
btn.addEventListener("click", function(e){
e.preventDefault();
body.classList.toggle(activeClass);
btn.classList.toggle('on');
}); </script>
<!-- onum -->
<script> var btn = document.getElementById("onum"),
body = document.getElementById("textA"),
activeClass = "onum";
btn.addEventListener("click", function(f){
f.preventDefault();
body.classList.toggle(activeClass);
btn.classList.toggle('on');
}); </script>
The variance between the scripts/buttons are some of the changes from different things I've done, but I've gone mostly back to the beginning so it's simpler.
In javascript, every variable that you declare is inherently available across the entire page. So, putting them in separate tags will have no effect.
So essentially, your second variable btn is actually overwriting the first one. Rename the second variable to say, btn2.
Or, as an alternative, change the line
btn.classList.toggle('on')
to
this.classList.toggle('on')
this within the click handler will always point to the current button being clicked.
You can do it in fewer lines of code
// you create the array of buttons
let butons = Array.from(document.querySelectorAll("button")),
// you define the _body
_body = document.getElementById("textA")
// for every button in the buttons array (map is an iterator)
butons.map((btn) =>{
//you define the activeClass to be the name attribute of the button
let activeClass = btn.getAttribute("name");
// everytime you click the button
btn.addEventListener("click", (e) =>{
/*this was in your code. I don't know why you need it
e.preventDefault();*/
//you toggle the activeClass & the on class
_body.classList.toggle(activeClass);
btn.classList.toggle("on");
})
})
button {
display: block;
cursor: pointer;
margin-left: 10px;
}
button:after {
content: " (off)";
}
button.on:before {
content: "✓ ";
}
button.on:after {
content: " ";
}
/* I'm using color to visualize the change */
.frac span {
color: red;
}
.onum span {
color: green;
}
<button name="frac" id="frac">Fractions</button>
<button name="onum" id="onum">Oldstyle Numbers</button>
<p id="textA">The variance between the <span>scripts/buttons</span> are some of the changes from different things I've done, but I've gone mostly back to the beginning so it's simpler.</p>
I want to create a star rating in JavaScript in which default is 5 i will go down to 1 but i didn't understand how to fill up from 1 to 5.
Here is my code :-
$(".star").click(function(){
var starselect = $(this).attr("id");
for( var j = 5 ; j>starselect ; j--)
{
$("#"+j).removeClass("starchecked");
}
if( j < starselect+1 ){
$("#"+j).addClass("starchecked");
}
$(".review-star").attr("data-rating",starselect);
});
Per my comment, I would do this using css, but if you need to use js, then I would use a mixture of nextAll, prevAll and andSelf - see comments in code
var $stars = $(".star")
$stars.click(function() {
var $star = $(this);
if ($star.hasClass('checked')) {
// if current clicked star is checked
if ($star.next().hasClass('checked')) {
// check if next star is also checked
$star.nextAll().removeClass('checked'); // if next is then disable all following
} else {
$star.nextAll().andSelf().removeClass('checked'); // if not then disable self and all after (shouldn't need all after, but just in case)
}
$star.prevAll().addClass('checked'); // check all before
// if you just want to remove all stars on click of an already checked box, remove the above 2 lines and just ue the below:
// $stars.removeClass('checked');
} else {
$star.nextAll().removeClass('checked'); // remove checked from all following the clicked
$star.prevAll().andSelf().addClass('checked'); // add checked to this and all previous
}
var starselect = $stars.index($star) + 1; // get current star rating
$(".review-star").attr("data-rating", starselect);
console.log(starselect);
});
.star {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.checked {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="star">1</span>
<span class="star">2</span>
<span class="star">3</span>
<span class="star">4</span>
<span class="star">5</span>
I have a problem with a loop inside a loop.
By selecting the number and clicking the "Generate boxes" button it generates boxes with numbers from 1 to 49.
If you click the first time everything works fine.
But if you add more boxes it once again adds those 49 numbers to the already existing boxes. That's the problem. I only want to generate new boxes with numbers from 1 to 49.
This is the code:
function kasterl() {
$(".plunder").click(function() {
var wert = $( "#wieviel option:selected").text();
MyInt = parseInt(wert);
createKaesten(MyInt);
});
}
function createKaesten(zahl) {
var gesamt = $(".kasten").length+1;
var numberOf = $(".kasten").length;
for(var i=1; i<=zahl; i++) {
$(".rahmen").append("<div class='kasten nr"+ gesamt +"'><ul></ul></div>");
}
for(var n=1; n<=49; n++) {
$(".kasten ul").append("<li class='nummer'>" + n + "</li>");
}
}
And here you can test it: link for testing
As you have found, $(".kasten ul").append(...) will append to all elements that matched the ".kasten ul" selector.
You said you had a problem with a "loop within a loop", but your current code doesn't in fact nest the loops. Following is a solution that actually does nest the loops:
function createKaesten(zahl) {
var gesamt = $(".kasten").length + 1;
var numberOf = $(".kasten").length;
var newUL;
for (var i = 1; i <= zahl; i++) {
newUL = $("<ul></ul>");
for (var n = 1; n <= 5; n++) {
newUL.append("<li class='nummer'>" + n + "</li>");
}
$("<div class='kasten nr" + gesamt + "'></div>").append(newUL).appendTo(".rahmen");
}
}
$("button").click(function() {
createKaesten(3);
});
li { display: inline-block; padding: 0 10px; }
.kasten { border: thin black solid; margin: 2px; padding: 0px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>Test</button>
<div class="rahmen"></div>
The outer loop creates a new, empty UL, then the inner loop appends the new LI items to that UL, then we create a DIV, append the new UL to it, then append the DIV to the .rahmen" container.
(Note that for demo purposes each click of the button only adds 3 x 5 items, rather than something x 49 items, and I've styled the LIs to go across the page horizontally so that it's easier to see what's happening. Click "Run code snippet" to try it out.)
Note in the code that you use the function $().append()
Appending does just that - it appends new content to the end of existing content. Append will be executed every time you click generate.
Edit: I added a codepen to illustrate this. Hit each button multiple times to see the difference.