Let's add item selection to the page shown in Figure 9-4. The idea is to transform the scrollable area of the
DataList
into something that closely resembles a custom list box. The
DataList
<SeparatorTemplate> </SeparatorTemplate>
Note that an empty SeparatorTemplate is not ignored but is rendered with an extra <br> element. To set up a functional and effective selection mechanism, we need to handle the SelectedIndexChanged event and define the SelectedItemStyle . In this case, we don't need to change the template of the item. That feature would be needed to make the component support drill-down functionality, however.
If you've
The first point to consider is, how can we make a displayed item selectable? We need to add a command button labeled with the special keyword
Select
. If, for some reason, you need more control over the whole process of selection, you could give the clickable element any command
The header of each item—that is, the customer's ID and company name—is a good candidate to become the trigger for the selection. Instead of rendering it as plain text, we surround it with a link button.
<asp:linkbuttonrunat ="server" CommandName="Select" Text='<%# SetCustomerName(Container.DataItem) %>' />
The text of the link is defined by the following function that simply
private string SetCustomerName(object dataItem) { DataRowView _row = (DataRowView) dataItem; string _output = "<b>{0} - {1}</b>"; _output = String.Format(_output, _row["
customerid
"], _row["companyname"]); return _output; }
The appearance of the link button is controlled by a couple of page-wide styles that we also used in previous chapters.
<style> a:hover {
color
:red;text-decoration:underline;} a {text-decoration:none;} </style> <body vlink="black" link="black" ... />
The combined effect of these settings is that the link displays as normal text until the mouse moves over it. When that happens, the color switches to red and an underline appears to restore the typical look and feel of HTML clickable elements, as shown in Figure 9-7.
Figure 9-7:
A selectable element in the output of a
DataList
control.
With DataGrid controls, the following tag would suffice to set the background color when a row is selected:
<SelectedItemStyle backcolor="cyan" />
If you use that tag with a DataList control, though, the graphical aspect of the selected row is not what you would expect. As Figure 9-8 shows, with a Flow layout only the individual controls have the background color changed.
Figure 9-8:
A selectable element in the output of a
DataList
control.
To extend the new background color to the whole row, use the following style declaration, which adds an explicit width. The result is shown in Figure 9-9.
Figure 9-9:
A listbox-like selection in a
DataList
control.
<SelectedItemStyle backcolor="cyan" width="100%" />
At this point, we have a fully functional infrastructure for handling the item selection event. To take a particular action on the selected item, register a handler for the SelectedIndexChanged event and update the
The second problem—ensuring that the portion of the
DataList
output that contains the selected item is included in the current view—requires a more subtle solution. In this example, we made the
DataList
output scrollable by
However, when the page posts back, the scrollbar that lets you move back and forth over the DataList output is not automatically restored to the position occupied beforehand. Suppose that you scroll down and select one of the last customers in the list. When the page is re-created after the selection, the scrollbar is reset and the selected item is not in the default view. As a result, you have to scroll down again to see its updated content. Is there an automatic way to scroll a particular item into view?
The Dynamic HTML (DHTML) object model provides a large number of HTML elements with a method named scrollIntoView . The following JavaScript function illustrates the working of the scrollIntoView DHTML method:
<SCRIPT> function ScrollToElem(ctlName) { var elem = document.all(ctlName); if (elem == null) return; elem.scrollIntoView(true); } </SCRIPT>
The function takes the ID of an HTML element as its sole argument. Next, it attempts to locate the element in the page object model. If successful, it calls the scrollIntoView method to ensure the specified element is visible within its container. The container can be the page as well as a container tag such as <div> . The Boolean argument you pass to the method indicates whether the element must be shown at the top or the bottom of the view. Although this trick is not acceptable with all browsers, it represents the only possibility we have to scroll an HTML element into view. Let's see how to integrate this piece of code with the existing DataList.
To call the scrollIntoView method, we need to assign a unique HTML name to all the data items displayed by the DataList . Furthermore, the ID we assign to each item must be easily accessible from within the SelectedIndexChanged event handler. Whenever a new item is selected, we execute some ASP.NET code that configures the HTML page's onload method. In particular, we link the page's onload method to the JavaScript function just shown. The function is passed as an argument the name of the DHTML object that represents the currently selected DataList item.
In the following code snippet, we surround the contents of an item with a <span> tag whose ID matches the value of the customerid column for the item being rendered:
<ItemTemplate> <span ID='<%# DataBinder.Eval(Container.DataItem, "customerid") %>'>
</span> </ItemTemplate>
In addition, we set the DataKeyField property of the DataList with the name of the customerid column and add a handler for the SelectedIndexChanged event.
<asp:datalist runat="server" id="Customers" RepeatLayout="Flow" DataKeyField="customerid" OnSelectedIndexChanged="SelectedIndexChanged">
When the user selects a new item, we retrieve the value of key field—the customerid column—and prepare the JavaScript call to link with the onload event on the HTML page.
public void SelectedIndexChanged(object sender, EventArgs e) { // Get the customerid of the currently selected item string custID = (string) Customers.DataKeys[Customers.SelectedIndex]; // Prepare the Javascript call. // For example, if the current item has an ID of ALFKI, the // Javascript call will be ScrollToElem('ALFKI') string js = "ScrollToElem('{0}')"; js = String.Format(js, custID); // Register the Javascript call with the onload event TheBody.Attributes["onload"] = js; }
The final step entails that we bind the JavaScript call to the onload event of the HTML page. There are two ways of accomplishing this from within ASP.NET code. You can either use the Page
RegisterStartupScript
method or set the onload attribute on the
<body>
tag. I have
<body runat="server" id="TheBody" ...>
The net effect of these changes is that whenever the user selects a new item, the page posts back and executes the
SelectedIndexChanged
method. The method updates the JavaScript function call associated with the page's onload event to reflect the current item. Next, the page is rendered and sent back to the browser. On the client, the
onload
event
The
DataList
control has a more free-form user interface than the
DataGrid
control. This consideration alone makes the
DataList
control particularly compelling to many developers who have the need to create interactive
Pagination is the control's ability to display equally
The following code snippets
private void OnPreviousPage(object sender, EventArgs e) { CurrentPageIndex -= 1; RefreshPage(); } private void OnNextPage(object sender, EventArgs e) { CurrentPageIndex += 1; RefreshPage(); }
The code
private void RefreshPage() { DataTable _data = GetData(); AdjustPageIndex(_data.Rows.Count); CurrentPage.Text = (CurrentPageIndex +1).ToString(); list.DataSource = GetPage(_data, CurrentPageIndex); list.DataBind(); } private DataTable GetData() { // Try to get the data from the session cache DataTable _data = (DataTable) Session["MyData"]; // If no data is available, read from the database if (_data == null) _data = LoadData(); return _data; }
The GetPage method is responsible for extracting the right subset of rows that fit into the current page. The page of data is returned as a DataTable object and is bound to the DataList .
private DataTable GetPage(DataTable dt, int pageIndex) { if (dt==null) dt = LoadData(); int firstIndexInPage = (CurrentPageIndex*PageSize); DataRowCollection rows = dt.Rows; DataTable target = dt.Clone(); for (int i=0; i<PageSize; i++) { int index = i+firstIndexInPage; if (index < rows.Count) target.ImportRow(rows[i+firstIndexInPage]); else break; } return target; }
The function
| Note |
As we saw in Chapter 6 and Chapter 7, the
DataGrid
control supports two flavors of pagination—automatic and custom. In automatic paging, you bind the control to a data source for the lifetime of the control and, while you page, the control itself takes care of extracting all the records that need to be displayed. Custom paging, on the other hand, loads in memory all the records to display. Implementing a form of custom paging for a
DataList
control is straightforward and, in a certain way, this is exactly what we did when we created a list of
|
For effective pagination, at least a couple of properties are needed:
CurrentPageIndex
and
PageSize
. The sample page in Figure 9-10 defines them as global properties on the page. If you plan to
Figure 9-10:
All the customers are displayed one page at a time in three columns of data. The link buttons provide for page movements and cause the
DataList
to refresh its contents.
The CurrentPageIndex property contains the 0-based index relative to the current page. The PageSize property defines the maximum number of rows permitted per page.
public int PageSize = 12; public int CurrentPageIndex { get { if (ViewState["CurrentPageIndex"] == null) return 0; int _tmp = (int) ViewState["CurrentPageIndex"]; return _tmp; } set {ViewState["CurrentPageIndex"] = value;} }
It's interesting to note that unlike
PageSize
,
CurrentPageIndex
must