Wednesday, November 26, 2008

Custom Scriptaculous Ajax InPlaceEditor

The following code demonstrates how to customize the Scriptaculous library's InPlaceEditor to make it look and feel exctly how you want.  It also demonstrates the general techniques that can be used to extend any of the Scriptaculous Event classes as well as the Prototype base classes (as I will demonstrate in a later post).  All of my customizations are controlled by options passed in when creating your IPE instance and do not alter the original behaviour of the IPE classes.

My intention here is to isolate my customizations so that the libraries can be updated at will and my modifications will continue to work.

I will post a sample of the code in action and describe what I am doing here at a later time. For now, here's the code.

// Custom controls.  Must be loaded after prototype & scriptaculous.

Object.extend(Ajax.InPlaceEditor.DefaultOptions, {
  showHighlight: true,  // false/true
  showSaving: true,     // false/true
  lockoutOnSave: true   // false/true
});

/**
 * The following fixes are from http://www.mail-archive.com/rubyonrails-spinoffs@googlegroups.com/msg11429.html  
 * They fix the cancelling/escape issues with the IPE.
 *
 */

/**
 * Removed the checkForEscapeOrReturn binding on keydown because it doesn't
 * do anything attached to the span element.
 */
Ajax.InPlaceEditor.Listeners = {
  click: 'enterEditMode',
  mouseover: 'enterHover',
  mouseout: 'leaveHover'
};
 
Ajax.InPlaceEditor.prototype.initialize = Ajax.InPlaceEditor.prototype.initialize.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  //proceed(args[0], args[1], args[2] ? args[2] : null);
  proceed.apply(null, args);
  this._boundKeyListener = this.checkForEscapeOrReturn.bindAsEventListener(this);
});

Ajax.InPlaceEditor.prototype.enterEditMode = Ajax.InPlaceEditor.prototype.enterEditMode.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  var e = args[0] ? args[0] : null;
  //proceed(); // args[0] ? args[0] : null 
  proceed.apply(null, args);
  // The form won't exist if we are saving, so check before setting the key listener
  if (this._controls.editor) {
    this._controls.editor.observe('keydown', this._boundKeyListener);
    if (e.rangeOffset != undefined) {
      setCaretTo(this._controls.editor, e.rangeOffset);
    }
    
  }
});

Ajax.InPlaceEditor.prototype.removeForm = Ajax.InPlaceEditor.prototype.removeForm.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  if (this._controls.editor) {
    this._controls.editor.stopObserving('keydown', this._boundKeyListener);
  }
  proceed.apply(null, args);
});

/**
 * Don't submit the form if nothing has changed.
 *
 */
 
Ajax.InPlaceEditor.prototype.handleFormSubmission = Ajax.InPlaceEditor.prototype.handleFormSubmission.wrap( function() {
  var args = $A(arguments), proceed = args.shift();
  var e = args[0] ? args[0] : null;
  //proceed(e);
  //return;
  //alert($A(arguments));
  var value = $F(this._controls.editor);
  var original_value = this.getText();
  //alert("Original value is: " + original_value + ", new value is " + value + ", values are equal? " + (original_value == value));
  if (original_value == value) {
    alert("nothing changed");
    // Stop the event and return.  Prepare submission removes the form and does
    // some cleanup that we need to do.
    this.prepareSubmission();
    if (e) Event.stop(e);
    return;
  } else {
    alert("submitting ajax request");
    proceed.apply(null, args);
  }
});

/**
 * Don't prevent the user from editing the field while the value is being saved.
 */

Ajax.InPlaceEditor.prototype.prepareSubmission = Ajax.InPlaceEditor.prototype.prepareSubmission.wrap( function() {
  var args = $A(arguments), proceed = args.shift();
  proceed.apply(null, args);
  if (!this.options.lockoutOnSave) {
    this._saving = false;
  }
});


/**
 * Don't show "Saving..." if showSaving is turned off.
 */

Ajax.InPlaceEditor.prototype.showSaving = Ajax.InPlaceEditor.prototype.showSaving.wrap(function() {
  if (!this.options.showSaving) {
    this._boundWrapperHandler();
    return;
  } else {
    var args = $A(arguments), proceed = args.shift();
    proceed.apply(null, args);
  }
});
  
/**
 * Don't highlight if highlighting is turned off.
 */
 
var checkIfHighlighting = function() {
  //alert("check if highlighting: " + this.options.hoverClassName);
  if (!this.options.showHighlight) return;
  var args = $A(arguments), proceed = args.shift();
  proceed.apply(null, args);
};

Debugging applications running in OC4J in Eclipse

Being able to breakpoint your Java code and step through it in Eclipse is essential for timely development.  But every time I go to create a new OC4J debugger instance in Eclipse I don't know where to start! And nothing on the web helped me out.  So here are my settings which work for me.  Hopefully they will work for you too.

A note on versions. I am using Eclipse 3.3.2 and OC4J 10.1.3.2.0 which I have installed at C:\appservers\oc4j10.1.3.2.0.  My Java version is 1.5.0_11.

Click Run -> Open Debug Dialog.  Create a new Java Application and give it a name.  Now configure each tab according to the screenshots.  The project I'm debugging is called regis. I don't know if you have to supply the project or not, but obviously whereever you see regis you'll want to replace that with your own project.

(Click on the screenshots to see a larger version.)

Some of the arguments here are optional. -Xmx512M just gives more memory to the process.

The libraries I have on the Source tab are also optional.  They're useful though when you want to step into Struts method calls etc.

Hopefully after duplicating my settings you are now ready to debug!