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
inlineeditor.js somewhere in your HTML file:<script type="text/javascript" src="inlineeditor.js"></script>
editable to your elements' classes:<table class="editable">...</table> or
<span class="editable">...</span>
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">.
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
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.
| Col 1 | Col 2 | Col 3 |
|---|---|---|
| Alpha | One | blah |
| Two | ugh | |
| Charlie | Three | wuh |
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.