I like to handle events within a class. That is, I need to access my instance in the event handler and use data encapsulated within the instance. When an event is triggered, this becomes the HTMLElement as expected. I could not find a nice method to reach the instance associated with the element.
A global variable does not help if there are two instances of the class assigned to two buttons. In this sample code button B1 increases p2 rather than p1.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="increment.js"></script>
</head>
<body>
<p>p1: <span id="idP1">1</span></p>
<p>p2: <span id="idP2">2</span></p>
<button id="idB1">B1</button>
<button id="idB2">B2</button>
<script>
const p1 = document.querySelector("#idP1");
const p2 = document.querySelector("#idP2");
const b1 = document.querySelector("#idB1");
const b2 = document.querySelector("#idB2");
const i1 = new Increment(p1, b1, 100);
const i2 = new Increment(p2, b2, 200);
</script>
</body>
</html>
let increment: Increment;
class Increment {
target: HTMLElement;
data = 0;
constructor(target: HTMLElement, button: HTMLElement, initialValue: number) {
increment = this; // problem
this.target = target;
this.data = initialValue;
this.someDataUpdate();
button.addEventListener("click", this.eventHandler);
}
eventHandler(e: Event) {
console.log("e.target:", e.target);
console.log("this:", this);
console.log("increment.date:", increment.data);
increment.someDataUpdate();
// this.plusOne(); // produces error
}
someDataUpdate() {
this.data += 2;
this.target.innerHTML = (this.data).toString();
}
}
Related
I tried write simple vanilla JS memories game. I have a problem when i tried use query selector for divs which are created in Square class. Console.log returns empty nodelist or array. I suspect this function is done before divs are created. How Can I gain this elementy by query selector or another method ?
// CLASS SQUARE
export class Square {
constructor (number,type,field) {
this.number = number;
this.type = type;
this.field = field;
this.colors = ['red','red','blue','blue','orange','orange','green','green','gold','gold','pink','pink','cadetblue','cadetblue','purple','purple','beige','beige','cyan','cyan']
this.divs = [];
}
creator() {
for(let i = 0; i < this.number; i++) {
let create = document.createElement(this.type)
this.field.appendChild(create)
create.classList.add('square')
create.setAttribute('id','square')
this.divs.push(create);
console.log(this.divs)
}
}
addColor() {
this.divs.forEach((div)=>{
let chooseColor = Math.floor(Math.random()* this.colors.length);
div.style.backgroundColor = this.colors[chooseColor]
this.colors.splice(chooseColor,1)
})
}
addShadow() {
this.divs.forEach((div)=>{
const shadow = () => {
div.classList.add('shadow')
}
setTimeout(shadow,2000)
})
}
}
// CLASS SHADOW
export class Shadow {
constructor(shadowDivs) {
this.shadowDivs = shadowDivs;
}
revealDiv() { // THERE IS MY PROBLEM
this.shadowDivs.forEach((shadowDiv)=>{
shadowDiv.addEventListener('click',()=>{
console.log(this.shadowDivs)
shadowDiv.classList.remove('shadow')
})
})
}
}
//MAIN JS FILE
import { Square } from "./Square.js";
import { Shadow } from "./Shadow.js";
class Game {
elements = {
number: 20,
type: 'div',
field: document.getElementById('field'),
divs: document.querySelectorAll('.shadow') //THERE I TRY GAIN ELEMENTS
}
init() {
const squareCreator = new Square(this.elements.number,this.elements.type,this.elements.field);
const shadows = new Shadow(this.elements.divs) // AND THERE I CREATE NEW SHADOW
squareCreator.creator();
squareCreator.addColor();
squareCreator.addShadow();
// squareCreator.revealDiv()
shadows.revealDiv()
}
}
const game = new Game();
game.init()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="gameField" id='field'></div>
<script src="js/script.js" type="module"></script>
</body>
</html>
You can simply do this:
addEventListener("DOMContentLoaded", () => {
game.init();
});
Remove the timeout
addShadow() {
this.divs.forEach((div)=>{
div.classList.add('shadow')
})
}
Reorder the ini() and use the querySelector after the addShadow else there will be nothing to select.
init() {
const squareCreator = new Square(this.elements.number,this.elements.type,this.elements.field);
squareCreator.creator();
squareCreator.addColor();
squareCreator.addShadow();
const shadows = new Shadow(document.querySelectorAll('.shadow')) // AND THERE I CREATE NEW SHADOW
shadows.revealDiv()
}
I am trying to create a function that lets me create an on/off toggle button for setTimeout loops and I am having difficulty figuring out how to get this to work the way I want it. The only way I was able to get the loop to stop was by declaring a variable in the global scope that stores the Id value for the setTimeout loop but which defeats the purpose. This also causes an issue if I try to create a second toggle button for a different loop as all the buttons access the same variable. Help would be appreciated. Thank you
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="mainContainer">
<h1 id='pointA'>Point A</h1>
<p id='pointB'>Point B</p>
</div>
</body>
<script>
function createCustomToolbar(){
var myToolbarCustom = document.createElement('div');
var targetPage= document.querySelector('body');
var myCSS=myToolbarCustom.style;
myToolbarCustom.id='myToolbarCustom';
targetPage.append(myToolbarCustom);
targetPage.style.padding='transform 0.4s ease';
targetPage.style.padding='40px';
myCSS.position='fixed';
myCSS.top=0;
myCSS.left=0;
myCSS.width='100%';
myCSS.height='35px';
myCSS.border=0;
myCSS.background='#e3e3e3'
myCSS.zIndex=9999999;
}
var x;
function toggleCreator(bttnName,callback,targetEle){
var state=false;
var mybttn=document.createElement('button');
mybttn.className='myCustomTog';
mybttn.innerHTML=bttnName;
var bttnTarget=document.getElementById('myToolbarCustom');
bttnTarget.appendChild(mybttn)
mybttn.onclick = function() {
state = !state;
if (state) {
mybttn.innerHTML = bttnName + " ON";
x = callback(targetEle);
} else {
mybttn.innerHTML = bttnName + " OFF";
clearTimeout(x);
}}
}
createCustomToolbar();
toggleCreator("start",testToggle,document.getElementById('pointA'));
toggleCreator("start",testToggle,document.getElementById('pointB'));
var i=0;
function testToggle(myTarget){
x= setTimeout(function(){
myTarget.innerHTML=i;
i++;
testToggle(myTarget);
},1000)
}
</script>
</html>
You're going to have multiple buttons and multiple timeOuts (one for each).
I'd create an empty array to store the timeOuts and use x as an index for this array.
var x = 0;
var timeouts_arr = [];
Inside testToggle...
x++;
myTarget.setAttribute('timeout_index', x);
timeouts_arr[x] = setTimeout(function(){...
inside mybttn.onclick...
state = !state;
if (state) {
mybttn.innerHTML = bttnName + " ON";
callback(targetEle); // don't use the x variable here
} else {
mybttn.innerHTML = bttnName + " OFF";
var timeout_index = mybttn.getAttribute('timeout_index');
clearTimeout(timeout_arr[timeout_index]);
}}
I have a group of images in my HTML with the ID's "Hole#" ex: "Hole1", "Hole2" ... "HoleN". These IMG tags are loading a locally stored image. My goal is to print an alert when one of the images is clicked.
I found another StackOverflow question that I thought would answer my question. I've incorporated it into my code below. Unfortunately it did not achieve the desired effect.
//Dynamically creates images
for (let i = 1; i <= NUM_HOLES; i++) {
let HoleID = `"hole${i}"`;
let HoleIDPic = `"holePic${i}"`;
holesString +=
`<div id=`+ HoleID + `>
<img id=` + HoleIDPic + ` src="" />
</div>`
}
window.onload = function() {
document.getElementById("img[id|=hole]").onclick = function()
{
alert("Clicked");
};
};
HTML:
<section id="holes">
</section>
replacing the code "img[id|=hole]" with "hole1" does work however (for hole1), So I've concluded its my syntax the ID selection.
The whole idea of using similar ids on all images is the wrong approach.
Use a common CSS class instead. Then, to find out which image was clicked, use a single delegate listener and make use of the event object that is automatically passed to your click handler. I'm showing you an example with buttons instead of images:
const buttonDiv = document.querySelector('.buttons');
// lets add some buttons
for (let i = 0; i < 5; i++) {
let button = document.createElement('button');
button.type = 'button';
button.className = 'button';
button.textContent = 'Button Number ' + i;
buttonDiv.appendChild(button);
}
// now let's add a delegate click listener on the div containing the buttons
buttonDiv.addEventListener('click', function(event) {
// in any event listener, the event object has a `target` property, telling you which element the event was raised on
// this allows us to only react in the click listener if the clicked element meets certain conditions
if (event.target.matches('button.button'))
console.log('you clicked on ' + event.target.textContent);
})
.buttons {
background-color: #f0f0f0;
padding: 10px;
}
<div class="buttons"></div>
Try this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<section id="holes"></section>
<script>
loadImages(2);
function loadImages(NUM_HOLES){
const sectionHoles = document.getElementById('holes');
//Dynamically creates images
for (let i = 1; i <= NUM_HOLES; i++) {
let HoleID = `hole${i}`;
let HoleIDPic = `holePic${i}`;
let div = document.createElement('div');
let img = document.createElement('img');
div.id = HoleID;
img.id = HoleIDPic;
img.src = "someimage.png";
// put image element in div
div.appendChild(img);
// put div in section
sectionHoles.appendChild(div);
// adding event listener to the img element
document.getElementById(HoleIDPic).addEventListener('click', function(){
alert(HoleIDPic + 'clicked');
});
}
}
</script>
</body>
</html>
I have some input type=button which are created dynamically using JavaScript. Here I need to shift those clockwise while click on button. Here is my code:
<!-- Enter your HTML code here -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Buttons Grid</title>
</head>
<body>
<div id="btns" style="width:75%;">
</div>
<script>
for(var i=0;i<9;i++){
var index=i+1;
var element = document.createElement("input");
element.type = "button";
element.value = index;
element.id = "btn"+index;
element.setAttribute("style","width:30%;height:48px;font-size:24px");
var foo = document.getElementById("btns");
//Append the element in page (in span).
foo.appendChild(element);
}
document.getElementById("btn5").onclick=function(){
}
</script>
</body>
</html>
Here I need when user will click one button 5 the buttons present around button5 will move clockwise means button4 will shift to first place without changing its ids.
Something like this?
let container = document.querySelector("#btns");
container.insertBefore(container.lastElementChild, container.firstElementChild);
// or this ?
// container.appendChild(container.firstElementChild);
I suppose you don't need to create the buttons in js. You can create in html code. And just play with innertext of btns. My approach was like this;
btn5.addEventListener('click', () => {
const textofBtn = btn1.innerText;
btn1.innerText = btn4.innerText;
btn4.innerText = btn7.innerText;
btn7.innerText = btn8.innerText;
btn8.innerText = btn9.innerText;
btn9.innerText = btn6.innerText;
btn6.innerText = btn3.innerText;
btn3.innerText = btn2.innerText;
btn2.innerText = textofBtn;
});
but I saw another solution looking like more elegant here is you can check;
let nums=[1,2,3,6,9,8,7,4];
const ids=[1,2,3,6,9,8,7,4];
let btn5=document.getElementById("btn5");
btn5.onclick=function() {
nums.unshift(nums.pop());
for (i=0; i<=7; i++) {
document.getElementById("btn"+ids[i]).innerHTML=nums[i];
}
}
// writed by mark_russellbro1(hackerrank username)
I am trying to make a noughts and crosses game with each cell being an object of the Cell class, each class will contain multiple properties such as the div element that specific cell is tied to in HTML so that i can then change the style of that div if it is clicked.
However when trying to assign each Cells onclick attribute to a method in the class the class variables i had defined in the constructor were undefined. I could not work out of it was the scope, i have attempted using var self = this; in the constructor and using that instead of this for the methods. I have read other multiple questions similar to this however i cannot seem to fix the problem.
class Cell
{
constructor(cellDiv)
{
this.clicked = false;
this.cellDiv = cellDiv;
this.player = 7;
this.cellDiv.className = "cell";
}
output()
{
console.log(this.player);
console.log(this.clicked);
console.log(this.cellDiv);
}
}
var grid = [];
function buildBoard ()
{
for (var i = 0; i < 9; i ++)
{
grid.push(new Cell(document.createElement("div")));
document.getElementById("gameBoard")
.appendChild(grid[i].cellDiv);
grid[i].cellDiv.onclick = grid[i].output;
grid[i].output();
}
}
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8">
<title>Task 3</title>
<link rel = "stylesheet"; type = "text/css"; href = "stylesheet.css" />
</head>
<body>
<script type = "application/javascript"; src ="main.js"></script>
<script type = "application/javascript"; src ="cell.js"></script>
<div class = "gameBoard"; id = "gameBoard"></div>
<script type="text/javascript">
buildBoard();
</script>
</body>
</html>
At the moment this creates 9 cells, places them all on a board, assigns their onclick attribute and then outputs what i would expect to be outputted when i click the cell. However, when the output function is called in the for loop everything is outputted as expected but when i click each div i receive undefined variables.
<code>
main.js:38 7
main.js:39 false
main.js:38 undefined
main.js:39 undefined
</code>
Cheers!
You need to bind the output method to your class. There are various approaches, but the most compact way to do that is to assign it to an arrow function.
class Cell {
constructor () { ... }
output = () =>
{
console.log(this.player);
console.log(this.clicked);
console.log(this.cellDiv);
}
}
The problem is this in function is determined when it's called, not at the time of creation.
So when output function is called, this does not refer to the cell object anymore.
Solution: use arrow function which use this of surrounding scope at the time of creation