locked
GridView with Expanding/Contracting Sub Row per each main row RRS feed

Answers

  • User1754323106 posted

    We have this functionality in our software, although, it's a bit different from your example as our detail is not another grid (just a repeater) but I guess your question is related to expand/collapse, so I'll leave the child grid to you.

    Here is a screen shot of our implementation



    What we did is on each row,  in the same colum of the "+/-" image, we had a hidden div. Inside the hidden div, we inserted our child repeater. To expand/collpase the child repeater, we used pure javascript which has been tested with firefox, ie and what not.  What the javascript does is



    1.  Get the image "src"
    2.  our image is named "icon-expand.png" and "icon-collapse.png". So if the current "src" has the word "expand", then that means we need to toggle it to "icon-collapse.png" and vice versa.
    3.  through javascript DOM, we get a reference to the hidden div relatives to the potison of the image in DOM.
    4.  we create a new table row right below the "clicked" row.
    5. we insert the hidden div content into the newly created row.
    6. if it's "collapsing", we remove the newly created row away from DOM.


    here is our grid code (please ignore stuff like DataSource, OnRowCreated and what not as it's specific to our implementation)

     

    <asp:GridView   ID="LateMileStonesGridView" <o:p></o:p>

                                                            Caption = "Late"<o:p></o:p>

                                                            runat="server" <o:p></o:p>

                                                            AutoGenerateColumns="False"<o:p></o:p>

                                                            AllowSorting="True"<o:p></o:p>

                                                            CssClass="grid"<o:p></o:p>

                                                            DataKeyNames="MilestoneID"<o:p></o:p>

                                                            EnableViewState="false"<o:p></o:p>

                                                            DataSourceID="LateMilestoneObjectDataSource"<o:p></o:p>

                                                            OnRowCreated="LateMileStonesGridView_OnRowCreated"<o:p></o:p>

                                            ><o:p></o:p>

                                                <RowStyle CssClass="odd" /><o:p></o:p>

                                                <AlternatingRowStyle CssClass="even" /> <o:p></o:p>

                                               <o:p></o:p>

                                                <Columns><o:p></o:p>

                                                    <asp:TemplateField><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <img id="CollpaseExpand"  runat="server" src="../images/grid/icon-expand.png"  alt="Hide Tasks" width="12" height="12" /><o:p></o:p>

                                                            <div style="display:none"><o:p></o:p>

                                                            <asp:Repeater ID="MilestoneDetailRepeater" DataSource='<%#Eval("TaskStatusList")%>' runat="server"><o:p></o:p>

                                                            <ItemTemplate><o:p></o:p>

                                                                <div id="MilestoneDetail" class="relPanel"><o:p></o:p>

                                                                                                <h3><%#Eval("TaskStatusName")%> Tasks</h3><o:p></o:p>

                                                                                                <ul><o:p></o:p>

                                                                                                <asp:Repeater ID="TaskDetail" runat="server" OnItemDataBound="TaskDetailDataBound" DataSource='<%#Eval("Tasks") %>'><o:p></o:p>

                                                                                                    <ItemTemplate><o:p></o:p>

                                                                                                          <li><o:p></o:p>

                                                                                                          <a href='../tasks/details.aspx?taskid=<%#Eval("TaskID")%>'><%#Eval("TaskTitle") %></a> <o:p></o:p>

                                                                                                          <span class="firstTxt"><o:p></o:p>

                                                                                                              <asp:Literal ID="DueStatusLiteral" runat="server" /><o:p></o:p>

                                                                                                          </span><o:p></o:p>

                                                                                                          <br /><o:p></o:p>

                                                                                                            <span class="secondTxt"><o:p></o:p>

                                                                                                                <asp:PlaceHolder ID="AssignedTo_PlaceHolder" runat="server" /><o:p></o:p>

                                                                                                            </span></li><o:p></o:p>

                                                                                                      </ItemTemplate>  <o:p></o:p>

                                                                                                    </asp:Repeater>        <o:p></o:p>

                                                                                                </ul><o:p></o:p>

                                                                                          </div><o:p></o:p>

                                                                                      </ItemTemplate><o:p></o:p>

                                                                                     </asp:Repeater><o:p></o:p>

                                                           </div>                                                                                      <o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:TemplateField HeaderText="Milestone" SortExpression="MilestoneTitle"><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <a href='details.aspx?milestoneid=<%#Eval("MilestoneID")%>' class="title"><%#Eval("MilestoneTitle")%></a><o:p></o:p>

                                                            <br /><o:p></o:p>

                                                                                      <span class="secondTxt"><%#Eval("ProjectName") %></span><o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:BoundField DataField="Published" HeaderText="Published?" SortExpression="Published" /><o:p></o:p>

                                                    <asp:BoundField DataField="StatusName" HeaderText="Status" SortExpression="StatusName" /><o:p></o:p>

                                                    <asp:TemplateField HeaderText="Due" SortExpression="MilestoneDueDate"><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <%# Eval("DueStatus") %><%# Eval("MilestoneDueDate", "{0: M/d/yyyy}")%><o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:TemplateField  HeaderText="Open Tasks" HeaderStyle-CssClass="statLabel" SortExpression="NumberOfOpenTasks" ><o:p></o:p>

                                                                <ItemTemplate><o:p></o:p>

                                                                    <div class="value"><o:p></o:p>

                                                                        <%#Eval("NumberOfOpenTasks")%><o:p></o:p>

                                                                    </div><o:p></o:p>

                                                                </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                </Columns><o:p></o:p>

                                            </asp:GridView>

     
    As you can see, our "Expand/Collpase" image is in a <asp:TemplateField>, and the repeater is in the same column under a hidden div.  In the background, we attached a javascirpt "onClick" function to the image and the javascript function is like this

    function ExpandCollapseMileStoneDetail(image, index)<o:p></o:p>

     {
    <o:p></o:p>

        // get the source of the image

       
    var src = image.getAttribute("src");<o:p></o:p>

         
        // if src is currently "icon-expand.png", then toggle it to "icon-collapse.png"

       
    if(src.indexOf("expand")>0)<o:p></o:p>

        {
             //  toggle the  image<o:p></o:p>

            image.src = src.replace("expand","collapse");<o:p></o:p>

            // Get a reference to the current row where the image is

           
    var tr = image.parentNode.parentNode;<o:p></o:p>

            // Get a reference to the next row from where the image is

           
    var next_tr = tr.nextSibling;<o:p></o:p>

            // Get a refernece to the <tbody> tag of the grid
           
    var tbody = tr.parentNode;<o:p></o:p>

             // Get a reference to the image's column<o:p></o:p>

            var td = image.parentNode;<o:p></o:p>

            var detailnode

             //loop through the content of the image's column. if hidden div is found, get a reference<o:p></o:p>

            for(var j =0; j<td.childNodes.length; j++)<o:p></o:p>

            {      <o:p></o:p>

                if(td.childNodes[j].nodeType == 1)<o:p></o:p>

                {<o:p></o:p>

                    if(td.childNodes[j].nodeName.toLowerCase() == 'div')<o:p></o:p>

                    {<o:p></o:p>

                         detailnode = td.childNodes[j].cloneNode(true);<o:p></o:p>

                         detailnode.setAttribute('style','');<o:p></o:p>

                    }<o:p></o:p>

                }<o:p></o:p>

            }<o:p></o:p>

            <o:p></o:p>

            // Create a new table row for "Expand"   <o:p></o:p>

            var newtr = document.createElement('tr');<o:p></o:p>

            var newtd = document.createElement('td');<o:p></o:p>

                var newfirsttd = newtd.cloneNode(true);<o:p></o:p>

            /* insert an empty cell first */<o:p></o:p>

                // var newempttd = document.creatElement('td');<o:p></o:p>

                // newempttd.innerHTML= "&nbsp;";<o:p></o:p>

                newfirsttd.innerHTML='&nbsp;';<o:p></o:p>

            newtr.appendChild(newfirsttd);<o:p></o:p>

            <o:p></o:p>

            newtd.colSpan = 5;<o:p></o:p>

            // insert the  hidden div's content  into the new row<o:p></o:p>

            newtd.innerHTML = detailnode.innerHTML;<o:p></o:p>

            newtr.appendChild(newtd);<o:p></o:p>

    <o:p></o:p>

            tbody.insertBefore(newtr, next_tr);<o:p></o:p>

            tr.className = "selected";<o:p></o:p>
    <o:p></o:p>

        }<o:p></o:p>

        else{<o:p></o:p>

            image.src = src.replace("collapse","expand");<o:p></o:p>

            var row = image.parentNode.parentNode;<o:p></o:p>

            var rowsibiling = row.nextSibling;<o:p></o:p>

            var rowparent = row.parentNode;<o:p></o:p>

            <o:p></o:p>

            rowparent.removeChild(rowsibiling);<o:p></o:p>

            <o:p></o:p>

            if(index%2==0)<o:p></o:p>

            {<o:p></o:p>

                row.className = "odd";<o:p></o:p>

            }<o:p></o:p>

            else{<o:p></o:p>

                row.className = "even";<o:p></o:p>

            }<o:p></o:p>

        }<o:p></o:p>

     }


    This is pretty specific to our implementation, so you'll have to customize it to your need. Hope that helps.
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, April 14, 2007 3:04 AM

All replies

  • User1754323106 posted

    We have this functionality in our software, although, it's a bit different from your example as our detail is not another grid (just a repeater) but I guess your question is related to expand/collapse, so I'll leave the child grid to you.

    Here is a screen shot of our implementation



    What we did is on each row,  in the same colum of the "+/-" image, we had a hidden div. Inside the hidden div, we inserted our child repeater. To expand/collpase the child repeater, we used pure javascript which has been tested with firefox, ie and what not.  What the javascript does is



    1.  Get the image "src"
    2.  our image is named "icon-expand.png" and "icon-collapse.png". So if the current "src" has the word "expand", then that means we need to toggle it to "icon-collapse.png" and vice versa.
    3.  through javascript DOM, we get a reference to the hidden div relatives to the potison of the image in DOM.
    4.  we create a new table row right below the "clicked" row.
    5. we insert the hidden div content into the newly created row.
    6. if it's "collapsing", we remove the newly created row away from DOM.


    here is our grid code (please ignore stuff like DataSource, OnRowCreated and what not as it's specific to our implementation)

     

    <asp:GridView   ID="LateMileStonesGridView" <o:p></o:p>

                                                            Caption = "Late"<o:p></o:p>

                                                            runat="server" <o:p></o:p>

                                                            AutoGenerateColumns="False"<o:p></o:p>

                                                            AllowSorting="True"<o:p></o:p>

                                                            CssClass="grid"<o:p></o:p>

                                                            DataKeyNames="MilestoneID"<o:p></o:p>

                                                            EnableViewState="false"<o:p></o:p>

                                                            DataSourceID="LateMilestoneObjectDataSource"<o:p></o:p>

                                                            OnRowCreated="LateMileStonesGridView_OnRowCreated"<o:p></o:p>

                                            ><o:p></o:p>

                                                <RowStyle CssClass="odd" /><o:p></o:p>

                                                <AlternatingRowStyle CssClass="even" /> <o:p></o:p>

                                               <o:p></o:p>

                                                <Columns><o:p></o:p>

                                                    <asp:TemplateField><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <img id="CollpaseExpand"  runat="server" src="../images/grid/icon-expand.png"  alt="Hide Tasks" width="12" height="12" /><o:p></o:p>

                                                            <div style="display:none"><o:p></o:p>

                                                            <asp:Repeater ID="MilestoneDetailRepeater" DataSource='<%#Eval("TaskStatusList")%>' runat="server"><o:p></o:p>

                                                            <ItemTemplate><o:p></o:p>

                                                                <div id="MilestoneDetail" class="relPanel"><o:p></o:p>

                                                                                                <h3><%#Eval("TaskStatusName")%> Tasks</h3><o:p></o:p>

                                                                                                <ul><o:p></o:p>

                                                                                                <asp:Repeater ID="TaskDetail" runat="server" OnItemDataBound="TaskDetailDataBound" DataSource='<%#Eval("Tasks") %>'><o:p></o:p>

                                                                                                    <ItemTemplate><o:p></o:p>

                                                                                                          <li><o:p></o:p>

                                                                                                          <a href='../tasks/details.aspx?taskid=<%#Eval("TaskID")%>'><%#Eval("TaskTitle") %></a> <o:p></o:p>

                                                                                                          <span class="firstTxt"><o:p></o:p>

                                                                                                              <asp:Literal ID="DueStatusLiteral" runat="server" /><o:p></o:p>

                                                                                                          </span><o:p></o:p>

                                                                                                          <br /><o:p></o:p>

                                                                                                            <span class="secondTxt"><o:p></o:p>

                                                                                                                <asp:PlaceHolder ID="AssignedTo_PlaceHolder" runat="server" /><o:p></o:p>

                                                                                                            </span></li><o:p></o:p>

                                                                                                      </ItemTemplate>  <o:p></o:p>

                                                                                                    </asp:Repeater>        <o:p></o:p>

                                                                                                </ul><o:p></o:p>

                                                                                          </div><o:p></o:p>

                                                                                      </ItemTemplate><o:p></o:p>

                                                                                     </asp:Repeater><o:p></o:p>

                                                           </div>                                                                                      <o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:TemplateField HeaderText="Milestone" SortExpression="MilestoneTitle"><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <a href='details.aspx?milestoneid=<%#Eval("MilestoneID")%>' class="title"><%#Eval("MilestoneTitle")%></a><o:p></o:p>

                                                            <br /><o:p></o:p>

                                                                                      <span class="secondTxt"><%#Eval("ProjectName") %></span><o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:BoundField DataField="Published" HeaderText="Published?" SortExpression="Published" /><o:p></o:p>

                                                    <asp:BoundField DataField="StatusName" HeaderText="Status" SortExpression="StatusName" /><o:p></o:p>

                                                    <asp:TemplateField HeaderText="Due" SortExpression="MilestoneDueDate"><o:p></o:p>

                                                        <ItemTemplate><o:p></o:p>

                                                            <%# Eval("DueStatus") %><%# Eval("MilestoneDueDate", "{0: M/d/yyyy}")%><o:p></o:p>

                                                        </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                    <asp:TemplateField  HeaderText="Open Tasks" HeaderStyle-CssClass="statLabel" SortExpression="NumberOfOpenTasks" ><o:p></o:p>

                                                                <ItemTemplate><o:p></o:p>

                                                                    <div class="value"><o:p></o:p>

                                                                        <%#Eval("NumberOfOpenTasks")%><o:p></o:p>

                                                                    </div><o:p></o:p>

                                                                </ItemTemplate><o:p></o:p>

                                                    </asp:TemplateField><o:p></o:p>

                                                </Columns><o:p></o:p>

                                            </asp:GridView>

     
    As you can see, our "Expand/Collpase" image is in a <asp:TemplateField>, and the repeater is in the same column under a hidden div.  In the background, we attached a javascirpt "onClick" function to the image and the javascript function is like this

    function ExpandCollapseMileStoneDetail(image, index)<o:p></o:p>

     {
    <o:p></o:p>

        // get the source of the image

       
    var src = image.getAttribute("src");<o:p></o:p>

         
        // if src is currently "icon-expand.png", then toggle it to "icon-collapse.png"

       
    if(src.indexOf("expand")>0)<o:p></o:p>

        {
             //  toggle the  image<o:p></o:p>

            image.src = src.replace("expand","collapse");<o:p></o:p>

            // Get a reference to the current row where the image is

           
    var tr = image.parentNode.parentNode;<o:p></o:p>

            // Get a reference to the next row from where the image is

           
    var next_tr = tr.nextSibling;<o:p></o:p>

            // Get a refernece to the <tbody> tag of the grid
           
    var tbody = tr.parentNode;<o:p></o:p>

             // Get a reference to the image's column<o:p></o:p>

            var td = image.parentNode;<o:p></o:p>

            var detailnode

             //loop through the content of the image's column. if hidden div is found, get a reference<o:p></o:p>

            for(var j =0; j<td.childNodes.length; j++)<o:p></o:p>

            {      <o:p></o:p>

                if(td.childNodes[j].nodeType == 1)<o:p></o:p>

                {<o:p></o:p>

                    if(td.childNodes[j].nodeName.toLowerCase() == 'div')<o:p></o:p>

                    {<o:p></o:p>

                         detailnode = td.childNodes[j].cloneNode(true);<o:p></o:p>

                         detailnode.setAttribute('style','');<o:p></o:p>

                    }<o:p></o:p>

                }<o:p></o:p>

            }<o:p></o:p>

            <o:p></o:p>

            // Create a new table row for "Expand"   <o:p></o:p>

            var newtr = document.createElement('tr');<o:p></o:p>

            var newtd = document.createElement('td');<o:p></o:p>

                var newfirsttd = newtd.cloneNode(true);<o:p></o:p>

            /* insert an empty cell first */<o:p></o:p>

                // var newempttd = document.creatElement('td');<o:p></o:p>

                // newempttd.innerHTML= "&nbsp;";<o:p></o:p>

                newfirsttd.innerHTML='&nbsp;';<o:p></o:p>

            newtr.appendChild(newfirsttd);<o:p></o:p>

            <o:p></o:p>

            newtd.colSpan = 5;<o:p></o:p>

            // insert the  hidden div's content  into the new row<o:p></o:p>

            newtd.innerHTML = detailnode.innerHTML;<o:p></o:p>

            newtr.appendChild(newtd);<o:p></o:p>

    <o:p></o:p>

            tbody.insertBefore(newtr, next_tr);<o:p></o:p>

            tr.className = "selected";<o:p></o:p>
    <o:p></o:p>

        }<o:p></o:p>

        else{<o:p></o:p>

            image.src = src.replace("collapse","expand");<o:p></o:p>

            var row = image.parentNode.parentNode;<o:p></o:p>

            var rowsibiling = row.nextSibling;<o:p></o:p>

            var rowparent = row.parentNode;<o:p></o:p>

            <o:p></o:p>

            rowparent.removeChild(rowsibiling);<o:p></o:p>

            <o:p></o:p>

            if(index%2==0)<o:p></o:p>

            {<o:p></o:p>

                row.className = "odd";<o:p></o:p>

            }<o:p></o:p>

            else{<o:p></o:p>

                row.className = "even";<o:p></o:p>

            }<o:p></o:p>

        }<o:p></o:p>

     }


    This is pretty specific to our implementation, so you'll have to customize it to your need. Hope that helps.
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, April 14, 2007 3:04 AM
  • User2023636496 posted

    Hi Liming,

     Thanks for your implementation of expanding/contracting gridview. I am trying to implement your idea in one of my web pages, however I am running into problem of how to put the onclick function. You have just mentioned to attach it to the onclick function, if you can provide me with the implementation code. It will be of much help.

    Thanks

    Monday, August 25, 2008 5:44 PM
  • User2002217640 posted

    We can use the AJAX Callback to improve the performace by fetching CHILD HTML on demand.

    Wednesday, September 3, 2008 12:54 AM
  • User-622324082 posted

    Forgive me if this question is too simple.... but I haven't worked with javascript in quite a long time..

     With the ExpandCollapse function, it takes 2 parameters, image and index.  I understand what these parameters represent - the image and the row of the repeater/grid control- my question is -

     what do you actually pass in?  I am trying this example to see if I can use it to accomplish some things (and mostly to learn) -- but it seems to me that in the img tag you would have..

    <img id="CollapseExpand" runat="Server" ..... onclick="ExpandCollapse(???, ???)" />

     how do you pass in the img and the index?

     

    Thanks in advance

     

    sb

    Thursday, October 30, 2008 10:45 PM
  • User2002217640 posted

    There can be many ways, but the best will be that you just pass the image itself using "this" keyword,

    <img id="CollapseExpand" runat="Server" ..... onclick="ExpandCollapse(this)" />

    Now you have image object itself in the function, ideally use the ASP.NET Image control or mark this img object as runat="server", and assign an ID.

    Since you are using it in GridView, the you can easily extract the parent row index either by image ID  parsing or using the Parent property 2 or 3 times recursively to get the actual row.

    Like if i have a gridview with ID "GridView1" and a template column in gridview has image control with ID "Image1" then the clientid of the image will be "GridView1_ctl02_Image1" for the very first row. similarly for second row it will be "GridView1_ctl03_Image1", So you see the ctl[NUM] index is increasing with every index.

     

    Friday, October 31, 2008 12:04 AM
  • User1754323106 posted
    sorry guys, havne't visisted here for awhile.

    As you can see, in my Gridview attributes, I attached this OnRowCreated eventhandler. In my codebehind eventhandler, i did

    if (e.Row.RowType == DataControlRowType.DataRow)

    {

    HtmlImage CollpaseExpand = (HtmlImage)e.Row.FindControl("CollpaseExpand");

    CollpaseExpand.Attributes.Add("onclick", "ExpandCollapseMileStoneDetail(this," + e.Row.RowIndex + ")");

    }

    also, i would like to add that the "index" parameter into the javascript was only used because I needed to track if the row is an even row or an odd row. Our designer has specific css attached to them, that's why I passed it into the javascript function. If it doesn't apply to you, then you can ignore it and simply hard code the onclick javascript handler in the .aspx page.

    Friday, October 31, 2008 9:32 AM
  • User-622324082 posted

     How do you maintain the expanded state once a postback occurs?

     

    I have implemented something similar to this (using this example) with a gridview - but the problem is - if the page is posted back, any of my rows that have been "expanded" are now collapsed.

     

    Thanks for any input.

     

    sb

    Tuesday, November 18, 2008 3:19 PM