Introduction
Taking a page from most mobile devices (ehem, iPhone), a sliding rolodex approach to accessing people in a list is a very handy and quick way to navigate. This project uses a combination of the SliderExtender
, GridView
, some CSS tricks, and UpdatePanel
s.
Background
I wanted my web page to be as handy as my phone.
Step 1: The Basic Slider Extender Setup
First, let's do some easy stuff. Put a slider on your AJAX enabled ASPX page and a text box. This is not very interesting. Notice however that I have set the Maximum
to 26 and Minimum
to 0 for the SliderExtender
. If you were to run this page, you would see a vertical slider and that is it.
<asp:TextBox runat="server" ID="SliderBox" />
<cc1:SliderExtender runat="server" ID="SliderBoxExtender"
Maximum="26" Minimum="0" TargetControlID="SliderBox" />
Here is what it should look like:
Step 2: Vertical Formatting
By default, the sliders go left to right. Of course, that would work just fine, but I wanted the slider to be vertical. So, using a bit of styling, I came up with this layout. Depending on other attributes in your style sheet, the height and length of your control may need to be adjusted. Frankly, I just adjusted the Length
attribute of my slider until it matched the height of my letter column. Notice that the font-size attribute for my paragraph tags is in px. This is very important. Styling fonts with px ensures that a user cannot resize them (thus messing up the careful height styling of our slider control!). I have a bunch of other styling rules here that will come into play later.
<style>
body{font-family: "trebuchet ms", sans-serif;
font-size: 10pt;
background-color: #fff;
color: #555;
line-height: 1.7em;}
p {paddding-top:0px;}
table {border:none; border-collapse:collapse;margin-bottom: 1.5em;}
table td{vertical-align:top;position:relative;}
td p {padding:4px;margin:-1px 0px;font-size:8px;width:12px;line-height:10px;
border: solid 1px #d1d1d1;cursor:hand;}
td p.letterSelected{font-weight:bold; width:14px;
background-color:#c1c1c1;font-size:10px;color:#fff;
padding:4px 4px 4px 6px; border:solid 1px #d1d1d1;}
.letterbox{
position:absolute;top:100px; left:100px; height:60px; width:100px; z-index:5;
border: solid 1px gray;background:#fff; filter:alpha(opacity=70); opacity:.7;
text-align:center; font-size:48px;padding-top:30px;
font-weight:bold;display:none;
}
.rowA{background:#c1c1c1;}
.rowB{background:#f1f1f1;}
</style>
<table>
<td>
<asp:TextBox runat="server" ID="SliderBox" />
<cc1:SliderExtender runat="server"
ID="SliderBoxExtender" Maximum="26" Length="425"
Minimum="0" TargetControlID="SliderBox" Orientation="vertical" />
</td>
<td>
<p id="row_0">All</p>
<p id="row_1">A</p>
<p id="row_2">B</p>
<p id="row_3">C</p>
<p id="row_4">D</p>
<p id="row_5">E</p>
<p id="row_6">F</p>
<p id="row_7">G</p>
<p id="row_8">H</p>
<p id="row_9">I</p>
<p id="row_10">J</p>
<p id="row_11">K</p>
<p id="row_12">L</p>
<p id="row_13">M</p>
<p id="row_14">N</p>
<p id="row_15">O</p>
<p id="row_16">P</p>
<p id="row_17">Q</p>
<p id="row_18">R</p>
<p id="row_19">S</p>
<p id="row_20">T</p>
<p id="row_21">U</p>
<p id="row_22">V</p>
<p id="row_23">W</p>
<p id="row_24">X</p>
<p id="row_25">Y</p>
<p id="row_26">Z</p>
</td>
</table>
Here is what it looks like:
Step 3: Changing Numbers to Letters
Unfortunately, slider extenders can only slide through numeric ranges. Of course, we want users to slide over letters. So, the problem is: how do we convert numbers to letters? I suppose there are a lot of ways to do this. In my example, I've used paragraph containers with numbered IDs. I then use JavaScript to determine which is selected and the innerHTML
attribute to pull out the matching letter. Sound confusing? It's not really.
First, we need to intercept all the Slider Extender actions. This means attaching our own functions to the control's events. This is surprisingly easy. Without going into each function just yet, here is how you attach a function to an Ajax control's events.
function pageLoad(sender, e) {
var slider = $find('<%=SliderBoxExtender.ClientID %>');
slider.add_slideStart(sliderStart);
slider.add_slideEnd(sliderEnd);
slider.add_valueChanged(valChanged);
}
I've used the SliderBoxExtender.ClientID
syntax to get the ControlID
as generated by .NET during render.
Now we can start coding what happens during each of these events. This is where the magic of displaying the selected letter and highlighting the selected <p>
tag will occur.
sliderStart
: Function that fires when the user first mouses down on the slider handle. We need to turn on the Letter Box (single letter box that will hover over our GridView
).
function sliderStart()
{
var fd = document.getElementById('LetterBox');
fd.style.display='inline';
}
sliderEnd
/ submitForm
: Functions that fire when the user releases the mouse button. We need to hide the Letter Box and submit our form. However, we want the GridView
in an UpdatePanel
, so instead of submitting the form, we will be clicking a hidden button that is wired to our UpdatePanel
as an Asynch trigger (see submitForm()
function). I've separated the logic for submitting the form because as you will, I wanted to be able to click on a letter as well as slide to them and achieve the same result - a posted back update of the grid.
function sliderEnd()
{
var fd = document.getElementById('LetterBox');
fd.style.display='none';
submitForm();
}
function submitForm()
{
var hidBtn = document.getElementById('<%=hiddenButton.ClientID %>');
hidBtn.click();
}
valChanged
: The real workhorse that does all the fancy bits. This function changes the text in the Letter Box and changes the formatting of the <p>
tag of the "active" letter.
function valChanged()
{
var fd = document.getElementById('LetterBox');
var valBox = document.getElementById('<%=SliderBox.ClientID %>');
var val = valBox.value;
var i = 0
var grow
for(i=0;i<27;i++)
{
grow = document.getElementById('row_' + i);
if(i==val)
{grow.className = 'letterSelected';
fd.innerHTML = grow.innerHTML;
}
else{grow.className = '';}
}
}
This is a good time to stop and see where we are. If we comment out the submitForm()
portion of the JavaScript in the sliderEnd
function, everything should be working as expected.
Step 4: Adding the UpdatePanel, GridView, and Data
I really like using ObjectDataSources
so in this example I use one. It won't mean much to you. However, this ODS uses a stored proc that does a search of my People list using SQL syntax like this:
CREATE PROCEDURE dbo.usp_GetEmployeeList_FirstLetter
(@FirstLetter nvarchar(2))
AS
SET NOCOUNT ON
SELECT
(E.LastName + ', ' + E.FirstName) as FullName,
E.EmployeeID
FROM
TrainingWeb_Employee as E
WHERE
E.LastName like @FirstLetter + '%'
and E.[State] = 'Active'
ORDER BY E.LastName, E.FirstName
RETURN
So, on the *.aspx page, add the UpdatePanel
, ObjectDataSource
, SelectParameter
of the ObjectDataSource
, and GridView
. Wire up the Gridview
to the datasource. Notice that I've also included a hidden button (at the bottom of the code below) that will be used to trigger the asynch postback. This is the button that is "clicked" from the submitForm()
JavaScript function.
<asp:UpdatePanel runat="server" ID="PersonGridUpdatePanel" UpdateMode="conditional">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="hiddenButton" EventName="Click" />
</Triggers>
<ContentTemplate>
<asp:ObjectDataSource runat="server" ID="PersonListData"
TypeName="myProject.Employee"
SelectMethod="GetEmployeeList_FirstLetter">
<SelectParameters>
<asp:Parameter Name="FirstLetter"
DefaultValue="" ConvertEmptyStringToNull="false" />
</SelectParameters>
</asp:ObjectDataSource>
<asp:GridView ID="PersonGrid" Visible=false
runat="server" DataSourceID="PersonListData" AllowPaging="true"
PageSize="20" AutoGenerateColumns="false" style="z-index:4;"
RowStyle-CssClass="rowA" AlternatingRowStyle-CssClass="rowB">
<Columns>
<asp:BoundField DataField="Fullname" HeaderText="Person's Name" />
</Columns>
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
<asp:Button runat="server" ID="hiddenButton" style="display:none;"
OnClick="LetterSelected" UseSubmitBehavior="false" />
And, we need a couple lines in the aspx.vb file to handle the postback. Namely, we want to set the value of the ObjectDataSource
's Select
parameter to whatever letter has been selected. I have enumerated the choices for each letter for convenience, but using this approach you could conceivable internationalize these controls by using ASCII coding instead of my simple 1 through 26 approach.
Protected Sub LetterSelected(ByVal sender As Object, ByVal e As EventArgs)
Dim choice As LetterChoice = CType(SliderBox.Text, LetterChoice)
If choice = LetterChoice.All Then
PersonListData.SelectParameters("FirstLetter").DefaultValue = ""
Else
PersonListData.SelectParameters("FirstLetter").DefaultValue = choice.ToString()
End If
End Sub
Public Enum LetterChoice
All = 0
A = 1
B = 2
C = 3
D = 4
E = 5
F = 6
G = 7
H = 8
I = 9
J = 10
K = 11
L = 12
M = 13
N = 14
O = 15
P = 16
Q = 17
R = 18
S = 19
T = 20
U = 21
V = 22
W = 23
X = 24
Y = 25
Z = 26
End Enum
Really, that's it. If you make sure the submitForm()
is uncommented (;)) you can run the page and it will work as expect. Of course, after doing this a few times, I realized that as a user I expected to be able to click on the letter as well as work the slider. No problem! Just add a little JavaScript to the <p>
tags and a little function that changes the value of the slider, and we have the whole package.
The new <p>
tags looks like this:
<td>
<p id="row_0" onclick="setVal(this.id)">All</p>
<p id="row_1" onclick="setVal(this.id)">A</p>
<p id="row_2" onclick="setVal(this.id)">B</p>
<p id="row_3" onclick="setVal(this.id)">C</p>
<p id="row_4" onclick="setVal(this.id)">D</p>
<p id="row_5" onclick="setVal(this.id)">E</p>
<p id="row_6" onclick="setVal(this.id)">F</p>
<p id="row_7" onclick="setVal(this.id)">G</p>
<p id="row_8" onclick="setVal(this.id)">H</p>
<p id="row_9" onclick="setVal(this.id)">I</p>
<p id="row_10" onclick="setVal(this.id)">J</p>
<p id="row_11" onclick="setVal(this.id)">K</p>
<p id="row_12" onclick="setVal(this.id)">L</p>
<p id="row_13" onclick="setVal(this.id)">M</p>
<p id="row_14" onclick="setVal(this.id)">N</p>
<p id="row_15" onclick="setVal(this.id)">O</p>
<p id="row_16" onclick="setVal(this.id)">P</p>
<p id="row_17" onclick="setVal(this.id)">Q</p>
<p id="row_18" onclick="setVal(this.id)">R</p>
<p id="row_19" onclick="setVal(this.id)">S</p>
<p id="row_20" onclick="setVal(this.id)">T</p>
<p id="row_21" onclick="setVal(this.id)">U</p>
<p id="row_22" onclick="setVal(this.id)">V</p>
<p id="row_23" onclick="setVal(this.id)">W</p>
<p id="row_24" onclick="setVal(this.id)">X</p>
<p id="row_25" onclick="setVal(this.id)">Y</p>
<p id="row_26" onclick="setVal(this.id)">Z</p>
</td>
The setVal
function looks like this:
function setVal(pID)
{
var slider = $find('<%=SliderBoxExtender.ClientID %>');
var val = pID.replace('row_','')
slider.set_Value(val);
valChanged();
submitForm();
}
Here is the finished product:
Conclusion
Designing web controls that require NO training is my passion. This rolodex control is so common to the human experience, any user should be able to figure out how to use it.
History
- 5th May, 2009: Initial post