JavaScript classes and EventListeners - javascript

I'm trying to use JavaScript class architecture in combination with addEventListener such that I can define a series of class methods once, which will be applied to all appropriate HTML and/or CSS objects. Below is an example with ONLY one circle, which should toggles red or blue when clicked. Unfortunately, as is, the class does architecture is unable to properly communicate with the DOM.
I've chosen to write the script in-line just to explore the basic concept more easily rather than juggling around multiple files for purposes of question cohesion (I do realize that in actuality, I'd likely use separate HTML and JS files.)
I'd like to code as is to be edited such that the circle changes color when clicked. (Also, the code DID function when class architecture was not used. I can add that if it's useful.)
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
height: 50px;
width: 50px;
background-color: #555;
border-radius: 50%;
}
</style>
</head>
<body>
<h2>Circle CSS</h2>
<div class="circle"></div>
<script>
class GameObject{
constructor(shape){
this.shape = shape;
this.clicks = 0;
};
clickEvent(){
this.shape.addEventListener('click',function(){
this.clicks += 1
if (this.clicks % 2 == 1){
this.shape.style.backgroundColor = 'red';
}else{
this.shape.style.backgroundColor = 'blue';
}
})
};
};
let shape = document.querySelector('.circle')
let s = new GameObject(shape);
console.log(s);
</script>
</body>
</html>
Also, the following questions/answers went above my head, though they are related:
Javascript Class & EventListener
Dealing with Scope in Object methods containing 'this' keyword called by Event Listeners
Edit: I took advice from comment and added clickEvent() into this constructor. However, the following error was triggered:
Uncaught TypeError: Cannot read property 'style' of undefined
at HTMLDivElement.<anonymous> (circle.html:31)

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
height: 50px;
width: 50px;
background-color: #555;
border-radius: 50%;
}
</style>
</head>
<body>
<h2>Circle CSS</h2>
<div class="circle"></div>
<script>
class GameObject{
constructor(shape){
this.shape = shape;
this.clicks = 0;
this.clickEvent(); // call the method to add the listener
};
clickEvent(){
this.shape.addEventListener('click',function(){
this.clicks += 1
if (this.clicks % 2 == 1){
this.shape.style.backgroundColor = 'red';
}else{
this.shape.style.backgroundColor = 'blue';
}
}.bind(this)) // bind callback function to the the correct 'this' context
};
};
let shape = document.querySelector('.circle')
let s = new GameObject(shape);
console.log(s);
</script>
</body>
</html>

You never call the function clickEvent.
The handler of addEventListener uses its own context, to solve this you can either declare a variable let that = this and then use it inside of the handler, or you can use an arrow function in order to use the right context (instance of GameObject).
This approach uses an arrow function
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
height: 50px;
width: 50px;
background-color: #555;
border-radius: 50%;
}
</style>
</head>
<body>
<h2>Circle CSS</h2>
<div class="circle"></div>
<script>
class GameObject{
constructor(shape){
this.shape = shape;
this.clicks = 0;
};
clickEvent(){
this.shape.addEventListener('click',() => {
this.clicks += 1
if (this.clicks % 2 == 1){
this.shape.style.backgroundColor = 'red';
}else{
this.shape.style.backgroundColor = 'blue';
}
})
};
};
let shape = document.querySelector('.circle')
let s = new GameObject(shape);
s.clickEvent();
console.log(s);
</script>
</body>
</html>

Change the name clickEvent to handleEvent, and move the .addEventListener call outside the class.
Then instead of a function, pass your GameObject instance as the event handler, and it'll work.
You don't event need to pass the element to the constructor that way. It's available from the event object defined on the handleEvent method.
class GameObject {
constructor() {
this.clicks = 0;
};
// Implement the EventListener interface
handleEvent(event) {
const shape = event.currentTarget;
this.clicks += 1
shape.style.backgroundColor = this.clicks % 2 == 1 ? 'red' : 'blue';
console.log(this); // `this` is your class instance
};
};
let g = new GameObject();
document.querySelector('.circle')
.addEventListener("click", g); // use the object as the handler
console.log(g);
.circle {
height: 50px;
width: 50px;
background-color: #555;
border-radius: 50%;
}
<h2>Circle CSS</h2>
<div class="circle"></div>
What's happening here is that by naming the method handleEvent, you're causing your GameObject class to implement the EventListener interface. Therefore instances of that class are eligible to be used as an event handler instead of a function.

Related

How to use p5.js sound in instance mode

I'm trying to make a website with multiple p5 canvases on the same page, so after a lot of research, I came to the conclusion that the most adequate way to do that is by using instance mode on p5.
I spent the whole day trying to understand the instance mode, i even found a converter online that converts it for me, but I'm trying to do it all by my self using it just to check errors. The problem is that i can't find a way to use sound in my sketch using instance mode. the code i have is a lot more complex, but even trying just the basic it still does not work.
var s = function(p) {
let song;
p.preload = function() {
p.song = load('thunder.mp3')
}
p.setup = function() {
p.createCanvas(720, 200);
p.background(255, 0, 0);
p.song.loop();
};
};
var myp5 = new p5(s, 'c1');
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
<style> body {padding: 0;
margin: 0;
}
<meta charset="UTF-8">
</style>
</head>
<body>
<div id="c1"></div> <br>
<div id="c2"></div>
</body>
</html>
you can test it here: https://editor.p5js.org/jgsantos.dsn/sketches/rUWb6Nurt
This code works great in the global mode:
let song;
function preload() {
song = loadSound('thunder.mp3');
}
function setup() {
createCanvas(720, 200);
background(255,0,0);
song.loop();
}
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
You can test it here https://editor.p5js.org/jgsantos.dsn/sketches/_lQcDqOsp
It shows this error: "Uncaught ReferenceError: load is not defined (: line 9)"
What am I'm doing wrong?
Thanks in advance!
Please try to post the exact code you're running. Your question contains different code than the link you posted in the comments.
But taking a step back, here's how I would think about instance mode and libraries:
Instance mode means that the variables and functions that belong to a sketch are now referenced via a variable, in your case the p variable.
But variables and functions that belong to a library are still referenced directly, i.e. in "global mode".
In other words, you don't want to reference the load() (or is it loadSound()?) function using instance mode. You should still reference that function directly, since it's coming from a library instead of from a specific sketch.

Set backgroundImage in HTML with URL pulled from JSON data

var ourRequest = new XMLHttpRequest();
ourRequest.open('GET', 'https://spreadsheets.google.com/feeds/cells/1PzTMW6xH5cjLgvLn_LOq25XEbmrCw2MP9C9vvt4rhoM/1/public/full?alt=json')
ourRequest.onload = function() {
var ourData = JSON.parse(ourRequest.responseText);
var TEXT = ourData.feed.entry[2].gs$cell.inputValue;
var IMG = ourData.feed.entry[3].gs$cell.inputValue;
console.log(TEXT);
console.log(IMG);
document.getElementById('testoutput').innerHTML = TEXT;
document.getElementById('backgroundIMG').style.backgroundImage = "url('IMG')";
};
ourRequest.send();
html, body {
height: 100%;
margin: 0;
overflow: hidden; /* Hide scrollbars */
}
div {
text-align: left;
min-height: 100%;
font-family: motiva-sans, sans-serif;
font-weight: 800;
font-size: 15vw;
padding: 15px;
padding-top: 30px;
padding-left: 10%;
}
<html lang="en">
<head>
<link rel="stylesheet" href="https://use.typekit.net/vbl6jef.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
<meta charset="utf-8">
<title>Google Sheets - Werkend</title>
</head>
<body>
<div id="backgroundIMG"><div>
<div id="testoutput"></div>
</body>
</html>
I'm working on a HTML document that gets its styling from Google Sheets JSON data. So far it works for setting properties such as backgroundColor.
Right now i'm trying to set the backgroundImage and have set a variable that contains the url:
document.getElementById('backgroundIMG').style.backgroundImage = "url('IMG')";
When I console.log the variable it returns the url just fine. When I place the URL directly into the code above, it works. When I put the variable in the code (like shown above) it won't load the URL.
My limited knowledge stops me from searching the right terms on what is going on here. I have tried reading up on variables but no succes yet.
Can anyone point me in the right direction by either telling me what to read up on or by offering a sollution based on my code below?
It's not using the IMG variable, just IMG as a string, so try:
document.getElementById('backgroundIMG').style.backgroundImage = "url(" + IMG + ")";
Adding a variable to a string:
"Traditional" ways (plus sign is expands a string):
let s = "number: " + num;
let s = 'number: ' + num;
ES6 template literals:
let s = `number: ${num}`
Get more information on these links:
JS Strings
Template literals

Manipulating DOM inside custom function constructor

I wrote a function constructor with a prototype that is creating svg picture and inserting it into the web page. I have 2 questions though:
1 Is it all right to take part of the code inside of constructor that is being used in the process of creating instance and move it to the prototype object. As far as I see these prototype methods are used generally with already created instances, for example var a = [2,3,4]; a.reverse()
2 Is it fine to manipulate DOM-objects that way inside of function constructor? DOM objects are not js native objects, they are host objects outside of js-engine.They are similar to js objects but created by browser somewhere else
Below is the code:
function Sektor(selector, options) {
// Find a DOM object representing the HTML element with the supplied selector
// create a property called element
// This DOM object is assigned to the element property
this.element = document.querySelector(selector);
// Some default values in case nothing is supplied
const defaultOptions = {
size: 100,
circleColor: '#b3c9e0'
};
// Merge options with default ones
options = Object.assign(defaultOptions, options);
// Circle dimensions
options.center = options.size / 2;
options.radius = options.center;
// create a property called options
this.options = options;
// Get SVG
const svg = this.getCircle();
// SVG is injected into the DOM element with JavaScript
this.element.innerHTML = svg;
}
// Assigning a method to the constructor's prototype
// This method generates SVG
Sektor.prototype.getCircle = function() {
const options = this.options;
return `
<svg class='Sektor' viewBox='0 0 ${options.size} ${options.size}'>
<circle class='Sektor-circle' fill=${options.circleColor} cx='${options.center}' cy='${options.center}' r='${options.radius}' />
</svg>
`;
};
var sektor = new Sektor( '.example-one', { circleColor: '#7da385' } );
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Draw</title>
<style>
body {
margin: 0;
padding: 0;
}
.examples {
display: flex;
justify-content: space-between;
padding: 20px;
}
.example {
margin: 0px auto;
}
.Sektor {
width: 200px;
height: 200px;
background-color:#f2f2f2;
}
</style>
</head>
<body>
<div class='examples'>
<div class='example'>
<div class='example-one'></div>
</div>
</div>
<script src='sektor.js'></script>
</body>
</html>

Defining Position in Phaser

I'm making a very basic game in Javascript using the Phaser Directory. I have made games with Phaser many times before but this time I have come across an issue that is new to me and I've never had before. I've looked it up online and no one seems to have had this problem. In phaser it is telling me my "x" is not defined when I refer to x as a position. How would I go about defining x as a position or what error am I facing? I and currently using VS Code on mac with a Chrome browser and newest version of Phaser.
Thanks for the Help,
Murdoc
var game = new Phaser.Game(640, 360, Phaser.Auto);
var car1;
var GameState = {
preload: function(){
game.load.image("thecar1", "../assets/car_1.png");
//game.load.image("thecar2", "../assets/car_2.png");
//game.load.image("backgroundImg", "../assets/thebackground.png");
},
create: function(){
var car1 = game.add.sprite(200, 270, "thecar1");
//var car2 = game.add.sprite(200, 270, "thecar2");
/*game.input.keyboard.addKey(Phaser.Keyboard.W)
.onDown.add(car1Up);*/
game.input.keyboard.addKey(Phaser.Keyboard.A)
.onDown.add(car1Left);
/*game.input.keyboard.addKey(Phaser.Keyboard.S)
.onDown.add(car1Down);
game.input.keyboard.addKey(Phaser.Keyboard.D)
.onDown.add(car1Right);*/
},
update: function(){
}
};
function car1Left() {
car1.x = car1.x + 10;
}
game.state.add('GameState', GameState);
game.state.start('GameState');
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pixel Soccer</title>
<script src="phaser.js"></script>
<script src="main.js"></script>
<style>
body {
background: white;
margin: 0;
padding: 0;
}
body canvas {
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
</body>
</html>
You're declaring the same variable twice. You're dealing with what's called "variable shadowing". When you do var car1 = game.add.sprite(200, 270, "thecar1"); in the create() function, the scope changes to function-only and the variable with the sprite you assign to it is only available in the scope of that function. The error basically comes to tell you that var car1 that you declared outside of the function scope is undefined (which it is). Try doing just car1 = game.add.sprite(200, 270, "thecar1"); inside create() and it should be fine.

Drag and Drop Jquery function not working in site

I'm having a problem getting my code to work when it's incororated into a live site. The fiddle works just fine, but when I include the identical code in a webpage, I can't get the function to load/work at all.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Drag and Drop Assignment</title>
<!doctype html>
<link rel="stylesheet" href="styles/style1.css"/>
<style>
.drop{
float: left;
width: 350px;
height: 400px;
border: 3px solid blue;
background-image: url("http://debsiepalmer.com/images/tardis 2.jpg");
background-repeat: no-repeat;
background-size: cover;
}
#right{float: left;
width: 350px;
height: 400px;
border: 3px solid red;
}
</style>
<script src="draganddrop.js" ></script>
<script src="http://code.jquery.com/jquery-2.0.2.js" ></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js" ></script>
<script type="text/javascript">
$ (init);
function image(id, image1) {
this.id = id;
this.image1 = image1;
}
$('#deal').click(function () {dealAll(
dealCard(randomCard()));
});
$(function() {
$( "#draggable" ).draggable({ containment: "#left"});
});
function init() {
$('.drop').droppable( {
drop: handleDropEvent
} );
$("img").draggable();
}
// global variables
var cardsInDeck = new Array();
var numberOfCardsInDeck = 15;
cardsInDeck[0] = "Ace";
cardsInDeck[1] = "Grace";
cardsInDeck[2] = "Susan";
cardsInDeck[3] = "Ian";
cardsInDeck[4] = "Barbara";
cardsInDeck[5] = "Brigadier";
cardsInDeck[6] = "Romana I";
cardsInDeck[7] = "K9";
cardsInDeck[8] = "Tegan";
cardsInDeck[9] = "Jamie";
cardsInDeck[10] = "Sarah Jane";
cardsInDeck[11] = "Jo";
cardsInDeck[12] = "Romana II";
cardsInDeck[13] = "Yates";
cardsInDeck[14] = "Leela";
var cardsDealt = new Array();
function dealAll(){
var z=0;
for (z=0;z<5;z++) {
cardsDealt[z] = new Image(z,dealCard(randomCard()));
}
}
function dealCard(i) {
if (numberOfCardsInDeck == 0) return false;
var $img = new Image();
$img.src = "images/Companions/" + cardsInDeck[i] + ".jpg";
// Here I set the ID of the object
$img.id=cardsInDeck[i];
$img.class='drag';
document.body.appendChild($img);
$('#'+$img.id).draggable();
removeCard(i);
return $img;
}
// deal randomly - works
function randomCard() {
return Math.floor(Math.random() * numberOfCardsInDeck);
}
function removeCard(c)
{
for (j=c; j <= numberOfCardsInDeck - 2; j++)
{
cardsInDeck[j] = cardsInDeck[j+1];
}
numberOfCardsInDeck--;
numberOfCardsInDeck--;
numberOfCardsInDeck--;
}
function handleDropEvent( event, ui ) {
alert("Fantastic! You chose " + ui.draggable.attr("id") + " to be your companion.");
// Here I want the id of the dropped object
}
</script>
</head>
<body>
<div id="container" div style="width:750px; margin:0 auto;">
<div id="page_content" style="left: 0px; top: 0px; width: 750px" class="auto-style8">
<!--Begin Assignment 10 --->
<div id="left" class="drop">
<img id="tardis" ></img>
</div>
<input type="button" value="Get Companions" id="deal" />
<div id="content" style="left: 0px; top: 0px; width: 750px">
</div>
</div>
</div>
</body>
</html>
It's supposed to generate 5 images, one of which can be selected to be dropped onto the target and generate an alert with the id of the image being dropped. Like I said, it works just fine in the fiddle - and the code is identical on the web page, so I don't understand what I'm doing wrong.
fiddle: http://jsfiddle.net/reaglin/FUvT8/6/
I ordered some code...and for me it worked
// global variables
var cardsInDeck = [],
numberOfCardsInDeck = 5;
cardsInDeck[0] = "Ace";
cardsInDeck[1] = "Grace";
cardsInDeck[2] = "Susan";
cardsInDeck[3] = "Ian";
cardsInDeck[4] = "Barbara";
cardsInDeck[5] = "Brigadier";
cardsInDeck[6] = "Romana I";
cardsInDeck[7] = "K9";
cardsInDeck[8] = "Tegan";
cardsInDeck[9] = "Jamie";
cardsInDeck[10] = "Sarah Jane";
cardsInDeck[11] = "Jo";
cardsInDeck[12] = "Romana II";
cardsInDeck[13] = "Yates";
cardsInDeck[14] = "Leela";
//load "init" when document it's ready
$(document).on('ready',init);
function init() {
$( "#draggable" ).draggable({ containment: "#left"});
$('.drop').droppable( {drop: handleDropEvent});
}
$('#deal').click(function () {
dealAll();
});
$('#reset-pictures').click(function(){
$('img.drag').remove();
numberOfCardsInDeck = 5;
});
// deal 5 cards at once - works
function dealAll(){
// 5 cards max, no repeat cards
while(numberOfCardsInDeck){
var rand = randomCard();
dealCard(rand);
}
}
//deal cards - works
function dealCard(i) {
//create id, remove space id
var id_picture = (cardsInDeck[i] +'-'+i).replace(/\s/g, '');
//validate not exist image
if (!!$('img#'+id_picture).length) {
return;
}
var $img = $('<img/>', { src : "http://debsiepalmer.com/images/companions/" + cardsInDeck[i] + ".jpg", id : id_picture, class : 'drag', 'data-info': cardsInDeck[i]
})
$('body').append($img);
$('img.drag').draggable();
numberOfCardsInDeck--;
}
// deal randomly - works
function randomCard() {
return Math.floor(Math.random() * cardsInDeck.length);
}
// this is what to do when card drops in tardis
function handleDropEvent( event, ui ) {
alert(ui.draggable.attr("data-info"));
}
DEMO
JSFinddle
Are you linking to JQuery correctly in the live version? Sometimes when moving from a development area to a live system the references mess up, you can find out if a reference is working or not by viewing the source code and entering in the link into the URL.
This is how you fix it: Take all the javascript (from $ (init) to the alert()) and place it at the bottom of the loaded page, inside the body, after all the elements. This ensures that the javascript knows what named elements you are talking about.
I would like to emphasise that this is not a tip for writing good javascript. The problem arises because it's bad javascript to begin with. Well written pages do not rely on the position of the code within the page, rather the opposite in fact.
Postscript: I literally did the following: I came here after googling "drag and drop"; I didn't fully read the actual question; I went to the jfiddle; I copied all the code for my own purposes; I couldn't get it to work; I fixed it (just trial and error really); I fixed the bug which is still present even on the jfiddle page; and then I came back here to find out what the original query had been!
The other bug is the fact that you can't drag the images of "Romana I", "Romana II" and "Sarah Jane". As the code makes the array values the id of the image elements, maybe others can instantly spot what causes that problem.

Categories

Resources