0

I'm currently stuck on the frontend of a plugin I am writing. I am trying to defer the execution of a filter function for HTML table row elements (hide(), show()) and markers on a leaflet map until after they are created, but Firefox always gives me the error

TypeError: job.tableRow is undefined

If I comment out the tableRow creation I get exactly same Problem with the markers on the leaflet map.

I've been reading a lot about jQuery deferred objects and tried 2 or three ways to implement them, but it seems like I am still missing something important.

What works: I can sucessfully filter the jobs that are shown/hidden in the list and on the map manually after the page was completely loaded.

However, if I try to do some initial filtering I get the error. Seems like the filtering function is executed before tableRow was created.

So my current code looks like this:

//inside the "main" function

if(jobs != null){
    /* this code works
       createMarkers(jobs);   
       createJobTable(jobs, tableBody);
    */
    //but like this it does not
    $.when( createMarkers(jobs), createJobTable(jobs, tableBody)).done(filterJobs);


function filterJobs(){

    ...

    //go through array of jobs and hide markers and list entry of those, with status that is not checked
    for (i = 0, max = jobs.length ; i < max; i++){
        job = jobs[i];
        if (shownJobStatus.includes (job['status']) ){
            job.tableRow.show();
            job.marker.addTo(jobmap);
        }
        else {
            job.tableRow.hide();
            job.marker.remove();
        }
    }
}

function createMarkers(jobs){

    jQuery(function($){
        var deferred = $.Deferred();

        for (i = 0; i < jobs.length; i++){

            //extract lat, lon, ... from job, I leave out again
            job.marker = L.marker([lat, lon], {icon: markerIcon}).addTo(jobmap);
            job.marker.bindPopup(jobName).openPopup();
        }

        deferred.resolve();
        return deferred;
    })
}


function createJobTable(jobs, tableBody){

    jQuery(function($){

    let promiseArray = [];

    for (i = 0; i < jobs.length; i++){
        job = jobs[i];
        promiseArray.push( createJobTableEntry(job, tableBody) );
    }
    deferred.resolve();
    return Promise.all(promiseArray); 

    })
}

function createJobTableEntry(job, tableBody){

    jQuery (function($){

        let defer = $.Deferred();

        const jobId = job['id'];

        job.tableRow = $("<tr></tr>");
        tableBody.append(job.tableRow);

        //put lots of stuff in the table like this
        job.tableRow.append($("<td></td>").text(job.jobName));

        //lots of user-role dependant stuff 

        defer.resolve();
        return defer.promise();

    })
}

I thought the line

$.when( createMarkers(jobs), createJobTable(jobs, tableBody)).done(filterJobs);

should execute filterJobs() only after the deferred that createMarkers and createJobTable return are resolved. The deferred should be resolved only after all the elements where created. So why does this not work?

CRS92
  • 1
  • 5
  • 4
    `.done(filterJobs())`<-- you are executing it, not assigning it.... `.done(filterJobs)` – epascarello May 30 '19 at 16:14
  • Related. https://stackoverflow.com/questions/7102413/why-does-click-event-handler-fire-immediately-upon-page-load – Taplar May 30 '19 at 16:17
  • @epascarello uooohh, thank you, I see now that I should have seen that. But still if I change it to [.done(filterJobs)] or [.done(function () {filterJobs();}] it's the same: TypeError: job.tableRow is undefined. Any other ideas? – CRS92 May 30 '19 at 20:00
  • Those `Deferred` objects don't defer anything. They just represent a result that might be asynchronous, but in your code there is nothing asynchronous so it runs immediately. If you actually want to defer execution of some code, use `setTimeout` (or whatever else you want to wait for). – Bergi May 30 '19 at 20:47
  • @Bergi what?! the [doku](https://api.jquery.com/deferred.done/) says "deferred.done( doneCallbacks [, doneCallbacks ] ) doneCallbacks Type: Function() A function, or array of functions, that are called when the Deferred is resolved." Am I completely mistaken on some very basic programming vocabulary here? – CRS92 May 31 '19 at 05:47
  • @CRS92 The point is that you are resolving your deferreds immediately, not asynchronously. So the callbacks will run immediately as well. – Bergi May 31 '19 at 08:39
  • @Bergi so, is it possible to make filterJobs wait for createJobTable and createMarkers with deferreds in principle? Simply setting a timeout seems kind of... dirty to me as I can not know the maximum time a browser will need to execute the create-functions. – CRS92 May 31 '19 at 12:03
  • @CRS92 What do the create-functions do? There must be some way to know when they are done executing - probably a callback if they are asynchronous? – Bergi May 31 '19 at 12:45
  • @Bergi they create table rows and markers that are stored in a global array with jobs. the filterJobs function takes each array element (job), checks job.status and depending on wich statuses the user selected as visible, it calles .hide() or .show() on these elements. – CRS92 May 31 '19 at 12:53
  • @CRS92 And *how* do they do that? Please post the code. Why store the rows and markers in a global array instead of simply returning it from the function? – Bergi May 31 '19 at 12:56
  • @Bergi in createMarkers [job.marker = L.marker([lat, lon], {icon: markerIcon}).addTo(jobmap);] creates the marker, in createJobTableEntry [job.tableRow = $("");] creates the tablerow. Hiding/showing is in filterJobs if/else-part. And I store the jobs in a global array bcs i need to re-access them later on user input. I keep (and link) all relevant information in a global array. like that i can for example easily center the map on the marker when the user clicks the corresponding table row. – CRS92 May 31 '19 at 13:44

0 Answers0