Editing Text In-line

Single-line

Some editable text

Multi-line

Description

Multi-line paragraph with lots...

and lots, well not really *lots*,

of editable text.

The EditableText widget allows users to edit document text in-place.

Don't forget to include the widget's CSS files!

  <script type="text/javascript" src="../../build/yui/3.0.0pr2/build/yui/yui.js"></script>
  <script type="text/javascript" src="../../build/inlineedit/editor.js"></script>
  <script type="text/javascript" src="../../build/anim/anim.js"></script>

  <link type="text/css" rel="stylesheet" href="../../build/inlineedit/assets/skins/sam/editor.css"></link>
YUI().use('lazr.editor', function(Y) {

    var editor = new Y.EditableText(...);

});

Widget setup

DOM Structure and initalization

The widget requires a well-structured block of elements. Various parts of the block will be discovered by the widget. Here's an example of some existing markup:

<div id="editable_text">
  <span id="text">Some editable text</span>
  <button id="trigger">Edit</button>
</div>

The editor requires three DOM elements to work correctly:

Optional:

Here is some example JavaScript to hook up the above DOM structure:

var etext = new Y.EditableText({
    contentBox: '#editable_text',
    text: '#text',
    trigger: '#trigger',
});

etext.render();

Here is the resulting markup, after the editor has be rendered:

<!-- This is the final DOM structure for the editor -->
<div class="yui-widget yui-editable_text">
  <div id="editable_text" class="yui-editable_text">
    <span id="text" class="yui-editable_text-text">Some editable text</span>
    <button id="trigger" class="yui-editable_text-trigger">Edit</button>
    <div class="yui-widget yui-ieditor yui-ieditor-hidden"/> <!-- The editor itself -->
  </div>
</div>

Automatic element discovery

The widget's DOM components, such as text and trigger, can be discoverd by the editor if they have the appropriate CSS classes attached to them. This is an alternative to specifying the text and trigger elements in the editor's constructor.

The first child element of the contentBox marked with the yui-editable_text-trigger CSS class will be used as the editor trigger.

The first child element of the contentBox marked with the yui-editable_text-text CSS class with become the editor's "text" node.

Extending the editor

The editor can be built upon using custom events, by overriding existing methods, or by injecting dependant objects.

Before extending the editor, you should understand that it is actually two widgets, one wrapping the other. The inner widget, an instance of Y.InlineEditor, is responsible for handling user input. The InlineEditor object is wrapped by a Y.EditableText object. EditableText shows and hides the editor widget, and handles updating the existing text with the user's new value.

The EditableText widget publishes all of the same events as the editor it wraps. The wrapped editor can be accessed using the EditableText instance's editor property.

Custom events

ieditor:save Called after an input value has been successfully saved by the editor widget.
ieditor:cancel Called after the user hits the 'Cancel' button.

Useful methods

These are the most useful methods to override on the InlineEditor class:

validate Responsible for validating the user's input, and displaying a helpful error message if something's wrong.
_saveData Responsible for writing the verified and validated user data to the editor's "value" attribute.

Dependency injection

You can customize the editor by supplying the 'editor' argument to the EditableText constructor, passing in an InlineEditor instance:

var myeditor = new MyCustomEditor();

var etext = new Y.EditableText({
    ...
    editor: myeditor
});

A save-delay Plugin

This example plugin adds a 2-second delay to the editor's save function. It also makes use of the UI's "waiting" state.

// Add a delay between the start and end of the inline editor widget's
// "save" event.
var DelayedSavePlugin = function(config) {
    DelayedSavePlugin.superclass.constructor.apply(this, arguments);
};

DelayedSavePlugin.NAME = 'delayedsave';
DelayedSavePlugin.NS = 'fx';

DelayedSavePlugin.ATTRS = {
    /*
     * Default duration of the 'saving' delay, in milliseconds.
     */
    duration: {
        value: 2000
    }
};

Y.extend(DelayedSavePlugin, Y.Plugin, {

    initializer: function(config) {
        // Save a reference to the original _saveData() method before
        // we wrap it up.
        this.original_save = config.owner._saveData;

        // We want to run our delayed-save code before the original
        // 'save' method.  Using doBefore() means that unplugging our
        // code will leave the original widget in a clean state.
        this.doBefore("_saveData", this._altSave);
    },

    destructor: function() {},

    /*
     * Our own _altSave() method, used to override the owner's default
     * behaviour.  This method will only be called if the editor's input
     * is valid.
     */
    _altSave: function() {
        var owner = this._owner,
            delay = this.get('duration');

        Y.log("Running alternative _saveData()", 'info');

        // Set the UI 'waiting' status.
        owner._uiSetWaiting();

        // Introduce a configurable delay around the owner's _saveData()
        // method.
        Y.later(delay, this, function() {

            Y.log("Running original 'save' method.", 'info');
            this.original_save.apply(owner, arguments[0]);

            // Make sure we clear the 'waiting' status.
            owner._uiClearWaiting();

        }, arguments);

        // Make sure we prevent the default _devSave() method from
        // running.
        return new Y.Do.Halt();
    }
});

var editable_text = new Y.EditableText({
    boundingBox: '#editable_text',
    contentBox:  '#editable_text_content',
    text: '#text',
    trigger: '#edit_btn'
});

editable_text.render();

// Add the 2 second delay to the underlying editor widget.
editable_text.editor.plug({fn:DelayedSavePlugin});

Known Issues