The scenario is as follows: You have a list of records being displayed in a ListView. Records are grouped (In my case, by Project) and you want to have a group footer to total each group.
How to get this done?First, we can use the ListView Layout Template to create the desired table structure:
<asp:ListView ID="lvGroupedLabor" runat="server" style="text-align: center; font-family: Arial, Helvetica, sans-serif; font-size: xx-small" ondatabound="lvGroupedLabor_DataBound"> <LayoutTemplate> <table cellspacing="0" cellpadding="5" rules="all" border="1" width="100%"> <thead class='grouptot'> <tr> <th>Project</th> <th>Work Order</th> <th>Date</th> <th>Time</th> <th>Comment</th> </tr> </thead> <tbody> <asp:PlaceHolder ID="itemPlaceholder" runat="server" /> </tbody> <tfoot> <tr class='grouptot'> <td colspan="6"><asp:Literal ID="litSubTotal" runat="server" /></td> </tr> <tr class='group'> <td colspan="6"><asp:Literal ID="litGrandTotal" runat="server" /></td> </tr> </tfoot> </table> </LayoutTemplate> <ItemTemplate> <%# fTotalbyProject(false) %> <%# fGroupingRowIfNewProject() %> <tr class='data<%# Container.DataItemIndex % 2 %>'> <td><%#Eval("project_cd")%></td> <td><%#Eval("woid")%></td> <td><%#Eval("labor_date", "{0:M/dd/yyyy}")%></td> <td><%#Eval("labor_time")%></td> <td><%#Eval("comment")%></td> </tr> </ItemTemplate> </asp:ListView>
There are several interesting things to point out here:
- Notice how there is a row in the template for the group footer <tfoot> in my case I decided to have a row to total per group and another row to total all the values.
- The structure of the template is such, that will insert a header row for the group, followed by the Total of the last group, then the record rows. Therefore we will need to address how to insert a total for the last row and the grand total
- The method fTotalbyProject(false) is used to display to numeric total of each group
- The method fGroupingRowIfNewProject() is used to signal a group break per row
- The sytle <tr class='data<%# Container.DataItemIndex % 2 %>'> is a nice little trick to zebra the rows in the ListView
The code assumes that you have a few static variables defined that we'll be using with our class
static string sLastProject = ""; static string sTotLastProj = ""; static decimal dLastSumTotal = 0; static decimal dGrandTotal = 0; static bool bTotalCntSt = false;
Let's review the fTotalbyProject method, this one is used to display the "Total for Project (XXX): "
What we do here is to return some HTML so the contained in the template displays the appropiate text for each group break.
protected string fTotalbyProject(bool aMode) { if (aMode == false) { //For the first row, set the flag to true so initialization happens only once String sCurrentProject = (string)Eval("project_cd"); if (bTotalCntSt == false) { sTotLastProj = (string)Eval("project_cd"); bTotalCntSt = true; } //No changes, do nothing if (sTotLastProj == sCurrentProject) { return string.Empty; } else { //There is a change, therefore compute the total. Decimal dGetTotalbyProject = fSumTotalbyProject(sTotLastProj, false); String sNewRow = ""; if (sTotLastProj == " ") sNewRow = String.Format("<tr class='grouptot'><td colspan='6'>" + "Total for tasks with unassigned Project: {1}</td></tr>", sTotLastProj, dGetTotalbyProject); else sNewRow = String.Format("<tr class='grouptot'><td colspan='6'>"+ "Total for Project {0}: {1}</td></tr>", sTotLastProj, dGetTotalbyProject); sTotLastProj = (string)Eval("project_cd"); return sNewRow; } } else { Decimal dGetTotalbyProject = fSumTotalbyProject(sTotLastProj, false); String sNewRow = String.Format("Total for Project {0}: {1}", sTotLastProj, dGetTotalbyProject); return sNewRow; } }
What we do here is to return some HTML so the contained in the template displays the appropiate text for each group break.
As you probably noticed the fTotalbyProject method calls another method named fSumTotalbyProject, which is the one that does the numeric sum of the records, it takes a second argument to indicate if total should be done by Project or calculate the grand total.
This function uses LINQ and assumes you have a LINQ DB Model previously defined.
protected Decimal fSumTotalbyProject(string aProject, bool bIsGrandTotal) { string aUserName = aUserNameVariable; DateTime dStDate = Convert.ToDateTime(YourTextBox1.Text); DateTime dEndDate = Convert.ToDateTime(YourTextBox2.Text); TasksLinqDataContext db = new TasksLinqDataContext(); if (aProject == " ") aProject = ""; try { if (!bIsGrandTotal) { var dbTaskLabor = (from vtlbr in db.tasksTable where vtlbr.staff_name == aUserName && vtlbr.labor_date >= dStDate && vtlbr.labor_date <= dEndDate && vtlbr.project_cd == aProject select vtlbr.labor_time).Sum(); return dbTaskLabor; } else { var dbTaskLabor = (from vtlbr in db.tasksTable where vtlbr.staff_name == aUserName && vtlbr.labor_date >= dStDate && vtlbr.labor_date <= dEndDate select vtlbr.labor_time).Sum(); return dbTaskLabor; } } catch (Exception Exc) { ErrorLbl.ForeColor = System.Drawing.Color.Red; ErrorLbl.Text = Exc.Message; return 0; } }
To signal that there is a new Project, we defined a row in our template to display the project name each time that there is a change. The method fGroupingRowIfNewProject handles that logic. Again, we are just returning some HTML to add a cell to the table and display the text "Project : " the HTML is placed in the appropiate row thanks to our template.
protected string fGroupingRowIfNewProject() { //Get the data field value of interest for this row String sCurrentProject = (string)Eval("project_cd"); //Specify name to display if current project is NULL if (sCurrentProject == " ") sCurrentProject = "Unassigned Project"; else sCurrentProject = "Project: " + sCurrentProject; //See if there's been a change in value if (sLastProject != sCurrentProject) { //There's been a change! Record the change and emit the table row sLastProject = sCurrentProject; return String.Format("<tr class='group'>"+ "<td colspan='6'>{0}</td></tr>", sCurrentProject); } else { //No Change, return an empty string return String.Empty; } }
Finally, the below code in the DataBound event of the ListView is used to invoke the above functions one last time to display and compute the Total for the last group and then display the GrandTotal for all records.
protected void lvGroupedLabor_DataBound(object sender, EventArgs e) { Literal lSubTotal = lvGroupedLabor.FindControl("litSubTotal") as Literal; lSubTotal.Text = fTotalbyProject(true); Literal lGrandTotal = lvGroupedLabor.FindControl("litGrandTotal") as Literal; lGrandTotal.Text = "Grand Total: " + Convert.ToString(fSumTotalbyProject(String.Empty, true)); }
Happy Coding!
Will
No comments:
Post a Comment