การย่อ-ยุบแถวข้อมูลบน GridView โดยประยุกต์ใช้ร่วมกับ jQuery และ Collapse ใน Bootstrap (C#)

           จากความเดิมตอนที่แล้ว เราได้พูดถึงวิธีการจัดการข้อมูลจำนวนมากด้วยการจัดกลุ่มหมวดหมู่ของข้อมูลใน GridView กันไปแล้ว ซึ่งหากผู้อ่านท่านใดต้องการทราบวิธีการจัดหมวดหมู่สามารถตามดูเนื้อหาในบทความได้จาก การจัดหมวดหมู่แถวของข้อมูลบน GridView ด้วย C# และในส่วนของบทความนี้จะเป็นเนื้อหาต่อยอดการทำงานจากการจัดหมวดหมู่ดังกล่าว โดยเพิ่มความสามารถให้หมวดหมู่หรือกลุ่มเหล่านั้นสามารถย่อ-ยุบได้ เพื่ออำนวยความสะดวกให้กับผู้ใช้ในการดูข้อมูลแยกส่วนกันชัดเจนมากยิ่งขึ้น หรือเพื่อตอบโจทย์ให้กับผู้ใช้บางท่านที่อาจมีความต้องการดูข้อมูลทีละส่วนได้ โดยจะนำ Component ที่ชื่อว่า Collapse ใน Bootstrap มาประยุกต์ใช้ในการแสดงผลร่วมกับ GridView และยังมี jQuery มาเป็นอีกหนึ่งตัวช่วยเพื่อให้สามารถแสดงผลตามที่ต้องการได้ โดยการอธิบายในบทความนี้ ทางผู้เขียนจะขอตัดตอนในส่วนของรายละเอียดขั้นตอนวิธีการจัดหมวดหมู่ไป และข้ามมาพูดถึงขั้นตอนที่ต้องจัดทำเพิ่มเติมในการทำย่อ-ยุบเลยละกันนะคะ

           ก่อนจะไปเริ่มในส่วนของการเขียนโค้ด เรามาทำความรู้จักกับ ค่าที่จำเป็นต้องใช้ในการระบุให้กับแถวหลัก(parent)เพื่อให้สามารถย่อยุบได้ กันก่อนนะคะ

  • data-toggle=”collapse
  • data-target=”.multi-collapse” เพื่อกำหนด target ที่เราต้องการให้ย่อยุบได้ โดยใช้สไตล์ชีทเป็นตัวช่วยเพื่อแยกแต่ละกลุ่มออกจากกัน ซึ่งในที่นี้จะตั้งชื่อสไตล์ชีท multi-collapse ตามด้วยรหัสของประเภทกลุ่มนั้น โดยต้องระบุสไตล์ชีทนี้ให้กับแถวย่อย(child)ด้วย
  • aria-controls=”demo1 demo2 demo3 demo4 demo5” เพื่อกำหนดพื้นที่ที่จะย่อยุบ โดยสามารถกำหนดได้มากกว่า 1 พื้นที่ ซึ่งจะแยกด้วยการเว้นวรรคชื่อ id ของแถวย่อย(child) ในตัวอย่างนี้ คือ แถวย่อยของแถวหลักนี้ ประกอบด้วย 5 แถว คือ แถวที่มี id ชื่อ demo1,demo2,demo3,demo4, demo5 นั่นเอง
  • class = “collapseToggle” เป็นการระบุสไตล์ชีทเพื่อใช้ในการระบุตำแหน่งในการเปลี่ยนไอคอนเวลากดย่อ-ยุบ โดยเรียกใช้งานผ่าน jQuery(ซึ่งจะกล่าวถึงในส่วนถัดไป)

           หลังจากเรารู้จักค่าที่จำเป็นต้องใช้กันไปแล้ว เราก็มาเริ่มปรับแก้โค้ดเพิ่มเติม เพื่อเพิ่มความสามารถให้ GridView ของเรากันเลยค่ะ

1. ปรับแก้ในส่วนของ GroupGv_DataBound เพิ่มเติม เพื่อกำหนดค่าที่ระบุไว้ข้างต้นในแถวของหมวดหมู่ที่แทรกเข้ามา

string lastCatName = "";
string AllName = "";

 
protected void GroupGv_DataBound(object sender, EventArgs e)
        {
            lastCatName = "";
            Table table = (Table)GroupGv.Controls[0];

            foreach (GridViewRow row in GroupGv.Rows)
            {
              
                HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                HiddenField HidID = (HiddenField)row.FindControl("hdID");

                ////ตั้งชื่อ id ให้กับแถวย่อย(child) แต่ละแถว
                row.Attributes.Add("id", "Name" + HidID.Value);

               ////กำนดสไตล์ชีทของตัวแถวย่อยและระบุเพื่อให้ย่อยุบได้ ด้วย accordian-body collapse และระบุพื้นที่เพื่อให้อ้างถึงจากแถวหลัก(parent)ได้ 
              /////ซึ่งเป็นในส่วนของ multi-collapse จะเป็น multi-collapse ตามด้วยชื่อรหัสประเภทนั่นเอง 
              /////โดยแถวย่อย(child) ที่มีแถวหลัก(parent)เดียวกัน ค่าของ multi-collapse จะตรงกันและตรงกับค่า target ที่ระบุไว้ในแถวหลัก(parent)ด้วย

                if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
                   row.CssClass = "Blue accordian-body collapse multi-collapse" + HidCatID.Value;
                else               
                  row.CssClass = "accordian-body collapse multi-collapse" + HidCatID.Value;

                if (HidCatName.Value != lastCatName)
                {

                   ////////วนเพื่อหาจำนวน child ในแต่ละประเภทเพื่อใช้ในการกำหนด aria-controls

                    var strList = GroupData.Select("CategoryID='" + HidCatID.Value + "'");
                    foreach (DataRow dr in strList)
                    {
                        AllName += " Name" + dr["ID"];
                    }

                   //// เป็นการหาผลรวมค่าของฟิลด์ Amt ซึ่งหมายถึงจำนวน โดยเป็นการรวมค่าฟิลด์แยกตามแต่ละ CategoryID นั่นเอง

                    var sumOfValuesInCategory = GroupData.AsEnumerable().Where(x => x.Field<string>("CategoryID") == HidCatID.Value).Sum(x => x.Field<int>("Amt")).ToString();
                   
                    int realIndex = table.Rows.GetRowIndex(row);
                    string text = HidCatName.Value;
                    GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);

                    /////กำหนดค่าต่างๆ (data-toggle  data-target  aria-controls )ให้กับแถว เพื่อให้สามารถย่อ-ยุบได้ 

                    newHeaderRow.Attributes.Add("data-toggle", "collapse");
                    newHeaderRow.Attributes.Add("data-target", ".multi-collapse" + HidCatID.Value);
                    newHeaderRow.Attributes.Add("aria-expanded", "true");
                    newHeaderRow.Attributes.Add("aria-controls", AllName);

                    TableCell newCell = new TableCell();
                    newHeaderRow.Cells.AddAt(0, newCell);

                   /////ปรับแก้การระบุค่า ColumnSpan จากเดิมที่รวมกันทุกคอลัมน์(GroupGv.Columns.Count) 
                   //// แต่กรณีนี้ต้องเว้นคอลัมน์ไว้แสดงผลจำนวนรวมแต่ละประเภทด้วย

                    newCell.ColumnSpan =1;
                    newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                    newCell.ForeColor = System.Drawing.Color.White;
                    newCell.Font.Bold = true;
                    newCell.Attributes.Add("class", "collapseToggle");

                    /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                    newCell.Text = "<i class='fas fa-plus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);

                    ///สร้าง TableCell หรือคอลัมน์ใหม่ เพื่อแสดงผลข้อมูลจำนวนรวมของแต่ละประเภท 

                    TableCell newCellTotal = new TableCell();

                    /////เพิ่ม TableCell ในแถวที่กำลังสร้างและระบุค่าต่างๆ
                    newHeaderRow.Cells.AddAt(1, newCellTotal);
                    newCellTotal.ColumnSpan = 1;
                    newCellTotal.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                    newCellTotal.ForeColor = System.Drawing.Color.White;
                    newCellTotal.Font.Bold = true;
                    newCellTotal.HorizontalAlign = HorizontalAlign.Right;

                    ////นำค่าผลรวมในตัวแปร sumOfValuesInCategory ที่คำนวณได้ข้างต้นมาจัดรูปแบบก่อนแสดงผลใน TableCell สร้าง

                    newCellTotal.Text = string.Format("{0:#,##0}",int.Parse(sumOfValuesInCategory));
                    newCellTotal.Attributes.Add("class", "collapseToggle");
                    table.Controls.AddAt(realIndex, newHeaderRow);
                
                      AllName = "";

                }
                lastCatName = HidCatName.Value;
            }
        }

2.จัดทำให้ไอคอนสามารถเปลี่ยนเป็น + หรือ – ได้ เมื่อกดย่อ-ยุบ ด้วย jQuery

 <script>
     $(document).ready(function () {

///เมื่อมีการคลิก element ที่มีสไตล์ชีท collapseToggle จะทำการเปลี่ยนค่าไอคอน หากเป็นภาพบวกจะเปลี่ยนเป็นลบ หากเป็นเครื่องหมายลบจะเปลี่ยนเป็นเครื่องหมายบวก
            $('.collapseToggle').click(function () {
                $(this).find('i').toggleClass('fa-plus fa-minus');
            });
        });
</script>

เพิ่มเติม : ท่านสามารถสร้างสไตล์ชีทตกแต่งเพิ่มเติมให้กับแถวของข้อมูลได้ ในกรณีนี้ได้ทำการสร้างสไตล์ชีทเพื่อไว้สำหรับเวลาเอาเม้าส์ชี้ที่แถวที่สามารถย่อ-ยุบได้ จะแสดงเป็นรูปมือ เพื่อให้ผู้ใช้ทราบว่าสามารถกดได้ค่ะ

    <style>
        .collapseToggle {
            cursor: pointer;
        }
    </style>

ผลลัพธ์

เกร็ดความรู้เพิ่มเติม : จากตัวอย่างข้างต้น ตอนเปิดหน้าจอครั้งแรก ทุกหมวดหมู่จะถูกยุบอยู่ หากต้องการให้การแสดงผลครั้งแรก ทุกหมวดหมู่ถูกขยายอยู่ สามารถปรับแก้โค้ดอีกเพียงเล็กน้อย ดังนี้ค่ะ

  1. เพิ่มคำว่า “show” เข้าไปในสไตล์ชีทตอนกำหนดให้แถวย่อย ดังนี้ค่ะ

แบบเดิม

                if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
                   row.CssClass = "Blue accordian-body collapse multi-collapse" + HidCatID.Value;
                else               
                  row.CssClass = "accordian-body collapse multi-collapse" + HidCatID.Value;

แบบใหม่

                  if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 

                    row.CssClass = "Blue accordian-body collapse show multi-collapse" + HidCatID.Value;
                else
                    row.CssClass = "accordian-body collapse show multi-collapse" + HidCatID.Value;

2.ปรับแก้ให้ไอคอนแรกที่ต้องการแสดงเป็นเครื่องหมายลบ ดังนี้ค่ะ

แบบเดิม

                   /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                    newCell.Text = "<i class='fas fa-plus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);

แบบใหม่

                    /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                    newCell.Text = "<i class='fas fa-minus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);

ผลลัพธ์

          จากตัวอย่าง การแสดงผลครั้งแรกก็จะเปลี่ยนเป็นขยายทั้งหมด และแสดงไอคอนเป็นเครื่องหมายลบ(-)ตั้งต้นไว้ให้ และสามารถย่อ-ยุบตามปกติได้แล้วค่ะ

เพิ่มเติม

          นอกจากนี้ ผู้เขียนขอแถมให้อีกนิดสำหรับท่านที่ต้องการจะย่อประเภทกลุ่มทั้งหมด หรือต้องการให้แสดงประเภทกลุ่มทั้งหมด อาจจะทำเป็นปุ่มให้ผู้ใช้กด ซึ่งมีวิธี ดังนี้ค่ะ

1. สร้างปุ่ม 2 ปุ่ม เพื่อกดขยายทั้งหมด และย่อทั้งหมด

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <div class="col pull-right">
                <asp:LinkButton ID="lnkShowAll" runat="server" CssClass="btn btn-sm btn-info mb-1" OnClientClick="javascript:ShowHideAll('1');return false;"><i class="fas fa-eye"></i>แสดงทั้งหมด</asp:LinkButton>
                <asp:LinkButton ID="lnkHideAll" CssClass="btn btn-sm btn-info mb-1" OnClientClick="ShowHideAll('0');return false;" runat="server"><i class="fas fa-eye-slash"></i>ซ่อนทั้งหมด</asp:LinkButton>
            </div>


        </ContentTemplate>
    </asp:UpdatePanel>

2. เขียนฟังก์ชั่นในการซ่อน/แสดงทั้งหมด

<script>
    
 function ShowHideAll(flag) {

          /////ล้างค่าการระบุการแสดงผลและการแสดงไอคอนทั้งหมด ทำให้ทุกแถวยุบอยู่ จนกว่าจะมีการสั่งให้ show
            $(".accordian-body").removeClass("show");
            $(".collapseToggle").find('i').removeClass("fa-plus").removeClass("fa-minus");

         /////กรณีต้องการให้แสดงจะเพิ่มสไตล์ชีท show ให้กับทุกแถว และแสดงไอคอนเป็น - ทั้งหมด เพื่อให้กดย่อได้

            if (flag == '1') {
                $(".accordian-body").addClass("show");
                $(".collapseToggle").find('i').addClass("fa-minus");
            }
        /////กรณีต้องการให้แสดงจะแสดงไอคอนเป็น + ทั้งหมด เพื่อให้กดขยายได้
            else
                $(".collapseToggle").find('i').addClass("fa-plus");
            }

</script>

ผลลัพธ์

หมายเหตุ : ในการทำงานนี้จะใช้ jQuery และ Bootstrap ร่วมด้วย ผู้ที่จะนำไปใช้งานอย่าลืมอ้างอิงไฟล์สไตล์ชีทและสคริปท์ของ Bootstrap รวมทั้งไฟล์ของ jQuery เพื่อให้โค้ดข้างต้นสามารถทำงานได้นะคะ

          ทั้งหมดนี้ก็เป็นเพียงหนึ่งในตัวอย่างวิธีการที่จะแก้ปัญหาในการแสดงผลข้อมูลแบบตารางด้วย GridView แบบจัดกลุ่มและสามารถย่อ-ยุบข้อมูลภายในกลุ่มได้ โดยนำความสามารถของ Component อย่าง collapse ใน Bootstrap เข้ามาช่วยเท่านั้น แต่ในส่วนของรูปแบบ วิธีการ แต่ละท่านสามารถปรับเปลี่ยนและพลิกแพลงเพิ่มเติมได้ตามความเหมาะสม อีกทั้งยังสามารถนำเกร็ดความรู้นี้ไปประยุกต์ใช้กับงานของท่านได้อีกด้วย และขอบคุณที่ติดตามนะคะ ^^

แหล่งอ้างอิง

https://getbootstrap.com/docs/4.0/components/collapse/
https://www.geeksforgeeks.org/how-to-change-symbol-with-a-button-in-bootstrap-accordion/