Link to home
Start Free TrialLog in
Avatar of MargusLehiste
MargusLehiste

asked on

Issues with Custom PhoneBox Control

There are couple of issues that annoy me about the PhoneBox control I have
(it has parenthesis 3 textboxes - arecode, prefix, last 4 digits and dashes):
1) It does not keep viewstate and therefore the values inside cannot be retrieved.
2) When you type in area code 3 numbers - and press tab it moves to the next box _prefix - however when you press <SHIFT> + <TAB> and then release it - it loses the focus again.

Here is the code:

Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Public Class PhoneNumberControl
    Inherits WebControl
    'Right after the Inherits... statement, declare variables for your three text boxes:
    ' declare text boxes.
    Private _areacode As TextBox
    Private _prefix As TextBox
    Private _lastfour As TextBox

    <Bindable(True), Category("Appearance"), DefaultValue("")> ReadOnly Property [PhoneNo]() As String
        Get
            Return _areacode.Text
            If _areacode.Text.Length = 3 And _prefix.Text.Length = 3 And _lastfour.Text.Length = 4 Then
                Return "(" & _areacode.Text & ") " & _prefix.Text & "-" & _lastfour.Text
            Else
                Return ""
            End If
        End Get
    End Property

    <Bindable(False), Category("Appearance"), DefaultValue(0)> Overrides Property [tabIndex]() As Short
        Get
            Return _areacode.TabIndex()
        End Get
        Set(ByVal Value As Short)
            _areacode.TabIndex = Value
            _lastfour.TabIndex = Value
            _prefix.TabIndex = Value
        End Set
    End Property

    'Then add the constructor (often referred to as ctor).  In VB, this is the New() sub:  
    Public Sub New()
        ' make sure CreateChildControls gets called.
        MyBase.EnsureChildControls()
    End Sub

    'Now you need to override three routines of the base class (which is WebControl): CreateChildControls(); OnInit() and Render().  The CreateChildControls() routine is where you instantiate the textbox classes declared above and add them to the base class' Controls collection.  In your New() method, you tell the base class to call this method right when the class is instantiated.  EnsureChildControls() causes the base class to call your overriden CreateChildControls().  Here is what that routine should look like:
    Protected Overrides Sub CreateChildControls()
        MyBase.Controls.Add(New LiteralControl("("))

        ' instantiate and add the area code box.
        Me._areacode = New TextBox
        Me._areacode.MaxLength = 3
        Me._areacode.Width = Unit.Pixel(24)
        Me._areacode.Height = Unit.Pixel(18)
        Me._areacode.CssClass = "standardFormType"
        MyBase.Controls.Add(Me._areacode)

        ' add a dash as a literal control.
        MyBase.Controls.Add(New LiteralControl(") "))

        ' instantiate and add the prefix box.
        Me._prefix = New TextBox
        Me._prefix.MaxLength = 3
        Me._prefix.Width = Unit.Pixel(24)
        Me._prefix.Height = Unit.Pixel(18)
        Me._prefix.CssClass = "standardFormType"
        MyBase.Controls.Add(Me._prefix)

        ' add a dash as a literal control.
        MyBase.Controls.Add(New LiteralControl("-"))

        ' instantiate and add the last four digits box.
        Me._lastfour = New TextBox
        Me._lastfour.MaxLength = 4
        Me._lastfour.Width = Unit.Pixel(32)
        Me._lastfour.Height = Unit.Pixel(18)
        Me._lastfour.CssClass = "standardFormType"
        MyBase.Controls.Add(Me._lastfour)
    End Sub

    'Now you want to override OnInit().  This is where you tell your page to check for the script block that points to your script file.  There is a function you will see below that checks to see if the script has already been registered.  This way if you put more than one of your control on a page, only one registration of the script block happens:
    Protected Overrides Sub OnInit(ByVal e As EventArgs)
        ' make sure client script block pointing to your script file is registered.
        If Me.Page.IsClientScriptBlockRegistered("PhoneNumberBoxScript") Then
            Dim js As String = "<script language='javascript'>" & _
" function validateKey()" & _
" {" & _
"     var key = window.event.keyCode; " & _
"     var test1 = (key >= 48     && key <= 57);  " & _
"     var test2 = (key >= 96     && key <= 105); " & _
"     var test3 = (key == 37     || key == 39);  " & _
"     var test4 = (key == 46     || key == 9);   " & _
"     var test5 = (key == 8); " & _
" " & _
"     if (!test1 && !test2 && !test3 && !test4 && !test5)" & _
"     {" & _
"          window.event.returnValue = false;" & _
"          window.event.cancelBubble = true;" & _
"     }" & _
"}" & _
"" & _
"function passFocus()" & _
"{" & _
"     var src = window.event.srcElement;" & _
"     var myMaxLen = maxlength; " & _
"     var maxlen = src.getAttribute(myMaxLen); " & _
"     if (src.value.length == maxlen && window.event.keyCode != 16 && window.event.keyCode != 17) src.nextSibling.nextSibling.focus();" & _
"}" & _
"</script>"

            'js = "<script language='javascript' src='PhoneNumberBox.js'></script>"
            Me.Page.RegisterClientScriptBlock("PhoneNumberBoxScript", js)
        End If

        ' let base complete the init process.
        MyBase.OnInit(e)
    End Sub

    'Here is one of the funny things about VB.NET.  These two script registering methods do not appear in Intellisense (they do appear in C#, go figure).  But they are there, just make sure your spelling is right.
    'Lastly, you will override Render().  By this time everything has been instantiated.  Your base class has it's id string, so it can pass it along to the three text box controls.  This is also where you add attributes to the text boxes so they call your JavaScript functions when a key has been pressed (onkeydown) to disallow non numeric characters and when the key comes up (onkeyup) to check the length of the value.  If it is the same as the maxlength property of the text box (3, 3, and 4 respectively), then it passes focus to the next box.  Here it is:

    Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
        'set ids.
        Me._areacode.ID = Me.ID & "_areacode"
        Me._prefix.ID = Me.ID & "_prefix"
        Me._lastfour.ID = Me.ID & "_lastfour"

        ' set attributes.
        Me._areacode.Attributes.Add("onkeydown", "javascript:validateKey();")
        Me._areacode.Attributes.Add("onkeyup", "javascript:passFocus();")
        Me._prefix.Attributes.Add("onkeydown", "javascript:validateKey();")
        Me._prefix.Attributes.Add("onkeyup", "javascript:passFocus();")
        Me._lastfour.Attributes.Add("onkeydown", "javascript:validateKey();")

        ' render control.
        MyBase.Render(writer)
    End Sub

End Class
ASKER CERTIFIED SOLUTION
Avatar of CJ_S
CJ_S
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jnhorst
jnhorst

Margus... you probably should take out the comments intervening the various parts of the code.  I provided those with the code so you would understand the reasoning behind each step.  You can keep em in there if you like, but it clutters things up a bit.

Let me look into the point at which you load and save viewstate and post here again.

John
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Also, I think your override of tabIndex is part if your problem.  You are assigning all three textboxes the same tab index.  The prefix should have an index 1 higher than the area code, and the last four 1 higher than the last four digits.

John
jnhorst,
I already pointed that out in my first comment and corrected the code.
CJ_S:

A few things here.  This is the result of a previous question.  I provided Margus with this code to get him started with a custom control.  Second: I do not see in your code edit where you addressed the TabIndex issue as I did above.  In your post, all three text boxes are getting the same tab index.  Third: If you look carefully at the original OnInit() override, IsClientScriptBlockRegistered is called to check if the script has already been registered.  Fourth: You took out the code assigning the text boxes id strings.  I put that there so the control could be used more than once on a page without running into problems.  If he tries to put this in a TemplateColumn of a grid or list, this will be important.  Fourth, his use of maxlen in the JavaScript routine is a variable that is declared and assigned immediately above in the code.

John
So you're more into the code than I am, or at least know exactly what MargusLehiste wants. So you may be right about the TabIndex, I do not know what he wants, but it does seem to tab correctly to me.

However, I disagree with you on the other points. Please do read my post and the code correctly.

1) IsClientScriptBlockRegistered
I assume that when I call IsClientScriptBlockRegistered that I get back a boolean value indicating whether it IS or is NOT registered. In the original code you will see that it has
If(IsClientScriptBlockRegistered(...)) Then
  .. write out code
End If

2) >> Fourth: You took out the code assigning the text boxes id strings.  I put that there so the control could be used more than once on a page without running into problems.  If he tries to put this in a TemplateColumn of a grid or list, this will be important.
Read about the INamingContainer, that way the controls will get correct naming by itself, as it should be. And thus also work in repeaters and other databound controls.

3) >> Fourth, his use of maxlen in the JavaScript routine is a variable that is declared and assigned immediately above in the code.
maxlen is indeed a variable, but maxlength was not. maxlength is a property of the textbox and should actually be written with a capital L (maxLength). Since it is a property of the object owning the event I prefer to add an additional 'this.' to the property.

Try running my code if you want.

CJ. :-)
1) IsClientScriptBlockRegistered
I assume that when I call IsClientScriptBlockRegistered that I get back a boolean value indicating whether it IS or is NOT registered. In the original code you will see that it has
If(IsClientScriptBlockRegistered(...)) Then
  .. write out code
End If

=

1) IsClientScriptBlockRegistered
I assume that when I call IsClientScriptBlockRegistered that I get back a boolean value indicating whether it IS or is NOT registered. In the original code you will see that it has
If(IsClientScriptBlockRegistered(...)) Then
  .. write out code
End If

and should actually be
If(Not IsClientScriptBlockRegistered(...)) Then
  .. write out code
End If
Margus:  I looked at my original code and CJ was correct in pointing out that

If Me.Page.IsClientScriptBlockRegistered("PhoneNumberBoxScript") Then

should be

If Not Me.Page.IsClientScriptBlockRegistered("PhoneNumberBoxScript") Then

John