VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "Table"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
'
' MDTHML Table
'
' This class implements a middle-tier object that an HTML table from a
' passed-in ADO/MD Cellset.  The object requires IIS 4.0 or better as it
' uses IIS's ScriptingContext object to acquire access to an output
' stream for the HTML.
'
' Typical usage:
'
' Set tbl = Server.CreateObject ("MDHTML.Table")
' tbl.Display (cs)
'
' 10.12.98  BillBak     Initial coding
' 10.14.98  BillBak     Clean-up, documentation
'

Option Explicit

Public m_sc As ScriptingContext         ' passed to us by IIS on the start page event

' Variables to hold the property values
Public m_szTableHTMLStart As String     ' HTML for <TABLE> tag
Public m_szTableHTMLEnd As String       ' HTML for </TABLE> tag
Public m_szRowHeaderHTMLStart As String ' HTML for <TD> tag for row headers
Public m_szRowHeaderHTMLEnd As String   ' HTML for </TD> tag for row headers
Public m_szColHeaderHTMLStart As String ' HTML for <TD> tag for col headers
Public m_szColHeaderHTMLEnd As String   ' HTML for </TD> tag for col headers
Public m_szDataCellHTMLStart As String  ' HTML for <TD> tag for data cells
Public m_szDataCellHTMLEnd As String    ' HTML for </TD> tag for data cells

Public Sub OnStartPage(sContext As ScriptingContext)

    ' IIS calls us here when it loads a page containing the control.  The IIS
    ' ScriptingContext object has methods to return references to the IIS
    ' Application, Session, Response and Request objects.  We use the Write
    ' method on the Response object to emit HTML streams.
    
    Set m_sc = sContext                 ' saved for use during the Display method
    
    ' Default HTML tags for the various elements of the table
    m_szTableHTMLStart = "<TABLE>"
    m_szTableHTMLEnd = "</TABLE>"
    m_szRowHeaderHTMLStart = "<TD>"
    m_szRowHeaderHTMLEnd = "</TD>"
    m_szColHeaderHTMLStart = "<TD>"
    m_szColHeaderHTMLEnd = "</TD>"
    m_szDataCellHTMLStart = "<TD>"
    m_szDataCellHTMLEnd = "</TD>"

End Sub


Sub Display(cs As ADOMD.Cellset)

    ' This is where we do the work.  We handle Cellsets with one or two
    ' axes.  We punt if the Cellset has zero axes.
    
    ' Get the IIS response object for our HTML output
    Dim response As response
    Set response = m_sc.response

    ' Do we have any interesting axes?
    If cs.Axes.Count < 1 Then
        response.Write "The cellset has no displayable axes.<BR>"
    End If
    
    Dim x As Integer, y As Integer      ' general purpose loop variables
    
    'These variables store the number of "layers" on each axis
    Dim nColHeaderDepth As Integer, nRowHeaderWidth As Integer
    
    ' How many "layers" do the headers have?
    nColHeaderDepth = cs.Axes(0).DimensionCount
    If cs.Axes.Count > 1 Then
        nRowHeaderWidth = cs.Axes(1).DimensionCount
    Else
        nRowHeaderWidth = 0
    End If
    
    ' Create the HTML for the upper-left cell.  This requires modifying the
    ' m_szRowHeaderHTMLStart variable to have a COLSPAN modifier.  So for
    ' example, if Axis(1) (the row labels) contains two dimensions, the
    ' width of the row labels is two columns.  The upper left cells are
    ' empty (because they are left of the leftmost column and above the
    ' first row.)  So we need to convert the HTML from "<TD></TD>" into
    ' "<TD COLSPAN=2></TD>".
    
    Dim szULHTML As String
    szULHTML = InsertColspan(m_szColHeaderHTMLStart, nRowHeaderWidth) & _
                        m_szColHeaderHTMLEnd
    
    ' Write the <TABLE> tag
    response.Write m_szTableHTMLStart
        
    ' Write out the layers of the column header.  We have one layer for
    ' each dimension in the axis.  So if the MDX query contains a CrossJoin,
    ' we have to emit more than one HTML Table row.  For example the function
    ' CrossJoin ({A, B, C}, {[1], [2], [3]}) produces an axis with nine
    ' positions.  Each position contains two members.  It looks like:
    '
    '   A  A  A  B  B  B  C  C  C
    '   1  2  3  1  2  3  1  2  3
    '
    ' The first pass through the loop will display A  A  A  B  etc.
    ' The second pass gets the inner members.
    
    Dim p As ADOMD.Position
    Dim nLayer As Integer
    For nLayer = 0 To nColHeaderDepth - 1
    
        response.Write "<TR>"               ' start this row
        response.Write szULHTML             'upper-left cell
        ' Run across the positions collection in axis 0 using only one
        ' of the members for each position.  We only output a cell when we notice
        ' that the caption for the current column (position) in this layer is
        ' different than the previous caption.  When we do output a cell, we use
        ' the COLSPAN tag to make that cell of the table span multiple columns.
        ' The goal is to display:
        '
        '   A  A  A  B  B  B  C  C  C
        '   1  2  3  1  2  3  1  2  3
        '
        ' as:
        '
        '   A        B        C
        '   1  2  3  1  2  3  1  2  3
        '
        
        ' Seed the previous-caption variable with the first caption.  We need to
        ' trigger output when we finally see something different, or hit the
        ' end of the position list.
        Dim szPreviousCaption As String, nColCount As Integer
        szPreviousCaption = cs.Axes(0).Positions(0).Members(nLayer).Caption
        nColCount = 0
        
        For Each p In cs.Axes(0).Positions
            If (p.Members(nLayer).Caption = szPreviousCaption) Then
                nColCount = nColCount + 1   ' one more just like the previous
            Else
                ' A new caption, write what we've saved up (szPreviousCaption and
                ' nColCount) and then reset these variables.
                response.Write InsertColspan(m_szColHeaderHTMLStart, nColCount)
                response.Write szPreviousCaption
                response.Write m_szColHeaderHTMLEnd
                nColCount = 1
                szPreviousCaption = p.Members(nLayer).Caption
            End If
        Next
        ' Write the last column
        response.Write InsertColspan(m_szColHeaderHTMLStart, nColCount)
        response.Write szPreviousCaption
        response.Write m_szColHeaderHTMLEnd
        response.Write "</TR>"          ' finish off this row
        
    Next nLayer
    
    ' Write out the rows of data including the row headers.  We have the same goal
    ' as before, to only write repeating captions once.  In this case we are able
    ' to write the caption at the front of the span since we are not using ROWSPAN.
    ' So we use an array of previous captions and check each caption against the
    ' previous caption at that level.
    '
    Dim m As ADOMD.Member
    ReDim szPrevCaption(nRowHeaderWidth) As String  ' an array of previous captions
    For x = 1 To nRowHeaderWidth
        szPrevCaption(x) = ""                       ' init to null strings
    Next x
    
    y = 0
    ' Loop over the Rows access or Axis(1)
    For Each p In cs.Axes(1).Positions
    
        response.Write ("<TR>")         ' start this row
        
        ' First write a cell for each member in the axis at this position
        x = 1
        For Each m In p.Members
            response.Write m_szRowHeaderHTMLStart
            If m.Caption <> szPrevCaption(x) Then
                ' This caption is different that the last one we wrote for this
                ' level.  Write it and update the previous caption for this level.
                response.Write m.Caption
                szPrevCaption(x) = m.Caption
            End If
            response.Write m_szRowHeaderHTMLEnd
            x = x + 1
        Next
                
        ' Now write the data cells for this row
        For x = 0 To cs.Axes(0).Positions.Count - 1
            response.Write m_szDataCellHTMLStart
            response.Write cs(x, y).FormattedValue
            response.Write m_szDataCellHTMLEnd
        Next x
    
        response.Write "</TR>"          ' finish this row
        y = y + 1
    Next
    
    ' Finish the table
    response.Write m_szTableHTMLEnd
    
End Sub

Public Property Let RowHeaderHTMLStart(ByVal myVal As String)

    m_szRowHeaderHTMLStart = myVal

End Property

Public Property Let RowHeaderHTMLEnd(ByVal myVal As String)

    m_szRowHeaderHTMLEnd = myVal

End Property


Public Property Let ColHeaderHTMLStart(ByVal myVal As String)

    m_szColHeaderHTMLStart = myVal

End Property

Public Property Let ColHeaderHTMLEnd(ByVal myVal As String)

    m_szColHeaderHTMLEnd = myVal

End Property


Public Property Let DataCellHTMLStart(ByVal myVal As String)

    m_szDataCellHTMLStart = myVal

End Property

Public Property Let DataCellHTMLEnd(ByVal myVal As String)

    m_szDataCellHTMLEnd = myVal

End Property


Public Property Let TableHTMLStart(ByVal myVal As String)

    m_szTableHTMLStart = myVal

End Property

Public Property Let TableHTMLEnd(ByVal myVal As String)

    m_szTableHTMLEnd = myVal

End Property

Function InsertColspan(szTag, nCols)
    
    ' Takes the passed tag string (similar to <TD Property=VALUE><B>) and adds
    ' COLSPAN=n (where n is nCols) right after the tag name

    Dim iInsertPoint As Integer         ' index to where we open the string
    Dim iFirstCaret As Integer          ' index to the caret
    
    iFirstCaret = InStr(1, szTag, "<")
    If iFirstCaret = 0 Then
        ' No HTML tag in the passed string, make our own
        InsertColspan = "<TD COLSPAN=" & Str(nCols) & ">"
        Exit Function
    End If
    
    ' Get first space after the tag
    iInsertPoint = InStr(iFirstCaret, szTag, " ")
    If iInsertPoint = 0 Then
        ' No spaces, insert before the first close caret
        iInsertPoint = InStr(iFirstCaret, szTag, ">")
        If iInsertPoint = 0 Then
            ' Sheesh, no closing caret!  Append the COLSPAN modifier and be done
            InsertColspan = szTag & " COLSPAN=" & Str(nCols) & " >"
            Exit Function
        End If
    End If
    
    ' Split the string into left and right parts around the insertion point
    Dim szLeft As String, szRight As String
    szLeft = Left(szTag, iInsertPoint - 1)
    szRight = Right(szTag, Len(szTag) - iInsertPoint + 1)
    
    InsertColspan = szLeft & " COLSPAN=" & Str(nCols) & szRight
    
End Function
