ASP Report Class
(Note: this is definitely an ASP topic, but because it relates directly to report-writing and using SQL Server easily and efficiently, I wanted to put an article here.)
Regular old ASP with ADO works great for printing the contents of recordsets as web pages, and I know many of us out there still use these technologies. A common need when outputing a recordset as HTML is to display what report writers often call “groups” on your web page, with headers and footers and subtotals for various columns in your recordset.
This can be tricky, because you need to keep track of when to print these group headers or footers, and you must also manually keep track with variables all the columns you wish to subtotal at all the different levels, and know when to reset them.
After writing essentially the same code over and over for each of these pages, I finally created a very handy ASP class: The “Report” class.
The Report Class
This class makes it really easy to handle footers, headers and all of your subtotal (and running total) needs. It also uses the GetRows() method of ADO to immediately return and close the entire recordset before processing. But, it still allows you to use a “movenext” method, and to check for “EOF” so you don't need to worry about keeping track of the current row. It also lets you reference columns by their names, just as you do with a recordset object.
The Report object works like this:
First, you open up the ADO recordset you wish to use. Make sure it is sorted by the columns you wish to group on. For example, if you wish to show headers and footers for each Office, Customer and Month, then be sure it is sorted by those 3 columns.
Once you have created an object as a new instance of the Report class, you must then initialize it by calling its init method, passing 3 parameters:
- The open recordset you wish to use
- an Array() of the column names you wish to group on
- an Array() of the column names you wish to subtotal
The init method does some initializing and calls the GetRows() method to bring in the entire recordset. It then closes the recordset so the connection to the database can then be closed if you like. Finally, it sets the current row in the Report object to the first row in the recordset.
Now, you can reference the following properties and methods of the Report object:
- EOF -- returns TRUE if you are at the end of the recordset.
- MoveNext -- This method moves to the next row of the recordset, and automatically keeps track of all totals and groups.
- Col(colname) -- returns the value in the column colname for the current row. This is also the default property of the class, just like in ADO. So if your report object is called Rpt, you can say Rpt(“Emp”) or Rpt.Col(“Emp”) to return the value of the Emp column.
- ColVal(colname) -- return the numeric value of the colname, or zero if it is not numeric or is null.
- ColTotal(colname) -- returns the running total of colname, which must be one of the columns in the init method you chose to total on. At the end of the report, this will return the grand total.
- Groups(groupname) -- this property returns a Group object for groupname, which also must have been specified in the init method.
The Group class has the following properties:
- Header -- returns true if you should output the Header for this group
- Footer -- returns true if you should output the Footer for this group
- ColTotal(colname) -- returns this group's running totals for the colname column. Again, this must be a column specified during the init method as one you wish to subtotal. When footer is true, this returns the group totals for each column.
So, you just print headers for each group when Header is true, then print the details, then footers when Footer is true, and then movenext until you reach EOF. All along the way you have access to running totals for all groups and for the recordset as a whole.
When you are done with the Report object, just set it to Nothing. Remember, it doesn't keep an open recordset so as soon as you call the init method, you can close the connection to the database if you no longer need it. (The init method closes the recordset automatically).
That's basically it. A typical ASP report with group headers, footers, and subtotals would look something like this:
dim Rpt ' this will be the Report object
set r = conn.execute(”some SQL or proc”)
set Rpt = new Report
' here's the key method: init the object telling it which ADO recordset to use,
' which groups to keep track of, and which columns to subtotal:
Rpt.init r, Array(“Group1” ,“Group2”) , Array(“Val1” ,“Val2”)
' and we can close the connection now:
conn.close
' then, just like with a recordset object, we have a main loop until EOF:
do while not Rpt.EOF
if rpt.Groups(“Group1”).Header then
'output the Grp1 header here
end if
if rpt.Groups(“Group2”).Header then
' group 2 header
end if
' output details here.
' you can reference any column in your report as:
rpt(“AnyColumn”)
' you can output running totals for any group here as:
rpt.Groups(“any group”).ColTotal(“any column”)
' unlike with ADO, we now know if we need to print footers before
' moving to the next row.
if rpt.Groups(“Group2”).Footer then
' group 2 footer
' reference this groups subtotals as:
rpt.Groups(“Group2”).ColTotal(“Val1”)
end if
if rpt.Groups(“Group1”).Footer then
' group1 footer
' reference this groups subtotals as:
rpt.Groups(“Group1”).ColTotal(“Val1”)
end if
rpt.movenext ' just like ADO
loop
' now, you can access grand totals as:
rpt.ColTotal(“Val1”)
rpt.ColTotal(“Val2”)
set rpt = nothing ' clean it up
At the end of this article is the code for this class. I hope you find it helpful. Keep in mind the following:
- For the groups and the totals to work, the data must be accessed sequentially. I.e., you can only movenext through the data until the EOF.
- Feel free to add more functionality as you see fit! It is just an open-source script, so modify as you like
- The script may seem kind of long, but the bulk of it is the init method which is called just once.
- References to the column and group names are not case-sensitive.
Enjoy! It will really make your ASP pages much shorter and very easy to read and maintain. Hopefully, they will perform better as well since you can now use the the speed of the GetRows() method with the ease of use of the old movenext/EOF technique.
Please give me some feedback ! I have posted a sample ASP page using this class and the Northwind database, in case none of this is clear at all yet. But if you do any complex report writing at all, this should really help you out.
class Group
public Header
public Footer
public ColTotal
end class
class Report
private GrpArray
public Groups
private Vals
private CurrRow
private TotalRows
private Columns
public ColTotal
public function Init(ADO_Recordset, ArrayOfGroupFields, ArrayOfTotalFields)
dim i, n, f
GrpArray = ArrayOfGroupFields
set Columns = server.createobject("Scripting.Dictionary")
columns.CompareMode = 2
with ado_recordset
for i=0 to .fields.count-1
columns.Add .fields(i).name, i
next
end with
if ADO_Recordset.EOF then
CurrRow=1
TotalRows = 0
ADO_Recordset.close
exit function
end if
Vals = ADO_Recordset.getRows
ADO_Recordset.close
set ADO_Recordset = nothing
set groups = server.createobject("Scripting.Dictionary")
groups.CompareMode = 2
CurrRow = 0
TotalRows = ubound(Vals,2)
for i = 0 to ubound(GrpArray)
set n = new Group
n.Header = true
n.Footer = isDifferent(grparray(i),1)
set n.ColTotal = server.createobject("Scripting.Dictionary")
for f = 0 to ubound(ArrayOftotalFields)
n.ColTotal.add arrayOfTotalFields(f), colval(arrayofTotalFields(f))
next
groups.Add grparray(i), n
next
'finally, grand totals:
set ColTotal = server.createobject("Scripting.Dictionary")
Coltotal.CompareMode = 2
for f = 0 to ubound(ArrayOftotalFields)
ColTotal.add arrayOfTotalFields(f), colval(arrayofTotalFields(f))
next
end function
public property Get Eof()
Eof = (CurrRow > TotalRows)
end property
private function isDifferent(colname, RelativeRow)
dim r
r = currRow + RelativeRow
if (r<0) or (r>TotalRows) then
isDifferent = True
exit function
end if
isDifferent = vals(columns(colname),currRow) <> vals(columns(colname),r)
end function
public function MoveNext()
dim i, RestHeads, RestFoots, s, t
currRow = currRow + 1
if Eof then exit function
RestHeads = false
RestFoots = false
for i = 0 to ubound(grpArray)
s = grparray(i) 'the group column name
with groups(s)
if RestFoots or isDifferent(s,1) then
restfoots = true
.footer = true
else
.footer = false
end if
if RestHeads or isDifferent(s,-1) then
restHeads = true
.header = true
for each t in .ColTotal
.ColTotal(t) = ColVal(t)
next
else
.header = false
for each t in .ColTotal
.ColTotal(t) = .coltotal(t) + ColVal(t)
next
end if
end with
next
for each t in colTotal
coltotal(t) = coltotal(t) + ColVal(t)
next
end function
public default property Get Col(ColName)
Col = Vals(Columns(colName),currRow)
end property
public property Get ColVal(ColName)
dim t
t = col(ColName)
if isnumeric(t) then
CoLVal = t
else
CoLVal = 0
end if
end property
end class
Legacy Comments
Adam Weigert
2003-11-17 |
re: ASP Report Class This is amazing, I can't believe I never thought of this before. I guess I have had such a mood when it comes to ASP I have just been copying code left and right... This is a definite time saver as I have several reports to write this week in ASP ... |
homer jay
2004-02-17 |
re: ASP Report Class I wish you had this for PHP! |
Tareq Khalil
2004-05-30 |
re: ASP Report Class Could you provide us with a northwind example as you said? Best wishes Tareq Khalil |
JeffS
2004-06-04 |
re: ASP Report Class Tareq -- there`s an example posted in my blogs. |
La'Chelle Luppnow
2005-07-12 |
re: ASP Report Class This code is absolutely amazing and so very, very easy to understand and implement. Thank you, thank you, thank you. Cheers La'Chelle |
Jeroen Jansen
2005-07-25 |
re: ASP Report Class Is it possible to use an array with values in stead of an ADO_Recordset? Could the class then read the first row of the array as fieldnames? This would make it possible to pivot my recordset or alter some values before gereating the report. Thanks, Jeroen |
asp.net
2005-10-01 |
re: ASP Report Class I wish you had this for PHP! |
Michael S.
2006-07-11 |
re: ASP Report Class I must be making a stupid mistake somewhere... Everything works for me except ColTotal. When I add the group footer code I get: Microsoft VBScript runtime error '800a01b6' Object doesn't support this property or method: 'ColTotal' Any ideas? Thanks, Michael |
ramya
2007-05-04 |
re: ASP Report Class The article was gud and helpful... i hav a doubt ,is there any events available for crystal reports... |