Reading datagrid row contents client-side

Posted on 2004-09-13
Last Modified: 2008-03-03
Siuppose I have a datagrid with a checkbox column and several data columns.
If the user clicks anywhere  on a row except on the checkbox then I need to call client-side function f1, which needs access to (a subset of) the values from the clicked row.
If the user clicks on the checkbox, then function f2 is executed with (in this particular case only) the same arguments (but it could be a different subset)

I know I can do this by setting up handlers server side in the ItemDataBound handler, e.g.:

oItem.Attributes("onclick") += GridRowClickedHandlerName + "(this, " & ClientSideRowHandlerArgs(oRow)  + ");"

Here ClientSideRowHandlerArgs( is  a function that generates the comma-separated list of arguments for the client-side call.
I can do the same for the onclick of the checkbox. It's arguments will be exactly the same.

The problem is that if I do it that way then I have several copies of the same data on the page:
-the row's cell contents
-the row conclick event handler
-the checkbox onclick event handler
-the viewstate representing the actually contains a copy of each of the above

Altogether that's 6 copies of (at least some of) the data in each row.

There has to be  a better way.

One idea I have had is to write the data as a client side array and give the client event handlers and index into this array as a argument. That cuts down the duplication to 2 copies (row + array) and saves on viewstate info too. This is straightforward to implement and flexible enough for my needs. However it still leaves one redundant copy of the data in the form of the array.

My other idea is to develop a client-side function that pullls the row values from the client-side row. This client-side function would be built (written)  on the fly server-side. The point of this is that only the server-side knows how the data is put together in a row i.e. which value goes in which column , how to access a value in a given column etc. This function would presumably take at most one parameter whic would be a row index and, via the DOM, extract the values from the relevant row.  

I'm looking for guidance on implementing my second suggestion or for alternatives to the approaches outlined.

Question by:monosodiumg
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 6
  • 3

Expert Comment

ID: 12049359
It's a bit of a hack, but given that the ClientIDs of the controls end up getting appended with a sequential row number (I think), maybe you can skip the DOM approach and simply register an array of ClientID prefix values to examine, appending the row number in your function, and using document.getElementByID() on all of the control names to get the values you're looking for.

I'd suggest storing the indices of the controls you want to examine within the cells, but, as you may have discovered, ASP.NET renders controls differently for different browswers, like <div forecolor='blue'> might become <font color='blue><div> or something for down-level browsers which makes it difficult to do javascript traversals.
LVL 12

Author Comment

ID: 12049559
THansk for the prompt reply.
I'm not sure about using making assumptions client-side about the structure of the rows.  Your point about the variations in rendering is actually an example of the problem (as it happens this is for ie5.5+ on wintel but the point is good).
The other reason for skepsis about client-side DOM based approaches is that different column types e.g. template columns render to client side HTML that simply can't be understood client-side without guidance from the server-side.
 That's why I envision some server side method for putting together a client-side (js) function that does the DOM-walking required. The server does in fact know what the clinet-sdie DOM will be and how to extract the values from it.  
While I have an idea there, I lack the knowledge and confidence to put this together in a reasonable time , not least because I've struggled (and failed) in the past  just to get to give me server-side the full client-side id it allocates to a control!  If I coudo at least get it to allocate and provide me server side with an id for each cell ina row, then I think I would be half-way there.

Expert Comment

ID: 12049918
You're hosed on the RowID, unfortunately (the DataGrid handles the row rendering and makes no provision for row IDs). I learned that the hard way when I tried to change the back-color of the row onclick.

What do you think about this approach (an elaboration of my first idea). Note that the code below WILL NOT work, I'm just illustrating the concept.

//put block this wherever
ArrayList controlsIWantValuesFrom = new ArrayList();

//get ClientID minus the row number that INamingContainer would add.
if( /*I want the control in some column to be included in the javascript evaluation*/ )
    controlsIWantValuesFrom.Add( Control.ClientID.TrimEnd( new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) );

//on item data bound
control.Attributes.Add( "onclick", "DoStuff(" + e.Item.ItemIndex + ");" );

string javascriptArray;
foreach( string s in controlsIWantValuesFrom )
      javascriptArray += "'" + s + "',";

//remove last comma
javascriptArray.TrimEnd( new char[]{ ',' } );

//... I forget the exact name of this function but anyway, pass "javascriptArray" to it
Page.RegisterArray( "myArray", javascriptArray );

Then in the javascript do something like:

function DoStuff( index )
    var total = 0;
    for( var i = 0; var i < myArray.length; i++ )
        //find control by using the clientID concatinated with the row index
        var control = document.getElementById( myArray[i] + index );
        total += control.value;
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

LVL 12

Author Comment

ID: 12053095
The problem with that approach is that all I get  client-side are IDs for controls. What I need is a way of accessing the data in those controls and that depends on exactly how the control is rendered. The ID's on their own would only do if the relationship between the DOM item identified by the ID wrapped up the data in a manner that is predictable clinet-side. That isn't generally the case, for example template columns coul gnerate arbitrary HTML so  having the ID of the outermost client-side element  doesn't tell the clinent-sdie code how to dig out the relevant value (possibly plural!) from that.

Your client-sdie array is reminiscent of the approach I outlined. That's what I;m developing right now. So far it looks like this:

//Generates the client-side representation for a single item. I chose to generate a client-side object.
      Protected Overrides Function GetClientItemRepresentation(ByVal oRow As DataRow) As String
            Dim sRes As String = ""
            sRes = "{"
            Dim sShortIdentifier As String = ...
            sRes = sRes & "sDocNo:'" & sShortIdentifier & "'"
            sRes = sRes & ",nRecno:" & CType(oRow.Item("_TASK_RECNO"), String)
            sRes = sRes & ",sTablename:'" & CType(oRow.Item("_DOCUMENT_TABLENAME"), String) & "'"
            sRes = sRes & ",sFilename:'" & CType(oRow.Item("_DOCUMENT_FILENAME"), String) & "'"
            sRes = sRes & "}"
            Return sRes
      End Function

//Generates the code for the client-side array:
      Protected Function GenerateClientSideDataArrayScript() As String
            Dim oItem As Object
            Dim sClientItemRepresentation As String = ""
            Dim nIdx As Integer
            For nIdx = 0 To Me.ListingSet.Count
                  sClientItemRepresentation = sClientItemRepresentation & "_items[" & CType(nIdx, String) & "]=" & GetClientItemRepresentation(Me.GridDataSourceTable.Rows(nIdx)) & vbCrLf
            Next nIdx
            If sClientItemRepresentation <> "" Then
                  sClientItemRepresentation = "var _items = new Array(" & CType(Me.ListingSet.Count, String) & ")" & sClientItemRepresentation
            End If
            Return sClientItemRepresentation
      End Function

The function whose name you were unsuer about is RegisterClientScriptBlock and I call it from PreRender with the output from the above function.

The row  and click handlers now both look like this (reaplcing
oItem.Attributes("onclick") += GridRowClickedHandlerName & "(this, _items[" & CType(e.Item.ItemIndex, String) & "]);"
oItem.Attributes("onclick") += GridCheckboxClickedHandlerName & "(this, _items[" & CType(e.Item.ItemIndex, String) & "]);"

The client side click handlers have no knowledge  of the client-side array stuff. They just get the fully populated object and the row itself (for highlighting purpuses).

I've seen a few people having trouble highlighting the row that is clicked on and de-higlighting the others. The way I did it was to iterate over the rows client-side to find the one whose style sheet was for the one for the highlighted state and that row's style gets changed to the default state (my rows are alternate colours) and the clicked row (available as a parameter) gets the highlighted state stylesheet assigned to it. Never thought of rowIDs. Passing "this" to the row handler means you get the DOM object for the row. whic is better than a rowID I think.

Accepted Solution

boulder_bum earned 500 total points
ID: 12057045
Your project is turning out to look pretty fun!

"What I need is a way of accessing the data in those controls and that depends on exactly how the control is rendered."

Well, the controls within a template (TextBoxes, CheckBoxes, etc) should have the ClientID referencing the control with the data no matter what extra rendering goes on in the periphery: some control might get wrapped in a <font> tag or something instead of having font info added to Style, but the core data element should always be the same. In this way, it actually IS quite predictable (it's how, for example, validators attach client script to inputs).

To clarify, though. I wasn't talking about referencing the rows, but rather their child controls. Like, if you wanted to reference three TextBoxes that are placed in a template column, you'd (theoretically) add their IDs minus the number suffix to the controlsIWantValuesFrom array.

Doing so would (in theory) let you easily derive the IDs of each control you want in the row because the IDs should be the ClientID base with a row number suffix.

The principal advantage of the approach is that it dramatically cuts down on redundant data. The function call would simply store a number value instead of a long control name or something, and the array you'd reference in the function call consists of only N elements, where N is the number of controls within the TemplateColumn that you want to get values from.

This N-element might prove advantageous because, while I'm not sure what "ListingSet" is in your code, it appears at first glance that you're storing N*RowCount elements in an array.

RE: <offtopic>...

In my case, I changed the color on the click of a CheckBox (or via a "check all" checkbox).  I looked up something on CodeProject that (once I fixed the errors) used the Style attribute of the CheckBoxes' parent element (assumed to be the row) to change its background color. The problem was that 1. you couldn't reference the parent using the author's methodology outside of IE and 2. in non-IE browsers, you can't set values of the Style attributes if none are originally present to begin with (which was the case with the rows). I ended up using row indexing in the function call and a color-set method better suited to cross-browswer compatibility based on the DOM. I also wrapped everything up in a nice self-contained server control, so you could use it in several projects with a simple reference and some dragging-and-dropping. It turned out pretty cool!

Expert Comment

ID: 12057078

"I wasn't talking about referencing the rows"

should be

"I wasn't talking about referencing the rows in the array"

Expert Comment

ID: 12245239
This one should probably just get deleted.
LVL 12

Author Comment

ID: 12246638
>This one should probably just get deleted.

Not at all. boulder_bums contributions are pertinent and valuable.
Sorry for leaving this outstanding so long.

Expert Comment

ID: 12430492
"Not at all. boulder_bums contributions are pertinent and valuable. "

Happy I could help! :-)

Featured Post

MS Dynamics Made Instantly Simpler

Make Your Microsoft Dynamics Investment Count  & Drastically Decrease Training Time by Providing Intuitive Step-By-Step WalkThru Tutorials.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article discusses the ASP.NET AJAX ModalPopupExtender control. In this article we will show how to use the ModalPopupExtender control, how to display/show/call the ASP.NET AJAX ModalPopupExtender control from javascript, how to show/display/cal…
In this Article, I will provide a few tips in problem and solution manner. Opening an ASPX page in Visual studio 2003 is very slow. To make it fast, please do follow below steps:   Open the Solution/Project. Right click the ASPX file to b…

726 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question