Search code examples
asp.netvb.netclassobjectascx

Grabbing user's textbox input from dynamically created textboxes


I am using Visual Studio with asp.net, vb.net, and webforms.

My issue is that I need to get the user's textbox input from my dynamically generated textboxes.

On my button click BtnStep4Next, I dynamically add ServiceForm.ascx (it is a form with FirstName, LastName, City, etc). I add the Service Form to a list, and the list is stored in a session. There could be, for example, 5 webforms (ServiceForm).

Protected Sub BtnStep4Next_Click(sender As Object, e As EventArgs) Handles BtnStep4Next.Click

    Dim formCount As Integer = 0

    'Create a list to hold all of the service forms
    Dim listServiceForms As New List(Of openapps_luesreg_ServiceForm)
    Session("vsServiceForms") = listServiceForms

    For Each i In CBLModifications.Items
        'Do not show a service form for membership (ID = 1)
        If i.Value <> "1" Then
            formCount += 1

            Dim serviceForm As openapps_luesreg_ServiceForm = New openapps_luesreg_ServiceForm()

            'Load a new Service Form to the page
            serviceForm = Page.LoadControl("ServiceForm.ascx")
            serviceForm.ID = "service" + formCount.ToString

            Dim txtCity As TextBox = CType(serviceForm.FindControl("txtCity"), TextBox)
            txtCity.ID = "serviceCity" + formCount.ToString

            'Dim txtCityRegEx As Regex = CType(serviceForm.FindControl("RegExCity"), Regex)
            'txtCityRegEx.ID = "serviceCity" + formCount.ToString


            'Add the new Service Form to the panel PnlService Forms
            PnlServiceForms.Controls.Add(serviceForm)

            'Add the name to the top of the Form
            Dim lblServiceName As Label = CType(serviceForm.FindControl("lblServiceName"), Label)
            lblServiceName.Text = i.Text

            'Add to listServiceForms
            listServiceForms.Add(serviceForm)

            'Set the list to the ViewState
            Session("vsServiceForms") = listServiceForms

        End If
    Next
 End Sub

On a different button click, now I want to grab the user's input they type into the textboxes. My code here though results in empty strings (the original form text) instead of the user's updated input. How can I actually get each textboxes' text that the user enters at this point?

Protected Sub BtnStep5Next_Click(sender As Object, e As EventArgs) Handles BtnStep5Next.Click


    Dim listServiceForms As List(Of openapps_luesreg_ServiceForm)
    listServiceForms = DirectCast(Session("vsServiceForms"), List(Of openapps_luesreg_ServiceForm))

    Dim myCount As Integer = 0
    For Each i In listServiceForms
        myCount += 1
        Dim ctlTxtCity As String = "serviceCity" + myCount.ToString

        Dim myCity2 = ctlTxtCity

        Dim txtNewCity As TextBox =  CType(i.FindControl(ctlTxtCity), TextBox)
        Dim myCity = txtNewCity.Text

    Next
End Sub

Solution

  • You are writing way too much code. I mean, if I need 10 variables, do I go

     dim v1    as integer
     dim v2    as integer
     ... and so on
    

    No! You do this:

    dim MyValues(10) as integer
    

    We use array's, collections. I mean even on desktop software. If I need a bunch of rows of repeating data - you use a grid, continues form or whatever other REPEATING type of control to do all this dirty work for you.

    Say, I want to enter some Hotel Names. But, ahead of time I had no idea of how many. I mean, any database system + problem WILL have to deal with this type of scenario.

    So, you don't try and inject controls. What happens now when you want to get that data back, say save it into the database? You now going to have a WHOLE set of controls that you have to have some numbering system to deal with.

    Software not done this way. If you have repeating data, then use a repeating data control.

    You can use repeater, listview, gridview. They all support repeating rows of data. The gridviewe or listview is for a grid layout. And the repeater would be the SAME idea, but you now laying out controls (repeating them) and you don't necessary have to use a grid format.

    So, lets do a example of adding hotel names.

    We have this grid markup:

    <div style="float:left;margin-left:20px">
    
    
      <style> .borderhide input {border:none}</style>
    
      <asp:GridView ID="GridView1" runat="server" 
                    AutoGenerateColumns="False" DataKeyNames="ID" CssClass="table table-hover borderhide" >
      
          <Columns>
            <asp:TemplateField  HeaderText="HotelName" >
                <ItemTemplate><asp:TextBox id="HotelName" runat="server" Text='<%# Eval("HotelName") %>' /></ItemTemplate>
            </asp:TemplateField>
    
            <asp:TemplateField  HeaderText="FirstName" SortExpression="ORDER BY FirstName" >
                <ItemTemplate><asp:TextBox id="FirstName" runat="server" Text='<%# Eval("FirstName") %>' /></ItemTemplate>
            </asp:TemplateField>
    
            <asp:TemplateField  HeaderText="LastName" >
                <ItemTemplate><asp:TextBox id="LastName" runat="server" Text='<%# Eval("LastName") %>' /></ItemTemplate>
            </asp:TemplateField>
    
           <asp:TemplateField  HeaderText="City" >
                <ItemTemplate><asp:TextBox id="City" runat="server" Text='<%# Eval("City") %>' /></ItemTemplate>
            </asp:TemplateField>
    
            <asp:TemplateField HeaderText="Active">
                <ItemTemplate><asp:CheckBox id="Active" runat="server"  Checked = '<%# Eval("Active") %>'  /></ItemTemplate>
            </asp:TemplateField>
        </Columns>
      </asp:GridView>
    
        <div style="clear:both;padding-top:20px">
            <asp:Button ID="cmdSave" runat="server" Text="Save" />
            <asp:Button ID="cmdAdd" runat="server" Text="Add row" style="margin-left:20px" />
            <asp:Button ID="cmdUnDo" runat="server" Text="UnDo" Style="margin-left:20px"/>
        </div>
    </div>
    

    Not too much markup, but we GET to layout our repeating controls ONE time!!

    Now, the code to fill this grid view is this:

    Dim rstTable As New DataTable
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    
        If IsPostBack = False Then
    
            LoadGrid()
            ViewState("rstTable") = rstTable
        Else
            rstTable = ViewState("rstTable")
        End If
    
    End Sub
    
    
    Sub LoadGrid()
    
        ' load up our drop down list from database (city)
        Dim strSQL As String
    
        strSQL = "SELECT ID, FirstName, LastName, HotelName, City, Active, Rating from tblHotels"
    
        Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST3))
    
            cmdSQL.Connection.Open()
            rstTable.Load(cmdSQL.ExecuteReader)
    
            GridView1.DataSource = rstTable
            GridView1.DataBind()
    
        End Using
    
    End Sub
    

    Clean, simple code, and the result is this:

    enter image description here

    Now, even more incredible? I can tab around in that grid - edit like Excel.

    So now how do we send back the changes to the database (the ONE save button).

    The code looks like this:

    Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
    
        ' pull repeater rows back to table.
        Dim bolDataOk As Boolean = True
    
        For Each rRow As GridViewRow In GridView1.Rows
    
            Dim RecordPtr As Integer = rRow.RowIndex
            Dim OneDataRow As DataRow
    
            OneDataRow = rstTable.Rows(RecordPtr)
            OneDataRow.Item("HotelName") = CType(rRow.FindControl("HotelName"), TextBox).Text
            OneDataRow.Item("FirstName") = CType(rRow.FindControl("FirstName"), TextBox).Text
            OneDataRow.Item("LastName") = CType(rRow.FindControl("LastName"), TextBox).Text
            OneDataRow.Item("City") = CType(rRow.FindControl("City"), TextBox).Text
            OneDataRow.Item("Active") = TryCast(rRow.FindControl("Active"), CheckBox).Checked
    
        Next
    
        ' now send table back to database with updates
    
        If bolDataOk Then
    
            Dim strSQL As String = "SELECT ID, FirstName, LastName, City, HotelName, Active from tblHotels WHERE ID = 0"
    
            Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST3))
    
                cmdSQL.Connection.Open()
                Dim daupdate As New SqlDataAdapter(cmdSQL)
                Dim cmdBuild As New SqlCommandBuilder(daupdate)
    
                daupdate.Update(rstTable)
    
            End Using
    
        End If
    
    End Sub
    

    So now, note how we can SAVE all the edits in that grid in ONE shot.

    And if we decide to add more columns, or maybe even a button we hit to see/view one row, we can do so with great ease.

    Lets wire up the add button (adds a row to the grid). When we press it, we get/have/add a new row to the grid.

    So the code could be like this:

    Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
    
        Dim MyOneRow = rstTable.NewRow
    
        ' set defaults for this new row
        MyOneRow("City") = "Edmonton"
        MyOneRow("Active") = True
    
        ' add htis new row to the table
        rstTable.Rows.Add(MyOneRow)
    
        ' now update the grid to show this new added row
    
        GridView1.DataSource = rstTable
        GridView1.DataBind()
    
    
    End Sub
    

    Wow! - was that not nice and easy to add a WHOLE new grid row?

    So, how this "should" work if using desktop MS-Access, FoxPro, or even the web?

    Use the controls and bits and parts that allow you to work with repeating data.

    There is VERY little need to try and inject controls into the web page. But worse, for repeating sets of data? Then for sure you don't' want to try and manage, and code and try to deal with a "set" of controls who's only job is to display repeating data.

    Study the above. I suggest you drop all those controls, the whacks of code, and adopt either a gridview, or even a repeater to manage this problem.

    I mean, what is even worse?

    What happens if you need to go back to that page to edit data? Now you have to pull the data, and now re-inject all those controls again. So, you NOW need two sets of code. One set to add the new controls, and then later on to go back and display/edit that data, you again have to write another loop to re-inject the controls for display.

    edit --------------------------------------

    So the follow up question was how to do this for NOT a grid.

    Well as noted, we have a "choice" of some controls, and their job is to "repeat" data for us. As noted, for grids, then GridView and ListView are top choices.

    But, for non grid, and just some layout (say some fields that is a form or some text boxes)?

    Sure, that suggest a Repeter. Note how NEAR exact the same code approach as above works. The is the wonderful part about this approch. Once you do this one time with a grid, then you know and have the skills to do this with say the repeater.

    So, say we have this:

    enter image description here

    Ok, so I might want 2, or 15 of them, right? So we take the layout for above, and put it "inside" of a repeater control.

    It will look like this:

    <asp:Repeater ID="Repeater1" runat="server">
        <ItemTemplate>
    
            <div style="border-style:solid;color:black;width:250px;float:left">
                <div style="padding:5px;text-align:right">
                    Hotel Name: <asp:TextBox ID="HotelName" runat="server" Text ='<%# Eval("HotelName") %>' Width="130px" />
                    <br />
                    First Name: <asp:TextBox ID="txtFirst" runat="server" Text ='<%# Eval("FirstName") %>'  Width="130px" />
                    <br />
                    Last Name: <asp:TextBox ID="txtLast" runat="server" Text ='<%# Eval("LastName") %>'  Width="130px" />
                    <br />
                    City: <asp:TextBox ID="txtCity" runat="server" Text ='<%# Eval("City") %>'  Width="130px" />
                    <br />
                    Active: <asp:CheckBox ID="Active" runat="server" Checked = '<%# Eval("Active") %>'/>
                </div>
            </div>
    
            <div style="clear:both;height:20px">
                <!-- this div starts a new line -->
            </div>
    
        </ItemTemplate>
    </asp:Repeater>
    

    So, it really just the markup, but note how we used Eval(). This is a data expression - it ONLY works inside of grids, listview, repeater. However, it even works for say a Radio button list!

    Ok, so the above is our markup. So, now the code to load up this repeater looks like this:

    Dim rstTable As New DataTable
    
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    
        If Not IsPostBack Then
            loadGrid()
            ViewState("MyTable") = rstTable
        Else
            rstTable = ViewState("MyTable")
        End If
    
    End Sub
    
    Sub loadGrid()
    
        Using cmdSQL As New SqlCommand("SELECT * FROM tblHotels ORDER BY HotelName", New SqlConnection(My.Settings.TEST3))
    
            cmdSQL.Connection.Open()
            rstTable.Load(cmdSQL.ExecuteReader)
    
            Repeater1.DataSource = rstTable
            Repeater1.DataBind()
    
        End Using
    
    End Sub
    

    And now we get this:

    enter image description here

    Now, I also dropped in two more plane jane buttons (add, and save).

    I just dropped those buttion right after the repater (outside of repeater)

    eg:

    <br />
     <asp:Button ID="cmdAdd" runat="server" Text="Add new" />
     <asp:Button ID="cmdSave" runat="server" Text="Save" Style="margin-left:20px" />
    

    If you edit ANY of the 3 - tab around - we can now hit save and again in ONE operation send all changes back to the database.

    First, lets wire up the add button. When you hit it, then in place of 3 examples, you have 4. The code is thus this:

    Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
    
    
        Dim MyOneRow = rstTable.NewRow
    
        ' set defaults for this new row
        MyOneRow("City") = "Edmonton"
        MyOneRow("Active") = True
    
        ' add this new row to the table
        rstTable.Rows.Add(MyOneRow)
    
        ' now update the grid to show this new added row
        Repeater1.DataSource = rstTable
        Repeater1.DataBind()
    
    
    End Sub
    

    Again, note how VERY clean and simple this code is.

    So, if I just hit that add button, then I would see this:

    enter image description here

    But, I am 100% free to tab around and edit ANY of the 4 displayed.

    So, now we need the save button. That will:

    send back to database any changes/edits to existing
    save + send back to database any NEW rows.
    and if we added a delete button - it would do that for us to!!
    

    The save button code is thus:

    Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
    
        ' move data from each repeater back to table
        For Each rRow As RepeaterItem In Repeater1.Items
    
            Dim OneRow As DataRow = rstTable.Rows(rRow.ItemIndex)
    
            OneRow("FirstName") = CType(rRow.FindControl("txtFirst"), TextBox).Text
            OneRow("LastName") = CType(rRow.FindControl("txtLast"), TextBox).Text
            OneRow("HotelName") = CType(rRow.FindControl("HotelName"), TextBox).Text
            OneRow("City") = CType(rRow.FindControl("txtCity"), TextBox).Text
            OneRow("Active") = CType(rRow.FindControl("Active"), CheckBox).Checked
    
        Next
    
        ' now write the table back to database 
    
        Using cmdSQL As New SqlCommand("SELECT * FROM tblHotels WHERE ID = 0",
                        New SqlConnection(My.Settings.TEST3))
            Dim DA As New SqlDataAdapter(cmdSQL)
            Dim daUpdate As New SqlCommandBuilder(DA)
    
            DA.Update(rstTable)
    
        End Using
    
    
    End Sub
    

    So, we move data from Repeater back to table

    Then we tell the system to update that table back to database.

    So we did NOT have very much markup.

    We were is REALLY great ease able to add a new row, but again let the repeater do all the work of showing that new data row.