How to change dropdownlist value based on another dropdownlist value without autopostback in asp.net using vb.net
Well, if you don't post-back, then no code can run.
You could start to use ajax calls. That allows you to call + use server side code without a page post-back. But this means you have to write some JavaScript code client side.
However, a post-back is not the end of the world here.
And there is a in-between choice, in which you ONLY post back the two combo boxes, and that results in quite much ONLY the two combo boxes being posted, and the rest of the page will remain un-touched.
This means asp.net DOES the ajax wire up for you!!!
So, way we have these two combo boxes. First one select city, and 2nd one a hotel in that city.
<div style="float:left">
<h3>Select City</h3>
<asp:DropDownList ID="cboCity" runat="server" Width="168px"
DataValueField="City" DataTextField="City" AutoPostBack="true"></asp:DropDownList>
</div>
<div style="float:left;margin-left:25px">
<h3>Select Hotel</h3>
<asp:DropDownList ID="cboHotels" runat="server" Width="168px"
DataValueField="ID" DataTextField = "HotelName"
></asp:DropDownList>
</div>
Ok, so note that our first combo box has auto post back.
And our code is this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
' load up City combo
cboCity.DataSource = MyrstP(New SqlCommand("SELECT City from City ORDER BY City"))
cboCity.DataBind()
cboCity.Items.Insert(0, "") ' Add blank row selecting
End If
End Sub
and first combo box code is this:
Protected Sub cboCity_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboCity.SelectedIndexChanged
' cascade City and limit hotels from given city.
Using cmdSQL As New SqlCommand("SELECT ID, HotelName FROM tblHotels
WHERE City = #City ORDER BY HotelName")
cmdSQL.Parameters.Add("#City", SqlDbType.NVarChar).Value = cboCity.SelectedItem.Text
cboHotels.DataSource = MyrstP(cmdSQL)
cboHotels.DataBind()
cboHotels.Items.Insert(0, New ListItem("Select Hotel", 0))
End Using
And our helper routine to make life easy:
Public Function MyrstP(sqlCmd As SqlCommand) As DataTable
Dim rstData As New DataTable
Using sqlCmd
sqlCmd.Connection = New SqlConnection(My.Settings.TEST4)
sqlCmd.Connection.Open()
rstData.Load(sqlCmd.ExecuteReader)
End Using
Return rstData
End Function
(and NOTE VERY careful, how we NEVER created a connection object and then used it later, but we use the BUILT IN one inside of sql command.
Ok,
now lets change this to NOT full page post back.
Do this:
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<div style="float:left">
<h3>Select City</h3>
<asp:DropDownList ID="cboCity" runat="server" Width="168px"
DataValueField="City" DataTextField="City" AutoPostBack="true"></asp:DropDownList>
</div>
<div style="float:left;margin-left:25px">
<h3>Select Hotel</h3>
<asp:DropDownList ID="cboHotels" runat="server" Width="168px"
DataValueField="ID" DataTextField = "HotelName"
></asp:DropDownList>
</div>
</ContentTemplate>
</asp:UpdatePanel>
Now give it a try!!! - note how the page does not post-back anymore.
This is still a partial page post back, but ONLY the combo box is sent back to the server, and only part is updated. In fact this is quite much how the new blazer pages with signalR quite much work.
So try the above. You will note that no page post back occurs.
Related
This is driving me potty and I can't see anything wrong with it. The control displays as expected, but you can't reorder the items. Whenever you try, you get an alert saying "Reorder failed, see details below.\r\n\r\nFailed to reorder.", which isn't particularly helpful and I can't see any issues on the console or on the network traffic.
I have checked all the table and field names and they all match up, including case. Would appreciate people having a look at it and pointing out my mistake (which I have a nasty feeling is on the side of stupid).
ASP Page
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="ScriptManager1" />
<div style="width: 90%; margin-left: 2.5%; padding-top: 5px;">
<ajax:ReorderList ID="rlActiveItems" runat="server" SortOrderField="Ordering" AllowReorder="true"
DataSourceID="sdsActiveItems" DataKeyField="ID" LayoutType="table" ItemInsertLocation="End" ShowInsertItem="True" Width="100%">
<DragHandleTemplate>
<span class="DragHandleClass"> </span>
</DragHandleTemplate>
<ReorderTemplate>
<div class="DragClass"> </div>
</ReorderTemplate>
<ItemTemplate>
<div class="etched bgBlue itemArea" data-item='<%#Eval("ID")%>' style="float: left; width: 100%; cursor: pointer;">
<div class="inner">
<div style="float: right; width: 30px; text-align: center;">
<asp:Image ID="iDelete" runat="server" ImageUrl="~/images/icon_delete.png" />
</div>
<div style="float: left; width: calc(100% - 40px); font-weight: bold;">
<asp:Label ID="lID" runat="server" Visible="false" Text='<%#Eval("ID")%>'></asp:Label>
<asp:Label ID="lOrder" runat="server" Visible="false" Text='<%#Eval("Ordering")%>'></asp:Label>
<%#Eval("Name")%>
</div>
</div>
</div>
</ItemTemplate>
</ajax:ReorderList>
</div>
<asp:SqlDataSource ID="sdsActiveItems" runat="server"
ConnectionString="<%$ ConnectionStrings:ApplicationConnectionString %>"
OldValuesParameterFormatString="original_{0}"
SelectCommand="SELECT ID, Name, Ordering FROM LOGREQItems WHERE Active = 1 AND FkGroup = 3 ORDER BY Ordering"
UpdateCommand="UPDATE [LOGREQItems] SET Ordering = #Ordering WHERE ID = #original_ID">
<UpdateParameters>
<asp:Parameter Name="Ordering" Type="Int32" />
<asp:Parameter Name="original_ID" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
</form>
Table
CREATE TABLE [dbo].[LOGREQItems](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
[FkGroup] [int] NOT NULL,
[Ordering] [int] NOT NULL,
[Active] [bit] NOT NULL
Well, the main issue is that you have the datasource on the page. So, it kind of persisting as a sorted data source. If you do NOT post-back, then it probably can work, but with your datasource, and the sort order fixed in that data source, then in theory you would have to update the table data.
So, I suggest you dump the on-page data source. I tend to not use them very much. (in fact, I will often use the wizards for a gridview or whatever, let the markup be generated and THEN delete the datasource, and the datasourceID from the given control.
so, lets do this in-memory, and without post-backs. This will allow you to see, try, play with the re-ordering.
(but, without updating the data source driving this, then we can save (but, I'll post a 2nd example that does).
Ok, so, our simple markup:
<ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
AllowReorder="True"
DataKeyField="ID"
SortOrderField="MyOrder" >
<DragHandleTemplate>
<span style="cursor:move"><i class="glyphicon glyphicon-move"></i></span>
</DragHandleTemplate>
<ReorderTemplate><div></div></ReorderTemplate>
<ItemTemplate>
<asp:TextBox ID="txtHotel" runat="server"
Text='<%# Eval("HotelName") %>'></asp:TextBox>
</ItemTemplate>
</ajaxToolkit:ReorderList>
<br />
<asp:Button ID="cmdSave" runat="server" Text="Save Changes" CssClass="btn" />
And our code to load:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData()
End If
End Sub
Sub LoadData()
ReorderList1.DataSource =
MyRst("SELECT ID, HotelName, MyOrder FROM tblHotelsA ORDER BY MyOrder")
ReorderList1.DataBind()
End Sub
And we now get this:
Now, above works just fine for me.
(not a whole lot use, since we DO NOT update the table, nor order here).
However, lets now do this, and PERSIT our data table, and ALSO update that table.
So, now, when we drag + drop (move items around), we ALSO will update the MyOrder column in our data table. To do this, we now MUST post-back, and that ALSO means we WILL have to re-bind the re-order list control.
So, now we have this for the markup.
(and I can stress that the MyOrder column setting MUST be set - for all of these examples).
Ok, so, now the markup is this:
<ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
AllowReorder="True"
DataKeyField="ID"
SortOrderField="MyOrder"
PostBackOnReorder ="true"
>
(no other changes).
but, our code NOW has to maintain, persist, and keep a copy of the data - we will use session (or, you could I suppose directly up the table data - but lets do that last).
So, now our code is this:
Dim rstData As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData()
Session("rstData") = rstData
Else
rstData = Session("rstData")
End If
End Sub
Sub LoadData()
rstData = MyRst("SELECT ID, HotelName, MyOrder FROM tblHotelsA ORDER BY MyOrder")
ReorderList1.DataSource = rstData
ReorderList1.DataBind()
End Sub
Note VERY careful - we maintain a copy of that table (in session()).
So, note how we declare rstData at the page class level - and not the page.load event, we load first time, shove into session (or for any other post-back, we restore from session() to that page class rstData var.
But, we now also have the event for the post-back, and that code has to update the rstData table with the results of the drag + drop.
We have this code:
Protected Sub ReorderList1_ItemReorder(sender As Object, e As AjaxControlToolkit.ReorderListItemReorderEventArgs) Handles ReorderList1.ItemReorder
If e.OldIndex < e.NewIndex Then
' move everything up (towards top) in list
For Each OneRow As DataRow In rstData.Rows
If OneRow("MyOrder") = e.OldIndex Then
OneRow("MyOrder") = e.NewIndex
Else
If OneRow("MyOrder") >= e.OldIndex And OneRow("MyOrder") <= e.NewIndex Then
OneRow("MyOrder") -= 1
End If
End If
Next
Else
For Each OneRow As DataRow In rstData.Rows
If OneRow("MyOrder") = e.OldIndex Then
OneRow("MyOrder") = e.NewIndex
Else
If OneRow("MyOrder") >= e.NewIndex And OneRow("MyOrder") <= e.OldIndex Then
OneRow("MyOrder") += 1
End If
End If
Next
End If
Session("rstData") = rstData
Dim Myview As New DataView(rstData)
Myview.Sort = "MyOrder"
Call MyDisplay(Myview)
ReorderList1.DataSource = Myview
ReorderList1.DataBind()
End Sub
Now, again, the above works fine. Thus, as the user re-orders items, we post-back, and update that table. But, I have to actually re-save the table as sorted. (its in memory).
Ok, so now, what about actually saving back to the database?
Well, we have that save button.
The code to write back changes to the database is this:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand("SELECT ID, HotelName, MyOrder FROM tblHotelsA", conn)
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
conn.Open()
da.Update(rstData)
End Using
End Using
End Sub
The above will write back To the database any changes To rstData (In fact, As written, any inserts, delete(s), Or updates that occurred over time To the persisted table will ALL be updated back To the source (sql server) database. Note VERY close In that cute above routine - we Do Not re-pull the data With that sqlCMD Object, we ONLY send/write back the rstData table. And in in fact, only rows actually updated generate update commands back to sql server.
I'm learning ASP.net Web Form and working on a simple project but I'm trying to do a couple of things but still couldn't figure them out!
First, I have countries as Radio buttons and states then cities as dropdown list! I put United States as the default value but the problem is when I choose another country from the Radio buttons the dropdown list doesn't update (the select statement is 100% right)
is there a simple way I can do what I'm trying to do? (also I want to do the same with states/cites, get the state from the first dropdown list and update the second dropdown list)
Second, How can I make the check box required? I've seen a question about that here before it was about using a custom validator I tried it but when I click submit the pages refreshes (it's basically submitting) then the validator message shows, not like other validators where I can't even submit if there's something wrong! how can I fix this too?
And because I'm new to asp I didn't know what code to share here so if someone tells me what you need to see to be able to help me I can edit. Thanks!
Ok, so we will assume some markup. You have to set auto-postback = true for the radio button list.
so, say we have this markup:
<div class="mylabel" style="width:40%">
<label>First Name:</label> <asp:TextBox ID="txtFirst" runat="server"></asp:TextBox><br/>
<label>Middle Initial:</label> <asp:TextBox ID="txtMiddle" runat="server" Width="35px"></asp:TextBox><br/>
<label>Last Name:</label> <asp:TextBox ID="txtLast" runat="server"></asp:TextBox><br/>
</div>
<div class="mylabel" style="float:left;line-height:75px">
<label>Country:</label>
</div>
<div style="float:left">
<asp:RadioButtonList ID="RadioButtonList1" runat="server" AutoPostBack="True">
<asp:ListItem Value="USA">United States</asp:ListItem>
<asp:ListItem Value="Canada">Canada</asp:ListItem>
<asp:ListItem Value="Other">Other</asp:ListItem>
</asp:RadioButtonList>
</div>
<div style="clear:both" class="mylabel">
<br />
<label>State/Province</label>
<asp:DropDownList ID="DropDownList1" runat="server" Height="22px" Width="169px"
style="font-size:14px"
DataValueField="ID"
DataTextField="HotelName">
</asp:DropDownList>
</div>
And then this code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
RadioButtonList1.SelectedIndex = 0
LoadCombo()
End If
End Sub
Protected Sub RadioButtonList1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles RadioButtonList1.SelectedIndexChanged
LoadCombo()
End Sub
Sub LoadCombo()
Dim strSQL As String =
"SELECT ID, HotelName FROM tblHotels Where Country = #Country ORDER BY HotelName"
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, conn)
cmdSQL.Parameters.Add("#Country", SqlDbType.NVarChar).Value = RadioButtonList1.SelectedItem.Value
conn.Open()
DropDownList1.DataSource = cmdSQL.ExecuteReader
DropDownList1.DataBind()
End Using
End Using
End Sub
And now we have this:
So, on first page load (post-back = false), we set the Radio buttion list to "0" (first choice), and then we call the routine to load the combo box.
And that same routine gets called when you change the RB list based on the Country.
Please note: I do not necessarily need working code. I just don't know how to word what I am looking for to even find an answer on the web. I guess i'm just asking for a little guidance on what kind of control I would to use to accomplish my goal.
Basically when I go to a job site I will use different amounts and types of inventory. So one line item would consist of
Item Description
Quantity Used
UsedByTech
I'm collecting these via webform text box. I would like to have a button that says "Add" and then allows me to input another line item, and so on.
Then at some point a submit button on the form would gather those line items and input them in to a MSSQL databse.
I'm currently using ASP.Net framwork and webforms.
Can somone please tell me what kind of control would allow this a may be give me some hints about what to search for?
Ok, this is actually quite easy - but we let asp.net do most of the work for us.
So, we assume we have these two tables:
so, we have a table to list out the Job Sites
And then as per above, we have a table to list out the Job items for each site.
Ok, so we display the Job Sites.
Drop in a Gridview - build it using the wizards:
Now, blow out (delete) the data source from the page - don't need it.
Remove the DataSource setting for the Gridview
And lets drop in a plane jane button into that grid.
Thus, this markup - most of it was generated for me
<div style="padding:25px">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table" Width="40%">
<Columns>
<asp:BoundField DataField="JobSite" HeaderText="JobSite" />
<asp:BoundField DataField="JobDate" HeaderText="JobDate" DataFormatString="{0:yyyy-MM-dd}" />
<asp:BoundField DataField="Foreman" HeaderText="Foreman" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="cmdView" runat="server" Text="View Job" CssClass="btn"
Onclick="cmdView_Click"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
And our code to load this grid is thus this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGrid
End If
End Sub
Sub LoadGrid()
GridView1.DataSource = MyRst("SELECT * from JobSites Order by JobDate")
GridView1.DataBind()
End Sub
Ok, we now have this:
Ok, so now we need to wire up that "view" button.
So, we grab the PK row value - jump to our edit items page;
Protected Sub cmdView_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.Parent.Parent
' get PK database ID
Dim PK As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
' Now jump to our job add job items page
Session("JobID") = PK
Response.Redirect("JobAddItems.aspx")
End Sub
So, now we need to build our 2nd page.
I could use a repeater - but I used DataList - to display the ONE job.
I again used the wizards - then blow out the DataSource setting and item on that page.
We then drop in a gridview for the "many" items (the job items).
I now have this:
<br />
<div style="border:solid;border-color:black;width:20%">
<asp:DataList ID="DataList1" runat="server" DataKeyField="ID">
<ItemTemplate>
JobSite:
<asp:Label ID="JobSiteLabel" runat="server" Text='<%# Eval("JobSite") %>' Font-Size="Larger" Font-Bold="true" />
<br />
JobDate:
<asp:Label ID="JobDateLabel" runat="server" Text='<%# Eval("JobDate", "{0:yyyy-MM-dddd}") %>' />
<br />
Foreman:
<asp:Label ID="ForemanLabel" runat="server" Text='<%# Eval("Foreman") %>' />
<br />
Description:
<asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' />
<br />
<br />
</ItemTemplate>
</asp:DataList>
<br />
</div>
<br />
<div style="float:left">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table borderhide" Width="30%">
<Columns>
<asp:TemplateField HeaderText="Description">
<ItemTemplate>
<asp:TextBox id="txtDesc" runat="server" Text='<%# Eval("ItemDescription") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Qty">
<ItemTemplate>
<asp:TextBox id="txtQty" runat="server" Text='<%# Eval("Qty") %>' TextMode="Number" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Used By">
<ItemTemplate>
<asp:TextBox id="txtUsedBy" runat="server" Text='<%# Eval("UsedBy") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div style="float:right">
<asp:Button ID="cmdAdd" runat="server" Text="Add Item" CssClass="btn" style="margin-top:-20px" />
</div>
</div>
<div style="clear:both"></div>
<asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn" />
<asp:Button ID="cmdCancel" runat="server" Text="Cancel" CssClass="btn" style="margin-left:40px"/>
</div>
Ok, and the code to load this up is this:
We load up the main record - just for display, and then the GridView of child items.
Dim rstJobItems As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ViewState("JobID") = Session("JobId")
LoadData()
Else
rstJobItems = ViewState("JobItems")
End If
End Sub
Sub LoadData()
DataList1.DataSource = MyRst("SELECT * from JobSites WHERE ID = " & ViewState("JobID"))
DataList1.DataBind()
rstJobItems = MyRst("SELECT * FROM JobItems where Job_ID = " & ViewState("JobID"))
GridView1.DataSource = rstJobItems
GridView1.DataBind()
ViewState("JobItems") = rstJobItems
End Sub
Ok, so now on the first page, when we click on a row, we jump to the 2nd page, and we see/have this:
So we need to wire up the button to add a new row.
That code looks like this:
Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
' user might have done some edits
GridToTable()
' create a new row
Dim NewRow As DataRow = rstJobItems.NewRow
NewRow("Job_id") = ViewState("JobID")
NewRow("Qty") = 0
rstJobItems.Rows.Add(NewRow)
GridView1.DataSource = rstJobItems
GridView1.DataBind()
End Sub
So, say in above, I click the add row, then you will see this:
(in fact I clicked it two times).
Ok, so you are free to tab around - edit data in that grid. You can edit/change existing rows, or add a new row and simple tab around and enter data.
So, now lets wire up the Save button. That save button has to allow and deal with editing rows - and also saving edits - all in ONE shot to the database.
The code for save button is thus this:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
GridToTable()
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand("SELECT * FROM JobItems WHERE ID = 0", conn)
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
da.Update(rstJobItems)
End Using
End Using
' data add, and any edit now saved - return to our job site listing page
Response.Redirect("JobSites.aspx")
End Sub
So we send grid to table, and then in ONE database update, send the table back to the database.
Of course after we save, we return back to our first page with the grid now read to edit more.
So, the other routine used in above was sending the Grid (rows) back to the table.
That code is this:
Sub GridToTable()
For Each gRow As GridViewRow In GridView1.Rows
Dim OneRow As DataRow = rstJobItems.Rows(gRow.RowIndex)
OneRow("ItemDescription") = CType(gRow.FindControl("txtDesc"), TextBox).Text
OneRow("Qty") = CType(gRow.FindControl("txtQty"), TextBox).Text
OneRow("UsedBy") = CType(gRow.FindControl("txtUsedBy"), TextBox).Text
Next
End Sub
And of course the cancel button? Well, if you add some rows, or just edit the rows, and hit cancel? Well, we don't save - but just return to the previous page.
eg:
Protected Sub cmdCancel_Click(sender As Object, e As EventArgs) Handles cmdCancel.Click
Response.Redirect("JobSites.aspx")
End Sub
And last but not least, I have a "general" helper routine that simple returns a data table - and I used it several times in above. That routine kind of gives me a FoxPro or MS-Access like ability to get a data table with great ease (in place of typing that same code over and over).
That routine was this:
Public Function MyRst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
Return rstData
End Function
So the above is quite much start to finish. It is of course quick code, and done rather fast/quick for stack overflow. But it is full of great ideas, and as noted, I let the wizards generate most of the markup. (but then remove the data source from the GV, and also the Data source item that gets created in the page.
So in summary:
I did not write most of that markup - I let the wizards generate most of it.
Note how SMALL and relative simple the code bits were. we broke up this into bite sized parts - thus each routine is not a lot of code.
We ALSO let .net built the sql update (and insert) commands for us. this eliminated BOAT LOADS of parameters, and BOATLOADS of work to wire that up. And it also MUCH better for performance, since ado.net is smart - for rows not changed - it don't send the updates to sql server.
Note also how ONE simple update method of
da.Update(rstJobItems)
Handles BOTH edits/updates, an inserts with one command. And we probably could/should drop in a delete row button - and the update would also handle that for us too!!!
So, there is a LOT of great ideas in above, but key concepts are:
Leverage as much as you can the wizards, and built in controls.
Leverage the ability of ado.net to update a table of edits in ONE shot back to the database - and as above shows, SAME single update also works for both inserts and updates at the same time.
I have a Table of Subject Marks and it is dynamic , I want to bind this data on my .aspx page and also want to save or edit these data into database from the page .
Table : SubjectMarks
Subject Marks
English 70
Math 80
Science 90
History 60
I want to save on the page like :
English
70 will be in textbox here
Math
80
Science
90
History
60
I can change the marks data from the textboxes and save the update ones into databse.
Please help me to find this stuff in asp.net. I have .aspx page and backend in .vb
Ok, there are MANY ways to do this.
And you can even use/get the wizards to wire up editing for you. However, I find the built in template editing system REALLY messy to work with.
Having come from desktop land (VB6, ms-access, foxpro), then I want to NOT have to write whacks of code, and I want mostly a drag + drop like easy experience.
So, how to edit some marks for a student that is taking some classes?
Well, I would assume in our database we have Students and StudentCourse table.
(lets keep this simple!)
So, say we have this:
Ok, so we need a combo box to select a student, and then display their courses.
So, first would be say a simple combo box (dropdown) to select a student.
Then we could display the courses, and let you edit the marks. And then an easy save button to save our edits.
Ok, so lets drop in the drop down (combo box). To select the student, and then drop in a gridview for the courses (for display and edit).
And then let’s drop in a button (for saving the edits).
In fact, in place of gridview, lets use a listview. They are VERY similar, but I like listview more since it allows use of standard controls being dropped into the grid display with LESS mess then the gridview. (but, either choice works – I just like the listview better. And it has FAR more customizing abilities. So time spent learning the lv is well worth it.
So, we have this simple markup:
<div style="margin:20px">
<asp:Label ID="Label1" runat="server" Text="Select Student: " Font-Size="Large"></asp:Label>
<asp:DropDownList ID="cboStudents" runat="server" Style="font-size:large;width:180px;margin-left:5px"
AutoPostBack="true"
DataTextField = "FullName"
DataValueField ="ID">
</asp:DropDownList>
<br />
<br />
<div style="width:25%;border:solid;border-width:thin">
<style> input {border:none}</style>
<asp:ListView ID="LstMarks" runat="server" DataKeyNames="ID" >
<ItemTemplate>
<tr style="">
<td><asp:Textbox ID="Course" runat="server" Text='<%# Eval("Course") %>' /></td>
<td><asp:Textbox ID="Mark" runat="server" Text='<%# Eval("Mark") %>' Width="30px"/></td>
<td>
<asp:CheckBox ID="DoneLabs" runat="server" Checked = '<%# Eval("DoneLabs") %>' Width="30px"/>
</td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table id="itemPlaceholderContainer" runat="server" border="0" class="table">
<tr runat="server" style="">
<th runat="server" >Course</th>
<th runat="server">Mark</th>
<th runat="server" >Completed Lab Work</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
</div>
<br />
<asp:Button ID="cmdSave" runat="server" Text="Save" class="btn"/>
<asp:Button ID="cmdAdd" runat="server" Text="Add Course" class="btn" Style="margin-left:20px" />
<asp:Button ID="cmdUndo" runat="server" Text="Undo Edits" class="btn" Style="margin-left:20px" />
</div>
Ok, so lets write code to fill up that combo box.
In Project Settings, we add the database connection string here:
(it puts in in web config for you - but this way is OH so much easy - and you get to use the connection string builder).
Ok, so now lets write code to load up the combo box
we have this:
Dim rstMarks As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
cboStudents.DataSource =
MyRst("SELECT *, FirstName + ' ' + LastName AS FullName from Students ORDER BY FirstName")
cboStudents.DataBind()
cboStudents.Items.Insert(0, "")
Else
rstMarks = ViewState("MyMarks")
End If
End Sub
So in above, we load up our combo box, and we ALSO have a table for the marks table (and we persisted the rstMarks table - since we need it for editing).
Ok, so we select a student, and fill the list view.
That code looks like this:
Protected Sub cboStudents_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboStudents.SelectedIndexChanged
LoadMarks()
End Sub
Sub LoadMarks()
Dim strSQL As String
strSQL = "SELECT * from StudentCourses WHERE Student_ID = " & cboStudents.SelectedValue &
" ORDER BY Course"
rstMarks = MyRst(strSQL)
LstMarks.DataSource = rstMarks
LstMarks.DataBind()
ViewState("MyMarks") = rstMarks
End Sub
Our output is now this:
So you can now tab around, set the course name, mark, and I tossed in for fun a check box for having completed "lab" work.
So, now we need our save button code. After the user tabs around, edits any mark, or changes a check box, we have to send the edits back to the database.
That code looks like this:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
GridToTable()
SaveToDB()
End Sub
Sub GridToTable()
' move grid edits back to table.
For Each gRow As ListViewItem In LstMarks.Items
Dim OneRow As DataRow = rstMarks.Rows(gRow.DataItemIndex)
OneRow("Course") = CType(gRow.FindControl("Course"), TextBox).Text
OneRow("Mark") = CType(gRow.FindControl("Mark"), TextBox).Text
OneRow("DoneLabs") = CType(gRow.FindControl("DoneLabs"), CheckBox).Checked
Next
End Sub
Sub SaveToDB()
Using cmdSQL As New SqlCommand("SELECT * FROM StudentCourses WHERE ID = 0",
New SqlConnection(My.Settings.TEST4))
Dim da As New SqlDataAdapter(cmdSQL)
Dim daSQL As New SqlCommandBuilder(da)
da.Update(rstMarks)
End Using
End Sub
So what the above does? It moves grid values back to the table.
THEN Sends the data table (with edits, or even new rows added) back to the database.
And that is quite much it.
I tossed in a add course button, and code for that is this:
Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
GridToTable() ' user might have made edits - send them to table
' add a new row to table
Dim OneRow As DataRow = rstMarks.NewRow
OneRow("Student_ID") = cboStudents.SelectedItem.Value
OneRow("DoneLabs") = False
OneRow("Mark") = 0
rstMarks.Rows.Add(OneRow)
LstMarks.DataSource = rstMarks ' send table to grid
LstMarks.DataBind()
End Sub
And I did create a helper routine to get data into a table, and that code was:
Function MyRst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST4))
cmdSQL.Connection.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
Return rstData
End Function
And the un-do edits button? It just re-loads the grid without saving, and thus is this:
Protected Sub cmdUndo_Click(sender As Object, e As EventArgs) Handles cmdUndo.Click
LoadMarks()
End Sub
(it just does the SAME thing as selecting a student in the combo box.
So, the above is FULL of great ideas on how to do this.
And really, it not a lot of code, and you can quite much use the above approach for just about any kind of data editing you need.
While Container.ItemIndex as a way to get current repeater's item index in data binding expression works perfectly fine <%# Container.ItemIndex %>, it does not work in pure code behind. Container is not declared or inaccessible.
How can I get repeater's current item index here:
<ItemTemplate>
<% If Container.ItemIndex = 2 Then %>
TRUE/some longer HTML here/
<% Else %>
false/some longer HTML here/
<% End If %>
</ItemTemplate>
EDIT
For cases with not much HTML code this will work, but I am looking for Code Render Block solution as per example above.
<%#: If(Container.ItemIndex = 2, "TRUE", "false") %>
Ok, just drop in a button and pick up the repeter row.
So, say we have this repeater markup:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div style="border-style:solid;color:black;width:290px;float:left;padding:10px">
<div style="padding:5px;text-align:right">
Hotel Name: <asp:TextBox ID="txtHotelName" runat="server" Text ='<%# Eval("HotelName") %>' />
<br />
First Name: <asp:TextBox ID="txtFirst" runat="server" Text ='<%# Eval("FirstName") %>' />
<br />
Last Name: <asp:TextBox ID="txtLast" runat="server" Text ='<%# Eval("LastName") %>' />
<br />
City: <asp:TextBox ID="City" runat="server" Text ='<%# Eval("City") %>' />
<br />
Province: <asp:TextBox ID="Province" runat="server" Text ='<%# Eval("Province") %>'/>
<br />
Active: <asp:CheckBox ID="chkActive" runat="server" Checked = '<%# Eval("Active") %>'/>
<br />
<asp:Button ID="cmdRowC" runat="server" Text="Row Click" OnClick="cmdRowC_Click"/>
</div>
</div>
<div style="clear:both;height:5px"></div>
</ItemTemplate>
</asp:Repeater>
and our code behind to fill this repeater:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LoadGrid()
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()
Repeater1.DataSource = cmdSQL.ExecuteReader
Repeater1.DataBind()
End Using
End Sub
And we now have this:
So now the button code. Note how I just dropped in a standard button.
But, we now can't double click on the button to automatic wire up and create a event.
BUT YOU CAN do this to create a event:
In the button code markup, type in OnClick=
WHEN YOU HIT "=", then intel-sense will pop up a dialog for you to create a button click event like this:
So, now click on create new event. It "seems" like nothing occured, but if we flip to code behind you find the click event stub.
So, now in our click event code we can easy pick up the current repeater row, the values and yes even the index.
The code works like this:
Protected Sub cmdRowC_Click(sender As Object, e As EventArgs)
Dim cBtn As Button = sender
Dim rRow As RepeaterItem = cBtn.Parent
Debug.Print("Row clicked = " & rRow.ItemIndex)
Debug.Print("First Name = " & DirectCast(rRow.FindControl("txtFirst"), TextBox).Text)
Debug.Print("Last Name = " & DirectCast(rRow.FindControl("txtLast"), TextBox).Text)
Debug.Print("Hotel Name = " & DirectCast(rRow.FindControl("txtHotelName"), TextBox).Text)
End Sub
output:
Row clicked = 1
First Name = Darcy
Last Name = Caroll
Hotel Name = Athabasca Hotel
So, just drop in a plane jane button. When you click on it the event stub runs, and as you can see, we pick up the "Repeater row item".
From that row, we can get the row index, and of course pluck out any other control value from that row using find control.
eg:
Dim txtHotel as TextBox = rRow.FindControl("txtHotel")
debug.print ("Hotel name = " & txtHotel.Text)
So, once you have the repeater row, you don't need some container.ItemIndex expression, since the row item lets you get the given index row with:
rRow.ItemIndex
So, ItemIndex is available, and is avilable regardless if you have or include or use the conttin.ItemIndex in the markup.
There is VERY LITTLE need to mess around with that code in the markup.
Note how VERY clean and simple the above is. I suggest you MAKE HUGE efforts to avoid dumping vb code inside of the markup like you are doing. If you even want to convert to c#, or even just maintain that code? Put that code in the code behind area - not in the markup.
As above shows, for repeating data etc., you can use a Repeater, or even often I use a listview - as it allows a repeating layout to be created automatic for you, and without having to write looping code.
******************** EDIT ***************************
Ok, now that we have this working, say we wanted to put in a message in the repeater that
This hotel is Active
or
This hotel is NOT active!
Ok, so in our markup, we could drop in a label like this:
And now from the repeater property sheet, double click here to create the item data bound event:
It is now a simple matter to put in our code logic for each row like this:
Protected Sub Repeater1_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles Repeater1.ItemDataBound
Dim rRow As RepeaterItem = e.Item
Dim ckbox As CheckBox = rRow.FindControl("chkActive")
Dim MyLabel As Label = rRow.FindControl("Label1")
If ckbox.Checked Then
MyLabel.Text = "This Hotel is Active!!!"
Else
MyLabel.Text = "Not active!"
End If
End Sub
And output is now this:
So, once again, to conditional format the repeater, add color to active hotel, or whatever? Use the item bound event.
Say i wanted add to above that the HotelName is to be color blue when active, then we could do this:
Protected Sub Repeater1_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles Repeater1.ItemDataBound
Dim rRow As RepeaterItem = e.Item
Dim ckbox As CheckBox = rRow.FindControl("chkActive")
Dim MyLabel As Label = rRow.FindControl("Label1")
If ckbox.Checked Then
MyLabel.Text = "This Hotel is Active!!!"
Else
MyLabel.Text = "Not active!"
End If
Dim txtHotel As TextBox = rRow.FindControl("txtHotelName")
If ckbox.Checked Then
txtHotel.BackColor = Drawing.Color.AliceBlue
End If
End Sub
Again, nice easy clean code - no mess markup. We seperate the code logic from the markup.
Output now is this:
Again, note how we did not even write ANY looping code here!!!!
So you want to leverage the built in options for the repeater, and for a GridView, listView, and a repeater?
Use the row data bind event - it lets you format, even do math, or whatever you want for EACH row of the repeating controls. And as you can see, its plain jane easy to write code as a bonus.