Search code examples
javascriptjquery-mobilejqueryjquery-mobile-collapsible

How I can I delay the expansion of a collapsible until its content is fetched?


I want to integrate a collapsible in my jQM app (1.3.2) that works as follows:

  1. It starts collapsed.
  2. On click it starts to fetch listitems for the collapsible from the server. The collapsible stays closed, a load icon may be spinning.
  3. After all elements are loaded and the listview refreshed and ready, the collapsible expands.
  4. If you click it again, it closes directly without delay, and starts from 1.

My initial idea was to grab the expand event and prevent its propagation. When loading is finished I de-register my custom event handler to turn the collapsible back to normal, and finally triggering the expand event from JavaScript to open.

The problem is that this works for the first round, but afterwards the collapsible opens anyway. Consider this example (also in jsfiddle):

<div data-role="collapsible" id="c">
  <h2>Collapse</h2> 
  <ul data-role="listview" id="lv">
    <li>You don't see me!</li>
  </ul>
</div>

<a href="#" data-role="button" onClick="$('#c').off('expand', stop)">
  Unlock the collapsible
</a>

<a href="#" data-role="button" onClick="$('#c').on('expand', stop)">
  Lock the collapsible
</a>

JavaScript:

var stop = function(event) {
  alert('Locked!');
  event.preventDefault();
}

// executed after the element is created ...
$('#c').on('expand', stop)

Here, if you click the collapsible after loading, it stays closed (good). When you click unlock, it opens and no alert is shown (good). If you lock it again it shows the alert (good) but opens anyways (bad).

Can anyone enlighten me what I am doing wrong? There seems to be side-effects from unlocking that I cannot see. There are several similar questions here, but most are happy with just preventing the expansion without turning it back on again.

So my question is: What is the best way to delay the expansion of a collapsible reliably?


Edit: I added another example that integrates the listview logic and also shows this error. jsFiddle is here. I also moved to .one() to make the de-registering more traceable.

New JavaScript:

var stop_n_load = function (event) {
  alert('stop opening! (or at least try to ...)');

  // try to stop the expanding by stopping the event
  event.preventDefault();

  // Do some work that takes time and trigger expand afterwards ...
  setTimeout(function (e) {
    dd = new Date();
    $('#lv').empty();
    $('#lv').append("<li><a href='#'>Item A ("+dd+")</a></li>");
    $('#lv').append("<li><a href='#'>Item B ("+dd+")</a></li>");
    $('#lv').listview('refresh');

    // when done, start expanding without this handler (was register only once)
    $('#c').trigger('expand');
    // for the next collapse register stop_n_load again
    $('#c').one('collapse', reset);
  }, 2000);
};

var reset = function (event) {
  $('#c').one('expand', stop_n_load);
};

$('#c').one('expand', stop_n_load);

On the first expansion it works as expected, it first updates and then opens. On the second run it opens without waiting, you can see the timestamp updating later, so the event is called properly.

There seems to be a problem with .preventDefault() I don't understand ...


Solution

  • When expand/collapse events are triggered, jQM adds several classes to show/hide contents as well as update collapsible button/header.

    In this case, event.preventDefault() isn't going to prevent event(s) from bubbling and stop them from executing. Thus you need to use event.stopImmediatePropagation() to stop expand event once header is clicked.

    When a collapsible is collapsed, it has a class ui-collapsible-collapsed and once expanded, the class is removed. Therefore, when collapse is triggered, it works normally without interruption.

    $(".ui-collapsible-heading-toggle").on("click", function (e) {
    
        // check if collapsible is whether expanded or collapsed
        if ($(this).closest(".ui-collapsible").hasClass("ui-collapsible-collapsed")) {
    
            // true
            // stop "expand" event on click,tap,vclick,touchstart, etc...
            e.stopImmediatePropagation();
    
            // show loading msg - optional
            $.mobile.loading("show", {
                text: "Loading...Please wait",
                textVisible: true
            });
    
            // for demo
            // add items, expand and hide loading msg
            setTimeout(function () {                
                $("#lv").empty().append(items).listview("refresh");
                $("#c").trigger("expand");
                $.mobile.loading("hide");
            }, 1000);
        }
    
        // false
        // collapse normally or do something else
    });
    

    Demo