Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

A Closable jQuery Plug-in


:P
On this page:

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

Posted in jQuery  ASP.NET  JavaScript  

The Voices of Reason


 

Rick Strahl
April 14, 2009

# re: A Closable jQuery Plug-in

Of course within the first few minutes of posting all the books in the sample have been wiped out. Ah yes the joys of samples online. If you find the sample without data in it, you can add new books.

Please, if you go in and delete one or more items be so kind as to add some back.

Aloha.

Josh Elster
April 14, 2009

# re: A Closable jQuery Plug-in

Rick,

Fantastic example, as usual! Two questions for you:

First, would it make sense for your server control / client side js to use live event handlers rather than hooking them up piecemeal, or is this not an appropriate situation for their use?

Second, neat integration with AWS! I'm working on a personal book website to track my own books and reading (I saw that you had some Scalzi going a few weeks ago - good choice!), and I'd be interested to know more about how you're using AWS for this.

Regards,

Josh

Mike S
April 14, 2009

# re: A Closable jQuery Plug-in

[x]html has no tags, rather elements. Unless you're into graffiti?

If I was juvenile I would be doing jQuery tags on trains. If only I had a blue box that could travel through time...

Majnun
April 14, 2009

# re: A Closable jQuery Plug-in

Rick: I think there's a problem with the demo. I tried to add some LGBT books and it wouldn't let me...

Roger Pence
April 14, 2009

# re: A Closable jQuery Plug-in

Damn, I so want to be you when I grow up (and I sure that I am a lot older than you)! Superb post. Great work.

Rick Strahl
April 14, 2009

# re: A Closable jQuery Plug-in

@Josh - you can download the sample app from the West Wind Web Toolkit site. I use this app on my site for managing the Web Log what am I reading now books. This is also why this isn't really a multi-user app which makes the sample a little difficult to use when it's busy as multiple people are updating the same list :-}. Anyway you can grab the sample from:

http://www.west-wind.com/WestwindWebToolkit/

and using the Download link. It's in the Ajax examples subtree.

Re: Live events. Live events are useful, but more so if you have really are adding items later over which you don't have easy control. In this case I have to do a bunch of stuff to the new element anyway - assign an id most importantly - that requires parsing over the list. Assigning behavior at that time makes more sense to me than using Live events which is a lot less transparent and also comes at a perf cost. Live events have their place, but I avoid them if there is a more direct way to assign event logic or if as in this case you're already going over the items anyway.

Rick Strahl
April 14, 2009

# re: A Closable jQuery Plug-in

@Mike - Tag names: Yes I used custom attributes which aren't XHTML compliant, but then this is dynamically added to the document so the logic in the document is post rendering that it doesn't make much of a difference for validation.

You got a good point though - in this scenario it would make more sense to assign the tag to the jQuery .data() element instead since the code is already parsing over the elements anyway. This is a hold over from the original server side code that had to generate some state back to the client so that the right book could be selected.

ChrisB
April 15, 2009

# re: A Closable jQuery Plug-in

Another nice post. I agree Rick plugins are great. Here is a very simple fieldset replacement that uses the jQueryUI css classes:

(function($) {

$.widget('ui.fieldset',
{
_init: function() {
this.element.addClass('ui-corner-all ui-widget-content');
this.element.css("float", "left");
this.element.css("margin-top", "30px");
this.element.children('span').addClass('ui-corner-all ui-widget-content ui-fieldset-legend');
this.element.children('div').addClass('ui-fieldset-body');
}
}
);

})(jQuery);

LeaseNut
April 15, 2009

# re: A Closable jQuery Plug-in

Man how do you get the time to come up with this stuff!! Awesome.. Its just given me some ideas for our new app/website! Legend... :)

David Robbins
April 25, 2009

# re: A Closable jQuery Plug-in

I like the fact that you used the template with your example, as it drives home the fact that you can simplify the process of assembling data on the server, and manipulate it on the client as you see fit. This is a nice re enforcement of single responsibility, and as you said, it keeps the mark up cleaner.

Good stuff!

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024