0

I'm trying to bind divs with an object such that the divs have methods that can be acted upon. In particular I want when a user to clicks on a a div, for it to start moving across the screen, then if you click another div, it too starts moving.

For some reason my this.element inside the moveCarForward method function is undefined.

function Car(element) {
  this.element = element;
  this.element.addEventListener("click", function() {
    this.style.border = "3px solid hotpink";
    startDrive();
  }, false);

  startDrive = function() {
    this.carDrivingInterval = setInterval(moveCarForward, 3000);
  }

  moveCarForward = function() {
    this.element.style.x = this.element.offsetLeft + 10 + "px";
  }
}

var arrayCarsElements = document.getElementsByClassName("car");
var arrCar = [];

for (var i = 0; i < arrayCarsElements.length; i++) {
  arrCar[i] = new Car(arrayCarsElements[i]);
}
<div class="car" id="tesla" style="width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;"></div>
CertainPerformance
  • 260,466
  • 31
  • 181
  • 209
Nathan Leggatt
  • 196
  • 1
  • 13
  • 4
    Possible duplicate of [How does the "this" keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Vasyl Moskalov Jun 25 '18 at 03:57
  • 2
    `moveCarForward` is a global variable, not a method. Consider using `"use strict";`. – melpomene Jun 25 '18 at 03:58
  • this inside the moveCarForward method will be the window. that's why it's undefined.use ES6 arrow functions – Aravind S Jun 25 '18 at 03:59
  • to continue from @melpomene's comment, you can make moveCarForward into a method by defining it as `this.moveCarForward` –  Jun 25 '18 at 04:15

4 Answers4

1

How does the "this" keyword work? explains how this works. I recommend checking it out.

Interacting with your Object while retaining its reference would necessary in this scenario. Defining this (new Object()) and referencing back to the Object within your functions will do the trick.

var Car = function(element) {
    var car = this;
    
    car.element = element;
    car.element.addEventListener("click", function() {
        car.element.style.border = "3px solid hotpink";
        car.startDrive();
    }, false);


    car.moveCarForward = function() {
        car.element.style.marginLeft = car.element.offsetLeft + 10 + "px";
    }

    car.startDrive = function() {
        car.carDrivingInterval = setInterval(car.moveCarForward, 3000);
    }

    return car;
}

var arrayCarsElements = document.getElementsByClassName("car");
var arrCar = [];

for (var i = 0; i < arrayCarsElements.length; i++) {
    arrCar[i] = new Car(arrayCarsElements[i]);
}
<div class="car" id="tesla" style="width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;"></div>
<div class="car" id="fiat" style="width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;"></div>
Aleksander Azizi
  • 9,595
  • 8
  • 57
  • 86
  • Thanks! I think through all my messing around, and late night, I failed to notice that I was using "this" referring to the global space rather than the specific object. My CSS etc wasn't hardened yet, I was just trying to get the function to loop, I'll correct the rest now. Also, another user commented about prototyping the methods, YES! I will do that, I was just getting the code to MVP and then I'll refine. – Nathan Leggatt Jun 25 '18 at 16:35
0

You have a handful of things going on here. First, as mentioned in the comments, you need to take control of this a little better. Arrow functions (() => {}) can help because they bind this lexically. You can also bind() explicitly. You need to do this when using event listeners and timers because the listener calls the function, which changes what this means. The following snippet shows both.

Putting the functions on the Car prototype makes it easy to manage this too.

Also, I think your css is off a bit, I changed the positioning to absolute and used left to move the box.

function Car(element) {
  this.element = element;
  this.element.addEventListener("click", () => { // use arrow function or bind()
    this.element.style.border = "3px solid hotpink";
    this.startDrive();
  }, false);
}

Car.prototype.startDrive = function() {
  this.carDrivingInterval = setInterval(this.moveCarForward.bind(this), 1000); // use bind or arrow function
}

Car.prototype.moveCarForward = function() {
  this.element.style.left = this.element.offsetLeft + 10 + "px";
}


var arrayCarsElements = document.getElementsByClassName("car");
var arrCar = [];

for (var i = 0; i < arrayCarsElements.length; i++) {
  arrCar[i] = new Car(arrayCarsElements[i]);
}
<div class="car" id="tesla" style="position:absolute; left: 10px; width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;"></div>
<div class="car" id="ford" style="position:absolute; top: 75px; left: 10px; width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;"></div>

There's probably still more to do (like what happens when you click a bit twice), but hopefully this will get you started.

Mark
  • 74,559
  • 4
  • 81
  • 117
0

Here is the correct code. function Car(element) - if you print the "element" in console, it will give two divs. When click on one div, you have to send the current div parameter through startDrive(element).

function Car(element) {
  element.addEventListener("click", function() {
    this.style.border = "3px solid hotpink";
    startDrive(element);
  }, false);

  startDrive = function(element) {
    carDrivingInterval = setInterval(function() { moveCarForward(element); }, 3000);
  }

  moveCarForward = function(element) {
    element.style.left = element.offsetLeft + 10 + "px";
  }
}

var arrayCarsElements = document.getElementsByClassName("car");
var arrCar = [];

for (var i = 0; i < arrayCarsElements.length; i++) {
  arrCar[i] = new Car(arrayCarsElements[i]);
}
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>javascript scope with object method</title>
</head>

<body>
<div class="car" id="tesla" style="position:absolute;width: 50px; height: 50px;top:10px; left: 10px; background: #ccc; margin-bottom: 10px;"></div>
<div class="car" id="bmw" style="position:absolute;width: 50px; height: 50px;top:100px; left: 10px; background: #ccc; margin-bottom: 10px;"></div>
</body>
</html>
Piyali
  • 441
  • 2
  • 11
-1

You can use an object to define your functionality and return that object so that you can have the functionality available from outside Car function too (example if you want to stop car from outside). see my following code, it has start/stop functionalities. just run the snippet and see.

function Car(element) {
  var car = {
    element: element,
    running: false,
    start: function() {
      this.carDrivingInterval = setInterval(this.moveCarForward.bind(this), 1000);
      this.running = true;
    },
    stop: function(){
      clearInterval(this.carDrivingInterval);
      this.running = false;
    },
    moveCarForward: function() {
      this.element.style.left = this.element.offsetLeft + 10 + "px";
    }
  };
  
  element.addEventListener("click", function(event) {
    if(!car.running){
      this.style.border = "3px solid hotpink";
      car.start();
    }else{
      this.style.border = "3px solid red";
      car.stop();
    }
  });
  
  return car;
}
    
var arrayCarsElements = document.getElementsByClassName("car");
var arrCar = [];

for (var i = 0; i < arrayCarsElements.length; i++) {
  arrCar[i] = new Car(arrayCarsElements[i]);
}
<div class="car" id="tesla1" style="position: absolute; width: 50px; height: 50px; background: #ccc; margin-bottom: 10px;">Car 1</div>
<div class="car" id="tesla2" style="position: absolute; width: 50px; height: 50px; background: #eee; margin-bottom: 10px; left: 50px;">Car 2</div>

as i am using object to define the functionalities, now i can use this with it perfectly.

Inus Saha
  • 1,795
  • 9
  • 17