Thursday, January 30, 2014

Displaying a Dialog Loading Message

Oftentimes, displaying a dialog widget requires loading remote data, used to populate the dialog content. This presents a usability issue — the user has to wait for the data to load, and so there should be some visual indication that the data is loading. One solution to this problem might be to open the dialog, and rather than displaying the actual dialog content (you don't have the actual content yet), display a simple loading message. Once the real content arrives, this temporary loading message is replaced. This approach works fine. The challenge, however, is that it's tough to make a loading message look good in a dialog. The other issue is the resizing of the dialog, once the content is formatted and inserted.

The better way to handle this scenario is to extend the dialog widget with a loading message option. When true, the loading message get's displayed on top of the modal overlay. Granted, this works only for modal dialogs, but that's a fairly common use case when populating dialogs with remote content. We can also add some subtle text animation to the loading text, as this example illustrates. Here is what the loading message looks like.

You can see that the dialog modal overlay is still displayed. But the dialog widget div itself is replaced with loading text. Once the loading option is turned off, the dialog div is displayed as normal.

Let's take a look at our extension of the dialog widget that adds the new loading option. We'll look at the _setOption() method we have to override to make use of the new option.

_setOption: function( key, value ) {

    if ( key === "loading" && value && this._isOpen ) {
                
        this.uiDialog.css( "display", "none" );
                
        $( "<div/>" ).appendTo( "body" )
                     .addClass( "ui-dialog-loading ui-widget" )
                     .text( "Loading..." )
                     .position( this.options.position );
                
    } else if ( key === "loading" && ! value && this._isOpen ) {
                
        $( ".ui-dialog-loading" ).remove();
        this.uiDialog.css( "display", "block" );
                
    }
                
}

The loading option is a boolean option — it can be toggled on and off. When it turns on, we want the main dialog div to hide and we want the loading message displayed. When it turns off, we want the message hidden, and the main dialog div displayed. The other constraint here is that the dialog must be in an open state, otherwise, we'd just be inserting text into the UI, outside of the dialog. We make use of the _isOpen property for this purpose.

The loading message itself is just a div that's inserted into the DOM, and positioned using the position option of the dialog. This ensures that the text is actually replacing the dialog, and the dialog is replacing the text, when it's displayed. In this example, we've actually got some fancy CSS happening to give a subtle text-shadow animation. Here's the animation itself.

@keyframes textshadow {
    from { text-shadow: none; }
    to { text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.4); }
}

And here's how the ui-dialog-loading class applies that animation.

.ui-dialog-loading {
    animation: textshadow 
               1s
               ease-in-out
               0s
               infinite
               alternate;
}

Of course, we'll need to supply vender prefixes for the various browsers. Here's how the new loading option is used.

$( "#dialog" ).dialog( "open" )
              .dialog( "option", "loading", true );
            
setTimeout(function() {
    $( "#dialog" ).dialog( "option", "loading", false );    
}, 5000);

You'll notice that we call the open() method before setting the loading option to true. Next, we're using a timeout to simulate latency with an API call, and when it's finished, it turns off the loading message.