0

I apologize for the amount of code, but I think this is actually a problem with AppMobi's getCurrentLocation function. Basically what happens is I delegate a tap event to each list element. Then when you tap it, it runs an asynchronous getCurrentLocation and updates some stuff. Then the next time you tap another list element, the variables bound in the callback of the getCurrentLocation Function only refer to the first time it was called. Why doesn't this work??

app = { events: [{text: "foo", time: new Date()}, {text: "bar", time: new Date()}] };
$(document).ready(refreshEvents);

function refreshEvents() {
    for (var index in app.events) {
        insertEventHTML(app.events[index]);
    }
}

function insertEventHTML(event) {
    var text = event.text;
    var time = event.time;

    var new_element = $('<li class="event_element"></li>');
    var new_checkin_element = $('<div class="check_in_button"></div>');

    new_checkin_element.bind('tap', function(e) {
        check_in(e);
        fade($(this), 1.0);
    });
    new_element.append(new_checkin_element);

    var text_element = $('<div class="text_element">' + text + '</div>');
    new_element.append(text_element);
    var time_element = $('<div class="time_element">' + time + '</div>');
    new_element.append(time_element);
    $('#your_events').append(new_element);
}

function check_in(e) {
    $(e.target).siblings('.time_element').text('just now');
    var time = new Date();                   // time and event_index are the trouble variables here
    var event_index = getEventIndex(e);      // the first time this function runs, event_index is correct
                                             // then each subsequent time, it remains the same value as the first
    if (!app.settings.use_location) {
        app.events[event_index].times.unshift({time: time, location: null});
    } else {
        AppMobi.geolocation.getCurrentPosition(onLocationFound, errorFunction);
    }

    function onLocationFound(response) {    
        var lat = response.coords.latitude;
        var lon = response.coords.longitude;
        var last_time = app.events[event_index].times[0];

        if (last_time != undefined && last_time.time == time) {
            // event_index and time here will only ever refer to the first time it was called.  WHY??? 
            add_checkin(response, event_index, time);        
        }else{
            console.log('onLocationFound was called twice');
        }
    }

    function errorFunction(error) {
        $.ui.popup({title: 'geolocation error', message: 'Geolocation error.  Turn off location services in settings.'});
    }

    function add_checkin(response, event_index, time) {
        // and then of course event_index and time are wrong here as well.  I don't get it.
        app.events[event_index].times.unshift(
        {
            time: time, 
            location: {
                latitude: response.coords.latitude,
                longitude: response.coords.longitude
            }
        });
        AppMobi.cache.setCookie('app', JSON.stringify(app), -1);
    }
}

function getEventIndex(e) {
    var target = $(e.target).parent();
    var siblings = target.parent().children('li');
    for (var i = 0; i < siblings.length; i++) {
        if ($(target)[0].offsetTop == $(siblings[i])[0].offsetTop) {
            return i;
        }
    }
}
The Puma
  • 1,352
  • 2
  • 14
  • 27
  • Which variables are you talking about? I think it's perfectly normal that you always get the same result from `getCurrentPosition` if you aren't moving the device. – plalx Sep 10 '13 at 01:31
  • @plalx the geolocation variables are the same, yes. but the element_index is not. that is the element that was tapped. it always thinks it was the first element that was tapped. – The Puma Sep 10 '13 at 01:35
  • and by first element i mean the first that was tapped. it could be any of them. – The Puma Sep 10 '13 at 01:37
  • Can you assert that `e.target` is the element being tapped in the `check_in` function? Also where is your `getEventIndex` function? – plalx Sep 10 '13 at 01:42
  • @plalx i added getEventIndex. And yeah, e.target is the correct element being tapped. event_index is correct when check_in is called – The Puma Sep 10 '13 at 01:49

2 Answers2

1

Well, your issue seems to be that you are declaring a private variable event_index inside the check_in function and try to resolve it's value by accessing a global event_index variable inside onLocationFound.

Here's what you could do instead:

AppMobi.geolocation.getCurrentPosition(function (response) {
   onLocationFound(response, event_index);
}, errorFunction);

function onLocationFound(response, event_index) { //... }

EDIT:

it is declared within check_in...

You are right, I totally missed that somehow. Well in that case it's very unlikely that the event_index variable inside onLocationFound isin't the same as in check_in. Do a console.log(event_index) inside onLocationFound and it should be the same. The only way it could be different is if you modify the local variable before the handler is called, which you doesn't seem to do, or if getCurrentPosition stores the first handler somehow and ignores subsequent handlers, but this API wouldn't make any sense.

EDIT 2:

As we suspect the handler might not be registered correctly the second time, I would suggest to check this way:

function check_in() {
    if (!check_in.called) {
        check_in.called = true;
        check_in.onLocationFound = onLocationFound;
    }

    //...

    function onLocationFound() {
        console.log(arguments.callee === check_in.onLocationFound);
    }
}

You can also simple do onLocationFound.version = new Date() and check arguments.callee.version to see if it stays the same.

plalx
  • 42,889
  • 6
  • 74
  • 90
  • It's not accessing a global though, it's accessing event_index that's re-calculated in check_in each time. right? Your code produces the same problem :( – The Puma Sep 10 '13 at 02:23
  • @ThePuma How does the `onLocationFound` function would access a private variable declared within `check_in`? It simply cant. The `onLocationFound` function would have to be declared within `check_in` to create closure over it's scope. That's what my answer does, it creates an intermediate handler function within `check_in` so that `event_index` is accessible in the handler and the passes the `event_index` value to `onLocationFound`. – plalx Sep 10 '13 at 02:27
  • see that's why this is so strange. I console.log(event_index) and it is correct when it is declared in check_in. Then it is different in onLocationFound. Like you said, the getCurrentPosition must store the variable or something. I do not modify the variable at all and there is no global variable with the same name or anything like that. Any idea how I can debug this??? or any ideas for a strategy that might work? – The Puma Sep 10 '13 at 03:09
  • Okay I zeroed it in...event_index stays the same after it enters getCurrentPosition. I have no idea how to even work around this bug... – The Puma Sep 10 '13 at 03:15
  • @ThePuma You could globally store a reference to the first generated `onLocationFound` function and make sure not to modify this reference the second time `check_in` is called. Then, within `onLocationFound` check if the handler is still the same one with `arguments.callee === firstOnLocationFoundFunction`. If it is it looks like the library has a serious issue. Depending on the implementation, you might not even be able to fix this. – plalx Sep 10 '13 at 03:15
  • I'm not sure I understand. So you're saying something like window.onLocationFound = function () {...} and then check what in getCurrentLocatoin? – The Puma Sep 10 '13 at 03:17
  • @ThePuma, I added an example – plalx Sep 10 '13 at 03:21
  • Okay I added that assertion and it evaluates to true every time. But the variables are still wrong. – The Puma Sep 10 '13 at 03:28
  • @ThePuma, Well it's not good if it evaluates to `true`. It should be `false`. `true` means that the `getCurrentLocation` function takes the first handler and will never allow you to change it on subsequent calls. That's quite a huge bug. I'm quite puzzled, because I doubt no one would have noticed before. – plalx Sep 10 '13 at 03:32
  • I managed to get it working storing event_index and time to the check_in object. check_in.event_index obviously remains unaffected by the getCurrentLocation scoping bug. I'm puzzled too, but I swear that is what's happening, unless if there's something in my code that I'm completely unaware of (not likely) – The Puma Sep 10 '13 at 03:36
  • @ThePuma, Yes that will work, unless someone taps very quickly on a second item before the handler gets executed. I would suggest you to test `getCurrentLocation` outside of your code and see if you observe the same behaviour. Good luck! Btw, mind accepting the answer? – plalx Sep 10 '13 at 03:41
  • But if someone taps very quickly, it will be (essentially) the same time, and same location...so that shouldn't matter, right? – The Puma Sep 10 '13 at 03:42
  • It would be the same time and location, but not the same index. – plalx Sep 10 '13 at 03:45
  • You know what I could do, is do a check_in.lock = true before and chcek_in.lock = false after and just not let you press the button while it's executing – The Puma Sep 10 '13 at 04:21
  • @ThePuma,Yes I believe that's your only solution considering the situation. – plalx Sep 10 '13 at 12:45
1

I know you marked this answered already but....it might not actually be a problem with the library. Can I direct you to a post by @Joel Anair where he has posted an article and the example number five seems to be the "gotcha" which might have gotcha ;)

How do JavaScript closures work?

Basically in the for loop they are all being set to the same reference of i so event_index will all be the same value/reference. (which explains why they're all the same).

Community
  • 1
  • 1
user1567453
  • 1,837
  • 2
  • 19
  • 22
  • You are correct. I can't believe it took me this long to get it working correctly. The callback function was being set to the original variables. Very tricky. – The Puma Sep 12 '13 at 03:01
  • @ThePuma, Are you sure he is correct? That seems incorrect. I understand closures very well and unless I missed something in your code, I really do not see where that could be an issue. – plalx Sep 12 '13 at 17:23
  • @plalx I think he is. I thought I understood closures extremely well too, but apparently this tripped us up. I was able to modify my code to make it work without using a semaphore. The callback function for getCurrentPosition gets defined with the original variable that it is called with. I had to make a callback function so that it went back into the scope of the new variables. – The Puma Sep 13 '13 at 14:03