    // for random key generator
    var RANDOM_CHARS = [
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9
        ];


    //
    // a jquery plugin to make any element editable
    //
    // ACCEPTS: {
    //  add_edit_button  : true/false (default: false)
    //  save             : function() { do something after save   }
    //  cancel           : function() { do something after cancel }
    // }
    //
    // RETURNS: each element updated
    //
    var EDITABLE_ORIGINAL_HTML_FOR_KEY   = {};
    var EDITABLE_SAVE_FUNCTION_FOR_KEY   = {};
    var EDITABLE_CANCEL_FUNCTION_FOR_KEY = {};
    jQuery.fn.editable = function(opts) {
        if (!opts) opts = {};
        var add_edit_button = opts[ 'add_edit_button' ];
        var save_func       = opts[ 'save'            ];
        var cancel_func     = opts[ 'cancel'          ];

        // add editable functions to each found element
        return jQuery(this).each(function(){
            // create edit button
            var this_key    = jQuery.random_key(24);
            var edit_button = document.createElement('a');
            jQuery(edit_button).attr({ 'class': '__jqcafe_editable_button_edit', 'id': '__jqcafe_editable_edit:' + this_key }).html('<span>edit</span>').click( editable_edit );

            // save this html
            EDITABLE_ORIGINAL_HTML_FOR_KEY[ this_key ] = jQuery(this).html();

            // and the callbacks
            EDITABLE_SAVE_FUNCTION_FOR_KEY[   this_key ] = save_func;
            EDITABLE_CANCEL_FUNCTION_FOR_KEY[ this_key ] = cancel_func;

            // append edit button to current html
            jQuery(this).append(' ').append(edit_button);
        });
    }

    function editable_save() {
        // get our key
        var this_key = editable_get_key(this);

        // get the html value from the input
        var new_html = jQuery(this).siblings('input').val();

        // remember this html
        EDITABLE_ORIGINAL_HTML_FOR_KEY[ this_key ] = new_html;

        // create edit button
        var edit_button = document.createElement('a');
        jQuery(edit_button).attr({ 'class': '__jqcafe_editable_button_edit', 'id': '__jqcafe_editable_edit:' + this_key }).html('<span>edit</span>').click( editable_edit );

        // change html for parent
        jQuery(this).parent().html(new_html + ' ').append(edit_button);

        // if we have a save callback, execute it here
        if (EDITABLE_SAVE_FUNCTION_FOR_KEY[ this_key ]) {
            EDITABLE_SAVE_FUNCTION_FOR_KEY[ this_key ](jQuery(this).parent,new_html);
        }

        return true;
    }

    function editable_cancel() {
        // get our key
        var this_key = editable_get_key(this);

        // get original html for this key
        var this_html = EDITABLE_ORIGINAL_HTML_FOR_KEY[ this_key ];

        // create edit button
        var edit_button = document.createElement('a');
        jQuery(edit_button).attr({ 'class' : '__jqcafe_editable_button_edit', 'id' : '__jqcafe_editable_edit:' + this_key }).html('<span>edit</span>').click( editable_edit );

        // change html for parent
        jQuery(this).parent().html(this_html + ' ').append(edit_button);

        // if we have a cancel callback, execute it here
        if (EDITABLE_CANCEL_FUNCTION_FOR_KEY[ this_key ]) {
            EDITABLE_CANCEL_FUNCTION_FOR_KEY[ this_key ](jQuery(this).parent,this_html);
        }

        return true;
    }

    function editable_edit() {
        // get our key
        var this_key = editable_get_key(this);

        // get html for this key
        var this_html = EDITABLE_ORIGINAL_HTML_FOR_KEY[ this_key ];

        // add to input element
        var input = document.createElement('input');
        jQuery(input).attr({ 'class': '__jqcafe_editable_input', value: this_html });
        
        // create save and cancel buttons
        var save_button   = document.createElement('a');
        var cancel_button = document.createElement('a');
        jQuery( save_button   ).attr({ 'class': '__jqcafe_editable_button_save',   id: '_jqcafe_editable_save:'   + this_key }).html( '<span>save</span>'   ).click( editable_save   );
        jQuery( cancel_button ).attr({ 'class': '__jqcafe_editable_button_cancel', id: '_jqcafe_editable_cancel:' + this_key }).html( '<span>cancel</span>' ).click( editable_cancel );

        // change html for parent
        jQuery(this).parent().html(input).append(' ').append(save_button).append(' ').append(cancel_button);

        return true;
    }

    function editable_get_key(elem) {
        // get id for this edit button
        var id = jQuery(elem).attr('id');
        // determine the key
        var id_bits  = id.split(':');
        var this_key = id_bits.pop();

        return this_key;
    }

    //
    // a jquery plugin to handle adding options to a select element
    //
    // ACCEPTS: { options: [ [ display text, value attribute ], .. ] }
    //
    // RETURNS: each element updated
    //
    jQuery.fn.selectOptions = function(opts) {
        var options = opts[ 'options' ];

        return jQuery(this).each(function(){
            // erase any existing options
            jQuery(this).html('');

            // add our new options
            for (var i=0;i<options.length;i++) {
                this.options[this.options.length] = new Option(options[i][0],options[i][1],false,false);
            }
        });

    }

    //
    // a jquery plugin to get form input and shove it into a hash
    //
    // ACCEPTS: your hash (optional)--should be called only on forms, though
    //  eg: var form_data = jQuery('form[name=foo]').formData();
    //
    // RETURNS: { key: [ value, value, value ], key: [ value ], ... etc. }
    //
    jQuery.fn.formData = function(form_data) {
        if (!form_data) form_data = {};
        jQuery(this).each(function(){
            jQuery(this).find(':input:not(:checkbox):not(:radio):not(:disabled),:checkbox:checked:not(:disabled),:radio:checked:not(:disabled)').each(function(){
                var this_name = jQuery(this).attr('name');
                var this_val  = jQuery(this).val();

                // make sure we have an array for this element
                if (!form_data[ this_name ]) form_data[ this_name ] = [];

                // if our value is an object (presumably a list), then we need to merge it with whatever we already have
                // otherwise we just push it on.
                (this_val && typeof this_val == 'object') ? jQuery.merge(form_data[ this_name ],this_val) : form_data[ this_name ].push(this_val);
            });
        });

        return form_data;
    }

    //      
    // update an element by "pulsing" it.
    //      
    // ACCEPTS: {
    //  fadeOut             : 100,
    //  fadeIn              : 500,
    //  html                : status,
    //  selector_to_update  : '#some_id', // this allows you to update the html of an element other than the one you're pulsing on and off
    //  addClass            : 'updated'
    // }    
    //          
    // RETURNS: true;
    //  
    jQuery.fn.htmlPulse = function(opts) {
        if (!opts) opts = {};

        var fadeOut             = opts[ 'fadeOut'            ];
        var fadeIn              = opts[ 'fadeIn'             ];
        var html                = opts[ 'html'               ];
        var addClass            = opts[ 'addClass'           ];
        var selector_to_update  = opts[ 'selector_to_update' ];

        if (!fadeOut) fadeOut = 'fast';
        if (!fadeIn)  fadeIn  = 'slow';

        return jQuery(this).each(function(){
            jQuery(this).fadeOut(fadeOut,function(){
                // handle html updating
                if (html && selector_to_update) {
                    jQuery(selector_to_update).html(html);
                }
                else if (html) {
                    jQuery(this).html(html);
                }

                // add our class if we have one
                if (addClass) jQuery(this).addClass(addClass);

                // fade in
                jQuery(this).fadeIn(fadeIn);
            });
        });
    }  


    //
    // utility functions follow
    //
    //
    // create a random key in 
    //
    jQuery.random_key = function(length) {
            var random_key = '';
            for (var i=0;i<length;i++) {
                var this_random_int = parseInt(Math.random() * 62);
                random_key = random_key + RANDOM_CHARS[ this_random_int ];
            }
            return random_key;
        }

