Question

I have a tricky problem, that can be solved with chained callbacks, but that results in quite complex code.

I have some nested components: a jQueryUI Dialog. Inside it, I have jQueryUI Tabs. And in each tab, I have a DataTables component, calling AJAX data.

My problem is that when I load all the HTML inside the Dialog, everything fires up in parallel: tabs tries to create itself, while each DataTable gathers it's data, all at the same time!

The result is a complete mess: the Tabs don't hold the correct elements, since each DataTable creates new nodes on the DOM, after Tabs initialization. The Dialog assumes the wrong dimensions, because the DOM was small when it initialized, but grew up when each DataTable displayed the data.

To make things work, I would need to:

1) Wait for each DataTable to initialize.
2) When they're all done, initialize the Tabs.
3) When Tabs are ready, finally open the Dialog.

Is there an easy way to do that?? I'm currently using lots of callbacks, and all the content is dynamic, database driven, so I have to do lots of generalization, almost nothing can be hardcoded. The thing works already, but it's ugly!

This is one of the situations where I would turn off JavaScript async behavior and make it all sync and sequential, if I could.

Maybe jQuery queue, promise or something like that can help me? Any help will be greatly appreciated!

Was it helpful?

Solution

I have found a suitable solution, so I will publish it here to help anyone else facing a similar problem.

First, I have found that DataTables has an event fired on creation. It's the "init" event:

$("#TableID").on("init", function() { whatever(); } );

Second, my other problem is the indeterminate number of DataTables I had to wait, before initializing the Tabs. So I created a small function, called RenderManager, to keep track of all my DataTables. When the last one finishes, a callback is called. I made this function very general, so it can be used on any situation where you have to wait for several parallel processing to finish before moving on:

function RenderManager(action, id, callback) 
{
  if(typeof RenderManager.groups == 'undefined' ) RenderManager.groups = {};

  switch(action)
  {
    case "init":
      RenderManager.groups[id] = { _callback:callback, _counter:0 };
    break;
    case "add":
      if(typeof RenderManager.groups[id] == 'undefined' ) 
        console.log('Group "'+id+'" referenced before init.');
      else 
        RenderManager.groups[id]._counter++;
    break;
    case "remove":
      if(typeof RenderManager.groups[id] == 'undefined' ) 
        console.log('Group "'+id+'" referenced before init.');
      else
      {
        RenderManager.groups[id]._counter--;
        if(RenderManager.groups[id]._counter == 0) 
          RenderManager.groups[id]._callback();
      }
    break;
  }
}

Usage is simple: first, call it using init, to inform the group name and desired callback:

RenderManager('init', 'MyGroup', function() { CreateTabs(); });

For each DataTable created (or parallel proccess started), add it to the group:

RenderManager('add', 'MyGroup');

And then bind a remove to each DataTable's init event (or to the finish of any parallel proccess), using:

$("#TableID").on("init", function() { RenderManager('remove', 'MyGroup'); } );

That's it! When the last DataTable finishes it's job, CreateTabs(); will be called, no matter the order of execution, no matter which DataTable finished first.

As you can see, this function is just a counter: when the "stack" goes back to zero, the callback fires. It can be improved, with registration of the IDs of each element, but this is overkill for the problem I'm currently solving.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top