Writing a Javascript Component

Ok, for my next blog post, I thought I’d actually put in something useful. Enough of writing about myself and where I’ve been and what I’ve been doing. Time to drop in on some javascripting action!

So, fair warning, if you’re not much of a javascripting fan, I think this is where we ought to part. Of course, you might want to take a look at some of my other posts.

Now that we’ve got the non-javascripters out of the way, let’s get started!

First, a bit of lame theory. What must your javascript component do? What must it not do? And what must it definitely not do?

Separate Content, Styles and Functionality

While designing a javascript component, it pays to keep the content, which generally involves (X)HTML, the styling done through CSS, and the functionality added on by your javascript code separate. This keeps your HTML, CSS and Javascript code from occassionally stepping on one another’s toes, and making what must otherwise have been simple tasks, such as adding a little bit of content, or changing how the component looks a nightmare.

By abstaining from using inline styling, and moving all your CSS styling into a separate file, you make your css code cacheable.

Avoiding the use of HTML attributes for javascript events (such as, onclick=”blah()”), and instead, using some event queuing mechanism that comes along with pretty much all the common general purpose javascript libraries (like jQuery, Prototype or YUI) makes your javascript code unobtrusive, meaning several scripts can play on the same page and on the same HTML elements, with no bullying involved.

Finally, always consider the case where your content is edited by one person, who is just reasonably good at HTML, and knows nothing about javascript or css. Keep his job simple, and there will be no trouble.

Multiple Instances or Not?

The next question to consider is whether your component ought to allow multiple instances of it being created on the same page or not. Except in some very few cases, for example, if your component is an abstraction for an event queue, etc., the answer is generally yes.

However, when you design a component to support multiple instances on a page, you need to take care that every bit of data you use in the component is confined to an instance, as members, rather than being in the global namespace (unless you intend to share something across instances, of course). The best way to take care of this might be to wrap your component into a class. The user of your component gets to instantiate your class as many times as he needs on a page. Or, if your component can be expressed as a set of procedures, such as a few event handlers, it might be easier to just bundle it as a single function that attaches the handlers to the desired events (an example of this is what I describe later on).

Design for Everyone, Degrade Gracefully

Another thing to keep in mind is that not everybody has or runs javascript. From the security freaks who live in perennial fear of everything cyber, and keep javascript off at all cost to the busy traveller who browses on a phone, your website mustn’t punish those who don’t use the correct version of Firefox or Internet Explorer.

Always design your components to degrade gracefully at times when javascript is disabled, or is not available, or when certain features that your script needs to survive are just not supported on the visitor’s browser.

Designing a Scrolling Tab System

Now we’ll see how to design a scrolling tab system, such as the one on the Pragyan Events page.

We first decide what the HTML should look like, for such a contraption on screen. The following is the structure I decided to use.

<div id="myscrolltab" class="scrolltab">
	<div class="scrolltabheadingsleft">
		<div class="scrolltabheading" rel="content1divid">Heading 1</div>
		<div class="scrolltabheading" rel="content2divid">Heading 2</div>
		...
	</div>
	<div class="scrolltabheadingsright">
		<div class="scrolltabheading" rel="content5divid">Heading 5</div>
		<div class="scrolltabheading" rel="content6divid">Heading 6</div>
		...
	</div>
	<div class="scrolltabcontainer">
		<div class="scrolltabcontent" id="content1divid">Content corresponding to heading 1.</div>
		<div class="scrolltabcontent" id="content2divid">Content corresponding to heading 2.</div>
		<div class="scrolltabcontent" id="content3divid">Content corresponding to heading 3.</div>
		...
	</div>
</div>

You might, of course, think of other (possibly better and more common sensical) ways to structure your content. This is the one that occurred to me off the top of my head. :)

Next, we’ll add just enough CSS to take care of basic styles, positioning, etc.

.scrolltab {
	width:660px;
	margin:8px auto;
	position: relative;
}

.scrolltabheadingsleft, .scrolltabheadingsright {
	width:120px;
}

.scrolltabheadingsleft {
	float:left;
	margin-right:-120px;
}

.scrolltabheadingsright {
	float:right;
	margin-left:120px;
}

.scrolltabheading {
	border: 1px solid white;
	padding: 4px;
	margin-bottom: 8px;
	background: white url(../images/scrolltab-inactive.gif) scroll repeat-x center center;
	font-size: 14px;
	font-weight: bold;
	cursor: pointer;
}

.scrolltabheadingsleft .scrolltabheading {
	margin-right: -1px;
	border-width: 1px;
	border-style: solid;
	border-color: #B9B9B9;
	text-align: right;
}

.scrolltabheadingsright .scrolltabheading {
	margin-left: -1px;
	border-width: 1px;
	border-style: solid;
	border-color: #B9B9B9;
	text-align: left;
}

.scrolltabheadingactive {
	background: blue url(../images/scrolltab-active.png) scroll repeat-x center center;
	color: white;
}

.scrolltabcontainer {
	position: relative;
	margin: 0 120px;
	border-left: 1px solid #B9B9B9;
	border-right: 1px solid #B9B9B9;
	padding:0;
}

.scrolltabcontent {
	padding: 0;
	border-top: 1px solid #B9B9B9;
	border-bottom: 1px solid #B9B9B9;
	height: 480px;
}

.scrolltabcontent ul {
	margin-top:0;
	margin-bottom:0;
}

Note that after adding the css, the component doesn’t quite look like what it should in the end. This is because we’re not going to mess with the display styles of the scrolltabcontent divs just yet. Why? Because if we add css to, say, show just the first scrolltabcontent, and hide the rest, and if javascript does not work on the client, the visitor ends up having no way to view the content in any of the other clusters. So, by moving a few things out of the css, and into the javascript, we’re determining how the component degrades in a javascript-less browser.

The next step, you guessed it, is adding the javascript. With a library such as jQuery, your work becomes reasonably simple and straight forward. The code, with necessary comments, is given below.

$(document).ready(function() {
	// hide everything but the active scrolltabcontent
	$('.scrolltab').css('overflow', 'hidden');

	$('.scrolltab').each(function(index) {
		// if the scrolltab hasn't been assigned an id, assign one
		if (this.id == '')
			this.id = 'scrolltab-' + index;

		var scrollTabId = this.id;

		// add an event handler to handle heading clicks
		$('#' + scrollTabId + ' .scrolltabheading').click(function() {
			var contentDivId = $(this).attr('rel');
			if (contentDivId == '')
				return;	// oops, no associated content? leave it alone

			// set the current item as the "active" heading
			$('#' + scrollTabId + ' .scrolltabheadingactive').removeClass('scrolltabheadingactive');
			$(this).addClass('scrolltabheadingactive');

			// make the container large enough to hold the current tab item's content
			$('#' + scrollTabId).css('height', $('#' + scrollTabId + ' #' + contentDivId).height() + 2 + 'px');

			// compute the top in pixels of the current tab item's content
			// and move the container up by the negative of that value
			var position = $('#' + scrollTabId + ' #' + contentDivId).position().top;
			$('#' + scrollTabId + ' .scrolltabcontainer').animate({top: '-' + position + 'px'}, 'normal');
		});

		// simulate a click of the first heading
		// so that the first heading is selected by default
		$('#' + scrollTabId + ' .scrolltabheading:eq(0)').click();
	});
});

The way the code works can be visualized like this. You have a big sheet of cardboard with a window in it. This is the scrolltab div, with overflow-y set to hidden. Behind the cardboard, there is a long scroll of paper with stuff written on it. This is the scrolltabcontainer div, with all the content inside it. When the user asks for some information, you scroll this sheet of paper by the correct amount so as to align the required content with the window for the user to see. Easy peasy.

Well, that does it for this reasonably short post. If you’ve got all the time in the world, please do test this out on different browsers, and post your comments.

- Rugged-Scripting-Rat

 

Notes:

  • Don’t forget to download and include jQuery, if you’re planning to use the above code off-the-shelf (or, off-the-page).
  • For my purposes, I had put in Yahoo’s Reset CSS. I’m not sure if that might change a few things on a few browsers. If things aren’t looking as expected on some browser, please also try including the reset css along with the styles given above.
  • In the css given above, I’ve put in a height for the scrolltabcontent class. This isn’t necessary. I just added it because the html given above has absolutely no content in any of the divs. You should probably try adding some javascript to handle short content too. This wasn’t much of a concern for me, so I haven’t bothered.
  • Bug fix for (a few versions of) opera: Setting overflow-y to hidden seems to mean nothing to the browser. The first line inside the document ready handler has therefore been changed to $(‘.scrolltab’).css(‘overflow’, ‘hidden’); as opposed to overflow-y.
Advertisement

No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.