Wednesday, January 05, 2011

Scrolling a ListView’s EditItemTemplate with some help from jQuery

I’ve been using ASP.Net’s ListView control and I cam across a little quirk while setting up the editing bits.  I have an EditItemTemplate defined that has some input controls and a couple of nested ListViews.  It’s a nice way of defining a hierarchal editing view.  If you haven’t used a ListView before, Scott Mitchell have a great tutorial.

My ItemTemplate displays the data for each line of data and has a couple of buttons, one to edit the item, the other to delete it.  When you click the edit button, you get a page refresh and ASP.NET renders the EditItemTemplate with the editing controls and the user can edit the item and save the changes.
The problem was when you needed to scroll down the page to make the EditItemTemplate visible in the browser.  If the height of the ListView was greater than the visible height of the browser, the user would have to scroll down the page to get to the first input control.  I ended up using a bit of jQuery to scroll the edit area into view for the user. 
This is an edited down version of the EditItemTemplate for a ListView that I’m currently working on:
<EditItemTemplate>
    <div style="padding: 0 0 10px 0;" class="MyEditItemTemplate">
        <b>Calendar Name:</b>
        <asp:TextBox ID="txtCalendarName" runat="server" 
            Text='<%# Bind("CalendarName") %>'></asp:TextBox>
        <asp:ImageButton ID="imgbUpdateCalendar" runat="server" 
            ToolTip="Update Calendar" AlternateText="Update Calendar" 
            CommandName="Update" 
            ImageUrl="~/Images/accept.png" />&nbsp;
        <asp:ImageButton ID="imgbCancelUpdate" runat="server" 
            ToolTip="Cancel" AlternateText="Cancel" 
            CausesValidation="false" CommandName="Cancel" 
            ImageUrl="~/Images/cancel.png" />
        <br />
        <div style="padding: 5px 0 0 40px;">
            <div style="border: 1px black solid">
                <asp:ListView ID="ListViewChildCalendars" 
                    runat="server" DataSourceID="odsChildCalendar"
                    DataKeyNames="RecordID" 
                    InsertItemPosition="FirstItem" 
                    OnItemInserting="ListViewChildCalendars_ItemInserting">
                    <LayoutTemplate>
                        <span class="SubListViewHeader"><strong>Associated Calendars</strong></span>
                        <blockquote>
                            <asp:PlaceHolder runat="server" 
                            ID="itemPlaceholder"></asp:PlaceHolder>
                        </blockquote>
                    </LayoutTemplate>
                    <ItemSeparatorTemplate>
                        <hr />
                    </ItemSeparatorTemplate>
                    <ItemTemplate>
                        <%# Eval("CalendarName") %>&nbsp;
                        <asp:ImageButton ID="imgbDeleteCalendar" 
                            runat="server" 
                            AlternateText="Remove Calendar from Group" 
                            ToolTip="Remove Calendar from Group" 
                            OnClientClick="return DeleteConfirmation('C', this.name);" 
                            CommandName="Delete" 
                            ImageUrl="~/Images/delete.png" />
                    </ItemTemplate>
                    <InsertItemTemplate>
                        <div>
                            <b>Calendar Name: </b>
                            <asp:DropDownList ID="ddlChildCalendars" 
                                runat="server" DataSourceID="odsCalendarList"
                                DataTextField="CalendarName" 
                                DataValueField="RecordID">
                            </asp:DropDownList>
                            &nbsp;
                            <asp:ImageButton ID="imgbAddChildCalendar" 
                                runat="server" 
                                ToolTip="Associate Calendar with Group" 
                                AlternateText="Associate Calendar with Group" 
                                CommandName="Insert" 
                                ImageUrl="~/Images/accept.png" />
                        </div>
                    </InsertItemTemplate>
                    <EmptyDataTemplate>
                        <span class="SubListViewHeader"><strong>Associated Calendars</strong></span>
                        <blockquote>
                            This list is empty</blockquote>
                    </EmptyDataTemplate>
                </asp:ListView>
            </div>
        </div>
        <asp:ImageButton ID="UsedForScrolling" runat="server" 
            ImageUrl="~/Images/0.gif" CssClass="UsedForScrolling"/>
    </div>
    <br />
</EditItemTemplate>


This EditItemTemplate has a nest ListView to associate other items to item currently being edited.  It’s basically a pick list populated from a drop down list.  Since each selection from the list generated a page refresh, it is important to keep the EditItemTemplate displayed on the page.

All of the controls in the EditeItemTemplate are wrapped inside a <div>, with the CSS class name set to “MyEditItemTemplate”.  Defining a CSS class is great for styling, it also makes it very easy to locate controls using jQuery. Also notice the last control in the template, the ImageButton with the ID of “UsedForScrolling”.  It uses an image of an invisible GIF file.  It’s a 1x1 image, with the single pixel set to the transparent color.  The browser will render it (briefly), but it wont be visible.  You can download the GIF from invisible GIF file link.  I had obtained it from a page by Nick Webb.

You can use the HTML input control instead of an ImageButton, it will not matter too much.  You want to avoid using a control that will render on the screen as a visible item, like a text input control or a check box.  While the code will hide the control after the page has loaded, there would be a flash as the control is rendered, and then removed from the page,  With the invisible GIF, you avoid the brief flash on the page.

This is how we scroll the edit template so that it’s entirely in view:


  1. Check for the existence of the invisible button.  It’s only visible when you are editing an item in the ListView.
  2. Set the focus to that button.  While it will render as a transparent image, it can still receive the focus.  This will force the browser to scroll the page (if necessary) so that the focused input control is in view.
  3. Set the focus to the first input control in the EditItemLayout.
  4. Hide the transparent input button.  While you can’t see it, you don’t want the user to be able to select it.

We implement this with some jQuery code that will do all 4 four steps.  I put the code in a javascript function that I named “ScrollIntoView()” and called that function from the jQuery ready event.


$(function () {
ScrollIntoView();
});

function ScrollIntoView()
{
    var TempInputControlForScrolling = $(".UsedForScrolling:input:visible:first");
    var FirstEditControl = $(".MyEditItemTemplate input:text:visible:first");

if (TempInputControlForScrolling.length)
{
TempInputControlForScrolling.focus();
FirstEditControl.focus();
TempInputControlForScrolling.hide();
}
else {
$("input:text:visible:first").focus();
}
}


The first line in ScrollIntoView() uses a jQuery Selector to match the first visible input control with a CSS class of “UsedForScrolling”.  The next line matches the first visible text input control that is a child of the control with the CSS class of “MyEditItemTemplate”.

If we found an input control, then the length property of the TempInputControlForScrolling variable will be greater than 0.  If no match had been made, the length will be 0.  With the match, we set the focus to the invisible GIF input button, then set it to the first text edit control, then finally hide the invisible button.  If the .UsedForScrolling selector did not match any controls, which will happen when you are not editing an item in the ListView, then the first text input control on the page will get the focus.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.