Inline Editing paradigm using a jQuery Inline Editor plugin

As our WebCenter 6 project is nearing release, we’re constantly trying to make our code more DRY.  The basic premise of this post is this: how do we duplicate the showing and hiding of elements needed for inline form field editing without re-inventing the wheel.

What is this inline editing that I’m speaking of?  Well, consider this content:

Customer details pane from WebCenter 6

Customer details pane from WebCenter 6

Rather than creating a whole new page, to edit the Username and other information, why not just pop an edit link next to the Username and allow editing right there on the page?  This eliminates the need for creating a second page, popup, or what have you.  When the edit link is clicked, the display of the username turns into an editor.  An associated cancel link and Save button would also appear.

username edit link

username edit link

editor, save, and cancel link

editor, save, and cancel link

I can accomplish this pretty easily with some nifty jQuery code using hide, show, toggle, etc., but what I really want to do is write this code once and be able to re-use it for other forms that would use the exact same functionality.

Enter the jQuery plugin.

Our wonderfully talented UX developer, Pete Klein suggested we wire this up with data attributes.  Without delving into the details of writing a jQuery plugin, or really optimizing the code, here it is:

/*!
 * jQuery Inline Editor
 * Copyright 2011, Andrew Cohen https://omegaluz.wordpress.com
 */

(function ($) {
    /// <param name="$" type="jQuery">Description</param>

    $.fn.inlineeditor = function (options) {

        var $this = this;

        var settings = $.extend({
            display: this.find('[data-inlineeditor-display]'),
            edit: this.find('[data-inlineeditor-edit]'),
            editlink: this.find('[data-inlineeditor-editlink]'),
            cancellink: this.find('[data-inlineeditor-cancellink]'),
            form: this.find('form[data-val=true]')
        }, options);

        settings = $.extend(settings, this.data('settings')); // apply the settings that are stored in the data['settings'] object
        this.data('settings', settings); // store the current settings in the settings data object
        this.attr('data-inlineeditor', 'true');
        settings.edit.hide();
        settings.cancellink.hide();

        settings.editlink.on('click', function (e) {
            /// <param name="e" type="jQuery.Event">jQuery Event Object</param>
            var target = e.target;
            var $target = $(target);
            var currentTarget = e.currentTarget;
            var $currentTarget = $(currentTarget);

            e.preventDefault();

            settings.display.hide();
            settings.editlink.hide();

            settings.edit.show();
            settings.cancellink.show();
        });
        settings.cancellink.on('click', function (e) {
            /// <param name="e" type="jQuery.Event">jQuery Event Object</param>
            var target = e.target;
            var $target = $(target);
            var currentTarget = e.currentTarget;
            var $currentTarget = $(currentTarget);

            e.preventDefault();

            settings.edit.hide();
            settings.cancellink.hide();

            var $form = $this.find('form');
            if ($form.length > 0) {
                var validator = $form.data('validator');
                if (validator) {
                    validator.resetForm();
                }
            }

            settings.display.show();
            settings.editlink.show();
        });
        /*// is the below code more efficient?
        this.on('click', settings.editlink.selector, function (e) {
        /// <param name="e" type="jQuery.Event">jQuery Event Object</param>
        e.preventDefault();
        settings.display.hide();
        settings.editlink.hide();
        settings.edit.show();
        settings.cancellink.show();
        });
        this.on('click', settings.cancellink.selector, function (e) {
        /// <param name="e" type="jQuery.Event">jQuery Event Object</param>
        e.preventDefault();
        settings.edit.hide();
        settings.cancellink.hide();
        settings.display.show();
        settings.editlink.show();
        });
        */

    };

} (window.jQuery));

Interesting to note, I’m making use of the new jQuery.on function instead of using click, bind, or live.

This code assumes the following:

  • Plugin and all associated functionality are instantiated like so: $(‘#sampleeditor’).inlineeditor();
  • There is an encapsulating element from which all other elements are nested.
  • Within the root there exists ‘[data-inlineeditor-display]’, ‘[data-inlineeditor-editlink]’, ‘[data-inlineeditor-edit]’, ‘[data-inlineeditor-cancellink]’.
  • ‘[data-inlineeditor-display]’ is an element that contains the original text
  • ‘[data-inlineeditor-editlink]’ is a link that when clicked hides the display and shows the editor
  • ‘[data-inlineeditor-edit]’ is a form input that serves to edit the content
  • ‘[data-inlineeditor-cancellink]’ cancels out of the editor, reset’s the form, hides the editor and shows the display again

My mvc 3 razor/html markup looks like so:

        <li><span class='key lfloat'><strong>Username:</strong></span>
            <span class='value lfloat' id="usernameeditor">
                <span data-inlineeditor-display>@Model.User.UserName</span>
                <a data-inlineeditor-editlink href="#">Edit</a>
                <span data-inlineeditor-edit id='usernameEdit'>
                    @{
                        Html.RenderPartial("RenameUserControl", Model.UserRenameViewModel);
                    }
                </span>
                <a data-inlineeditor-cancellink href="#">Cancel</a>
            </span>
            <script type="text/javascript">
                $(function () {
                    $('#usernameeditor').inlineeditor();
                });
            </script>
            <div class='clear'>
            </div>
        </li>

The content of the RenameUserControl:

@model UserRenameViewModel

@using (Ajax.BeginForm("RenameUser",
    "Home",
    null,
    new AjaxOptions
    {
        HttpMethod = "POST",
        UpdateTargetId = "usernameEdit"
    },
    new { id = "renameUserForm" }))
{
        @Html.HiddenFor(model => model.UserID)
        @Html.HiddenFor(model => model.UserName)
        <span class="editor-field">
            @Html.EditorFor(model => model.NewUserName)
            @Html.ValidationMessageFor(model => model.NewUserName)
        </span>

        @Html.ValidationSummary(true)

        <input type="submit" value="Save" />
}

<script type="text/javascript">
    $(function () {
        $.validator.unobtrusive.parse('#renameUserForm');
    });
</script>

Interesting to note, we’re using MVC 3’s built in unobtrusive ajax with data annotations to accomplish the actual form submission.  Again, I didn’t have to write any javascript to wire that up.

I can now re-use this plugin wherever we’re doing inline editing on our site.  I’ll still need to hook into my client side and server side validation when I’m clicking the save button.  I’ll probably wire up an onsuccess event to check whether there are any validation errors and then if not, fire some kind of hide event, rather than the plugin’s cancel code.

** I have updated the source for this. You can find it here.

Leave a comment