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

Namespace Stonebroom

  Public Class StandardSpinBox

    ' specify base class to extend
    Inherits WebControl

    ' need to be able to handle postbacks
    Implements IPostBackDataHandler

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

    ' private internal member variables
    Private _autopostback As Boolean = False
    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

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

    ' 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 SPAN
      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 postback collection
      Dim NewValue As String = vals(key & "_textbox")

      ' get value from viewstate - i.e. when page was last created
      Dim ExistingValue As String = ViewState(key & "_textbox")
      If NewValue <> ExistingValue Then

        ' value in control has been changed by user
        ' set internal member to posted value and write message
        ' return True so PostDataChangedEvent will be raised
        _text = NewValue
        Context.Trace.Write("LoadPostData:" & key, "Loaded new value '" _
                            & NewValue & "' 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 = ExistingValue
        Context.Trace.Write("LoadPostData:" & key, "Loaded existing value '" _
                             & ExistingValue & "' from viewstate")
        Return False

      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 the 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 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

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

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

      ' set properties (attributes) of root SPAN element
      Me.Style("position") = "relative"

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

      ' create Textbox control, set properties
      ' and add to Controls collection
      oTextBox = New TextBox()
      With oTextBox
        .id = sCID & "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
      Controls.Add(oTextBox)

      ' create "up" ImageButton control, set
      ' properties and add to Controls collection
      oImageUp = New ImageButton()
      With oImageUp
        .id = sCID & "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
      Controls.Add(oImageUp)

      ' create "down" ImageButton control, set
      ' properties and add to Controls collection
      oImageDown = New ImageButton()
      With oImageDown
        .id = sCID & "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
      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("StonebroomSpinBox") 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("StonebroomSpinBox", 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 & ")")

      ' display control property values in Trace
      Context.Trace.Write("Property Values", Me.UniqueID _
              & ".AutoPostback = " & Me.AutoPostback.ToString())
      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