How can I have an eventListener inside a class that also has access to the class data?

Hello. Hope I am posting this in the right place, sorry if it is not.

I have a “Piece” class for a board game I am making that looks like this:

require "globals" local Piece = {} local Piece\_mt = { \_\_index = Piece} function Piece.New(type, img, gridPosX, gridPosY) local newPiece = {} newPiece.gridPosX = gridPosX; newPiece.gridPosY = gridPosY; newPiece.img = display.newImage(img, myGrid[gridPosX][gridPosY].x, myGrid[gridPosX][gridPosY].y); newPiece.thisPieceSelected = false; newPiece.availableMoves = {}; newPiece.type = type; myGrid[gridPosX][gridPosY].taken = type; return setmetatable( newPiece, Piece\_mt ) end

And (in the same file) I have class functions which interact with the data like for instance:

function Piece:getType() return self.type; end

The main problem is with adding an event listener to the “img”. I currently have a function called touch and another function called listen to add the listener like so:

function Piece:touch ( event ) --do stuff like calculating moves end function Piece:listen() self.img:addEventListener("touch", Piece); end

The problem is that while this works to some extent (program enters the Piece:touch function when I click the image) I can not access any of the data in the class inside Piece:touch. If i try to access for instance “self.type” it says it is a nil value. This works fine in other functions, like Piece:getType, but not in Piece:touch.

It used to work fine when I had both the Piece:touch function and the functions it used, as local functions inside the constructor, but I am now adding an AI that needs access to these functions, so they have to be outside the constructor (unless there is some way to call local functions inside the constructor from outside it, I could not find any way to do this). Does anyone know how I can access the data inside the class, while still having the listener function outside the constructor?

Another minor issue is that I can not figure out how to add the listener without using a separate function. As you can see the listener is now added through the Piece:listen function, is there any way to do this without a function call? I tried placing it right before “return Piece” in the class but got an error that self is nil.

Thanks in advance for any help

I encountered this same issue some time ago and it took me a while to understand. Basically what happens is, when you do self.img:addEventListener, you are adding the touch function to the self.img display object. So now, when the touch function gets activated, self no longer refers to Piece, but to self.img. As you can see, you set the type property on the Piece class, but not on self.img which is why you get the nil error.

To get around this you should wrap Piece:touch in an anonymous function like so

function Piece:listen() local piece = self self.img.touch = function(self, event) --inside this function, self refers to self.img, so we store Piece in a local --variable above so that we can reference it in here piece:touch(event) end self.img:addEventListener("touch") --the 2nd parameter is not necessary if touch is already a function of the object you're adding the listener to end

As for your second question, you want to automatically added the touch listener when you create a new Piece? If so, I THINK you can do this, but I have not tried it myself!

function Piece.New(type, img, gridPosX, gridPosY) local newPiece = {} newPiece.gridPosX = gridPosX; newPiece.gridPosY = gridPosY; newPiece.img = display.newImage(img, myGrid[gridPosX][gridPosY].x, myGrid[gridPosX][gridPosY].y); newPiece.thisPieceSelected = false; newPiece.availableMoves = {}; newPiece.type = type; myGrid[gridPosX][gridPosY].taken = type; --return setmetatable( newPiece, Piece\_mt ) setmetatable( newPiece, Piece\_mt ) newPiece:listen() return newPiece end

Hope that makes sense!

Hey.

Thank you so much for your reply. I tried your first solution and it works perfectly :slight_smile:

Yes I do want to add the event listener automatically when I create a new Piece. I tried your second piece of code but I get an error saying listen is nil. It is not a big deal since I can just loop through all the pieces and do piece:listen at the start of the game. The main problem was the first one, thanks again for helping me out.

Glad to hear the first part worked!

For the second part there might be one more thing to try. I’m still trying to fully grasp metatables myself so this is in the name of experimentation/science.

function Piece.New(type, img, gridPosX, gridPosY) local newPiece = {} newPiece.gridPosX = gridPosX; newPiece.gridPosY = gridPosY; newPiece.img = display.newImage(img, myGrid[gridPosX][gridPosY].x, myGrid[gridPosX][gridPosY].y); newPiece.thisPieceSelected = false; newPiece.availableMoves = {}; newPiece.type = type; myGrid[gridPosX][gridPosY].taken = type; --return setmetatable( newPiece, Piece\_mt ) local piece = setmetatable( newPiece, Piece\_mt ) piece:listen() return piece end

re last problem - might want to double-check contents of Piece_mt?  either version should have worked

Vince’s solution (wrapping the listener inside a closure holding a reference to original class) is probably the better approach, but thought i’d share another pattern i’ve seen - give the display object a reference to its containing lua class, fe:

function Class:new(whatever)   local instance = {}   setmetatable(instance,Class\_mt)   instance.displayObject = display.newWhatever(... -- give display object a ref to lua class instance:   instance.displayObject.selfie = instance -- put listener on display object, to a method on instance:   instance.displayObject:addEventListener("touch",instance)   return instance end function Class:touch(event)   -- when called, "self" will be the "instance.displayObject", if you instead want "instance", then:   self = self.selfie -- (don't have to use the name "selfie" :D just match whatever was used above)   -- now continue as expected (can still access display object via event.target, or self.displayObject) end

Hey, you are absolutely correct. I just tried it again and the first solution to the second problem works, not sure what I did wrong the first time I tried it, sorry about that. Thank you so much for your help both of you :slight_smile:

Is there a reason that you used

function Piece:listen() self.img:addEventListener("touch", Piece); end

Instead of

function Piece:listen() self.img:addEventListener("touch", self); end

which works fine for me.

I encountered this same issue some time ago and it took me a while to understand. Basically what happens is, when you do self.img:addEventListener, you are adding the touch function to the self.img display object. So now, when the touch function gets activated, self no longer refers to Piece, but to self.img. As you can see, you set the type property on the Piece class, but not on self.img which is why you get the nil error.

To get around this you should wrap Piece:touch in an anonymous function like so

function Piece:listen() local piece = self self.img.touch = function(self, event) --inside this function, self refers to self.img, so we store Piece in a local --variable above so that we can reference it in here piece:touch(event) end self.img:addEventListener("touch") --the 2nd parameter is not necessary if touch is already a function of the object you're adding the listener to end

As for your second question, you want to automatically added the touch listener when you create a new Piece? If so, I THINK you can do this, but I have not tried it myself!

function Piece.New(type, img, gridPosX, gridPosY) local newPiece = {} newPiece.gridPosX = gridPosX; newPiece.gridPosY = gridPosY; newPiece.img = display.newImage(img, myGrid[gridPosX][gridPosY].x, myGrid[gridPosX][gridPosY].y); newPiece.thisPieceSelected = false; newPiece.availableMoves = {}; newPiece.type = type; myGrid[gridPosX][gridPosY].taken = type; --return setmetatable( newPiece, Piece\_mt ) setmetatable( newPiece, Piece\_mt ) newPiece:listen() return newPiece end

Hope that makes sense!

Hey.

Thank you so much for your reply. I tried your first solution and it works perfectly :slight_smile:

Yes I do want to add the event listener automatically when I create a new Piece. I tried your second piece of code but I get an error saying listen is nil. It is not a big deal since I can just loop through all the pieces and do piece:listen at the start of the game. The main problem was the first one, thanks again for helping me out.

Glad to hear the first part worked!

For the second part there might be one more thing to try. I’m still trying to fully grasp metatables myself so this is in the name of experimentation/science.

function Piece.New(type, img, gridPosX, gridPosY) local newPiece = {} newPiece.gridPosX = gridPosX; newPiece.gridPosY = gridPosY; newPiece.img = display.newImage(img, myGrid[gridPosX][gridPosY].x, myGrid[gridPosX][gridPosY].y); newPiece.thisPieceSelected = false; newPiece.availableMoves = {}; newPiece.type = type; myGrid[gridPosX][gridPosY].taken = type; --return setmetatable( newPiece, Piece\_mt ) local piece = setmetatable( newPiece, Piece\_mt ) piece:listen() return piece end

re last problem - might want to double-check contents of Piece_mt?  either version should have worked

Vince’s solution (wrapping the listener inside a closure holding a reference to original class) is probably the better approach, but thought i’d share another pattern i’ve seen - give the display object a reference to its containing lua class, fe:

function Class:new(whatever)   local instance = {}   setmetatable(instance,Class\_mt)   instance.displayObject = display.newWhatever(... -- give display object a ref to lua class instance:   instance.displayObject.selfie = instance -- put listener on display object, to a method on instance:   instance.displayObject:addEventListener("touch",instance)   return instance end function Class:touch(event)   -- when called, "self" will be the "instance.displayObject", if you instead want "instance", then:   self = self.selfie -- (don't have to use the name "selfie" :D just match whatever was used above)   -- now continue as expected (can still access display object via event.target, or self.displayObject) end

Hey, you are absolutely correct. I just tried it again and the first solution to the second problem works, not sure what I did wrong the first time I tried it, sorry about that. Thank you so much for your help both of you :slight_smile:

Is there a reason that you used

function Piece:listen() self.img:addEventListener("touch", Piece); end

Instead of

function Piece:listen() self.img:addEventListener("touch", self); end

which works fine for me.