A Closable jQuery Plug-in

Posted by Rick Strahl on West-Wind See other posts from West-Wind or by Rick Strahl
Published on Mon, 29 Nov 2010 20:30:45 GMT Indexed on 2010/12/18 17:14 UTC
Read the original article Hit count: 534

Filed under:
|
|

In my client side development I deal a lot with content that pops over the main page. Be it data entry ‘windows’ or dialogs or simple pop up notes. In most cases this behavior goes with draggable windows, but sometimes it’s also useful to have closable behavior on static page content that the user can choose to hide or otherwise make invisible or fade out.

Here’s a small jQuery plug-in that provides .closable() behavior to most elements by using either an image that is provided or – more appropriately by using a CSS class to define the picture box layout.

/*
* 
* Closable
*
* Makes selected DOM elements closable by making them 
* invisible when close icon is clicked
*
* Version 1.01
* @requires jQuery v1.3 or later
* 
* Copyright (c) 2007-2010 Rick Strahl 
* http://www.west-wind.com/
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php

Support CSS:

.closebox
{
    position: absolute;        
    right: 4px;
    top: 4px;
    background-image: url(images/close.gif);
    background-repeat: no-repeat;
    width: 14px;
    height: 14px;
    cursor: pointer;        
    opacity: 0.60;
    filter: alpha(opacity="80");
} 
.closebox:hover 
{
    opacity: 0.95;
    filter: alpha(opacity="100");
}

Options:

* handle
Element to place closebox into (like say a header). Use if main element 
and closebox container are two different elements.

* closeHandler
Function called when the close box is clicked. Return true to close the box
return false to keep it visible.

* cssClass
The CSS class to apply to the close box DIV or IMG tag.

* imageUrl
Allows you to specify an explicit IMG url that displays the close icon. If used bypasses CSS image styling.

* fadeOut
Optional provide fadeOut speed. Default no fade out occurs
*/
(function ($) {

    $.fn.closable = function (options) {
        var opt = { handle: null,
            closeHandler: null,
            cssClass: "closebox",
            imageUrl: null,
            fadeOut: null
        };
        $.extend(opt, options);

        return this.each(function (i) {
            var el = $(this);
            var pos = el.css("position");
            if (!pos || pos == "static")
                el.css("position", "relative");
            var h = opt.handle ? $(opt.handle).css({ position: "relative" }) : el;

            var div = opt.imageUrl ?
                    $("<img>").attr("src", opt.imageUrl).css("cursor", "pointer") :
                    $("<div>");
            div.addClass(opt.cssClass)
                    .click(function (e) {
                        if (opt.closeHandler)
                            if (!opt.closeHandler.call(this, e))
                                return;
                        if (opt.fadeOut)
                            $(el).fadeOut(opt.fadeOut);
                        else $(el).hide();
                    });
            if (opt.imageUrl) div.css("background-image", "none");
            h.append(div);
        });
    }

})(jQuery);

The plugin can be applied against any selector that is a container (typically a div tag). The close image or close box is provided typically by way of a CssClass - .closebox by default – which supplies the image as part of the CSS styling. The default styling for the box looks something like this:

.closebox
{
    position: absolute;        
    right: 4px;
    top: 4px;
    background-image: url(images/close.gif);
    background-repeat: no-repeat;
    width: 14px;
    height: 14px;
    cursor: pointer;        
    opacity: 0.60;
    filter: alpha(opacity="80");
} 
.closebox:hover 
{
    opacity: 0.95;
    filter: alpha(opacity="100");
}

Alternately you can also supply an image URL which overrides the background image in the style sheet. I use this plug-in mostly on pop up windows that can be closed, but it’s also quite handy for remove/delete behavior in list displays like this:

Closable

you can find this sample here to look to play along: http://www.west-wind.com/WestwindWebToolkit/Samples/Ajax/AmazonBooks/BooksAdmin.aspx

For closable windows it’s nice to have something reusable because in my client framework there are lots of different kinds of windows that can be created: Draggables, Modal Dialogs, HoverPanels etc. and they all use the client .closable plug-in to provide the closable operation in the same way with a few options. Plug-ins are great for this sort of thing because they can also be aggregated and so different components can pick and choose the behavior they want. The window here is a draggable, that’s closable and has shadow behavior and the server control can simply generate the appropriate plug-ins to apply to the main <div> tag:

$().ready(function() {
    $('#ctl00_MainContent_panEditBook')
        .closable({ handle: $('#divEditBook_Header') })
        .draggable({ dragDelay: 100, handle: '#divEditBook_Header' })
        .shadow({ opacity: 0.25, offset: 6 });
})

The window is using the default .closebox style and has its handle set to the header bar (Book Information). The window is just closable to go away so no event handler is applied. Actually I cheated – the actual page’s .closable is a bit more ugly in the sample as it uses an image from a resources file:

.closable({ imageUrl: '/WestWindWebToolkit/Samples/WebResource.axd?d=TooLongAndNastyToPrint',
handle: $('#divEditBook_Header')})

so you can see how to apply a custom image, which in this case is generated by the server control wrapping the client DragPanel.

More interesting maybe is to apply the .closable behavior to list scenarios. For example, each of the individual items in the list display also are .closable using this plug-in. Rather than having to define each item with Html for an image, event handler and link, when the client template is rendered the closable behavior is attached to the list. Here I’m using client-templating and the code that this is done with looks like this:

function loadBooks() {

    showProgress();
    
    // Clear the content
    $("#divBookListWrapper").empty();    
    
    var filter = $("#" + scriptVars.lstFiltersId).val();
    
    Proxy.GetBooks(filter, function(books) {
        $(books).each(function(i) {
            updateBook(this); 
            showProgress(true); 
        });
    }, onPageError);    
}
function updateBook(book,highlight)
{    
    // try to retrieve the single item in the list by tag attribute id
    var item = $(".bookitem[tag=" +book.Pk +"]");

    // grab and evaluate the template
    
    var html = parseTemplate(template, book);

    var newItem = $(html)
                    .attr("tag", book.Pk.toString())
                    .click(function() {
                        var pk = $(this).attr("tag");
                        editBook(this, parseInt(pk));
                    })
                    .closable({ closeHandler: function(e) {
                            removeBook(this, e);
                        },
                        imageUrl: "../../images/remove.gif"
                    });
                    

    if (item.length > 0) 
        item.after(newItem).remove();        
    else 
        newItem.appendTo($("#divBookListWrapper"));
    
    if (highlight) {
        newItem
            .addClass("pulse")
            .effect("bounce", { distance: 15, times: 3 }, 400);
        setTimeout(function() { newItem.removeClass("pulse"); }, 1200);            
    }    
}

Here the closable behavior is applied to each of the items along with an event handler, which is nice and easy compared to having to embed the right HTML and click handling into each item in the list individually via markup. Ideally though (and these posts make me realize this often a little late) I probably should set up a custom cssClass to handle the rendering – maybe a CSS class called .removebox that only changes the image from the default box image.

This example also hooks up an event handler that is fired in response to the close. In the list I need to know when the remove button is clicked so I can fire of a service call to the server to actually remove the item from the database. The handler code can also return false; to indicate that the window should not be closed optionally. Returning true will close the window.

You can find more information about the .closable class behavior and options here:
.closable Documentation

Plug-ins make Server Control JavaScript much easier

I find this plug-in immensely useful especial as part of server control code, because it simplifies the code that has to be generated server side tremendously. This is true of plug-ins in general which make it so much easier to create simple server code that only generates plug-in options, rather than full blocks of JavaScript code.  For example, here’s the relevant code from the DragPanel server control which generates the .closable() behavior:

if (this.Closable && !string.IsNullOrEmpty(DragHandleID) )
{
    string imageUrl = this.CloseBoxImage;
    if (imageUrl == "WebResource" )
        imageUrl = ScriptProxy.GetWebResourceUrl(this, this.GetType(), ControlResources.CLOSE_ICON_RESOURCE);
    
    StringBuilder closableOptions = new StringBuilder("imageUrl: '" + imageUrl + "'");

    if (!string.IsNullOrEmpty(this.DragHandleID))
        closableOptions.Append(",handle: $('#" + this.DragHandleID + "')");

    if (!string.IsNullOrEmpty(this.ClientDialogHandler))
        closableOptions.Append(",handler: " + this.ClientDialogHandler);
       
    if (this.FadeOnClose)
        closableOptions.Append(",fadeOut: 'slow'");
    
    startupScript.Append(@"   .closable({ " + closableOptions + "})");
}

The same sort of block is then used for .draggable and .shadow which simply sets options. Compared to the code I used to have in pre-jQuery versions of my JavaScript toolkit this is a walk in the park. In those days there was a bunch of JS generation which was ugly to say the least.

I know a lot of folks frown on using server controls, especially the UI is client centric as the example is. However, I do feel that server controls can greatly simplify the process of getting the right behavior attached more easily and with the help of IntelliSense. Often the script markup is easier is especially if you are dealing with complex, multiple plug-in associations that often express more easily with property values on a control.

Regardless of whether server controls are your thing or not this plug-in can be useful in many scenarios. Even in simple client-only scenarios using a plug-in with a few simple parameters is nicer and more consistent than creating the HTML markup over and over again. I hope some of you find this even a small bit as useful as I have.

Related Links

© Rick Strahl, West Wind Technologies, 2005-2010
Posted in jQuery   ASP.NET  JavaScript  
kick it on DotNetKicks.com

© West-Wind or respective owner

Related posts about jQuery

Related posts about ASP.NET