Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
How to pass variables through Event listeners ('attempt to index a nil value')? — Gideros Forum

How to pass variables through Event listeners ('attempt to index a nil value')?

MellsMells Guru
edited April 2012 in Building a team?
Hi,

I'm having some troubles to pass some variables through an Event listener.
Could someone point me in the right direction?
-- [ scenecontroller.lua ]
function SceneController:init()
        self.testvar = "Test"
end
 
-- [ interface.lua ]
function Interface:init(sceneController)
        print (sceneController.testvar)
        -- Test ok
 
	self:createInterface(sceneController)
end
 
function Interface:createInterface(sceneController)
        print (sceneController.testvar)
        -- Test ok
 
	*******|| self.nextPage:addEventListener("click", self.nextPageClick, sceneController) ||*******
end
 
function Interface:nextPageClick(sceneController)
	print (sceneController.testvar)
        -- Error attempt to index a nil value
end
 
-- [ main.lua ]
sc = SceneController.new()
interface = Interface.new(sc)

How can I give sceneController to nextPageClick through the Event listener?

Thank you,
Mells

Dislikes: yvz5, lesi2003

twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
+1 -1 (+0 / -2 )Share on Facebook

Comments

  • ndossndoss Guru
    edited April 2012
    @Mells, one way would be to wrap your function call inside another function:
    self.nextPage:addEventListener("click", function() self:nextPageClick(sceneController) end)
  • MellsMells Guru
    edited April 2012
    @ndoss
    Thank you very much (and also for all your contributions to this community).

    I have 2 other questions :
    1. I was wondering how the third argument of the addEventListener function (the data parameter) could be used?
    I thought it was straightforward to use sceneController as an argument (like "self" is often used in that case) and I don't really get why it didn't work.

    (Also See the way it's used in the Custom Events section in the Ultimate Guide)

    How can one take advantage of the third argument (data) that is passed in the addEventListener function?

    2. How do you remove the EventListener that is created the way suggested :
    self.nextPage:addEventListener("click", function() self:nextPageClick(sceneController) end)
    ?
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
  • ndossndoss Guru
    edited April 2012

    1. I was wondering how the third argument of the addEventListener function (the data parameter) could be used?
    I thought it was straightforward to use sceneController as an argument (like "self" is often used in that case) and I don't really get why it didn't work.
    I gave you a crappy answer. The best answer is probably is to call:
    self.nextPage:addEventListener("click", self.nextPageClick, self, sceneController)
    The call to self:nextPageClick(sceneController) is the same as self.nextPageCLick(self, sceneController).

    2. How do you remove the EventListener that is created the way suggested :
    self.nextPage:addEventListener("click", function() self:nextPageClick(sceneController) end)
    ?
    I don't know -- don't do it that way :)
  • Thank you again for your help @ndoss.

    However
    -- [ interface.lua ]
    function Interface:createInterface(sceneController)
            print (sceneController.testvar)
            -- Test ok
     
            self.nextPage:addEventListener("click", self.nextPageClick, self, sceneController)
    end
     
     
    function Interface:nextPageClick(sceneController)
    	print (sceneController.testvar)
            -- ***** nil *****
    end
    gives me an error.

    Maybe I am missing something?
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps

  • Maybe I am missing something?
    Looks like I gave you two crappy answers, my apologies for that. I should have tested before I answered. I'm not sure about the best way to do what you want. The following code works, but I'm not sure it's the best way ... hopefully someone else will point out something better ...
    EventTest = Core.class()
    function EventTest:init(arg)
       local function onEvent(handler, event)
          self:print(arg)
          stage:removeEventListener(Event.MOUSE_DOWN, handler, handler)
       end
       stage:addEventListener(Event.MOUSE_DOWN, onEvent, onEvent)
    end
     
    function EventTest:print(event, arg)
       print(inspect(event))
    end
     
    et = EventTest.new("abc")
  • hopefully someone else will point out something
    I will wait patiently, because I don't know much where else I can learn about it and this will be very useful for the future.

    :) ....
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
  • ar2rsawseenar2rsawseen Maintainer
    In case, when I need to pass a variable to event listener, I usually hook it up to object that calls listener as property, and use getTarget inside event listener. But I haven't used custom event listeners, so I don't know if there are any specifics there.

    Here's a quick example:
    local ball = Bitmap.new(Texture.new("ball.png"))
    ball.cnt = 1	
    ball:addEventListener(Event.MOUSE_DOWN, function(e)
    	local target = e:getTarget()
    	print(target.cnt)
    end)
  • CarolineCaroline Guru
    edited May 2012
    I think if I were doing that exact same thing, I would do this:
    function Interface:init(sceneController)
            print (sceneController.testvar)
            self.sceneController = sceneController
    end
    Then it would be available at any time.
  • However, I think I have done this exact same thing, but it is quite convoluted going between classes. I will have to work it out...
  • CarolineCaroline Guru
    edited May 2012
    I hope I am understanding what you want. This is distilled from what I have done in an app:

    main.lua
    EventClass = gideros.class(EventDispatcher)
    eventDispatcher = EventClass.new()
     
    dispatcher = Dispatcher.new()
    receiver = Receiver.new()
     
    eventDispatcher:addEventListener("myEvent", receiver.doEvent, receiver)
     
    dispatcher:sendEvent("Hello World")
    dispatcher.lua
    Dispatcher = gideros.class()
     
    function Dispatcher:init()
     
    end
     
    function Dispatcher:sendEvent(withData)
    	local event = Event.new("myEvent")
    	event.data1 = withData
    	print("Dispatching event with data:", withData)
    	eventDispatcher:dispatchEvent(event)
    end
    receiver.lua
    Receiver = gideros.class()
     
    function Receiver:init()
    end
     
    function Receiver:doEvent(event)
    	print("Event done:", event.data1)
    end

    I don't know whether this is good practice, to have a global event dispatcher, but it worked for me :).


    Likes: newbie2018

    +1 -1 (+1 / -0 )Share on Facebook
  • @ar2rsawseen & @Caroline
    Those answers go a little bit beyond my current level but I will study it a few hours and with no doubt learn a lot.
    Thank you for taking the time to answer :)

    I was still curious about the use of "data" in :
    EventDispatcher:addEventListener(type, listener, data)
    I will try to find more examples on top of the ebook/game templates to get an understanding of this parameter.
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
  • ar2rsawseenar2rsawseen Maintainer
    In system events, parameter data represents the scope, the context of function.
    For example
    --let's create our own class based on Sprite
    Scene = Core.class(Sprite)
     
    function Scene:init()
    	--variable self is referencing to instance of the class scene
    	--so if we do this
    	self.somevar = "somevalue"
    end
     
    --we will be able to access it like this:
    local scene = new Scene()
    print(scene.somevar)
     
    --now if we add a system event to this scene like this
    scene:addEventListener(Event.MOUSE_DOWN, function()
    	--we won't be able to access somevar
    	print(self.somevar) -- results nil
    	--because function we defined has it's own scope
    	--and variable self is now referencing to it's scope
    	--and note scene's scope
    end)
     
    --but if we pass scene as data parameter
    scene:addEventListener(Event.MOUSE_DOWN, function()
    	--we can access somevar
    	print(self.somevar) -- results "somevalue"
    	--because self is now referencing to whatever we passed as data parameter
    	--which was scene
    end, scene)
    I don't know if the same applies to custom events

    But all in all, it's really similar to whether you use . or :
    --let's create our own class based on Sprite
    Scene = Core.class(Sprite)
     
    function Scene:init()
    	--variable self is referencing to instance of the class scene
    	--so if we do this
    	self.somevar = "somevalue"
    end
     
    --create instance
    local scene = new Scene()
     
    --let's define function using :
    --meaning we will use same scope
    function scene:samescope()
    	--somevar is still accessible
    	print(self.somevar) --results "somevalue"
    end
     
    --now let's use .
    --meaning we will use different scope
    scene.differentcope = function()
    	--somevar is NOT accessible
    	print(self.somevar) -- results nil
    end

    Likes: newbie2018

    +1 -1 (+1 / -0 )Share on Facebook
  • edited July 2013
    Hmm, what about "self" reference at onTimer function?
    Suppose there are many enemy instance that each has a timed behaviour like this:
    -- Enemy class
    Enemy = gideros.class(Sprite)
     
    local enemyX, enemyY
    local moveSpeed
     
    local enemyTimer
     
    function Enemy:init(enemyX, enemyY)	
    	self.enemyX = enemyX
    	self.enemyY = enemyY
    	self:moveSpeed = 10
     
    	self:setSpeed()
     
    	self.enemyTimer = Timer.new(math.random(2000, 5000), 1)
     
    	-- "self" reference?
    	self.enemyTimer.parent = self
     
    	self.enemyTimer:addEventListener(Event.TIMER, self.onTimer, self.enemyTimer)
    	self.enemyTimer:start()
    end
     
    function Enemy:setSpeed() 	
    	if math.random(1, 6) <= 3  then
    		self.moveSpeed = 10
    	else 
    		self.moveSpeed = -10
    	end
    end
     
    function Enemy:onTimer(enemyTimer)
    	enemyTimer.parent:setSpeed() -- nil?
     
    	enemyTimer = Timer.new(math.random(2000, 5000), 1)
    	enemyTimer:addEventListener(Event.TIMER, enemyTimer.parent.onTimer, enemyTimer)
    	enemyTimer:start()
    end
    I've tried to have "self" reference (parent) on enemyTimer, but it became nil on Enemy:onTimer..

    Can someone explain this? I'm still new with this lua thing..

    Thanks~
  • MellsMells Guru
    edited July 2013
    @fajarnugroho23

    Quickly drafted on my ipod and modified later (but didn't test) but should work as a basis :

    =================
    Enemy.lua
    =================
    Enemy = Core.class(Sprite)
    function Enemy:init(parent, x, y)
         self.parent = parent
     
         -- Vars
         self.originX = x
         self.originY = y
         self.speedLimitMin = 0
         self.speedLimitMax = 6
         self.speedValue = 10
         self.timerLimitMin = 2000
         self.timerLimitMax = 5000
         self.timerLimitNbRepeat = 1
     
         -- Position
         -- self:setPosition(x, y) ? It is not part of your code but might be what you want to achieve?
     
         -- Speed
         self:setSpeed(self.speedValue, self.speedLimitMin, self.speedLimitMax)
     
         -- Timer
         self.enemyTimer = self:loopTimer(self.timerLimitMin, self.timerLimitMax, self.timerNbRepeat)
    end
     
     
    function Enemy:setSpeed(limitValue, limitMin, limitMax)
            -- Set defaults so you can use without parameters if you want
     	local limitMin = limitMin or self.speedLimitMin
     	local limitMax = limitMin or self.speedLimitMax
     	local limitValue = limitValue or self.speedLimitValue
    	if math.random(limitMin, limitMax) <= ((limitMax-limitMin)*0.5)  then
    		self.moveSpeed = limitValue
    	else 
    		self.moveSpeed = -limitValue
    	end
    end
     
     
    function Enemy:loopTimer(limitMin, limitMax, nbRepeat, prevTimer)
     
          -- Clean and nil current Timer
          if not(prevTimer == nil) then
                prevTimer:removeEventListener(Event.TIMER, self.onTimer, self)
                prevTimer = nil
          end
     
          -- Create new
          local timer = Timer.new(math.random(limitMin, limitMax), nbRepeat)
          timer:addEventListener(Event.TIMER, self.onTimer, self)      
          timer:start() -- If you want to play it by default
          return timer
    end
     
    function Enemy:onTimer()
     
         self:setSpeed() -- Should work without parameters
     
         -- ####################
         -- I don't get what you are trying to do here, recreating another timer? Want it to loop?
         -- if yes, then
         --[[
         self.enemyTimer = self:loopTimer(self.timerLimitMin, self.timerLimitMax, self.timerNbRepeat, self.enemyTimer)
         ]]--
    end
    =================
    Scene.lua
    =================
    Scene = Core.class(Sprite)
    function Scene:init()
         local enemy = Enemy.new(self, 100, 100)
         self:addChild(enemy)
    end
    =================
    main.lua
    =================
    local scene = Scene.new()
    stage:addChild(scene)
    Make sure that Scene.lua is loaded before main.lua (code dependencies).

    I hope it helps.
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
    +1 -1 (+1 / -0 )Share on Facebook
  • ar2rsawseenar2rsawseen Maintainer
    edited July 2013
    What @Mells posted should work
    but just to explain the difference, if you define method through colon (:), as you did with function Enemy:onTimer(enemyTimer), then it means that it converts first parameter to self variable inside function. thus if you add event listener like this:
    self.enemyTimer:addEventListener(Event.TIMER, self.onTimer, self.enemyTimer)
    Then inside your onTimer method you should use it like:
    function Enemy:onTimer()
    	--now what you passed as self.enemyTimer is self here
    	self.parent:setSpeed() -- would do what you want
    end
    +1 -1 (+1 / -0 )Share on Facebook
  • edited July 2013
    @Mells @ar2rsawseen

    whoa, thank you!
    i'll try it this night~
  • MellsMells Guru
    edited July 2013
    @fajarnugroho23
    as @ar2rsawseen just did there are many little subtleties that you will discover if you take time to look at the code. I don't have time to comment but it should get you started.

    Good luck :)
    (ps : when I joined this forum I had no experience with lua at all; so no doubt you'll be able to get used to it fast.)
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
  • yep it works! :)
    now i have a better understanding about variable passing in lua..

    thanks again @Mells
  • @fajarnugroho23
    you're welcome
    twitter@TheWindApps Artful applications : The Wind Forest. #art #japan #apps
Sign In or Register to comment.