I'm trying to build a website with a very basic functionality. Essentially the website is supposed to contain various buttons and display images based on which series of buttons are clicked. Unfortunately my experience in web development is limited (to non-existent), but I've found some code that works to some extent here on stackoverflow. Building on that, I wanted to alter the code in a way that allows me to achieve the functionality I desire.
Here's how the website is supposed to look like:
As you can notice, the website contains various buttons from A-D and 0-9. The button clicks are logged in the field beneath and once a combination has been put in that matches the name of a file, the image will be displayed.
Here's the code:
$(document).ready(function() {
/**
* Select the buttons.
* The $display and $clickedButtons are just to output
* the values that are stored.
*/
const $buttons = $('.button');
const $display = $('#display');
const $clickedButtons = $('#clicked-buttons');
const $reset = $('#reset');
$reset.on('click', function() {
values = [];
$clickedButtons.text(values);
});
/**
* Array which tracks your clicked buttons.
* If a button is clicked, the value of that button
* should be added to this array. The combination
* of the values will then later represent the key.
*/
var values = [];
/**
* Listen for the click event on all the buttons.
* When clicked, get the value of that clicked button
* and add that to the values array.
* After that the clicked button values will be combined
* to form a single key and check if that key matches
* a combination. If there is a match the content should
* be anything other than undefined.
*/
$buttons.on('click', function() {
// This is the currently clicked button.
const $button = $(this);
// Get the value of the button.
const value = $button.val();
// If there already are 15 previously clicked buttons,
// then empty the array, so we can start a new combination.
if (values.length === 15) {
values = [];
}
// Now add the newly clicked value.
values.push(value);
// This will output the current values in the array.
$clickedButtons.text(values);
// Transform the array into a single string.
// This will be the key to select content.
// ["1", "2", "3"] becomes "123".
const key = values.join('');
// Check if key has a match in the combinations object.
$display.attr('src', 'output/' + key + '.png');
});
});
Now to my problem: The code requires that they combination of buttons is clicked exactly in the order of the name of the image. So for example, inputting A-B-C-1-2-3 will display ABC123.png. For my purpose however, I would need the code to display ABC123.png even if the inputs are 31B2AC or any other combination of these 6 inputs. I've looked into the option of 'sorting' but this creates a problem on the other hand, namely that some of the pictures are named like this e.g. D9B6C4.png so there is no applicable logic like alphabetic or numerical that would be required for a sorting algorithm to function. Every image in the folder has an unique however so when ABC123.png exists, BCA321 cannot.
What I would need the script to do is to parse through the pictures and find the unique picture that contains all letters & numbers that are entered regardless of their order. Is that even possible? How would I go about achieving this?
///////// EDIT ////////
My attempt to add a display, a tracker of clicked buttons and as well as a remove button:
So not really sure why literally nothing is working. Neither the inputs are displayed in the appropriate field nor is a picture being displayed ...
const $buttons = $('.button');
const $display = $('#display');
const $clickedButtons = $('#clicked-buttons');
const $removeButton = $('#remove-button');
const values = [];
var imgs = ["ABC123.png", "1A2B4C.png", "ABC132.png", "123ABC.png"];
function case_insensitive_comp(strA, strB) {
return strA.toLowerCase().localeCompare(strB.toLowerCase());
}
function reSortFiles() {
var all = {};
imgs.forEach(function(a) {
d = a.split(".");
img = d[0].split("");
img = sortStr(img);
img = img.join("");
all[img] ? all[img].push(a) : all[img] = [a];
});
return all;
}
function sortStr(str) {
return str.sort(case_insensitive_comp)
}
function tryCombination() {
// This will output the current values from the array.
$clickedButtons.text(values);
const key = values.join('');
allImages = reSortFiles()
console.log(allImages)
buttons = document.querySelectorAll("button")
clicked = "";
buttons.forEach(function(btn) {
btn.addEventListener("click", function(e) {
clicked += e.target.dataset.value;
clicked_s = sortStr(clicked.split("")).join("")
console.log(clicked, clicked_s)
img = allImages[clicked_s]
if (img) {
console.log("Found: ", img.join(","))
clicked="";
}
});
});
.container {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: 200px 1fr;
grid-gap: 1em;
border: 1px solid #d0d0d0;
background-color: #f7f7f7;
padding: 1em;
border-radius: 5px;
}
.buttons {
grid-area: 1 / 1 / 2 / 3;
}
#display {
grid-area: 2 / 1 / 3 / 2;
width: 200px;
height: 200px;
background-color: #d0d0d0;
border-radius: 5px;
}
#clicked-buttons {
grid-area: 2 / 2 / 3 / 3;
display: block;
background-color: #d0d0d0;
border-radius: 5px;
padding: 1em;
margin: 0;
}
#remove-button {
grid-area: 1 / 2 / 2 / 3;
}
.hidden {
opacity: 0;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="buttons">
<button class="button" id="1" value="1" >1</button>
<button class="button" id="2" value="2" >2</button>
<button class="button" id="3" value="3" >3</button>
<button class="button" id="4" value="4" >4</button>
<button class="button" id="5" value="5" >5</button>
<button class="button" id="6" value="6" >6</button>
</div>
<img id="display" class="hidden">
<button id="remove-button">Remove last input</button>
<code id="clicked-buttons"></code>
</div>
Here is my working solution, albeit its kind of hacky.
Have all of your images loaded in an array, just the file names.
Then loop through ALL of those files and create an object with the sorted name as the key and the file name as the value. That way no matter how the images are named, they will have a key like 999XXX.
Then it's just a matter of getting the buttons clicked and sorting its string until that string exists as a key.
var imgs = ["ABC123.png", "1A2B4C.png"];
function case_insensitive_comp(strA, strB) {
return strA.toLowerCase().localeCompare(strB.toLowerCase());
}
function reSortFiles() {
var all = {};
imgs.forEach(function(a) {
d = a.split(".");
img = d[0].split("");
img = sortStr(img);
img = img.join("");
all[img] = a;
});
return all;
}
function sortStr(str) {
return str.sort(case_insensitive_comp)
}
allImages = reSortFiles()
buttons = document.querySelectorAll("button")
clicked = "";
buttons.forEach(function(btn) {
btn.addEventListener("click", function(e) {
clicked += e.target.dataset.value;
clicked = sortStr(clicked.split("")).join("")
img = allImages[clicked]
if (img) {
console.log(img)
}
});
});
<button type="button" data-value="A">A</button>
<button type="button" data-value="B">B</button>
<button type="button" data-value="C">C</button>
<button type="button" data-value="1">1</button>
<button type="button" data-value="2">2</button>
<button type="button" data-value="3">3</button>
Version that allows multiple
var imgs = ["ABC123.png", "1A2B4C.png", "ABC132.png", "123ABC.png"];
function case_insensitive_comp(strA, strB) {
return strA.toLowerCase().localeCompare(strB.toLowerCase());
}
function reSortFiles() {
var all = {};
imgs.forEach(function(a) {
d = a.split(".");
img = d[0].split("");
img = sortStr(img);
img = img.join("");
all[img] ? all[img].push(a) : all[img] = [a];
});
return all;
}
function sortStr(str) {
return str.sort(case_insensitive_comp)
}
allImages = reSortFiles()
console.log(allImages)
buttons = document.querySelectorAll("button")
clicked = "";
buttons.forEach(function(btn) {
btn.addEventListener("click", function(e) {
clicked += e.target.dataset.value;
clicked_s = sortStr(clicked.split("")).join("")
console.log(clicked, clicked_s)
img = allImages[clicked_s]
if (img) {
console.log("Found: ", img.join(","))
clicked="";
}
});
});
<button type="button" data-value="A">A</button>
<button type="button" data-value="B">B</button>
<button type="button" data-value="C">C</button>
<button type="button" data-value="1">1</button>
<button type="button" data-value="2">2</button>
<button type="button" data-value="3">3</button>
<button type="button" data-value="4">4</button>
Related
<!DOCTYPE html>
<html>
<body>
<h2>What Can JavaScript Do?</h2>
<p>JavaScript can change HTML attribute values.</p>
<p>In this case JavaScript changes the value of the src (source) attribute of an image.</p>
<button onclick="document.getElementById('bulb0').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb0" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb0').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick="document.getElementById('bulb1').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb1" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb1').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick="document.getElementById('bulb2').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb2" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb2').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick = turnOn()>Turn All lights ON</button>
<button onclick = turnOff()>Turn All lights OFF</button>
<button onclick = turnRandomOn()>Turn Any light ON</button>
<script>
function turnOn() {
document.getElementById('bulb0').src='pic_bulbon.gif';
document.getElementById('bulb1').src='pic_bulbon.gif';
document.getElementById('bulb2').src='pic_bulbon.gif';
console.log('All lights were turned on');
}
function turnOff() {
document.getElementById('bulb0').src='pic_bulboff.gif';
document.getElementById('bulb1').src='pic_bulboff.gif';
document.getElementById('bulb2').src='pic_bulboff.gif';
console.log('All lights were turned off');
}
function turnRandomOn() {
let random = Math.floor(Math.random() * 3);
console.log(random);
turnOff();
if (random == 0) {
document.getElementById('bulb0').src='pic_bulbon.gif';
} else
if (random == 1) {
document.getElementById('bulb1').src='pic_bulbon.gif';
} else
if (random == 2) {
document.getElementById('bulb2').src='pic_bulbon.gif';
}
}
</script>
</body>
</html>
Hello there! I'm on my first steps learning JS. I practiced with an interesting self-invented exercise that I just finished, the task consist in creating N-lamps that you can switch On/Off with a button for each lamp, I also added the idea of turning all on/off with just one button and the same for setting on/off a random lamp.
The code I shared works fine but I know is not scalable at all since I had to put the lamps by hand. My question is the following:
How can I improve this code to select an HTML element defined by a recursive variable? What I'm trying to say is, for example, instead having a line with the object "document.getElementById('bulb0')", how could I change it for something like "document.getElementById('bulbN')"(where N is variable).
I know it is with an iterative loop but every time I try defining the argument it throws me an error. I tried these two ways:
getElementById('"' + bulb + N + '"').innerHTML
Defining the argument ('"' + bulb + N + '"') as a separated variable and later adding it with the same procedure
I will truly appreciate your help, even more if you can share the code
PS: The exercise can be found here https://www.w3schools.com/js/tryit.asp?filename=tryjs_intro_lightbulb
you can do it using template literal strings.
function turnOn() {
for(let i of [0,1,2]) {
document.getElementById(`bulb${i}`).src='pic_bulbon.gif';
}
console.log('All lights were turned on');
}
function turnOff() {
for(let i of [0,1,2]) {
document.getElementById(`bulb${i}`).src='pic_bulboff.gif';
}
console.log('All lights were turned off');
}
Now, what you are doing wrong?
getElementById('"' + bulb + N + '"').innerHTML
// '"' + bulb + N + '"' -> bulb is taken as variable, not like string.
// instead of this you can just use "bulb" + N
getElementById("bulb" + N).innerHTML
e.g.
let N = 0
console.log("bulb" + N)
// now here, bulb is taken as a variable
// this will give you an error. because bulb variable is not defined
// console.log('"' + bulb + N + '"') throw an error
// if you define bulb variable,
{
const bulb = 'bulb'
console.log('"' + bulb + N + '"')
console.log(bulb + N)
// you can see '"' + bulb + N + '"' !== bulb + N
}
if you need to know more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
There are several techniques you can use which I've commented on in the code but I'll mention here with some links to the documentation.
Use a loop as you suggested to build some HTML using template strings. Push each string into an array, and then join that array into one string and add it to the element that contains the lamps: lampsContainer.
Attach event listeners to the lampsContainer, and the buttons. When they're clicked they'll call a handler. With regards to lampsContainer we're using event delegation - attaching one listener to a container that catches events from its child elements as they "bubble up" the DOM.
If the lampsContainer is clicked we first check to see if the element that we clicked on is a button, and then we get that button's lamp element using previousElementSibling. We toggle the class on that lamp on/off.
The other buttons, as mentioned, have their own handlers. They either turn off (remove the on class) or turn on (add the on class) on all of the lamps. The random button simply toggles one random lamp from the lamp node list on/off.
// Initialise the lamp number,
// and an array to store the HTML string
const n = 4;
const html = [];
// Using a template string create a series of
// sections each containing a button, and a "lamp"
// element. Push each string into the array
for (let i = 0; i < n; i++) {
const str = `
<section class="lampContainer">
<div class="lamp"></div>
<button>lamp${i + 1}</button>
</section>
`;
html.push(str);
}
// Cache the elements for the lamps container,
// and the other buttons
const lampsContainer = document.querySelector('.lampsContainer');
const toggleAllOn = document.querySelector('.toggleAllOn');
const toggleAllOff = document.querySelector('.toggleAllOff');
const random = document.querySelector('.random');
// Add the HTML string to the lampsContainer element
lampsContainer.innerHTML = html.join('');
// Add some listeners to the lamps container, and the buttons
lampsContainer.addEventListener('click', handleLamps);
toggleAllOn.addEventListener('click', handleToggleAllOn);
toggleAllOff.addEventListener('click', handleToggleAllOff);
random.addEventListener('click', handleRandom);
// When an element in the lamps container is clicked
// check that it was a button, and then find its lamp
// element. Toggle the lamp `on` class.
function handleLamps(e) {
if (e.target.matches('button')) {
const lamp = e.target.previousElementSibling;
lamp.classList.toggle('on');
}
}
// Find all the lamps in the lamps container,
// and add an `on` class
function handleToggleAllOn() {
const lamps = lampsContainer.querySelectorAll('.lamp');
lamps.forEach(lamp => lamp.classList.add('on'));
}
// Find all the lamps in the lamps container,
// and add an `on` class
function handleToggleAllOff() {
const lamps = lampsContainer.querySelectorAll('.lamp');
lamps.forEach(lamp => lamp.classList.remove('on'));
}
// Find all the lamps in the lamps container,
// find a random lamp from that node list,
// and toggle its `on` class
function handleRandom() {
const lamps = lampsContainer.querySelectorAll('.lamp');
const rnd = Math.floor(Math.random() * lamps.length);
lamps[rnd].classList.toggle('on');
}
.lampContainer { display: flex; flex-direction: row; width:20%; align-items: center; }
.lamp { margin: 0.25em; border: 1px solid #dfdfdf; width: 20px; height: 20px; background-color: white; }
.on { background-color: yellow; }
button, .lamp { border-radius: 5px; }
button { text-transform: uppercase; }
button:hover { cursor: pointer; background-color: lightgreen; }
.buttonsContainer { margin-top: 1em; }
<section class="lampsContainer"></section>
<section class="buttonsContainer">
<button class="toggleAllOn">Toggle all on</button>
<button class="toggleAllOff">Toggle all off</button>
<button class="random">Toggle random on/off</button>
</section>
Use Document.createElement() to create element with any iterator
Then assign attributes. Then simply add that to DOM 👍
Extending it further you can even customize each element while creation base on your conditions
I'm trying to get this program to add a special character when the assigned button for the character is pressed. The problem is, I'm going to have a lot of functions. Can I somehow just make one function that I can use for all of the buttons?
//These are buttons
var aa = document.querySelector('#aa')
var oo = document.querySelector('#oo')
var uu = document.querySelector('#uu')
var khii = document.querySelector('#khii')
//This is the text box
var textBox = document.querySelector('#typeIn')
//Functions to add a character into the text box
function addAa() {
textBox.innerHTML += "ā";
}
function addOo() {
textBox.innerHTML += "ō";
}
function addUu() {
textBox.innerHTML += "ū";
}
function addKhii() {
textBox.innerHTML += "χ";
}
//Telling the buttons to call on the functions when clicked
aa.onclick = addAa
oo.onclick = addOo
uu.onclick = addUu
khii.onclick = addKhii
Additionally: why does this not work?
var aa = document.querySelector('#aa')
var textBox = document.querySelector('#text')
function addLetter(a) {
textBox.innerHTML += a
}
aa.onclick = addLetter("ā")
This just adds the character once into the text box. Clicking on the button then does nothing. Why does it do that?
Yes you can do it with only one function. Pass the character as parameter to the function. Like that:
version with addEventListener (prefered)
const btns = document.querySelectorAll('button');
const textBox = document.querySelector('#typeIn');
btns.forEach(b => {
b.addEventListener('click', e => {
textBox.innerHTML += e.target.getAttribute('data-char')
})
});
#typeIn {
margin:10px;
padding: 10px;
color: white;
min-height:40px;
background: gray;
}
<button data-char="aa">aa</button>
<button data-char="X">X</button>
<button data-char="ō">ō</button>
<button data-char="ū">ū</button>
<div id="typeIn"></div>
Generally try to avoid onclick events and use eventListener instead.
Version onclick Event
const textBox = document.querySelector('#typeIn');
function add(what) {
textBox.innerHTML += what;
}
#typeIn {
margin:10px;
padding: 10px;
color: white;
min-height:40px;
background: gray;
}
<button onclick="add('aa')">aa</button>
<button onclick="add('X')">X</button>
<button onclick="add('ō')">ō</button>
<button onclick="add('ū')">ū</button>
<div id="typeIn"></div>
You could do something like this:
Add (for example) data-value attributes to each of your buttons
<button data-value="A">A</button>
<button data-value="B">B</button>
<button data-value="C">C</button>
Grab all these buttons, and add "click" event listeners to each, like so:
document
.querySelectorAll('button') // Use appropriate class name here
.forEach(button =>
button
.addEventListener("click", (e) =>
console.log(e.target.dataset.value) // Do whatever you want here
)
)
Modify the listener function's body as per your needs
Here's a link to a JsFiddle that I've created for demo.
I'm trying to create a to-do list application. I'm using JS to dynamically create list elements on the web page when a user clicks on the submit button along with their user input.
Here's what I have so far:
const inputTXT = document.querySelector('.inputText'); //input element
const submitBUTT = document.querySelector('.submitButton'); //button element
const listITEMS = document.querySelector('.items'); //list element
function createListElement(inputString){
const newListItem = document.createElement("li");
const newEditButton = document.createElement("button");
const newDeleteButton = document.createElement("button");
const listText = document.createTextNode(inputString);
newListItem.appendChild(listText);
const editText = document.createTextNode("Edit");
newEditButton.appendChild(editText);
const deleteText = document.createTextNode("Delete");
newDeleteButton.appendChild(deleteText);
newListItem.appendChild(newEditButton);
newListItem.appendChild(newDeleteButton);
//assign class to each list element for line below:
newDeleteButton.className = "deleteCLASS";
newEditButton.className = "editCLASS";
//delete function:
var deleteButtonArray = document.querySelectorAll(".deleteCLASS");
for(var i=0; i < deleteButtonArray.length ; ++i){
deleteButtonArray[i].onclick = function(){
this.parentNode.remove();
}
}
return newListItem;
}
function addTask(){
listITEMS.classList.remove('hidden');
const ITEM = createListElement(inputTXT.value);
document.getElementsByClassName("items")[0].appendChild(ITEM);
inputTXT.value = ''; //Resets user input string
}
submitBUTT.addEventListener("click", addTask);
*{
margin: 0;
padding: 0;
}
.container{
width: 800px;
margin: 0 auto;
font-family: sans-serif;
}
.main-header{
background: #e7e7e7;
text-align: center;
margin: 15px;
padding: 20px;
}
.inputText{
margin-top: 20px;
}
h1{
font-size: 35px;
}
ul{
list-style: square;
margin-left: 275px;
}
.hidden{
display: none;
}
.itemLIST{
margin-bottom: 5px;
}
.editBUTT{
margin-left: 20px;
}
.deleteBUTT{
margin-left: 4px;
}
<!DOCTYPE html>
<head>
<title> "To-do List" </title>
<link href="style.css" rel="stylesheet"/>
<meta charset = "UTF-8"/>
</head>
<body>
<div class = "container">
<div class = "main-header">
<h1>My To-do List</h1>
<input type="text" placeholder = "Enter Item" class= "inputText" required>
<button class = "submitButton">Submit</button>
</div>
<ul class ="items hidden">
</ul>
</div>
<script src="script.js"></script>
</body>
</html>
This works for the most parts, but I cannot delete the last list element for some reason.
The only debugging I've done to figure out what is happening is print out the "deleteButtonArray" variable, which uses the keyword "querySelectorAll". From this, I found out that when the user hits the submit button, an empty NodeList printed. When the user hits the submit button a second time, only then do we get a NodeList with 1 element. When the user hits the submit button a third time, we get a NodeList with 2 elements.
It looked like the problem had something to do with querySelectorAll not properly updating in time when the user hits the submit button. I replaced it with getElementsByClassName, and still the same issue.
Now, I think the problem has something to do with the way I'm trying to implement the delete function within the createListElement function.
The way I think my code works:
Every time a user hits the submit button, a list element is created along with an array (actually a NodeList) that contains all the list elements present so far. This means that if I delete list elements, the array will update with the correct number of list elements.
And, it does update correctly, for the most parts. I just don't know why the array is empty when we first create an element. Shouldn't querySelectorAll or getElementsByClassName return a non-empty NodeList when the user first hits the submit button?
All right, so here's a solution I've tried and tested it, and it seems to be working.
I removed the delete item portion out of the createListElement function, made it its own function, and added an event listener to each delete button that's created, so it will run the now separate delete function when clicked.
No changes were made to the HTML or the CSS.
const inputTXT = document.querySelector('.inputText'); //input element
const submitBUTT = document.querySelector('.submitButton'); //button element
const listITEMS = document.querySelector('.items'); //list element
function createListElement(inputString){
const newListItem = document.createElement("li");
const newEditButton = document.createElement("button");
const newDeleteButton = document.createElement("button");
const listText = document.createTextNode(inputString);
newListItem.appendChild(listText);
const editText = document.createTextNode("Edit");
newEditButton.appendChild(editText);
const deleteText = document.createTextNode("Delete");
newDeleteButton.appendChild(deleteText);
newListItem.appendChild(newEditButton);
newListItem.appendChild(newDeleteButton);
//Here is the new addEventListener
newDeleteButton.addEventListener("click", deleteStuff);
//assign class to each list element for line below:
newDeleteButton.className = "deleteCLASS";
newEditButton.className = "editCLASS";
return newListItem;
}
//Here is the new delete function. The onclick function that was there before has been removed because, in this case, it's not needed.
function deleteStuff() {
//delete function:
var deleteButtonArray = document.querySelectorAll(".deleteCLASS");
for(var i=0; i < deleteButtonArray.length ; ++i){
this.parentNode.remove();
}
}
function addTask(){
listITEMS.classList.remove('hidden');
const ITEM = createListElement(inputTXT.value);
document.getElementsByClassName("items")[0].appendChild(ITEM);
inputTXT.value = ''; //Resets user input string
}
submitBUTT.addEventListener("click", addTask);
Please look at the fiddle
jsfiddle
I have multiple cards in a container. And I have a unique value "objectid".
I have "selactAll" button, If I click that button, every card should get selected and save button is enable. That time I need to pass all cards in a parameter.
After "selectAll", if i click on any of the card, that particular card should get unselect. That time if I click save button, the remaining selecteds cards should go in a array format.
Third one is I can select single card on click inside on button. That time I need to pass the single card in a save parameter. How can I pass these datas in a Array format. Only selected Datas.
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', function($scope) {
$scope.unselectBtn = true;
$scope.selectBtn = false;
$scope.saveBtn = true;
$scope.divs = [1, 2, 3]; // for eample
$scope.containerSelected = $scope.divs.map(val => false);
$scope.buttonVisible = $scope.divs.map(val => true);
$scope.toggleButton = function(e, i) {
console.log('toggleButton')
$scope.containerSelected[i] = true;
if($scope.containerSelected[i] = true){
$scope.saveBtn = false;
}
$scope.buttonVisible[i] = false;
e.stopPropagation();
};
$scope.toggleContainer = function(i) {
if ($scope.containerSelected[i]) {
$scope.containerSelected[i] = false;
$scope.buttonVisible[i] = true;
}
};
$scope.toggleAllButton = function() {
$scope.unselectBtn = false;
$scope.selectBtn = true;
$scope.saveBtn = false;
$scope.containerSelected = $scope.containerSelected.map(val => true);
$scope.buttonVisible = $scope.buttonVisible.map(val => false);
console.log($scope.containerSelected)
}
$scope.toggleUnselectAllBtn = function(){
$scope.unselectBtn = true;
$scope.selectBtn = false;
$scope.saveBtn = true;
$scope.containerSelected = $scope.containerSelected.map(val => true);
$scope.buttonVisible = $scope.buttonVisible.map(val => false);
console.log($scope.containerSelected)
}
$scope.saveData = function(data){
console.log(data);
}
});
#myApp {
width: 100%;
display: -webkit-inline-box;
border: 1px solid black;
margin-top: 55px;
}
.container {
width: 10%;
height: 100px;
border: 1px solid black;
margin: 10%;
}
button.btn.btn-primary {
margin-top: 25%;
margin-left: 40%;
}
.selectedBorder {
border: 3px solid blue;
}
#selectALL {
margin-top: 20px;
}
<div title="Angular Scope" ng-app ng-controller="MainCtrl">
<button id="selectALL" ng-hide="selectBtn" ng-click="toggleAllButton()">Select All</button>
<button id="unselectALL" ng-hide="unselectBtn" ng-click="toggleUnselectAllBtn()">UnSelect All</button>
<button id="save" ng-hide="saveBtn" ng-click="saveData(div)">Save</button>
<div id="myApp">
<div class="container" ng-repeat="div in divs track by $index" ng-class="{ 'selectedBorder': containerSelected[$index] }" ng-click="toggleContainer($index)">
<button id="okBtn" class="btn btn-primary" ng-click="toggleButton($event, $index)" ng-show="buttonVisible[$index]">ok</button>
</div>
</div>
</div>
It looks like the position of the value in the $scope.containerSelected array corresponds to the ID of the div.
For example, if $scope.containerSelected is [true, false, false], then the first div is selected, but the remaining two are not.
With this assumption, you could use the reduce method to create an array that gives the position of all true elements, which would correspond to the div. If the div IDs start at 1, just add 1 to the values in the array.
For example:
let dataToSend = $scope.containerSelected.reduce(function(a, e, i) {
if (e === true)
a.push(i);
// or, if they start at 1, use a.push(i+1)
return a;
}, []);
Updated fiddle: https://jsfiddle.net/jmg157/pfzg4t1n/
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>