jQuery custom events example

"Cowboy" Ben Alman http://benalman.com/news/2010/02/jquery-custom-events/

by Ben Alman

HTML

<!--
    jQuery Custom Events - "Cowboy" Ben Alman
    http://benalman.com/news/2010/02/jquery-custom-events/
-->

<h1>Custom events example</h1>

<div id="error"></div>

<div class="widget" id="widget_a">
    <div class="widget-head">
        <h2>Widget A</h2>
        <a href="#" class="close">X</a>
    </div>
    <div class="widget-body">
        <p>
            This is sample widget A. It can be removed without constraints.
        </p>
    </div>
</div>

<div class="widget" id="widget_b">
    <div class="widget-head">
        <h2>Widget B</h2>
        <a href="#" class="close">X</a>
    </div>
    <div class="widget-body">
        <p>
            This is sample widget B. It cannot be removed unless its checkbox is checked.
        </p>
    </div>
    <div class="widget-footer">
        <label>
            <input type="checkbox" name="can_remove" value="1">
            Allow widget removal?
        </label>
    </div>
</div>

<div class="widget" id="widget_c">
    <div class="widget-head">
        <h2>Widget C</h2>
        <a href="#" class="close">X</a>
    </div>
    <div class="widget-body">
        <p>
            This is sample widget C. It cannot be removed unless all preceding widgets have also been removed.
        </p>
    </div>
</div>

<div id="admin_control">
    <h2>Widget admin control</h2>
    <p><a href="#" class="widget_a_remove">Remove widget A</a></p>
    <p><a href="#" class="widget_b_remove">Remove widget B</a></p>
    <p><a href="#" class="widget_c_remove">Remove widget C</a></p>
</div>

CSS

body {
    font-size: 80%;
    font-family: Arial, Helvetica, sans-serif;
}

h1, h2 {
    font-weight: 700;
    font-family: "Gill Sans", "Gill Sans MT", Arial, Helvetica, sans-serif;
}

h1 {
    color: #C4884F;
    font-size: 150%;
    margin-bottom: 0.6em;
}

h2 {
    font-size: 120%;
    font-weight: 700;
}

#error {
    display: none;
    color: #c00;
    border: 2px solid #c00;
    background: #fcc;
    padding: 0.5em;
    position: absolute;
    top: 5px;
    left: 10px;
    right: 10px;
    z-index: 100;
}

.widget {
    border: 2px solid #C4884F;
    background: #FDEBDC;
    margin-bottom: 0.6em;
    position: relative;
}

.widget .close {
    position: absolute;
    top: 0.2em;
    right: 0.5em;
    line-height: 0.9em;
    padding: 0.1em;
    font-weight: 700;
    color: #C4884F;
    background: #FDEBDC;
    border: 1px solid #C4884F;
    text-decoration: none;
}

.widget .close:hover {
    color: #fff;
    background: #f00;
    border-color: #fff;
}

.widget-head {
    padding: 0.2em 0.5em;
    color: #fff;
    background: #C4884F;
}

.widget-body {
    padding: 0.5em 0.5em;
}

.widget-footer {
    padding: 0.2em 0.5em;
    border-top: 1px solid #C4884F;
}

#admin_control {
    margin-top: 2em;
}

#admin_control h2 {
    margin-bottom: 0.6em;
}

JavaScript

// Bind a default handler for the custom "remove" event, to display error messages.
var message_id;
$(document).bind( 'remove', function(event){
    var message = 'Widget could not be removed, ' + event.error_message;
    
    // Show the error message.
    $('#error').stop( true ).text( message )
        removeAttr( 'style' ).fadeIn( 'fast' );
    
    // Fade error message out after 2 seconds.
    message_id && clearTimeout( message_id );
    message_id = setTimeout(function(){
        $('#error').fadeOut( 'fast' );
    }, 2000 );
});

// Bind widget A's custom "remove" event.
$('#widget_a').bind( 'remove', function(event){
    // Remove widget A from the DOM.
    $(this).remove();
    
    // Stop the event from bubbling up the DOM tree.
    event.stopPropagation();
});

// Bind widget B's custom "remove" event.
$('#widget_b').bind( 'remove', function(event){
    var can_remove = $(this).find('input[name=can_remove]:checked').length;
    
    if ( !can_remove ) {
        event.error_message = 'removal checkbox must be checked first.'
    } else {
        // Remove widget A from the DOM.
        $(this).remove();
        
        // Stop the event from bubbling up the DOM tree.
        event.stopPropagation();
    }
});

// Bind widget C's custom "remove" event.
$('#widget_c').bind( 'remove', function(event){
    var can_remove = $(this).prevAll('.widget').length === 0;
    
    if ( !can_remove ) {
        event.error_message = 'all preceding widgets must be closed first.'
    } else {
        // Remove widget A from the DOM.
        $(this).remove();
        
        // Stop the event from bubbling up the DOM tree.
        event.stopPropagation();
    }
});

// When a widget 'X' close link is clicked, trigger the
// 'remove' event for that widget.
$('.widget .close').bind( 'click', function(event){
        $(this).closest('.widget').trigger('remove');
});

// Admin control logic. Nice and dumb, the way we like it.
$('#admin_control').each(function(){
    
   ...