How I can I delay the expansion of a collapsible u

2019-07-22 02:52发布

问题:

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 ...

回答1:

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



回答2:

I'm not intimately familiar with JQM but my first thought was empty the list before sending for more data....which seems to prevent it expanding.

var stop = function(event) {    
    $(this).find('.ui-listview').empty()
    event.preventDefault();

    /* do ajax and then refresh component with expand enabled */
}

$('#c').on('expand', stop);

DEMO - without ajax



回答3:

After wading around in the event system, I concluded for now that if you set preventDefault() once you are stuck with it forever. Based on the solution of charlietfl I added code to make the visual impact minimal, maybe there is also a way to prevent the GUI from refreshing for the 20ms to make it fully unnoticable.

var stop_n_load = function (event) {
  $('#lv').empty();
  $('#lv').listview('refresh');

  setTimeout(function() {
    $('#c').trigger('collapse');
  }, 20);

  // Do some work that takes time and trigger expand afterwards ...
  setTimeout(function () {
    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');

    $('#c').trigger('expand');
    $('#c').one('collapse', reset);
  }, 2000);
};

I'm still searching for a solution that resets the event to its previous state, though.