Friday, February 19, 2010

A strategy for building a Section 508/WCAG Telerik radGrid page

Accessibility and translations can often result in a medusa head for code. On one hand you want a feature rich grid but you are constrained by Section 508.  Managing and tracking resource mnemonic across a large project becomes a nightmare (and very very boring code typing for developers which usually impacts quality control).  Another pain with a large site is consistency of presentation.


What if I claim that all you need to do is toss the code below into a page and be done with coding!


The Web Page Content:

   1: <telerik:RadGrid runat="server" ID="AccountSummaryGrid" DataSourceID="AccountSummaryDb" AccessKey="X">
   2:     <MasterTableView AutoGenerateColumns="false" DataKeyNames="AccountName">
   3:         <Columns>
   4:             <telerik:GridBoundColumn DataField="AccountName" UniqueName="Grid_AccountName">
   5:             </telerik:GridBoundColumn>
   6:             <telerik:GridBoundColumn DataField="Description" UniqueName="Grid_AccountDescription">
   7:             </telerik:GridBoundColumn>
   8:             <telerik:GridBoundColumn DataField="Balance" UniqueName="Grid_AccountBalance">
   9:             </telerik:GridBoundColumn>
  10:             <telerik:GridBoundColumn DataField="CreditLimit" UniqueName="Grid_AccountCreditLimit">
  11:             </telerik:GridBoundColumn>
  12:             <telerik:GridBoundColumn DataField="OwnershipType" UniqueName="Grid_OwnershipType">
  13:             </telerik:GridBoundColumn>
  14:         </Columns>
  15:     </MasterTableView>
  16: </telerik:RadGrid>

With the page behind code being a horrible:

   1: protected void Page_Load(object sender, EventArgs e)
   2: { 
   3:     RadGridUtilities.ApplyDefaultSettings(AccountSummaryGrid);
   4: }

There are many tricks (and enhancement not shown), but the first item is to give the user the option to put the site into Accessibility Mode. If you give that option then only the pages while in Accessibility Mode must comply with Section 508. You can be as feature rich as you want elsewhere!!


This is what the one C# statement above does.

  • Provide web site defaults to the grid (every grid…), you want the defaults changed – it occurs in one spot only not in 100 pages!
  • Provide translation that is based off of the Control.Id (SO no more “Button1” but descriptive “ButtonToRetrieveStatement”
  • Modify the grid for what you want to do for accessibility.

There are a few items that I excluded (like automatically creating Resx entries for every phrase dynamically and self-auditing for 508 compliance) so the code is clearer.

/// <summary>
/// Applies the default settings to the page. If user customization is implemented
/// the settings here would come from their preferences stored in a Session object.
/// </summary>
/// <param name="grid">a radGrid control</param>
/// <param name="onGetOnly">Determines if the settings are down on every postback or only on first GET</param>
public static void ApplyDefaultSettings(RadGrid grid, bool onGetOnly)
    // Uniquenames should have an underscore (_) in it. grid_ is the recommended prefix so that
    //localization may identify items used in grids
    char[] sepUnique = { '_', ' ' };

    // Items requiring     
    grid.ItemCreated += grid_ItemCreatedAddRowScope;

    // Get translations. For items that Telerik may have translated use fallback version. 
    grid.MasterTableView.Caption = ResourceTranslation.TranslateField(string.Format(CultureInfo.CurrentCulture, "%ContentPage%{0}_Caption%", grid.ID));
    grid.MasterTableView.CommandItemSettings.AddNewRecordText = ResourceTranslation.TranslateField("%MasterPage%Grid_AddNewRecordText%", grid.MasterTableView.CommandItemSettings.AddNewRecordText);
    grid.MasterTableView.CommandItemSettings.ExportToCsvText = ResourceTranslation.TranslateField("%MasterPage%Grid_ExportToCsvText%", grid.MasterTableView.CommandItemSettings.ExportToCsvText);
    grid.MasterTableView.CommandItemSettings.ExportToExcelText = ResourceTranslation.TranslateField("%MasterPage%Grid_ExportToExcelText%", grid.MasterTableView.CommandItemSettings.ExportToExcelText);
    grid.MasterTableView.CommandItemSettings.ExportToPdfText = ResourceTranslation.TranslateField("%MasterPage%Grid_ExportToPdfText%", grid.MasterTableView.CommandItemSettings.ExportToPdfText);
    grid.MasterTableView.CommandItemSettings.ExportToWordText = ResourceTranslation.TranslateField("%MasterPage%Grid_ExportToWordText%", grid.MasterTableView.CommandItemSettings.ExportToWordText);
    grid.MasterTableView.CommandItemSettings.RefreshText = ResourceTranslation.TranslateField("%MasterPage%Grid_RefreshText%", grid.MasterTableView.CommandItemSettings.RefreshText);
    grid.MasterTableView.CssClass = ResourceTranslation.TranslateField("%MasterPage%Grid_CssClass%", grid.MasterTableView.CssClass);
    grid.MasterTableView.Summary = ResourceTranslation.TranslateField(string.Format(CultureInfo.CurrentCulture, "%ContentPage%{0}_Summary%", grid.ID));

    // This section would be used if images are to be customized for languages (unlikely)
    //    grid.MasterTableView.CommandItemSettings.AddNewRecordImageUrl = ResourceTranslation.TranslateField(grid.MasterTableView.CommandItemSettings.AddNewRecordImageUrl);
    //    grid.MasterTableView.CommandItemSettings.ExportToCsvImageUrl = ResourceTranslation.TranslateField(grid.MasterTableView.CommandItemSettings.ExportToCsvImageUrl);
    //    grid.MasterTableView.CommandItemSettings.ExportToExcelImageUrl = ResourceTranslation.TranslateField(grid.MasterTableView.CommandItemSettings.ExportToExcelImageUrl);
    //    grid.MasterTableView.CommandItemSettings.ExportToPdfImageUrl = ResourceTranslation.TranslateField(grid.MasterTableView.CommandItemSettings.ExportToPdfImageUrl);
    //    grid.MasterTableView.CommandItemSettings.ExportToWordImageUrl = ResourceTranslation.TranslateField(grid.MasterTableView.CommandItemSettings.ExportToWordImageUrl);
    // Site specific options. 
    // If User Options are allowed, the user's setting would be assigned instead.
    grid.AllowPaging = true;
    grid.AllowSorting = true;
    grid.ExportSettings.FileName = grid.ID;
    grid.ExportSettings.IgnorePaging = true;
    grid.ExportSettings.OpenInNewWindow = true;
    grid.ExportSettings.Pdf.Author = "Lassesen Consulting, LLC";
    grid.ExportSettings.Pdf.PageTitle = grid.MasterTableView.Caption;
    grid.ExportSettings.Pdf.Subject = grid.ID;
    grid.MasterTableView.AllowPaging = true;
    grid.MasterTableView.AllowSorting = true;
    grid.MasterTableView.CommandItemDisplay = GridCommandItemDisplay.TopAndBottom;
    grid.MasterTableView.CommandItemSettings.ShowExportToCsvButton = true;
    grid.MasterTableView.CommandItemSettings.ShowExportToExcelButton = true;
    grid.MasterTableView.CommandItemSettings.ShowExportToPdfButton = true;
    grid.MasterTableView.CommandItemSettings.ShowExportToWordButton = true;
    grid.MasterTableView.PagerStyle.Mode = GridPagerMode.NextPrevNumericAndAdvanced;
    grid.PageSize = 60;

    // Changes for accessibility on a Grid Level
    // Remember hanidcap can be cognitive or physical -- so trim lots
    // of features
    if (Section508.UserSetting)
        grid.AllowCustomPaging = false;
        grid.AllowFilteringByColumn = false;
        grid.AllowMultiRowEdit = false;
        grid.AllowMultiRowSelection = false;
        grid.MasterTableView.AllowPaging = false;
        grid.MasterTableView.AllowSorting = false;
        grid.MasterTableView.CommandItemDisplay = GridCommandItemDisplay.None;
        grid.MasterTableView.CommandItemSettings.ShowExportToCsvButton = false;
        grid.MasterTableView.CommandItemSettings.ShowExportToExcelButton = false;
        grid.MasterTableView.CommandItemSettings.ShowExportToPdfButton = false;
        grid.MasterTableView.CommandItemSettings.ShowExportToWordButton = false;
        grid.MasterTableView.ShowGroupFooter = false;
        grid.ShowGroupPanel = false;
        grid.ShowStatusBar = false;

    int colNo = 0;
    foreach (GridColumn col in grid.MasterTableView.Columns)
        // uniquekeys are assumed to be compounded with an underscore
        //separator. Only last part is used as HeaderAbbr
        var translationkey = col.UniqueName;
        var parts = col.UniqueName.Split(sepUnique, System.StringSplitOptions.RemoveEmptyEntries);
        col.HeaderAbbr = parts[parts.Length - 1];
        // CHANGES for Accessibilty at a Column Level
        if (Section508.UserSetting)
            col.AutoPostBackOnFilter = false;
            col.Groupable = false;
            col.HeaderButtonType = GridHeaderButtonType.PushButton;
            col.Resizable = false;
        string tooltipMnemonic = string.Empty;
        //Determine if sortable and adopt appropriate tooltip
        if (grid.AllowSorting)
            if (! string.IsNullOrEmpty(col.SortExpression))
                tooltipMnemonic = "Sort";
            tooltipMnemonic =string.Format(CultureInfo.CurrentCulture, "%ContentPage%{0}_{1}Tooltip%", translationkey,tooltipMnemonic);
            col.HeaderTooltip = ResourceTranslation.TranslateField(tooltipMnemonic);
        col.HeaderText = ResourceTranslation.TranslateField(string.Format(CultureInfo.CurrentCulture, "%ContentPage%{0}%", translationkey));


The first item of importance is the event handler that is added

grid.ItemCreated += grid_ItemCreatedAddRowScope;


Grids require at least one <td scope=”row”> for Section 508 which is not natively provided by Telerik, so we must add it while the item is being created. This is done with the code below. The columns are those that are in the DataKeyNames, which makes it almost happens by designed-accident.

   1: /// <summary>
   2: /// Routine to add scope="row" to the grid for 508 compliance. The items are those specified in
   3: /// DataKeyNames
   4: /// </summary>
   5: /// <param name="sender">a telerik radgrid control</param>
   6: /// <param name="e"></param>
   7: static void grid_ItemCreatedAddRowScope(object sender, GridItemEventArgs e)
   8: {
   9:     if (e.Item is GridDataItem)
  10:     {
  11:         GridDataItem dataItem = e.Item as GridDataItem;
  12:         foreach (string key in dataItem.OwnerTableView.DataKeyNames)
  13:         {
  14:             foreach (GridColumn col in dataItem.OwnerTableView.Columns)
  15:             {
  16:                 if (col.IsBoundToFieldName(key))
  17:                 {
  18:                     TableCell cell = dataItem[col.UniqueName];
  19:                     cell.Attributes["scope"] = "row";
  20:                 }
  21:             }
  22:         }
  23:     }
  24: }

The other item is the ResourceTranslation.TranslateField which is a widget between the code and the usual handling of resources. It allows me to identify if a resource is missing as well as do some fancy stuff like “lookup the term and if you do not find it, then use what Telerik provides”;


That’s it.

No comments:

Post a Comment