Javascript Inline Editor Support This Project

About

InlineEditor is a JavaScript class (or what passes for a class) that makes it easy to let users dynamically edit an element on your web page (especially good for table contents). The class is extensible to let you hook in to these changes and make appropriate calls to a backend database or do whatever else you might want after a change is made.

The delay with the gray text is artificial. I added an artificial delay to simulate saving data back with AJAX calls.

Please let me know if you find any bugs or can make any improvements. The code is Public Domain, so if you make a contribution, I will take that as your consent to have your contribution Public Domain as well. -Robert Harder, rharder at users,sf,net

Download

inlineeditor.js
This is the script file that you must include in your HTML file.
ajax.js (optional)
This has just a few handy "ajaxy" functions that I find helpful.

Basic Usage

Step 1
Include inlineeditor.js somewhere in your HTML file:
<script type="text/javascript" src="inlineeditor.js"></script>
Step 2
Add editable to your elements' classes:
<table class="editable">...</table> or <span class="editable">...</span>
Step 3
There is no Step 3!

This will let users edit all <td> cells in all tables that have the class editable as well as any other elements with class editable such as these words right here or the paragraph below. In the case of tables you can make individual cells off-limits by adding uneditable to their class. For example, if you have a column that has commands (delete, insert, whatever) you might have multiple class names like this: <td class="commands uneditable">.

Advanced Usage

You can define/override certain functions to extend the functionality of your pages. If you need something more complex than a simple input element, you can define the InlineEditor.customEditor function. Don' forget to

  1. Set the editor's starting value
  2. Set the editor's size
The editor will replace what you're editing by setting the innerHTML property to nothing ("") and then calling appendChild.
    InlineEditor.customEditor = function( theElement )
    {
        if( theElement.id != 'state' )
            return;

        var editor = document.createElement( 'select' );
        editor.options[0] = new Option( "CA", "California" );
        editor.options[1] = new Option( "CO", "Colorado" );
        ...
        
        return editor;
    }   // end customEditor

...

If you use a custom editor, you may need to provide a way to determine what the value is. The default behavior, which will still take over if you return nothing, is to check for the presence of the value property. If your editor has no value property then the innerHTML property is used. If this suits your needs even with your custom editor, then there's no need to use this function.

    InlineEditor.editorValue = function( editor )
    {
        // Hypothetical editor with some obscure way
        // of determing what the user selection is.
        if( editor.tagName == 'SomeObscureControl' )
            return editor.squareRootOfSelectedMenuItem;
            
    }   // end editorValue

If you have anything "funny" going on, you're welcome to define/override the function InlineEditor.elementValue to determine just what the starting value is. The default behavior, which will be employed if you return nothing, is to use innerHTML.

   
   InlineEditor.elementValue = function( theElement )
    {
        // Ignore the extra 'span' I threw in there. Just give me text.
        return theElement.innerText;
        
    }   // end elementValue
 

Unless you just want people to dink around with the transient-by-nature current page, you'll probably want to define/override the InlineEditor.elementChanged function and do something that saves the user's changes. Here is an example using AJAX to immediately post a change. In this case, I used my handy ajax.js code to do it.

   
   InlineEditor.elementChanged = function( theElement, oldVal, newVal )
    {
        mySavingIndicator( theElement );
        
        var url = "http://www.myserver.com/update.php?id=" + cell.id + "&val="+newVal;
        
        AJAX.getText( url, function( response ){
        
            clearMySavingIndicator( theElement );
            alert( 'Did the save work? ' + response );
        
        }); // end ajax callback function

    };  // end elementChanged

It's a good idea to provide some kind of feedback to the user if your "save" routine will take more than a few milliseconds. In the example on this page, we define a saving class that we add to the element. We also add the special uneditable class so that the user cannot try to edit the cell while the save is taking place:

function mySavingIndicator( theElement )
{
    // Make some indication of saving theElement
    InlineEditor.addClass( theElement, 'uneditable' );
    InlineEditor.addClass( theElement, 'saving' );
    theElement.title = "Saving your change...";

}   // end mySavingIndicator

Notice that we use the handy InlineEditor.addClass( myElement, myClassName ) function. There is also a similar InlineEditor.removeClass( myElement, myClassName ) function that does what it sounds like. We'll use that in our clearMySavingIndicator( el ) function.

function clearMySavingIndicator( el )
{
    InlineEditor.removeClass( el, 'uneditable' );
    InlineEditor.removeClass( el, 'saving' );
    el.title = "";

}   // end clearMySavingIndicator

Though not used here, InlineEditor.checkClass( myElement, myClassName ) also takes an element and a classname and returns true if the class contains that classname and false otherwise. The function InlineEditor.swapClass( myElement, class1, class2 ) takes an element and two classnames and replaces class1 with class2.

Other useful helper functions include InlineEditor.columnNumber( tdCell ), InlineEditor.rowNumber( tdCell ), and InlineEditor.rowID( tdCell ) to help you identify which cell was changed, if indeed it was a table's td cell that was changed. They all expect the current cell to be passed as their single argument. The InlineEditor.rowID( tdCell ) is particularly helpful if you use that to tie the row to a record in a database when/if you dynamically generate your tables.

Note about window.onload: InlineEditor uses window.addEventListener(...) and window.attachEvent(...) to add InlineEditor.init() to the window's onload event. If those two functions are unavailable (like with Netscape 4), it falls back to window.onload = function(){... which is less friendly since it either wipes out any existing onload function you have or else your function will wipe it out, depending on the sequence of calls. If you aren't using window.onload for anything else, you should have nothing to worry about. If you're using window.onload for anything, feel free to modify the last few lines of "InlineEditor.js" to remove the window.onload code and simply ensure that you call InlineEditor.init() somewhere along the way.

Example

Col 1Col 2Col 3
AlphaOneblah
BravoTwough
CharlieThreewuh

Change Log

Thanks to MT Jordan for these improvements:

Updates by MT Jordan <mtjo62 # gmail com>

This update adds support for Opera 8.5+ and Safari 3
--------------------------------------------------------

Tested browsers:

Firefox 1.5 and 2.0
Safari 3 on WinXP
Konquorer 3.5.6
Opera 8.5, 9 and 9.2
Internet Explorer 6 and 7
-------------------------------------------------------

* Added global var to test for IE.

* Changed recursiveAddDblClickHandler and handleDblClick to recursiveAddOnClickHandler 
  and handleOnClick respectively.  This was done because Opera opens a context menu 
  when plain text is double clicked.

* Changed recursiveAddOnClickHandler to add onclick handler.

* In the function fixEvent, added a conditional to test for only IE for E.srcElement. 
  Opera craps out if it sets this object.

* In the functions addEvent and removeEvent, removed the conditionals 
  target.eval('on'+eventName) = func; and target.eval('on'+eventName) = null;
  Safari craps out if these are included.