Workaround for “hidden” Column Problem in ZEN dataGrid Component

Primary tabs

The Issue

I help support a legacy ZEN application that makes extensive use of the “dataGrid” component. The application reuses a ZEN page with a“dataGrid” for different views of similar data; depending on the view, the application hides or displays some of the columns of the “dataGrid”. The application does this client-side, setting the “hidden” property of the “columnDescriptor” true or false as needed.

Users have reported the following bug in the app: On views which hide a column, data in the other columns are shifted so as to appear under the wrong headers. In order to demonstrate the issue, I’ve created the following ZEN page which uses the “Cinema” data in the “SAMPLES” namespace (code further below).

In this initial view, a dataGrid shows 4 fields from the “Cinema.Film” table.

 

The code behind the “Hide Ratings (Client-Side)” button marks the “Rating” columnDescriptor as “hidden” if it’s currently displayed and refreshes the dataGrid. The following shows the application after clicking that button:

 

The “Rating” column is hidden as expected. But what happened to the other data? The “Tickets Sold” data no longer appears in the dataGrid and the “Playing Time” data has shifted into its place.

Clicking the “Show Ratings (Client-Side)” button sets the “hidden” value of the “Ratings” columnDescriptor to 0, causing that column to be redisplayed, with data showing up properly in the other columns:

 

 

The Workaround

After some experimentation, I found a “server-side” solution that consists of removing the “columnDescriptor” entry for the column to be hidden from the “dataGrid” component. Going back to the server for a UI change is not ideal in terms of responsiveness, but it works. Here is the dataGrid after clicking the “Hide Ratings (Server-Side)” button:

Note that the “Ratings” column is gone, but the data in the “Tickets Sold” and “Playing Time” remains visible (and correct).

Below is the Cinema.Home.cls ZEN page source that shows both the client-side code that highlights the issue and the server-side code that implements the workaround. The code works with the existing code in the "SAMPLES" namespace.

This is one approach to addressing the issue. Since this is software, there are likely others. Please share your ideas. Thanks for reading.

/// Created using the page template: Default
Class Cinema.Home Extends %ZEN.Component.page
{
/// This Style block contains page-specific CSS style definitions.
XData Style
{
<style type="text/css">
</style>
}

/// This XML block defines the contents of this page.
XData Contents [ XMLNamespace = "http://www.intersystems.com/zen]
{
<page xmlns="http://www.intersystems.com/zentitle="dataGrid Demo">
<html id="title" align="center" 
enclosingStyle="border-bottom:none; background:khaki; font-family: Verdana; font-size: 1.5em; font-weight: bold;">
DataGrid Demo</html>
<jsonProvider id="jsonAC" OnGetArray="GetMovies" >
</jsonProvider>
<spacer height="5" />
<vgroup id="wrapper" align="center">
<hgroup width="600px;">
<button id="clientToggle" caption="Hide Ratings (Client-Side)" onclick="zenPage.ToggleColumnClient('rating');"/>
<button id="serverToggle" caption="Hide Ratings (Server-Side)" align="right" onclick="zenPage.ToggleColumnServer('rating')"/>
</hgroup>
<spacer height="5" />
<dataGrid id="agrid" controllerId="jsonAC" pageSize="40"  
 canResizeColumns="true" enclosingStyle="height: 500px;">
<columnDescriptor headerAlign="center" caption="Title" id="filmtitle" value="=[@Title]" align="right" readOnly="true" hidden="false"/>
<columnDescriptor headerAlign="center" caption="Rating" id="rating"  value="=[@Rating]"  align="right" readOnly="true" hidden="false"/>
<columnDescriptor headerAlign="center" caption="Tickets Sold" id="sold"  value="=[@TicketsSold]"  align="right" readOnly="true"/>
<columnDescriptor headerAlign="center" caption="Playing Time" id="length"  value="=[@Length]"  align="right" readOnly="true"/>
</dataGrid>
</vgroup>
</page>
}
Method GetMovies(ByRef pParam As %String, Output pMetaData, Output pData) As %Status
{
                   // set what=pParam("what")
                   set pMetaData = $lb("Title","Rating","TicketsSold","Length")
                  
                   set sql="select %Exact(Title) Title, Rating, TicketsSold, Length from Cinema.Film order by Length"
                   set rs=##class(%ResultSet).%New()
                   set sc=rs.Prepare(sql)
                   set sc=rs.Execute()
                  
                   set pData = "", row = 0
                   While rs.Next() {
                                      Set row = row + 1
                                      Set title = rs.GetData(1)
                                      Set rating = rs.GetData(2)
                                      Set sold = rs.GetData(3)
                                      Set length = rs.GetData(4)
                                      Set pData(row) = $lb(title,rating,sold,length)
                   }
                  
                   Quit $$$OK
}
ClientMethod ToggleColumnClient(colName) [ Language = javascript ]
{
                   var hidden zen(colName).hidden;
                   if (hidden == false)
                   {
                                      zen(colName).setProperty("hidden",1);
                                      zen('clientToggle').setProperty("caption","Show Ratings (Client-Side)");
                   }
                   else {
                                      zen(colName).setProperty("hidden",0);

                                      zen('clientToggle').setProperty("caption","Hide Ratings (Client-Side)");
                   }
                   zen('agrid').renderContents();
}
Method ToggleColumnServer(colName As %String) As %Status [ ZenMethod ]
{
                   set tSC = $$$OK /// Assume all will be well.
                  
                   // Get reference to bottom dataGrid named "agrid"; we'll be removing unneeded columns instead of hiding them (there is a bug in dataGrid related to hidden columns).
                   set agrid=%page.%GetComponentById("agrid")
                   set columnDescriptors = agrid.columnDescriptors
                   set columnCount = columnDescriptors.Count()
                   // We'll go by the count, if 4, need to remove one of the columns
                   if (columnCount = 4)
                   {
                                      for i=columnCount:-1:1
                                      {
                                                         set columnDescriptor = columnDescriptors.GetAt(i)
                                                          if $isobject(columnDescriptor)
                                                          {
                                                                             // These are the columns in 'agrid' that are displayed in table for current year, but not for previous or future years.
                                                                             if (columnDescriptor.id = colName)
                                                                             {
                                                                                                set success = ""
                                                                                                set result = columnDescriptors.RemoveAt(i,.success)
                                                                                                if ('success)
                                                                                                {
                                                                                                                   Set tSC = $$$ERROR($$$GeneralError, "Couldn't remove columnDescriptor with id=<"_columnDescriptor.id_">, result=<"_result_">.")
                                                                                                }
                                                                             }
                                                          }
                                      }
                                      &js<zen('agrid').refreshContents();>
                                      set serverToggleButton = %page.%GetComponentById("serverToggle")
                                      set serverToggleButton.caption = "Show Ratings (Server-Side)"
                   }
                   else {
                                      // One of the original columns is missing, return to the original page with all columns.
                                      &js<zenPage.gotoPage("Cinema.Home.cls");>
                   }
                   quit tSC
}
}