Renaming jQuery UI Tabs With Validation

Recently, one of our projects requires me to use jQuery UI Tabs for its user interface. One of the problem was, to figure a way for the user to rename the tab and validate the input at the same time. Most of the site’s client-side validation is through jQuery Validate plugin, so that should be straight-forward.

Want to see the it in action? Why not play around with the online demo or download the source files.

The markup

First off, let’s start by including jQuery, jQuery UI and jQuery livequery plugin. For this demo, I’ll point the files to Google CDN whenever possible, and start with the jQuery UI Tabs default functionality demo markup.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>jQuery UI Tabs - Default functionality</title>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"></script>
		<script type="text/javascript" src="js/jquery.livequery.js"></script>
		<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>

		<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/themes/smoothness/jquery-ui.css" rel="stylesheet" />
	</head>
	<body>
		<div id="demo">
			<div id="tabs">
				<ul id="tabs-nav">
					<li><a href="#tabs-1"><span>Nunc tincidunt</span></a></li>
					<li><a href="#tabs-2"><span>Proin dolor</span></a></li>
					<li><a href="#tabs-3"><span>Aenean lacinia</span></a></li>
				</ul>
				<div id="tabs-1">
				<!-- Content for the first tab will be here -->
				</div>
				<div id="tabs-2">
				<!-- Content for the second tab will be here -->
				</div>
				<div id="tabs-3">
				<!-- Content for the third tab will be here -->
				</div>
			</div><!-- /#tabs -->
		</div><!-- /.demo -->
		<script type="text/javascript"></script>
	</body>
</html>

The JavaScript

We’ll be including most of the scripts inside the <script> tag on line 32. We’ll split the whole interaction to few phases so that we can see clearly what we should do. The happy path should be something like:

  1. Click on the tab name to edit.
  2. Display input text with the tab name filled in by default.
  3. If the name is changed, save it. Otherwise, revert to the old name.

We’ll also limit the ability to rename a tab to just the currently selected tab only.

Step 1: Click Tab Name To Edit.

For this, we’ll be taking advantage of LiveQuery to bind the click events to the selected tab’s span.

$(function() {
	// Initialize tabs with the default options
	$("#tabs").tabs();
	// Bind "click" event to the span, so that it calls the renameTab function
	$("li.ui-tabs-selected span").livequery("click", function(){
		renameTab(this);
	});
});

Step 2: Display input text with the tab name as default value

Now we need to create a function to handle the “click”, and inject the form. We’ll also save the current tab name in a variable, and bind the save and cancel buttons.

function renameTab(obj) {
	var  obj = $(obj),
		oldName = $(obj).html(),
		editMode = '<div class="editable"><form id="rename_tab_form"><input type="text" id="new_tab_name" value="' + oldName + '" name="new_tab_name" maxlength="20" /><button id="saveRename" class="btn"><span>Save</span></button><button id="cancelRename" class="btn"><span>Cancel</span></button></form></div>',
		form = $("#rename_tab_form");
	// Inject the form after the span, and then remove the span from DOM
	obj.after(editMode).remove();
	//We might not need this now, but it will be useful later when we add in validation
	$("div.editable", "#tabs-nav").closest("a").addClass("editing");
	$("#new_tab_name").bind("focus", function() {
		this.select();
	}).focus();

	$("#saveRename").bind("click", function() {
		$("#rename_tab_form").submit();
		return false;
	});

	$("#cancelRename").bind("click", function() {
		replaceName( false, this, oldName );
		return false;
	});
	$("body").bind("click", function(e) {
		var target = e.target.id;
		if ( target != 'cancelRename' ) {
			replaceName(false, $("#cancelRename"), oldName);
		}
		return false;
	});

	$("#rename_tab_form").bind("submit", function(e) {
		replaceName( true, $("#cancelRename"), $("#new_tab_name").val() );
		e.preventDefault();
		return false;
	});
}

Save or cancel?

Now that we’ve the form shown to the user and properly bind them to an event, they could either save it, or cancel it by clicking the cancel button or anywhere within the page. We write a function to handle these request and handle them properly. I’ve also included a check if the name is empty, it will be named as “Untitled” instead. But this could be removed based on your validation criteria later on.

// replaceName (boolean, object, value)
function replaceName(action, obj, val) {
	var name = '';

	if (action) { // If action is "true", we save the data
		// You could use the .ajax() method to save the data as you see fit
	} else {
		name = val;
	}

	if ( val == '' ) {
		val = 'Untitled';
	}

	$(obj).parents("div.editable").after('<span>'+ val +'</span>').remove();
	$("li.ui-tabs-selected span","#tabs-nav").closest("a").removeClass("editing");
	$("#saveRename, #cancelRename, body").unbind();

}

Using the jQuery Validation plugin

If you’re already familiar with the Validation plugin, this should be easy. If not, I suggest you to go through the documentation quickly.

$("#rename_tab_form").validate({
	errorClass : "error validation",
	success : "valid",
	rules : {
		new_tab_name : {
			required : true,
			maxlength : 20,
			minlength: 3
		}
	},
	errorElement : "div",
	errorPlacement : function (error, element) {
		var nextSibling  = element.siblings("div.error"),
		counter = 1;
		nextSibling.remove().empty();
		var hasErrorElement = nextSibling.val();
		if (hasErrorElement == null || hasErrorElement == '') {
			error.insertAfter(element).wrap("<div class='error-wrapper'></div>").parent().append("<div class='error-pointer'></div>");
			// Not sure why I'm getting multiple error elements, so we need to remove this manually
			$("div.error-wrapper").eq(0).siblings("div.error-wrapper").remove();
			return false;
		}
	},
	unhighlight : function() {
		$("div.error-wrapper").remove();
	},
	submitHandler: function(form) {
		return false;
	}
});

Easy eh? Now all we have to do is revisit our renameTab function and include the call for validation, on line 13 and 34 – 37 as per below:

function renameTab(obj) {
	var  obj = $(obj),
		oldName = $(obj).html(),
		editMode = '<div class="editable"><form id="rename_tab_form"><input type="text" id="new_tab_name" value="' + oldName + '" name="new_tab_name" /><button id="saveRename" class="btn"><span>Save</span></button><button id="cancelRename" class="btn"><span>Cancel</span></button></form></div>',
		form = $("#rename_tab_form");

	obj.after(editMode).remove();

	$("div.editable", "#tabs-nav").closest("a").addClass("editing");

	$("#new_tab_name").bind("focus", function() {
		this.select();
		validate();
	}).focus();

	$("#saveRename").bind("click", function() {
		$("#rename_tab_form").submit();
		return false;
	});

	$("#cancelRename").bind("click", function() {
		replaceName( false, this, oldName );
		return false;
	});
	$("body").bind("click", function(e) {
		var target = e.target.id;
		if ( target != 'cancelRename' ) {
			replaceName(false, $("#cancelRename"), oldName);
		}
		return false;
	});

	$("#rename_tab_form").bind("submit", function(e) {
		var isValid = $("#new_tab_name").valid();
		if ( isValid ) {
			replaceName( true, $("#cancelRename"), $("#new_tab_name").val() );
		}
		e.preventDefault();
		return false;
	});
}

And that’s it! To make the validation message appears a bit more nicer, we could apply the polygonal CSS technique as mentioned by the fine guys in Filament, as per below :

#rename_tab_form { position: relative; }
#rename_tab_form .error-wrapper { position: absolute; top: -37px; padding: 5px 10px; background: #9a0000; border-top: 1px solid #740000; border-bottom: 1px solid #740000; width: auto; }
#rename_tab_form .error-wrapper .error { padding: 0; color: #fefefe; }
#rename_tab_form .error-pointer { width:0; height:0; position: absolute; bottom: -10px; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 10px solid #9a0000; border-bottom: 0; }

Comments

One response to “Renaming jQuery UI Tabs With Validation”

  1. AdI Jajuli Avatar
    AdI Jajuli

    great writeup – more to come?