Extending Tabs in jQuery UI 1.7

Exclusive offer: get 50% off this eBook here
jQuery UI 1.7: The User Interface Library for jQuery

jQuery UI 1.7: The User Interface Library for jQuery — Save 50%

Build highly interactive web applications with ready-to-use widgets from the jQuery User Interface library

$26.99    $13.50
by Dan Wellman | November 2009 | MySQL Open Source PHP Web Development

In this article by Dan Wellman, we will look at the tabs component of jQuery UI; a simple but effective means of presenting structured content in an engaging and interactive widget.

In this article, we will discuss the following topics:

  • Controlling tabs using their methods
  • Custom events defined by tabs
  • AJAX tabs

 

The tab widget defines a series of useful options that allow you to add callback functions to perform different actions when certain events exposed by the widget are detected. The following table lists the configuration options that are able to accept executable functions on an event:

Property

Usage

add

Execute a function when a new tab is added.

disable

Execute a function when a tab is disabled.

enable

Execute a function when a tab is enabled.

load

Execute a function when a tab's remote data has loaded.

remove

Execute a function when a tab is removed.

select

Execute a function when a tab is selected.

show

Execute a function when the content section of a tab is shown.

 

Each component of the library has callback options (such as those in the previous table), which are tuned to look for key moments in any visitor interaction. Any function we use with these callbacks are usually executed before the change happens. Therefore, you can return false from your callback and prevent the action from occurring.

In our next example, we will look at how easy it is to react to a particular tab being selected using the standard non-bind technique. Change the final <script> element in tabs7.html so that it appears as follows:

<script type="text/javascript">
$(function(){
function handleSelect(event, tab) {
$("<p>").text("The tab at index " + tab.index +
 " was selected").addClass("status-message ui-corner-all")
.appendTo($(".ui-tabs-nav","#myTabs")).fadeOut(5000);
}

var tabOpts = {
select:handleSelect
};
$("#myTabs").tabs(tabOpts);
});
</script>

Save this file as tabs8.html. We also need a little CSS to complete this example, in the <head> of the page we just created add the following <link> element:


<link rel="stylesheet" type="text/css" href="css/tabSelect.css">

Then in a new page in your text editor add the following code:

.status-message {
position:absolute; right:3px; top:4px; margin:0;
padding:11px 8px 10px; font-size:11px;
background-color:#ffffff; border:1px solid #aaaaaa;
}

Save this file as tabSelect.css in the css folder.

We made use of the select callback in this example, although the principle is the same for any of the other custom events fired by tabs. The name of our callback function is provided as the value of the select property in our configuration object.

Two arguments will be passed automatically to the function we define by the widget when it is executed. These are the original event object and a custom object containing useful properties from the tab which is in the function's execution context.

To find out which of the tabs was clicked, we can look at the index property of the second object (remember these are zero-based indices). This is added, along with a little explanatory text, to a paragraph element that we create on the fly and append to the widget header.

 jQuery UI 1.7: The User Interface Library for jQuery

In this example, the callback function was defined outside the configuration object, and was instead referenced by the object. We can also define these callback functions inside our configuration object to make our code more efficient. For example, our function and configuration object from the previous example could have been defined like this:

var tabOpts = {
select: function(event, tab) {
$("<p>").text("The tab at index " + tab.index + " was selected")
.addClass("status-message ui-corner-all").appendTo($(".ui-tabs-nav",
"#myTabs")).fadeOut(5000);
}
}

Check tabs8inline.html in the code download for further clarification on this way of using event callbacks. Whenever a tab is selected, you should see the paragraph before it fades away. Note that the event is fired before the change occurs.

Binding to events

Using the event callbacks exposed by each component is the standard way of handling interactions. However, in addition to the callbacks listed in the previous table we can also hook into another set of events fired by each component at different times.

We can use the standard jQuery bind() method to bind an event handler to a custom event fired by the tabs widget in the same way that we could bind to a standard DOM event, such as a click.

The following table lists the tab widget's custom binding events and their triggers:

Event

Trigger

tabsselect

A tab is selected.

tabsload

A remote tab has loaded.

tabsshow

A tab is shown.

tabsadd

A tab has been added to the interface.

tabsremove

A tab has been removed from the interface.

tabsdisable

A tab has been disabled.

tabsenable

A tab has been enabled.

jQuery UI 1.7: The User Interface Library for jQuery Build highly interactive web applications with ready-to-use widgets from the jQuery User Interface library
Published: November 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

The first three events are fired in succession in the order event in which they appear in the table. If no tabs are remote then tabsselect and tabsshow are fired in that order. These events are sometimes fired before and sometimes after the action has occurred, depending on which event is used.

Let's see this type of event usage in action, change the final <script> element in tabs8.html to the following:

<script type="text/javascript">
$(function() {
$("#myTabs").tabs();
$("#myTabs").bind("tabsselect", function(e, tab) {
alert("The tab at index " + tab.index + " was selected");
});
});
</script>

Save this change as tabs9.html. Binding to the tabsselect in this way produces the same result as the previous example using the select callback function. Like last time, the alert should appear before the new tab is activated.

All the events exposed by all the widgets can be used with the bind() method, by simply prefixing the name of the widget to the name of the event.

Using tab methods

The tabs widget contains many different methods, which means it has a rich set of behaviors. It also supports the implementation of advanced functionality that allows us to work with it programmatically. Let's take a look at the methods which are listed in the following table:

ethod

 

Usage

 

abort

Stops any animations or AJAX requests that are currently in progress.

add

 

Add a new tab programmatically, specifying the URL of the tab's content, a label, and optionally its index number as arguments.

destroy

Completely remove the tabs widget.

disable

Disable a tab based on index number

enable

Enable a disabled tab based on index number.

length

Return the number of tabs in the widget.

load

Reload an AJAX tab's content, specifying the index number of the tab.

option

Get or set any property after the widget has been initialized.

remove

 

Remove a tab programmatically, specifying the index of the tab
to remove.

rotate

Automatically changes the active tab after a specified number of milliseconds have passed, either once or repeatedly.

select

Select a tab programmatically, which has the same effect as when a visitor clicks a tab, based on index number.

url

 

Change the URL of content given to an AJAX tab. The method expects the index number of the tab and the new URL. See also load (above).

Enabling and disabling tabs

We can make use of the enable or disable methods to programmatically enable or disable specific tabs. This will effectively switch on any tabs that were initially disabled or disable those that are currently active. Let's use the enable method to switch on a tab, which we disabled by default in an earlier example. Add the following new <button> directly after the markup for the tabs widget in tabs4.html:

<button id="enable">Enable!</button><button id="disable">Disable!</button>

Next change the final < script> element so that it appears as follows:

<script type="text/javascript">
$(function(){
var tabOpts = {
disabled:[1]
};
$("#myTabs").tabs(tabOpts);
$("#enable").click(function() {
$("#myTabs").tabs("enable", 1);
});
$("#disable").click(function() {
$("#myTabs").tabs("disable", 1);
});
});
</script>

Save the changed file as tabs10.html. On the page we've added two new <button> elements—one will be used to enable the disabled tab and the other used to disable it again.

In the JavaScript, we use the click event of the Enable! button to call the tabs constructor. This passes the string "enable", which specifies the enable method and the index number of the tab we want to enable. The disable method is used in the same way. Note that a tab cannot be disabled while it is active.

All methods exposed by each component are used in this same easy way which you'll see more of as we progress through the book.

I mentioned that each widget has a set of common methods consisting of enable, disable, and destroy. These methods are used in the same way across each of the different components, so we won't be looking at these methods again.

Adding and removing tabs

Along with enabling and disabling tabs programmatically, we can also remove them or add completely new tabs dynamically. In tabs10.html add the following new code directly after the underlying HTML of the widget:

<label>Enter a tab to remove:</label>
<input id="indexNum"><button id="remove">Remove!</button><br>
<button id="add">Add a new tab!</button>
<div id="newTab" class="ui-helper-hidden"> This content was added
after the widget was initialized!</div>

Then change the final < script> element to this:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#remove").click(function() {
var indexNumber = $("#indexNum").val();
$("#myTabs").tabs("remove", indexNumber);
});
$("#add").click(function() {
var newLabel = "A New Tab!"
$("#myTabs").tabs("add", "#newTab", newLabel);
});
});

Save this as tabs11.html. On the page we've changed the <button> from the last example and have added a new <label>, an < input>, and another <button>.These new elements are used to add a new tab.

We have also added some new content on the page, which will be used as the basis for each new tab that is added. We make use of the ui helper-hidden framework class to hide this content, so that it isn't available when the page loads,. Even though this class name will remain on the element once it has been added to the tab widget, it will still be visible when its tab is clicked. This is because the class name will be overridden by classes within ui.tabs.css.

In the <script>, the first of our new functions handles removing a tab using the remove method. This method requires one additional argument—the index number of the tab to be removed. In this example, we get the value entered into the text box and pass it to the method as the argument. If no index is passed to the method, the first tab will be removed.

The add method that adds a new tab to the widget, can be made to work in several different ways. In this example, we've specified that content already existing on the page (the <div> with an id of newTab) should be added to the tabs widget. In addition to passing the string "add" and specifying a reference to the element we wish to add to the tabs, we also specify a label for the new tab.

Optionally, we can also specify the index number where the new tab should be inserted. If the index is not supplied, the new tab will be added as the last tab. We can continue adding new tabs and each one will reuse the <div> for its content because our content <div> will retain its id attribute after it has been added to the widget. After adding and perhaps removing tabs, the page should appear something like this:

jQuery UI 1.7: The User Interface Library for jQuery

jQuery UI 1.7: The User Interface Library for jQuery Build highly interactive web applications with ready-to-use widgets from the jQuery User Interface library
Published: November 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Simulating clicks

There may be times when you want to programmatically select a particular tab and show its content. This could happen as the result of some other interaction by the visitor. We can use the select method to do this, which is completely analogous with the action of clicking a tab. Alter the final <script> block in tabs11.html so that it appears as follows:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#remove").click(function() {
var indexNumber = $("#indexNum").val() - 1;
$("#myTabs").tabs("remove", indexNumber);
});
$("#add").click(function() {
var newLabel = "A New Tab!"

$("#myTabs").tabs("add", "#newTab", newLabel);
var newIndex = $("#myTabs").tabs("length") - 1;
$("#myTabs").tabs("select", newIndex);
});
});
</script>

Save this as tabs12.html in your jqueryui folder. Now when a new tab is added, it is automatically selected. The select method requires just one additional parameter, which is the index number of the tab to select.

As any tab we add will be the last tab in the interface (in this example) and as the tab indices are zero based, all we have to do is use the length method to return the number of tabs and then subtract 1 from this figure to get the index. The result is passed to the select method.

Creating a tab carousel

One method that creates quite an exciting result is the rotate method. The rotate method will make all of the tabs (and their associated content panels) display one after the other automatically.

It's a great visual effect and is useful for ensuring that all, or a lot, of the individual tab's content panels get seen by the visitor. For an example of this kind of effect in action, see the homepage of http://www.cnet.com. There is a tabs widget (not a jQuery UI one) that shows bogs, podcasts, and videos.

Like the other methods we've seen, the rotate method is easy to use. Change the final <script> element in tabs9.html to this:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs().tabs("rotate", 1000, true);
});
</script>

Save this file as tabs13.html. We've reverted back to a simplified page with no additional elements other than the underlying structure of the widget. Although we can't call the rotate method directly using the initial tabs method, we can chain it to the end like we would with methods from the standard jQuery library.

The rotate method is used with two additional parameters. The first parameter is an integer, that specifies the number of milliseconds each tab should be displayed before the next tab is shown. The second parameter is a Boolean that indicates whether the cycle through the tabs should occur once or continuously.

The tab widget also contains a destroy method. This is a method common to all the widgets found in jQuery UI. Let's see how it works. In tabs13.html, after the widget add a new <button> as follows:

<button id="destroy">Destroy the tabs!</button>

Next change the final <script> element to this:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#destroy").click(function() {
$("#myTabs").tabs("destroy");
});
});
</script>

Save this file as tabs14.html. The destroy method that we invoke with a click on the button, completely removes the tab widget, returning the underlying HTML to its original state. After the button has been clicked, you should see a standard HTML list element and the text from each tab, just like in the following screenshot:

jQuery UI 1.7: The User Interface Library for jQuery

Once the tabs have been reduced to this state it would be common practice to remove them using jQuery's remove() method. As I mentioned with the enable and disable methods earlier, the destroy method is used in exactly the same way for all widgets and therefore will not be discussed again.

Getting and setting options

Like the destroy method the option method is exposed by all the different components found in the library. This method is used to work with the configurable options and functions in both getter and setter modes. Let's look at a basic example, add the following <button> after the tabs widget in tabs9.html:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#destroy").click(function() {
$("#myTabs").tabs("destroy");
});
});
</script>

Then change the final <script> element so that it is as follows:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#show").click(function() {
$("<p>").text("The tab at index " + $("#myTabs")
.tabs("option", "selected") + " is active")
.addClass("status-message ui-corner-all"
).appendTo($(".ui-tabs-nav", "#myTabs")).fadeOut(5000);
});
});
</script>

Save this file as tabs15.html. The <button> on the page has been changed so that it shows the currently active tab. All we do is add the index of the selected tab to a status bar message as we did in the earlier example. We get the selected option by passing the string selected as the second argument. Any option can be accessed in this way.

To trigger setter mode instead, we can supply a third argument containing the new value of the option that we'd like to set. Therefore, to change the value of the selected option, we could use the following HTML to specify the tab to select:

<label>Enter a tab index to activate</label><input id="newIndex" type="text">
<button id="set">Change Selected!</button>

And the following click-handler:

<script type="text/javascript">
$(function(){
$("#set").click(function() {
$("#myTabs").tabs("option", "selected", parseInt($("#newIndex").
val()));
});

Save this as tabs16.html. The new page contains a <label> and an <input>, as well as a <button> that is used to harvest the index number that the selected option should be set to. When the button is clicked, our code will retrieve the value of the <input> and use it to change the selected index. By supplying the new value we put the method in setter mode.

When we run this page in our browser, we should see that we can switch to the second tab by entering its index number and clicking the Changed Selected button.

jQuery UI 1.7: The User Interface Library for jQuery

AJAX tabs

We've looked at adding new tabs from already existing content on the page. In addition to this we can also create AJAX tabs that load content from remote files or URLs. Let's extend our previous example of adding tabs so that the new tab content is loaded from an external file. In tabs16.html remove the <label> and the <input> from the page and change the <button> so that it appears as follows:

<button id="add">Add a new tab!</button>

Then change the click-handler so that it appears as follows:

$("#add").click(function() {
$("#myTabs").tabs("add", "tabContent.html", "A Remote Tab!");
});

Save this as tabs17.html. This time, instead of specifying an element selector as the second argument of the add method, we supply a relative file path. Instead of generating the new tab from inline content, the tab becomes an AJAX tab and loads the contents of the remote file.

The file used as the remote content in this example is basic and consists of just the following code:

<div>This is some remote content!</div>

Save this as tabContent.html in the jqueryui folder. After the <button> has been clicked, the page should appear like this:

jQuery UI 1.7: The User Interface Library for jQuery

Instead of using JavaScript to add the new tab, we can use plain HTML to specify an AJAX tab as well. In this example, we want the tab that will display the remote content to be available all the time, not just after clicking the button. Add the following new <a> element to the underlying HTML for the widget in tabs17.html:

<li><a href="tabContent.html">AJAX Tab</a></li>

The final < script> element can be used to just call the tabs method:

$("#myTabs").tabs();

Save this as tabs18.html. All we're doing is specifying the path to the remote file (the same one we created in the previous example) using the href attribute of an <a> element in the underlying markup from which the tabs are created.

If you use a DOM explorer, you can see that the file path we added to link to the remote tab has been removed. Instead, a new fragment identifier has been generated and set as the href. The new fragment is also added as the id of the new tab (minus the # symbol of course).

jQuery UI 1.7: The User Interface Library for jQuery

Along with loading data from external files, it can also be loaded from URLs. This is great when retrieving content from a database using query strings or a web service. Methods related to AJAX tabs include the load and url methods. The load method is used to load and reload the contents of an AJAX tab, which could come in handy for refreshing content that changes very frequently.

The url method is used to change the URL that the AJAX tab retrieves its content from. Let's look at a brief example of these two methods in action. There are also a number of properties related to AJAX functionality. Add the following new <select> element in tabs18.html:

<select id="fileChooser">
<option>tabContent.html</option>
<option>tabContent2.html</option>
</select>

Then change the final <script> element to this:

<script type="text/javascript">
$(function(){
$("#myTabs").tabs();
$("#fileChooser").change(function() {
this.selectedIndex == 0 ? loadFile1() : loadFile2();
function loadFile1() {
$("#myTabs").tabs("url", 2, "tabContent.html").tabs("load", 2);
}
function loadFile2() {
$("#myTabs").tabs("url", 2, "tabContent2.html").tabs("load", 2);
}
});
});
</script>

Save the new file as tabs19.html. We've added a simple <select> element to the page that lets you choose the content to display in the AJAX tab. In the JavaScript, we set a change handler for the <select> and specified an anonymous function to be executed each time the event is detected.

This function checks the selectedIndex of the <select> element and calls either the loadFile1 or loadFile2 function. The <select> element is in the execution scope of the function, so we can refer to it using the this keyword.

These functions are where things get interesting. We first call the url method, specifying two additional arguments, which are the index of the tab whose URL we want to change followed by the new URL. We then call the load method that is chained to the url method, specifying the index of the tab whose content we want to load.

We'll need a second local content file, change the text on the page of tabContent1.html and resave it as tabContent2.html.

Run the new file in a browser and select a tab. Then use the dropdown <select> to choose the second file and watch as the content of the tab is changed. You'll also see that the tab content will be reloaded even if the AJAX tab isn't active when you use the <select> element.

The slight flicker in the tab heading is the string value of the spinner option that by default is set to Loading…. Although, we don't get a chance to see it in full as the tab content is changed quickly when running it locally. Here's how the page should look after selecting the remote page in the drop down select and the third tab:

jQuery UI 1.7: The User Interface Library for jQuery

Displaying data obtained via JSONP

Let's pull in some external content for our final tabs example. If we use the tabs widget, in conjunction with the standard jQuery librarygetJSON method, we can bypass the cross-domain exclusion policy and pull in a feed from another domain to display in a tab. In a new file in your text editor, create the following new page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<link rel="stylesheet" type="text/css"
href="development-bundle/themes/smoothness/ui.all.css">
<link rel="stylesheet" type="text/css" href="css/flickrTabTheme.css">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>jQuery UI AJAX Tabs Example</title>
</head>
<body>
<div id="myTabs">
<ul>
<li><a href="#a"><span>Nebula Information</span></a></li>
<li><a href="#flickr"><span>Images</span></a></li>
</ul>
<div id="a">
<p>A nebulae is an interstellar cloud of dust, hydrogen gas, and plasma.
It is the first stage of a star's cycle. In these regions the formations
of gas, dust, and other materials clump together to form larger masses,
which attract further matter, and eventually will become big enough to form stars.
 The remaining materials
are then believed to form planets and other planetary system objects.
 Many nebulae form from the gravitational collapse of diffused gas in the interstellar
 medium or ISM. As the material collapses under its own weight, massive stars may form
in the center, and their ultraviolet radiation ionizes the surrounding gas, making it
visible at optical wavelengths.</p>
</div>
<div id="flickr"></div>
</div>
<script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="development-bundle/jquery-1.3.2.js"></script>
<script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="development-bundle/ui/ui.core.js"></script>
<script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="development-bundle/ui/ui.tabs.js"></script>
</body>
</html>

The HTML seen here is nothing new. It's basically the same as the previous examples so I won't describe it in any detail. The only point worthy noting is that unlike the previous AJAX tab examples, we have specified an empty <div> element that will be used for the AJAX tab's content. Now, just before the </body> tag, add the following script block:

<script type="text/javascript">
$(function(){
var tabOpts = {
select: function(event, ui) {
ui.tab.toString().indexOf("flickr") != -1 ? getData() : null ;
function getData() {
$("#flickr").empty();
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?
tags=nebula&format=json&jsoncallback=?", function(data) {
$.each(data.items, function(i,item){
$("<img/>").attr("src", item.media.m).appendTo("#flickr").height(100).width(100);
return (i == 5) ? false : null;
});
});
}
}
}
$("#myTabs").tabs(tabOpts);
});
</script>

Save the file as flickrTab.html in your jqueryui folder. Every time a tab is selected, our select callback will check to see if it was the tab with an id of flickr that was clicked. If it is, then the getData() function is invoked that uses the standard jQuery getJSON method to retrieve an image feed from http://www.flickr.com.

Once the data is returned, the anonymous callback function iterates over each object within the feed and creates a new image. We also remove any preexisting images from the content panel to prevent a buildup of images following multiple tab selections.

Each new image has its src attribute set using the information from the current feed object and is then added to the empty Flickr tab. Once iteration over six of the objects in the feed has occurred, we exit jQuery's each method. It's that simple.

We also require a bit of CSS to make the example look right. In a new file in your text editor add the following selectors and rules:

#myTabs { width:335px; }
#myTabs .ui-tabs-panel { padding:10px 0 0 7px; }
#myTabs p { margin:0 0 10px; font-size:75%; }
#myTabs img { border:1px solid #aaaaaa; margin:0 5px 5px 0; }

Save this as flickrTabTheme.css in your css folder. When you view the page and select the Images tab, after a short delay you should see six new images, as seen in the following screenshot:

jQuery UI 1.7: The User Interface Library for jQuery

Summary

We covered the range of methods that we can use to programmatically make the tabs perform different actions, such as simulating a click on a tab, enabling or disabling a tab, and adding or removing tabs.

We briefly looked at some of the more advanced functionality supported by the tabs widget such as AJAX tabs and the tab carousel. Both these techniques are easy to use and can add value to any implementation.

In this article, we have discussed the following topics:

  • Controlling tabs using their methods
  • Custom events defined by tabs
  • AJAX tabs

If you have read this article you may be interested to view :

  • Tabs in jQuery UI 1.7

  • About the Author :


    Dan Wellman

    Dan Wellman is an author and frontend engineer living on the South Coast of the UK and working in London. By day he works for Skype and has a blast writing application-grade JavaScript. By night he writes books and tutorials focused mainly on frontend web development. He is also a staff writer for the Tuts+ arm of the Envato network, and occasionally writes for .Net magazine. He's the proud father of four amazing children, and the grateful husband of a wonderful wife.

    Books From Packt

    jQuery 1.3 with PHP
    jQuery 1.3 with PHP

    Drupal 6 JavaScript and jQuery
    Drupal 6 JavaScript and jQuery

    Learning jQuery 1.3
    Learning jQuery 1.3

    PHP Team Development
    PHP Team Development

    ICEfaces 1.8: Next Generation Enterprise Web Development
    ICEfaces 1.8: Next Generation Enterprise Web Development

    Symfony 1.3 Web Application Development
    Symfony 1.3 Web Application Development

    JBoss AS 5 Development
    JBoss AS 5 Development

    JBoss RichFaces 3.3
    JBoss RichFaces 3.3

    Your rating: None Average: 5 (1 vote)

    Post new comment

    CAPTCHA
    This question is for testing whether you are a human visitor and to prevent automated spam submissions.
    Q
    2
    U
    p
    W
    n
    Enter the code without spaces and pay attention to upper/lower case.
    Code Download and Errata
    Packt Anytime, Anywhere
    Register Books
    Print Upgrades
    eBook Downloads
    Video Support
    Contact Us
    Awards Voting Nominations Previous Winners
    Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
    Resources
    Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software