AJAX Tabs (Rails redux)
Links
About
This blog is filled with Rails tidbits, presentations, tutorials, and other web related mumbo-jumbo. The author is Flinn Mueller a veteran web programmer.
Flinn is a proud husband and father, an out doors enthusiast, a foodie, and occasionally allows pictures of his bad yoga form.
Purpose
The purpose of doing Tabs in Ajax is not just to do something in Ajax. The context for which this is used is not for novelty. In this demo we are using Ajax to load information that does not need to be retrieved every time the user pulls the record. The methods used here are to yield absolute control over where the content comes from, what conditions trigger action, and how the panes are presented. This is really meant to be used in an application because the javascript and css are heavy if you are only using them on one page. However if you consider them groundwork for using tabs throughout a larger application then they are pretty light compared to something like YUI.
Background
About a year ago I wrote a tutorial on how to use prototype, plain old html, and css to create AJAX Tabs. The tutorial was a plug for a PHP based Rails clone I was working on called Biscuit (no I didn't name it that, someone else did). Anyhow Biscuit is now defunct (Long live Rails), but the tutorial still works just fine in Rails. So read on...
Concept
The concept here is simple, I have a Part Vendor, which consists of a `vendor` model with a has_many :through associaton to `parts`, and polymorphic association to `notes`. I want to display the Vendor info in the first tab, and have a long list of parts in the second tab, a map of the vendor location and any related notes, however since Parts and Notes can be extensive I will not preload these. I don't want to reload Parts or Notes every time I click their respective tabs either. So when you click the tabs for parts and notes they load into their containers, but when you click them again the innerHTML is already set, so no need to grab the data again.
The Results
-
Company Right Traq, Inc. Street 7 S. Main St. Suite 225 City Wilkes-Barre State PA Zip Code 18701 Contact Flinn Mueller Title President Email info.righttraq.com Phone (570) 408-9825 Alternate Fax Alternate Mobile Text Pager Skype Website http://www.righttraq.com
See How It's Done
The AJAX Tabs
<ul class="tabselector" id="tabcontrol1">
<li class="tab-selected" id="vendor_tab">
<%= link_to_function('Vendor', "tabselect($('vendor_tab')); paneselect($('vendor_pane'))") %></li>
<li class="tab-unselected" id="part_tab">
<%= link_to_function('Parts', "loadPane($('part_pane'), '" + url_for(:action => 'parts', :id => @vendor) + "'), tabselect($('part_tab')); paneselect($('part_pane'))") %></li>
<li class="tab-unselected" id="map_tab">
<%= link_to_function('Map', "loadPane($('map_pane'), '" + url_for(:action => 'map', :id => @vendor) + "'), tabselect($('map_tab')); paneselect($('map_pane'))") %></li>
<li class="tab-unselected" id="notes_tab">
<%= link_to_function('Notes', "loadPane($('notes_pane'), '" + url_for(:controller => 'notes', :action => 'list', :notable_id => @vendor, :notable_type => @vendor.class) + "'), tabselect($('notes_tab')); paneselect($('notes_pane'))") %></li>
</ul>
<ul class="panes" id="panecontrol1">
<li id="vendor_pane" class="pane-selected">
<%= render :partial => 'show' %>
</li>
<li id="part_pane" class="pane-unselected"></li>
<li id="map_pane" class="pane-unselected">
<%= render :partial => 'map' %>
</li>
<li id="notes_pane" class="pane-unselected"></li>
<ul>
My Javascript
var tablist = $('tabcontrol1').getElementsByTagName('li');
var nodes = $A(tablist);
var lClassType = tab.className.substring(0, tab.className.indexOf('-') );
nodes.each(function(node){
if (node.id == tab.id) {
tab.className=lClassType+'-selected';
} else {
node.className=lClassType+'-unselected';
};
});
}
function paneselect(pane) {
var panelist = $('panecontrol1').getElementsByTagName('li');
var nodes = $A(panelist);
nodes.each(function(node){
if (node.id == pane.id) {
pane.className='pane-selected';
} else {
node.className='pane-unselected';
};
});
}
function loadPane(pane, src) {
if (pane.innerHTML=='' || pane.innerHTML=='<img alt="Wait" src="/images/spinner.gif" style="vertical-align:-3px" /> Loading...') {
reloadPane(pane, src);
}
}
function reloadPane(pane, src) {
new Ajax.Updater(pane, src, {asynchronous:1, evalScripts:true, onLoading:function(request){pane.innerHTML='<img alt="Wait" src="/images/spinner.gif" style="vertical-align:-3px" /> Loading...'}})
}
My Fancy CSS
width: auto;
border-bottom: 1px solid #c0c0c0;
padding: 10px 0 0 20px;
}
.tab-unselected {
display: inline;
padding: 2px 7px 0 7px;
background: #f0f0f0;
border: 1px solid #c0c0c0;
border-bottom: 0;
color: #c0c0c0;
}
.tab-selected {
display: inline;
padding: 3px 7px 1px 7px;
background: #ffffff;
border: 1px solid #c0c0c0;
border-bottom: 0;
}
.tab-unselected a {
padding: 6px;
color: #a0a0a0;
}
.tab-selected a {
font-weight: bold;
color: #0066CC;
padding: 6px;
}
.panes, .pane-selector {
width: 97%;
padding-left: 0px;
margin: 2%;
min-height: 300px;
overflow: auto;
}
.pane-selected {
list-style-type: none;
display: block;
padding: 10px;
}
.pane-unselected {
list-style-type: none;
display: none;
}
