var xmlHttp /*function getForm(str) { xmlHttp=GetXmlHttpObject() if (xmlHttp==null) { alert ("Browser does not support HTTP Request") return } var url="/index.php?task=ajax" url=url+"&type="+str if(getForm.arguments[1]!=null) { url=url+"&id="+getForm.arguments[1] } xmlHttp.open("GET",url,true) xmlHttp.onreadystatechange=stateChanged xmlHttp.send(null); } function stateChanged() { if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete") { response = xmlHttp.responseText; } } */ function GetXmlHttpObject() { var xmlHttp=null; try { // Firefox, Opera 8.0+, Safari xmlHttp=new XMLHttpRequest(); } catch (e) { //Internet Explorer try { xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); } } return xmlHttp; } /** * Class: InlineEditor * * Version 0.1 (pending) * * License: Public Domain * * * User-overridable functions: * More documentation further down in code. * * InlineEditor.customEditor = function( theElement ) { ... * InlineEditor.editorValue = fuction( theEditor ) { ... * InlineEditor.elementValue = function( theElement ) { ... * InlineEditor.elementChanged = function( theElement, oldVal, newVal ) { ... * * * Key CSS class names: * editable: Tables with this class will have their 'td' cells made editable. * uneditable: At event time, cells with this class will NOT be editable. * editing: When editing, the 'td' element will have this class. * * * Useful utility functions: * * InlineEditor.addClass( element, classname ) * Adds classname to the 'class' attribute of the element. * * InlineEditor.removeClass( element, classname ) * Removes classname from the 'class' attribute of the element. * * InlineEditor.checkClass( element, classname ) * True if class attribute of the element contains classname, false otherwise. * * InlineEditor.swapClass( element, classname1, classname2 ) * Replaces classname1 with classname2 in class attribute of the element. * * InlineEditor.columnNumer( cell ) * Returns column number of cell (zero index) or -1 if there are problems. * * InlineEditor.rowNumer( cell ) * Returns row number of cell (zero index) or -1 if there are problems. * * InlineEditor.rowID( cell ) * Returns row ID, useful if you use that to tie to a database primary key. * * * Change Log: * * v0.1.1 - More reliable window.onload event adding. Added customEditor * extensible function. * v0.1 - Initial release. * * * Author: * * Robert Harder * rharder # users,sf,net */ /** Global var to test for IE */ var inlineeditor_isIE = ( navigator.userAgent.toLowerCase().search( 'msie' ) != -1 && navigator.userAgent.toLowerCase().search( 'opera' ) == -1 ) ? true : false; var InlineEditor = { /* ******** F U N C T I O N S Y O U M I G H T C A L L ******** */ alreadyInited : false, // In case we get called twice. /** * This should be called automatically when the page loads, * but if you also are setting up a function to run on * window.onload then this might get bumped out of position. * If that's the case, then make sure you call InlineEditor.init() * yourself. * * If you create some nodes programmatically, you can also rerun * this code to scan for "editable" classes. Like this: * * var foo document.createElement('div'); * foo.className = 'editable'; * ... * InlineEditor.init( foo ); */ init: function( arg ) { // What is the arg? var isNode = false; var isEvent = false; if( arg.nodeType ) isNode = true; // If we're already inited and we're not being asked to // init a new a node, bail out. if( !isNode && InlineEditor.alreadyInited ) return; // Find all elements with class 'editable' and make them editable var rootEl = isNode ? arg : document; var allEl = rootEl.getElementsByTagName('*'); for (var i= 0; i < allEl.length; i++ ){ if( InlineEditor.checkClass( allEl[i], 'editable' ) ){ switch( allEl[i].tagName ){ // For tables, set up individual cells. case 'TABLE': var tds = allEl[i].getElementsByTagName( 'td' ); for( var j = 0; j < tds.length; j++ ) InlineEditor.recursiveAddOnClickHandler( tds[j] ); break; // Default behavior is to set up all editable elements // which requires setting up all its children elements. // This shouldn't be necessary, but these 'dblclick' events // don't seem to propagate through the elements to parents. default: InlineEditor.recursiveAddOnClickHandler( allEl[i] ); } // end switch: tagname } // end if: editable } // end for: each element if( !isNode ) InlineEditor.alreadyInited = true; }, // end init addClass: function(o,c) { return InlineEditor.jscss('add',o,c); }, removeClass: function(o,c) { return InlineEditor.jscss('remove',o,c); }, checkClass: function(o,c) { return InlineEditor.jscss('check',o,c); }, swapClass: function(o,c1,c2) { return InlineEditor.jscss('swap',o,c1,c2); }, /** * Return the column number, if a table cell. */ columnNumber: function( cell ) { // Ensure we have a 'td' cell if( cell.nodeType != 1 ) return -1; if( cell.tagName != 'TD' ) return -1; // Find cell and return column number if( !cell.parentNode || cell.parentNode.tagName != 'TR' ) return -1; var tr = cell.parentNode; var tds = tr.getElementsByTagName('TD'); for( var i = 0; i < tds.length; i++ ) if( tds[i] == cell ) return i; return -1; }, // end columnNumber /** * Return the row number, based on the row's immediate * parent, which may be a 'tbody' or the actual 'table'. */ rowNumber: function( cell ) { // Ensure we have a 'td' cell if( cell.nodeType != 1 ) return -1; if( cell.tagName != 'TD' ) return -1; // Find cell's parent row and return row number if( !cell.parentNode || cell.parentNode.tagName != 'TR' ) return -1; var tr = cell.parentNode; var trs = tr.parentNode.childNodes; for( var i = 0; i < trs.length; i++ ) if( trs[i] == tr ) return i; return -1; }, // end rowNumber /** * Returns the ID of the parent row. Useful if you use * that to track the row to some sort of database primary key. */ rowID: function( cell ) { // Ensure we have a 'td' cell if( cell.nodeType != 1 ) return -1; if( cell.tagName != 'TD' ) return -1; if( !cell.parentNode || cell.parentNode.tagName != 'TR' ) return -1; var tr = cell.parentNode; return tr.id; }, // end rowID sizeTo: function( changeMe, model ) { changeMe.style.position = 'absolute'; changeMe.style.zindex = 99; changeMe.style.left = model .offsetLeft + 'px'; changeMe.style.top = model .offsetTop + 'px'; changeMe.style.width = model .offsetWidth + 'px'; changeMe.style.height = model .offsetHeight + 'px'; return changeMe; }, // end sizeTo getID: function( id ) { var request = GetXmlHttpObject(); // I was using Google's tools var url = "/index.php?task=ajax&call=edit&id=" + id; request.open("GET", url, true); request.onreadystatechange = function() { if (request.readyState == 4) { if( navigator.appName == "Opera" ) { document.getElementById("ebox").value = request.responseText; } else { document.getElementById("ebox").innerHTML = request.responseText; } } // end if: readystate 4 }; // end onreadystatechange request.send(null); }, getHTML: function( id ) { var request = GetXmlHttpObject(); // I was using Google's tools var url = "/index.php?task=ajax&call=editHTML&id=" + id; request.open("GET", url, true); request.onreadystatechange = function() { if (request.readyState == 4) { document.getElementById(id).innerHTML = request.responseText; } // end if: readystate 4 }; // end onreadystatechange request.send(null); }, /* ******** F U N C T I O N S Y O U M I G H T O V E R R I D E ******** */ // These are examples of how you might override certain functions // if you want to add more complex behaviors. // The default editor is a one-line 'input' element. // If you need anything more complex like a textarea // or a select box or something, return it here. // // Remember to code the following: // // - Set the editor's starting value // - Set the editor's size /* // InlineEditor.customEditor: = function( theElement ) { // Only interested in setting up something custom // for paragraph tags. if( theElement.tagName != 'span' ) return; var editor = document.createElement( 'textarea' ); editor.innerHTML = theElement.innerHTML; editor.style.width = "100%"; editor.style.height = theElement.offsetHeight + "px"; 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. return editor.value; } // end editorValue // If you have anything "funny" going on you're welcome // to define/override this function 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 ) { alert(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 this 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 was using Google's Map APIs, so that's how I create // the HttpRequest. // */ elementChanged: function( id, newVal,theElement ) { InlineEditor.addClass( theElement, 'uneditable' ); // Special InlineEditor class var request = GetXmlHttpObject(); // I was using Google's tools var newvale = newVal.replace(/\n/g, '.||.'); var url = "/index.php?task=ajax&call=upedit&id=" + id +"&text=" + newvale; request.open("GET", url, true); request.onreadystatechange = function() { if (request.readyState == 4) { InlineEditor.removeClass( theElement, 'uneditable' ); document.getElementById(id).innerHTML = request.responseText; } // end if: readystate 4 }; // end onreadystatechange request.send(null); }, // end elementChanged // end elementChanged /* ******** F U N C T I O N S Y O U S H O U L D N O T C A L L ******** */ recursiveAddOnClickHandler: function( element ) { element.onclick = InlineEditor.handleOnClick; if( element.childNodes ){ children = element.childNodes; for( i = 0; i < children.length; i++ ){ if( children[i].onclick ){ InlineEditor.recursiveAddOnClickHandler( children[i] ); } // end if: child also needs handler } // end for: each child } // end if: children }, // end recursiveAddOnClickHandler /** * Called when element is double-clicked. * At the time of this event, the class is checked to see * if the cell is marked 'uneditable'. */ handleOnClick: function( evt ) { var evt = InlineEditor.fixEvent( evt ); //var target = evt.target; var target = InlineEditor.findEditableTarget( evt.target ); // If element is "uneditable" or "editing" don't edit. if( InlineEditor.checkClass( target, 'uneditable' ) || InlineEditor.checkClass( target, 'editing' ) ) return; // Save original value. var oldHTML = target.innerHTML; var oldVal = null; if( InlineEditor.elementValue ) // USER CAN PROVIDE OVERRIDE FUNCTION oldVal = InlineEditor.elementValue( target ); if( !oldVal ) oldVal = target.innerHTML; // Set up editor element // User overridable function var editor = null; if( InlineEditor.customEditor ){ // USER CAN PROVIDE OVERRIDE FUNCTION editor = InlineEditor.customEditor( target ); } // end if: customEditor if( !editor ) { // If user didn't provide custom editor editor = document.createElement('textarea') //editor.innerHTML = oldVal; editor.setAttribute('id','ebox') editor.setAttribute('name','ebox') // editor.style.position = 'absolute'; // editor.style.zindex = 99; // editor.style.left = target.offsetLeft + 'px'; // editor.style.top = target.offsetTop + 'px'; editor.style.width = '500px'; editor.style.height = '70px'; } // end else: default // Listen for when focus is lost. editor.onblur = function(){ InlineEditor.handleInputBlur( editor, target.id, oldVal, oldHTML ); } // Prep target InlineEditor.addClass( target, 'editing' ); // Add editor InlineEditor.getID(target.id); target.innerHTML = ""; target.appendChild( editor ); editor.focus(); return false; // Don't propagate up. No need. Right? }, // end handleDoubleClick /** * Called when user is done editing the cell. */ handleInputBlur: function( editor, id, oldVal, oldHTML ) { // Gather values var parent = editor.parentNode; var newVal = null; if( InlineEditor.editorValue ) newVal = InlineEditor.editorValue( editor ); if( !newVal ) newVal = editor.value ? editor.value : editor.innerHTML; // Fallback // Save value in the element // If user wants to know of the change, pass it on. if( InlineEditor.elementChanged ) InlineEditor.elementChanged( id, newVal, parent ); //InlineEditor.getHTML(id); InlineEditor.removeClass( parent, 'editing' ); }, // end handleInputBlur /** * Thanks to http://www.onlinetools.org/articles/unobtrusivejavascript/cssjsseparation.html * for this bit of code. */ jscss: function(a,o,c1,c2) { switch (a){ case 'swap': o.className=!InlineEditor.jscss('check',o,c1) ? o.className.replace(c2,c1) : o.className.replace(c1,c2); break; case 'add': if(!InlineEditor.jscss('check',o,c1)){o.className+=o.className?' '+c1:c1;} break; case 'remove': var rep=o.className.match(' '+c1)?' '+c1:c1; o.className=o.className.replace(rep,''); break; case 'check': return new RegExp('\\b'+c1+'\\b').test(o.className) break; } // end switch: action }, // end jscss fixEvent: function( evt ) { var E = evt ? evt : window.event; // 'event' seems to be a special word in IE, so I'm using 'E' instead. if( E.target ) if( E.target.nodeType == 3 ) E.target = E.target.parentNode; //make sure Opera doesn't set this object if( inlineeditor_isIE ) if( E.srcElement ) E.target = E.srcElement; return E; }, // end fixEvent findEditableTarget: function( target ) { // If a table cell, we assume that's editable if( target.nodeType == 1 && target.tagName == 'TD' ) return target; if( InlineEditor.checkClass( target, 'editable' ) ) return target; if( target.parentNode ) return InlineEditor.findEditableTarget( target.parentNode ); return null; }, // end findEditableTarget addEvent: function( target, eventName, func, capture ) { if( target.addEventListener ){ target.addEventListener( eventName, func, capture ); return true; } // end if: addEventListener else if( target.attachEvent ) return target.attachEvent( 'on'+eventName, func ); }, // end addEvent removeEvent: function( target, eventName, func, capture ) { if( target.removeEventListener ){ target.removeEventListener( eventName, func, capture ); return true; } // end if: removeEventListener else if( target.detachEvent ) return target.detachEvent( 'on'+eventName, func ); } // end removeEvent } // end class InlineEditor /** * Add InlineEditor.init() to window.onload. */ InlineEditor.addEvent(window,'load',InlineEditor.init,false);