Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Collections.Specialized
Imports System.Reflection

<assembly:AssemblyKeyFileAttribute("GACSpinBox.snk")>
<assembly:AssemblyVersionAttribute("1.0.0.0")>
Namespace Stonebroom

  Public Class GACSpinBox

    ' specify base class to extend
    Inherits WebControl

    ' need to be able to handle postbacks
    Implements IPostBackDataHandler

    ' ----------------------------------------------

    ' enumeration of target browser types
    Public Enum ClientTargetType
      AutoDetect = 0
      Version6 = 1
      DownLevel = 2
    End Enum

    ' ----------------------------------------------

    ' private internal variables
    Private _autopostback As Boolean = False
    Private _caption As String = ""
    Private _client As ClientTargetType = ClientTargetType.AutoDetect
    Private _columns As Integer = 3
    Private _cssclass As String = ""
    Private _increment As Integer = 1
    Private _maxvalue As Integer = 99
    Private _minvalue As Integer = 0
    Private _text As String = ""

    ' to hold child control references
    Private oTextBox As TextBox
    Private oImageUp, oImageDown As ImageButton
    Private oSpan As HtmlGenericControl

    ' ----------------------------------------------

    ' public event
    Public Event ValueChanged As EventHandler

    ' ----------------------------------------------

    ' public constructor
    Public Sub New()

      ' call base method first with element type
      ' root element for control will be a DIV
      MyBase.New("span")

    End Sub


    ' ----------------------------------------------

    OverRides Protected Sub OnInit(e As EventArgs)

      ' first event that control can handle
      ' must always call base method first
      MyBase.OnInit(e)

      ' must register to receive postback events
      ' required because "root" control is a SPAN
      ' does not receive postback events by default
      Page.RegisterRequiresPostBack(Me)

    End Sub

    ' ----------------------------------------------

    Overridable Function LoadPostData(key As String, _
                         vals As NameValueCollection) _
                         As Boolean _
      Implements IPostBackDataHandler.LoadPostData

      ' occurs when data in postback is available to control
      ' get value from viewstate - i.e. when page was last created
      Dim sExistingValue As String = ViewState(key & "_textbox")
      Context.Trace.Write("LoadPostData:" & key, "Loaded existing value '" _
                           & sExistingValue & "' from viewstate")

      ' get client target type from viewstate
      Dim sClientType As String = ViewState(key & "_target")
      Context.Trace.Write("LoadPostData:" & key, "Loaded target '" _
                          & sClientType & "' from viewstate")

      If sClientType = ClientTargetType.Version6.ToString() Then

        ' client type is "version 6" and value was incremented
        ' by client-side script so get value from postback collection
        Dim sNewValue As String = vals(key & "_textbox")

        If sNewValue <> sExistingValue Then

          ' value in control has been changed by user
          ' set internal member to posted value and write message
          ' return True so that PostDataChangedEvent will be raised
          _text = sNewValue
          Context.Trace.Write("LoadPostData:" & key, "Loaded new value '" _
                              & sNewValue & "' from postback data")
          Return True

        Else

          ' value in control has not changed
          ' set internal member to viewstate value and write message
          ' return False because no need to raise ValueChanged event
          _text = sExistingValue
          Return False

        End If

      Else

        ' client type is "down-level" and value will NOT have been
        ' incremented so check if "up" or "down" button caused postback
        If vals(key & "_imageup.x") <> "" Then

          ' "up" image button was clicked so increment value
          ' new value will be checked in CreateChildControls event
          ' to ensure its within maximum and minimum value limits
          ' use Try..Catch in case viewstate empty or text not a number
          Try
            _text = CType(Int32.Parse(sExistingValue) + _increment, String)
            Context.Trace.Write("LoadPostData:" & key, _
                                "Incremented value to '" & _text)
          Catch
            Context.Trace.Write("LoadPostData:" & key, _
                                "Error reading viewstate: " & sExistingValue)
          End Try
          ' return True so that PostDataChangedEvent will be raised
          Return True

        End If

        If vals(key & "_imagedown.x") <> "" Then

          ' "down" image button was clicked so decrement value
          Try
            _text = CType(Int32.Parse(sExistingValue) - _increment, String)
            Context.Trace.Write("LoadPostData:" & key, _
                                "Decremented value to '" & _text)
          Catch
            Context.Trace.Write("LoadPostData:" & key, _
                                "Error reading viewstate: " & sExistingValue)
          End Try
          ' return True so that PostDataChangedEvent will be raised
          Return True

        End If

      End If

    End Function

    ' ----------------------------------------------

    Overridable Sub RaisePostBackDataChangedEvent() _
           Implements IPostBackDataHandler.RaisePostDataChangedEvent

      ' called after all controls have loaded postback data,
      ' but only if LoadPostData handler (above) returned True
      ' call event handler for ValueChanged event
      OnValueChanged(EventArgs.Empty)

    End Sub

    ' ----------------------------------------------

    Protected OverRidable Sub OnValueChanged(e As EventArgs)

      ' write message to Trace and raise public ValueChanged
      ' event with appropriate EventArgs values
      Context.Trace.Write("OnValueChanged:" & Me.UniqueID, _
                          "Raising ValueChanged event")
      RaiseEvent ValueChanged(Me, e)

    End Sub

    ' ----------------------------------------------

    ' public property accessor declarations

    Public Property AutoPostback As Boolean
      Get
        Return _autopostback
      End Get
      Set
        _autopostback = value
      End Set
    End Property

    Public Property Caption As String
      Get
        Return _caption
      End Get
      Set
        _caption = value
      End Set
    End Property

    Public WriteOnly Property ClientTarget As ClientTargetType
      Set
        _client = value
      End Set
    End Property

    Public Property Columns As Integer
      Get
        Return _columns
      End Get
      Set
        If (value > 0) And (value < 1000) Then
          _columns = value
        Else
          Throw New Exception("Columns must be between 1 and 999")
        End If
      End Set
    End Property

    Public OverRides Property CssClass As String
      Get
        Return _cssclass
      End Get
      Set
        _cssclass = value
      End Set
    End Property

    Public Property Increment As Integer
      Get
        Return _increment
      End Get
      Set
        If value > 0 Then
          _increment = value
        Else
          Throw New Exception("Increment must be greater than zero")
        End If
      End Set
    End Property

    Public Property MaximumValue As Integer
      Get
        Return _maxvalue
      End Get
      Set
        If value > _minvalue Then
          _maxvalue = value
        Else
          Throw New Exception("MaximumValue must be greater than " _
                    & "the current MinimumValue")
        End If
      End Set
    End Property

    Public Property MinimumValue As Integer
      Get
        Return _minvalue
      End Get
      Set
        If value < _maxvalue Then
          _minvalue = value
        Else
          Throw New Exception("MinimumValue must be less than " _
                    & "the current MaximumValue")
        End If
      End Set
    End Property

    Public Property Text As String
      Get
        Return _text
      End Get
      Set
        Dim iValue As Integer
        Try
          iValue = Int32.Parse(value)
        Catch
          Throw New Exception("Text property must represent " _
                    & "a valid Integer value")
        End Try
        If (value >= _minvalue) And (value <= _maxvalue)
          _text = value
          SetMaxMinValues()
        Else
          Throw New Exception("Text property must be within" _
                    & "the current MinimumValue and MaximumValue")
        End If
      End Set
    End Property

    Public Property Value As Integer
      Get
        Try
          Return Int32.Parse(_text)
        Catch
        End Try
      End Get
      Set
        If (value >= _minvalue) And (value <= _maxvalue)
          _text = value.ToString()
        Else
          Throw New Exception("Value property must be within the " _
                    & "current MinimumValue and MaximumValue")
        End If
      End Set
    End Property

    ' ----------------------------------------------

    OverRides Protected Sub CreateChildControls()
      ' called when its time to create the child controls
      ' create HTML elements and ASP.NET server controls
      ' set properties and add to Controls collection

      ' check if value is within max and min limits
      SetMaxMinValues()

      ' save current value of Textbox in viewstate
      ViewState(Me.UniqueId & "_textbox") = _text
      Context.Trace.Write("CreateChildControls:" & Me.UniqueID, _
                          "Saved value '" & _text & "' in viewstate")


      ' check if the current browser supports features
      ' required for "smart" operation and if user specified
      ' the mode they want (Version6 or Downlevel)
      If _client <> ClientTargetType.DownLevel Then

        ' start by assuming DownLevel
        _client = ClientTargetType.DownLevel

        ' get reference to BrowserCapabilities object
        Dim oBrowser As HttpBrowserCapabilities = Context.Request.Browser

        ' must support client-side JavaScript
        If oBrowser("JavaScript") = True Then

          ' get browser type and version
          Dim sUAType As String = oBrowser("Browser")
          Dim sUAVer As String = oBrowser("MajorVersion")

          ' see if the current client is IE5 or above
          If (sUAType = "IE") And (sUAVer >= 5) Then
            _client = ClientTargetType.Version6
          End If

          ' see if the current client is Netscape 6.0/Mozilla 1.0
          If (sUAType = "Netscape") And (sUAVer >= 5)  Then
            _client = ClientTargetType.Version6
          End If

          ' see if the current client is Opera 6.0
          If (sUAType = "Opera" And sUAVer >= 6) Then
            _client = ClientTargetType.Version6
          End If

        End If

      End If

      ' save current value of _client in viewstate
      ViewState(Me.UniqueId & "_target") = _client.ToString()

      ' display detected client type value in Trace
      Context.Trace.Write("CreateChildControls:" & Me.UniqueID, _
              "Saved target '" & _client.ToString() & "' in viewstate")

      ' now ready to create the appropriate output
      If _client = ClientTargetType.Version6 Then

        ' ----------------------------------------
        ' serving to version-6 client

        ' control ID prefix for contained controls
        Dim sCID As String = Me.UniqueID & "_"

        ' add caption to "root" SPAN element
        Me.Controls.Add(New LiteralControl(_caption))

        ' create contained SPAN element for textbox
        ' and image buttons, and set properties
        Dim oSpan As New HtmlGenericControl("span")
        oSpan.Style("position") = "relative"
        Controls.Add(oSpan)

        ' create Textbox control, set properties
        ' and add to Controls collection
        oTextBox = New TextBox()
        With oTextBox
          .id = Me.UniqueID & "_textbox"
          If _cssclass <> "" Then
            .CssClass = _cssclass
          End If
          .Columns = _columns
          .Style("top") = "0"
          .Style("left") = "0"
          .Style("width") = _columns * 10
          .Style("text-align") = "right"
          .Text = _text
        End With
        oSpan.Controls.Add(oTextBox)

        ' create "up" ImageButton control, set
        ' properties and add to Controls collection
        oImageUp = New ImageButton()
        With oImageUp
          .id = Me.UniqueID & "_imageup"
          .Style("position") = "absolute"
          .Style("top") = "0"
          .Style("left") = oTextBox.Style("width")
          .Width = New Unit(16)
          .Height = New Unit(10)
          .ImageUrl = "~/images/spin-up.gif"
          .AlternateText = "+" & _increment.ToString()
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .Attributes.Add("border", "0")
        End With
        oSpan.Controls.Add(oImageUp)

        ' create "down" ImageButton control, set
        ' properties and add to Controls collection
        oImageDown = New ImageButton()
        With oImageDown
          .id = Me.UniqueID & "_imagedown"
          .Style("position") = "absolute"
          .Style("top") = "10"
          .Style("left") = oTextBox.Style("width")
          .Width = New Unit(16)
          .Height = New Unit(10)
          .ImageUrl = "~/images/spin-down.gif"
          .AlternateText = "-" & _increment.ToString()
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .Attributes.Add("border", "0")
        End With
        oSpan.Controls.Add(oImageDown)

        ' create true/false string for JavaScript code
        Dim sAutoPostback As String = "false"
        If _autopostback Then
          sAutoPostback = "true"
        End If

        ' create JavaScript parameter string - used to set
        ' parameters for client-side control event handlers
        Dim sParams As String = "'" & sCID & "textbox', " _
          & _minvalue.ToString() & ", " _
          & _maxvalue.ToString() & ", " _
          & _increment.ToString() & ", " _
          & sAutoPostback

        ' see if previous instance of this control has already
        ' added the required JavaScript code to the page
        If Not Page.IsClientScriptBlockRegistered("StonebroomAdaptiveSpinBox") Then
          Dim sPath As String = "/aspnet_client/custom/"
          Dim sScript As String = "<script language='javascript' " _
            & "src='" & sPath & "spinbox.js'><" & "/script>"
          ' add this JavaScript code to the page
          Page.RegisterClientScriptBlock("StonebroomAdaptiveSpinBox", sScript)
        End If

        ' set client-side event handlers for controls
        oImageUp.Attributes.Add("onclick", "return incrementValue(" & sParams & ")")
        oImageDown.Attributes.Add("onclick", "return decrementValue(" & sParams & ")")
        oTextBox.Attributes.Add("onblur", "return checkValue(" & sParams & ")")
        oTextBox.Attributes.Add("onkeydown", "return keyDown(event, " & sParams & ")")

        ' ----------------------------------------
      Else
        ' ----------------------------------------
        ' serving to down-level client

        ' create Table control and set properties
        Dim oTable As New Table()
        With oTable
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .CellPadding = 0
          .CellSpacing = 0
        End With

        ' create TableRow and add to Table
        Dim oRow As New TableRow()
        oTable.Controls.Add(oRow)
        With oRow
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
        End With

        ' create first TableCell and add to Row
        ' insert value of Caption property
        Dim oCell As New TableCell
        oRow.Controls.Add(oCell)
        With oCell
          .Controls.Add(New LiteralControl(_caption))
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .VerticalAlign = VerticalAlign.Middle
        End With

        ' create second TableCell and add to Row
        oCell = New TableCell()
        oRow.Controls.Add(oCell)
        With oCell
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .VerticalAlign = VerticalAlign.Middle
        End With

        ' create Textbox control, set properties
        ' and add to second cell in table
        oTextBox = New TextBox()
        oCell.Controls.Add(oTextBox)
        With oTextBox
          .id = Me.UniqueID & "_textbox"
          If _cssclass <> "" Then
            .CssClass = _cssclass
          End If
          .Columns = _columns
          .Style("width") = _columns * 10
          .Style("text-align") = "right"
          .Text = _text
        End With

        ' create third TableCell and add to Row
        oCell = New TableCell()
        oRow.Controls.Add(oCell)
        With oCell
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .VerticalAlign = VerticalAlign.Middle
        End With

        ' create "up" ImageButton control, set
        ' properties and add to third cell in table
        oImageUp = New ImageButton()
        With oImageUp
          .id = Me.UniqueID & "_imageup"
          .Width = New Unit(16)
          .Height = New Unit(10)
          .ImageUrl = "~/images/spin-up.gif"
          .AlternateText = "+" & _increment.ToString()
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .Attributes.Add("border", "0")
        End With
        oCell.Controls.Add(oImageUp)

        ' create an HTML <br /> element - use LiteralControl
        ' because HtmlGenericControl creates <br></br> which
        ' causes blank line to appear in some browsers
        ' add to cell so down image wraps to next line
        Dim oBR As New LiteralControl("<br />")
        oCell.Controls.Add(oBR)

        ' create "down" ImageButton control, set
        ' properties and add to third cell in table
        oImageDown = New ImageButton()
        With oImageDown
          .id = Me.UniqueID & "_imagedown"
          .Width = New Unit(16)
          .Height = New Unit(10)
          .ImageUrl = "~/images/spin-down.gif"
          .AlternateText = "-" & _increment.ToString()
          .BorderStyle = BorderStyle.None
          .BorderWidth = New Unit(0)
          .Attributes.Add("border", "0")
        End With
        oCell.Controls.Add(oImageDown)

        ' add Table control to SPAN element Controls collection
        Controls.Add(oTable)

        ' ----------------------------------------
      End If

      ' display control property values in Trace
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".AutoPostback = " & Me.AutoPostback.ToString())
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".Caption = '" & Context.Server.HtmlEncode(Me.Caption) & "'")
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".Columns = " & Me.Columns.ToString())
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".CssClass = '" & Me.CssClass & "'")
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".Increment = " & Me.Increment.ToString())
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".MaximumValue = " & Me.MaximumValue.ToString())
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".MinimumValue = " & Me.MinimumValue.ToString())
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".Text = '" & Me.Text & "'")
      Context.Trace.Write("Property Values", Me.UniqueID _
        & ".Value = " & Me.Value.ToString())

    End Sub

    ' ----------------------------------------------

    ' check if current value of Textbox (in _text member variable)
    ' is within current max and min limits, and reset if not
    Private Sub SetMaxMinValues()
      Dim iValue As Integer
      Try
        iValue = Int32.Parse(_text)
      Catch
        iValue = _minvalue
      End Try
      If iValue < _minvalue Then
        iValue = _minvalue
      End If
      If iValue > _maxvalue Then
        iValue = _maxvalue
      End If
      _text = iValue.ToString()
    End Sub

    ' ----------------------------------------------

  End Class

End Namespace