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);
};

No comments: