A Closable jQuery Plug-in
- by Rick Strahl
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:
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
Download jquery.closable
West Wind Web Toolkit jQuery Plug-ins
© Rick Strahl, West Wind Technologies, 2005-2010Posted in jQuery ASP.NET JavaScript