Tuesday, September 1, 2015

MAKE LISTVIEW DIVIDERS COLLAPSIBLE

The listview widget in the jQuery Mobile (jQM) library allows for divider listitems which can be inserted manually or for sorted lists even be created automatically by the library.  The dividers organize larger sets of listitems  into smaller groups making it easier for the user to view the desired information. In this article I will implement a small trick that uses clicks/taps on the divider items to collapse expand the listitems in the divider group. This works particularly well for autodivider lists saving you the need to manually create collapsible widgets with separate contained lists.


<ul data-role="listview" data-autodividers="true" data-inset="true" data-divider-theme="a" >
  <li><a href="#">Aaron</a></li>
  <li><a href="#">Adam</a> </li>
  <li><a href="#">Alexander</a></li>
  <li><a href="#">Alice</a></li>
  <li><a href="#">Andrew</a></li>
  <li><a href="#">Anna</a></li>
  <li><a href="#">Anthony</a></li>
  <li><a href="#">Audrey</a></li>
  <li><a href="#">Benjamin</a></li>
  <li><a href="#">Brandon</a></li>
  ...
  <li><a href="#">Tristan</a></li>
  <li><a href="#">Tyler</a></li>
  <li><a href="#">Violet</a></li>
  <li><a href="#">Vivienne</a></li>
  <li><a href="#">William</a></li>
  <li><a href="#">Xavier</a></li>
  <li><a href="#">Zachary</a></li>
  <li><a href="#">Zoe</a></li>
</ul>


To indicate that clicking the dividers collapses the group, I will add a MINUS icon at the left of the divider (later when we collapse the group, I will add code that switches the icon to a PLUS). During the pagecreate event, I create the markup for an inline icon span and prepend it to all dividers so the icon appears to the left of any text. jQM assigns a class of ui-li-divider to all divider listitems:


$(document).on("pagecreate", "#page1", function () {
    var ic = '<span class="ui-icon-minus ui-btn-icon-notext inlineIcon"></span>';
    $(".ui-li-divider").prepend(ic);
});


To make the icon appear inline with the text, I add the following CSS rule to override the jQM default settings for icons:


.inlineIcon {
    display: inline-block;
    position: relative;
    vertical-align: middle;
    margin-right: 6px;
}



Once the icon is in place, I add a click handler for the divider listitems. This handler uses a while loop to step through subsequent listitems using the jQuery next() method, toggling their visibility  until the next divider is reached or the list ends. By using the jQuery slideToggle() method, the group expands/collapses with a sliding animation. Then to switch the icon on the divider, I determine if the group is collapsed or not by the CSS display attribute of the listitems in the group. With that, I simply remove one icon class and add the other:

$(document).on("click", '.ui-li-divider', function (e) {
    var IsCollapsed = false;
    var TheDivider = $(this);
    var li = TheDivider.next(':not(.ui-li-divider
    while (li.length > 0) {
        IsCollapsed = li.css('display= 'none';
        li.slideToggle(300);
        li = li.next(':not(.ui-li-divider
    }
    var $icon = TheDivider.find('.inlineIcon');
    if (!IsCollapsed) {
        $icon.removeClass('ui-icon-minus').addClass('ui-icon-plus');
    } else {
        $icon.removeClass('ui-icon-plus').addClass('ui-icon-minus');
    }
    e.stopPropagation();
    return false;
});

If the list is pretty long and you have many dividers, it is a good idea to provide the user with ‘Expand All’ and Collapse All’ buttons. For this example I add the 2 buttons in a jQM grid and give them both a class of collapseExpand:

<div class="ui-grid-a ui-mini">
  <div class="ui-block-a"><a id="btnExpand" class="ui-btn ui-corner-all collapseExpand">Expand All</a></div>
  <div class="ui-block-b"><a id="btnCollapse" class="ui-btn ui-corner-all collapseExpand">Collapse All</a></div>
</div>


Then I use the common class to create a single event handler. If  ‘Collapse All’ is clicked, I trigger the click event on all dividers with ui-icon-minus class assigned to a child; otherwise I trigger the click event on all dividers with the ui-icon-plus class assigned to a child:

$(document).on("click", ".collapseExpand", function () {
    var collapseAll = this.id == "btnCollapse";
    if (collapseAll) {
        $(".ui-li-divider .ui-icon-minus").click();
    } else {
        $(".ui-li-divider .ui-icon-plus").click();
    }
});
This technique can be enhanced by using the jQM page transitions to show and hide lisitems during the collapse/expand events. This is a way to add some ‘sizzle’ to your projects. I create a new function that takes the listitem jQuery object and a boolean indicating whether we are showing or hiding the item. If we are showing the item, I make the item visible and then add the animation classes, ‘flow’ and ‘in’, then I attach an event handler for the end of the animation where these classes are removed.  The event is attached with the one() method as it only needs to run once. If we are hiding the item, I add the animation classes, ‘flow’ and ‘out’, then I attach the end of animation handler where the classes are removed and item is hidden:

function ApplyTransition(li, IsCollapsed) {
    if (IsCollapsed) {
        li.show(0, function () {
            $(this).addClass('flow in').one('webkitAnimationEnd oanimationend msAnimationEnd mozAnimationEnd animationend', function (e) {
                $(this).removeClass('flow in');
            });
        });
    } else {
        li.addClass('flow out').one('webkitAnimationEnd oanimationend msAnimationEnd mozAnimationEnd animationend', function (e) {
            $(this).hide();
            $(this).removeClass('flow out');
        });
    }
}
To use this animation, I change the divider click handler from above to use this function instead of the slideToggle:

$(document).on("click", '.ui-li-divider', function (e) {
    var IsCollapsed = false;
    var TheDivider = $(this);
    var li = TheDivider.next(':not(.ui-li-divider
    while (li.length > 0) {
        IsCollapsed = li.css('display= 'none';
        ApplyTransition(li, IsCollapsed);
        li = li.next(':not(.ui-li-divider
    }
    var $icon = TheDivider.find('.inlineIcon');
    if (!IsCollapsed) {
        $icon.removeClass('ui-icon-minus').addClass('ui-icon-plus');
    } else {
        $icon.removeClass('ui-icon-plus').addClass('ui-icon-minus');
    }
    e.stopPropagation();
    return false;
});

Popular Posts