Introduction
I am working on a college project where I need to build a shopping website.
I am currently building a "Manage Shopping Cart" page with the following structure:
What is important for my question is the "products". The "products" is a ASP.Net Repeater that only uses the ItemTemplate control. The repeater is then populated with a list of products in a user's cart.
What is my problem?
The particular area I am having trouble with is the "Save" Button.
The user can change the quantity of each product they want; however, they must click the "Save" button for the information to go to the database.
The trouble is accessing the correct quantity textbox inside my Save button's onclick event.
How can I access the correct text box in all of my Save button's OnClick Events?
What have I tried?
I was initially thinking of using the WebControl.FindControl method.
Of course though, the FindControl requires an ID and I would need different IDs for each textbox.
In order to the fix this ID problem, I tried...
firstly...
Tried binding the productId to the textbox id.
<asp:TextBox ID='quantity<%#Eval("productId") %>' runat="server" TextMode="Number" min="1" Text='<%#Eval("selectedQuantity") %>' max='<%#Eval("availableQuantity") %>' />
I then found out that is not is possible; because, when setting an ID this way, you must use "simple ids" (Example: "button1"). Supposedly I could set the ID in the code-behind though.
Secondly...
I then tried using the Repeater's OnItemCreated Event to dynamically set the ID of the TextBox to "quantityX", where X is an Product ID.
What I did in is bind the productId into a WebControl attribute:
<asp:TextBox ID='quantity' productId='<%#Eval("productId") %>' runat="server" TextMode="Number" min="1" Text='<%#Eval("selectedQuantity") %>' max='<%#Eval("availableQuantity") %>' />
After this, I then using the FindControl() method to get TextBoxs with an ID of "quantity". After finding the TextBox, I would take the productId attribute and append its value to the TextBox ID.
protected void productsList_ItemCreated(object sender, RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item)
{
TextBox quantity = (TextBox)e.Item.FindControl("quantity");
quantity.ID = String.Format("quantity{0}", quantity.Attributes["productId"]);
}
}
I then found out that the productId attribute did not have a value; because, the DataBinding has not occurred. In otherwords, Eval("productId") was not running.
thirdly...
I took the same approach as the one before with 1 difference. I used the Repeater's OnItemDataBound event instead of the OnItemCreated Event.
protected void productsList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item)
{
TextBox quantity = (TextBox)e.Item.FindControl("quantity");
quantity.ID = String.Format("quantity{0}", quantity.Attributes["productId"]);
}
}
This partially worked; however, there was one problem. The product with the biggest Product ID was skipped. (E.X, if I had products 4, 32, and 68. The Product ID of 68 would be skipped)
As you can see in the image below, we have 3 products. I edited some text the picture to clearly point out the TextBoxs (as they are rendered).
The IDs present are 1, 32 and 34. If you look at the ID attribute of 1 and 32, you will see they have "quantity" with their Product ID appended.
Now look at the ID attribute of 34. The ID attribute is only "quantity".
Now I am kinda of stuck.
What am I using?
Visual Studio 2017
.NET Framework 4.6.1
2-Tier WebForms Project
Google Chrome
To get text from textbox in your save button click use below code:
// get item on where button is clicked
RepeaterItem item = (RepeaterItem)((Button) sender).NamingContainer;
// get correct textbox from the item
TextBox txtQuantity = (TextBox)item.FindControl("quantity");
string qtyText = txtQuantity.Text;
In your third approach your're only checking DataItems but you have to check it on AlternatingItems also:
if (e.ItemType == ListItemType.Item || e.ItemType == ListItemType.AlternatingItem)
Related
I have a gridview looks like below.
Name Attended_Exam
Raj English
Hindi
Das Korea
Rahul Spanish
English
And the query used to bind datatable to this gridview contains a submission_id. Which is unique for each student and his subject.
Each attended exam name is shown as a linkbutton. Now, when clicking on it, I want to get the Submission_id of each subject. What is the best way to achieve this?
<asp:GridView ID="gvSubmissionHeaders" runat="server" AutoGenerateColumns="true"
Width="80%" OnRowDataBound="gvSubmissionHeaders_RowDataBound"
Font-Bold="false" RowStyle-Height="30px" >
</asp:GridView>
protected void gvSubmissionHeaders_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{ //for adding linkbutton to Attended_Exam
//loop through the cell.
for (int j=1;j< e.Row.Cells.Count;j++)
string[] arrLinks =null;
if (!string.IsNullOrEmpty(e.Row.Cells[j].Text.ToString()) && e.Row.Cells[j].Text.ToString()!= " ")
{
arrLinks = e.Row.Cells[j].Text.Split(',');
}
if(arrLinks!=null)
{
for (int i = 0; i < arrLinks.Length; i++)
{
LinkButton btnLink = new LinkButton();
btnLink.ID = "Id" + arrLinks[i] + i;
btnLink.Text = arrLinks[i] + "<br>";
e.Row.Cells[j].Controls.Add(btnLink);
}
}
}
Ok, the detail here is that you could have simply noted that you have cell/colum in the grid, and you might add 1 or maybe 4 link buttons into that cell. So you have "N" buttons that you add, and you need/want particular information from that button.
If the button was static (a single link button), then you can add custom attributes to that button, and even additional columns data (ones not displayed in the grid) like this:
<td align="center" >
<asp:LinkButton ID="pUploadFiles" runat="server"
CommandArgument='<%# Eval("ID")%>' CommandName='cmdView'
Width="120px" align="center"
ContactNameID = '<%# Eval("ContactNameID")%>'
QuoteNum = '<%# Eval("QuoteNum")%>'
ProjectHeaderID = '<%# Eval("ID")%>'
>
</asp:LinkButton>
</td>
So now when you get the sender, or do a findcontrol, you can do this in code:
Dim btn As LinkButton ' we get required data from btn on row.
btn = lvd.FindControl("pUploadFiles")
With btn.Attributes
Session("ContactID") = .Item("ContactNameID")
Session("ContactGeneralID") = .Item("ContactGeneralID")
Session("QuoteNum") = .Item("QuoteNum")
End With
So linkbtn.Attributes.Item("my custom value") will get you any extra values (columns) that you attached to that link button. And with the above eval(), you can even pull any column from the data source as long as those column exist in the datatable/datasource that drives the listview or gridview. (the great part here is that you don't need actual columns in the gridview/listview to try and store and "hide" these values. The extra values are simply part of that given control as custom attributes.
Now you are adding the link btn in code, but you can do the same thing.
eg:
LinkButton btnLink = new LinkButton();
btnLink.ID = "Id" + arrLinks[i] + i;
btnLink.Text = arrLinks[i] + "<br>";
btnLink.Attributes.Add("Submission_id","100");
e.Row.Cells[j].Controls.Add(btnLink);
Now of course you would replace the hard coded "100" in above with the value you are pulling or want to store as a custom attribute. So you can add 1 or "many" custom attributes to that link button. When the click on that link button, then you grab/get the additional attributes that are associated with that link button by using Mybtn.Attributes.Item("Submission_id").
So be it one link button that is part of the grid, you can add those extra attributes (without even extra code), and even rows from the databind that are not in the grid.
So I can have several buttons, and when they click, then additional information such as PK row, or even several other values can be part of (or added) to that one button. And in your case this should work fine if you dynamic adding 1 or 5 buttons as you are. So, those additonal values you want can simply become additonal attributes of that button.
Edit:
Ok, the problem is that controls that require events that are created "after" the page has been rendered cannot really be wired up. You would have to move the code to a earlier event. So you are free to add controls, but they will in "most" cases be rendered TOO LATE to have events attached. Thus when you click on the link button, nothing fires.
So there are two solutions I can think of that will work.
First, set the control to have a a post back URL, and include a parameter on that post back.
eg this:
Dim lnkBtn As New LinkButton
lnkBtn.Text = "<br/>L" & I
lnkBtn.ID = "cL" & I
lnkBtn.PostBackUrl = "~/GridTest.aspx?r=" & bv.RowIndex
If you put a PostbackUrl, then when you click on the button, the page will post back. However, the grid row events such as rowindex change, or row click event etc. will NOT fire. So, if you willing to have a parameter passed back to the same page as per above, then you can pass the 1-3 (or 1-N) values you have for each control.
Of course that means you now have a parameter on the web page URL (and users will see this). You of course simply pick up the parameter value on page load with the standard
Request.QueryString["ID"] or whatever.
However, another way - which I think is better is to simple wire up a OnClickClick() event in js, and thus do this:
I = 1 to N
Dim lnkBtn As New LinkButton
lnkBtn.Text = "<br/>L" & I
lnkBtn.ID = "cL" & I
lnkBtn.OnClientClick = "mycellclick(" & I & ");return false;"
Now in above note how I am passing "I" to the js routine. You would pass your 200, 300 or whatever value you want.
then you script will look like this:
<script>
function mycellclick(e) {
__doPostBack("MySelect", e);
}
</script>
So above simply takes the value passed from the cell click (and linkbutn), and then does the postback with a dopostback. I used "MySelect", and you can give that any name you want.
Now, in the on-load event, you can simply go like this:
If Request("__EVENTTARGET") = "MySelect" Then
Dim mypassvalue As String = Request("__EVENTARGUMENT").ToString
Debug.Print("row sel for MySelect = " & mypassvalue)
End If
So, you are 100% correct - clicking on those controls does NOT fire server side event, and they are wired up too late for this to occur. so you can and often do say add some columns or controls to a gridview, but they are created and rendered TOO LATE for the events to be wired up (and thus they don't fire when clicked on).
But, you can add a postback to the lnkbutton, and you can also add a OnClickClick() event (JavaScript function call) and they will both work. I don't like parameters in the URL appearing when you click, so I think the js script call as per above works rather nice.
So while in the comments I noted (and suggested) that you have to set the CommandName="Select". This suggesting still holds true (without CommandName = select, then the rowindex will not fire. You can't use just ANY name - it MUST be select. However this ONLY works if the control is part of the grid and not added on the fly. As noted, it might be possible to move the grid event to "earlier" event (page initialize) but it going to be a challenge and will require you to re-organize the page. The most clean, and one that does not require parameters in the URL is adding that js OnClientClick() event. You can however set the controls postbackurl and along with a parameter in the URL, and that also can work well if you open to URL with parameters (I don't like them).
First you declare your table column ID on the DataKeyNames on GridView eg:
<asp:GridView DataKeyNames="cTableColumnID" ID="gvSubmissionHeaders" runat="server" ...
Then you can get this ID per Row using this line
gvSubmissionHeaders.DataKeys[CurrectRowNum]["cTableColumnID"]
I want to preface this by saying that I am a beginner to asp.net, especially when it comes to working with the FormView controls. I have searched long and hard and have spent hours debugging this issue.
I have 3 FormViews on one aspx page. Each FormView has its own EditItemTemplate and PagerTemplate with DefaultMode="Edit". I am not using a SqlDataSource, but instead databinding programatically on the PageLoad when if(!Page.IsPostBack) and also calling the databinding method when the PageIndexChanging method is called. The pager template contains a 'Back' and a 'Next' button set with CommandArgument="Prev" and CommandArgument="Next", respectively, and both set with CommandName="Page".
The paging works great on the first FormView. When I hit the back or next button, it pages (i.e. re-binds) appropriately. During the 1st FormView's paging event, I also successfully call the binding methods for the 2nd and 3rd FormView since I want them to bind data that is specific to the page selected in the 1st FormView.
But, when I page back to the first page of the 1st FormView (i.e. PageIndex = 0), and then try to page forward in the 2nd FormView, the datakey for the 1st FormView is null. In fact, the formview1.DataSource is null for the 1st FormView when I try to click a navigation button on the 2nd FormView.
Then, here's where I thought it was weird, ... if I click back a second time on the 1st FormView, THEN the formview1.DataSource is fine, and I can then navigate in the 2nd FormView.
All viewstates for the formviews and the buttons are set to true.
I have tried calling formview1.DataBind() inside formview2's paging event before any paging occurs but no success there. I have also tried setting properties: UseSubmitBehavior="False" and CausesValidation="False" on the paging buttons. Admittedly, I did this without really understanding the behavior but rather implemented after seeing it suggested in solutions for other somewhat related problems.
The templates are rather long since there are many fields in each. But the FormView tags look like this:
<asp:FormView ID="fvHeader" runat="server" DataKeyNames="ObjectID" DefaultMode="Edit" AllowPaging="True" OnModeChanging="fvHeader_ModeChanging" OnPageIndexChanging= "fvHeader_PageIndexChanging">
<EditItemTemplate> ..... </EditItemTemplate> </asp:FormView>
PagerTemplates:
<PagerSettings Mode="NextPrevious" />
<PagerTemplate>
<span class="labels">Page: <%#fvHeader.PageIndex+1%> of <%#fvHeader.PageCount %></span>
<asp:Button ID="btnBack" runat="server" CommandArgument="Prev" CommandName="Page" CssClass="btnHdr" Text="<< Back" />
<asp:Button ID="btnNext" runat="server" CommandArgument="Next" CommandName="Page" CssClass="btnHdr" Text="Next >>" /> </PagerTemplate>
Note that 'fvHeader' is what I'm calling 'formview1' for simplicity in my question.
Back/Next buttons C# code and databinding the 1st formview:
protected void fvHeader_PageIndexChanging(object sender, FormViewPageEventArgs e)
{
fvHeader.PageIndex = e.NewPageIndex;
bindFV_Initial();
//rebind fvSub1 (2nd formview) to get the 1st obs of the newly selected header record
fvSub1.ChangeMode(FormViewMode.Edit);
fvSub1.PageIndex = 0;
bindSub1_Initial();
//rebind 2nd subform
fvSub2.ChangeMode(FormViewMode.Edit);
fvSub2.PageIndex = 0;
bindSub2_Initial();
}
private void bindFV_Initial()
{
if (conn.State == ConnectionState.Open)
{
conn.Close();
}
conn.Open();
if (dtEOS == null || dtEOS.Rows.Count == 0)
{
sqlda = new SqlDataAdapter("USE dbWEF SELECT * FROM tblHeader WHERE [UserID] = '" + Session["User"] + "' AND [ProjectName] = '" + Session["Project"] + "'", conn);
sqlda.Fill(dtEOS);
}
fvHeader.DataSource = dtEOS;
fvHeader.DataBind();
conn.Close();
if (dtEOS.Rows.Count > 0)
{
fillDD_fvHeader(); //Fill dropdowns and databind ddls
}
}
}
Below is the error message I receive when I attempt to navigate the 2nd formview.
Error in: bindSub1_Initial.
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.Collections.ArrayList.get_Item(Int32 index)
at System.Collections.Specialized.OrderedDictionary.get_Item(Int32 index)
at System.Web.UI.WebControls.DataKey.get_Item(Int32 index)
at RenewableEnergyDataEntry.Forms.EagleObservationSurvey.bindSub1_Initial()
During debugging, I find that this error occurs right when the 2nd formview is being binded because the datatable is empty which is a result of it needing the datakey from formview1 to pull the correct data, which again, is null because formview1's datasource goes to null. But clicking formview's back button one more time, refills and KEEPS the datasource, thereby allowing formview2 to fill and navigate perfectly. So strange...
I suggest you simplify you code.
more than likely you are creating conflicts with the view state.
We can't see where and how your dataSource: (dtEOS) is being defined.
But I am going to guess that it's being reset conditionally on a call back.
This is a common problem in ASP.net (conflict with view sate and /or databinding).
Try using a grid instead of a formview and it will handle the paging much more gracefully without needing a postback and data rebind.
I have master page named MPHome.master contains a RadComboBox here is the code of
<telerik:RadComboBox CheckBoxes="true" EnableCheckAllItemsCheckBox="true" ID="TaskID" runat="server" Height="100" Width="150" EmptyMessage="Select a Task"
EnableLoadOnDemand="true" OnClientItemsRequesting="OnClientItemsRequesting" WebServiceSettings-UseHttpGet="true" ShowMoreResultsBox="true" EnableVirtualScrolling="true">
<WebServiceSettings Method="GetTasks" Path="~/ProjectDropDownWebService.asmx" />
</telerik:RadComboBox>
Now this combo works fine it fills values from webservice and also shows checkboxes on it.Now what I do is I inherit this master page to Default.aspx page and inside button of submit in default page I have used this code
protected void btnViewAllTask_Click(object sender, EventArgs e)
{
RadComboBox TaskID = (RadComboBox)Master.FindControl("TaskID");
var selectedtask = TaskID.CheckedItems;
}
This selectedtask returns me count of zero either i have selected all the records on it.
As far as I know. You are trying to use the CheckBoxes along with Load On Demand feature which is not a supported scenario with telerik RadComboBox with checkboxes=true.
Please find more details about this topic here.
What I'm trying to do is basicly what this photo shows.
When I select something from the treeview it passes a parameter to a linq command that selects some data from the database. For every item in the selection I want to make a Icon and a text that represents if the item is a folder or a file.
When I push the Icon or the Link i want it to do the same as i would push the treeview, pass a parameter to a linq command that selects again from the database and populates the placeholder.
The way I'm doing this now is to make at runtima a Panel that holds the ImageButton and LinkButton. Then i add the Panel to the ContentPlaceHolder.
The problem with this that it does it every time i select something new and also i cant get it to work if the push the icon or the linkbutton, only the from the treeview.
Could i use some controller and css to get this look for the Icons ?
Is there another better way ?
This is basicly the same system as the Explorer uses in Windows, Treeview shows only the folder but the window shows the folders and files. When i click a folder that folder opens up and the main window is populated with items that are inside that folder. If i click a file a editor opens up with the contents of the file.
Not sure I understand you question 100% but I think I got the gist.
I'm assuming that you want the folders first, then the files. I would create two repeaters in this area, one to hold the Folder Image and link buttons, and the other for the file image and link buttons.
Break your linq command into two queries, one to get the folders and one for files. Then just bind the repeaters to the corresponding repeaters.
Here's a bit of code to get you started:
<asp:Repeater ID="rptFolders" runat="server" OnItemCommand="rptFolders_ItemDataBound">
<ItemTemplate>
<div>
<asp:ImageButton ID="btnImage" runat="server" />
<asp:LinkButton ID="btnLink" runat="server" />
</div>
</ItemTemplate>
</asp:Repeater>
And the code behind after calling DataBind():
protected void rptFolders_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Book book = (Book)e.Item.DataItem; //Or whatever your passing
ImageButton btnImage = e.Item.FindControl("btnImage");
LinkButton btnLink = e.Item.FindControl("btnLink");
btnLink.Text = book.Name;
btnLink.Click += new EventHandler(FolderClicked);
btnImage.Click += new ImageClickEventHandler(FolderClicked);
}
}
You can obviously do whatever you want with Click Events, just added those in for good measure.
I would probably create a Folder and File Control and use those instead of the imagebutton / linkbutton combo, this way I could store more information about the Folder / File to access them later without having to do another query to get the ID or what not. But there are a million approaches to this, pick the one you think is best.
Let me know if you need more guidance w/ this solution, or if I didn't understand your question.
Happy Coding...
Sorry had to add as another Answer. Here's a quick sample of the folder user control.
Create your Control... Format however you want.
<%# Control Language="C#" AutoEventWireup="true" CodeFile="FolderButton.ascx.cs" Inherits="FolderButton" %>
<div>
<asp:ImageButton ID="btnImage" runat="server" ImageUrl="yourfolder.jpg" />
<asp:LinkButton ID="btnTitle" runat="server" />
</div>
Add Properties and Click Event to the Code Behind (don't forget to fire the click event when your image and link buttons are clicked):
public partial class FolderButton : System.Web.UI.UserControl
{
public int DatabaseId { get; set; }
public string Name { get; set;} // you can even set your linkbutton text here.
public event EventHandler Click;
}
Create your Repeater of the FolderButton Controls:
<asp:Repeater ID="rptFolders" runat="server" OnItemDataBound="rptFolders_ItemDataBound">
<ItemTemplate>
<uc1:FolderButton ID="FolderButton1" runat="server" />
</ItemTemplate>
</asp:Repeater>
Set Folder Id on DataBinding:
protected void rptFolders_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Book book = (Book)e.Item.DataItem; //Or whatever your passing
FolderButton btnFolder = e.Item.FindControls("FolderButton1");
btnFolder.Name=book.Name;
btnFolder.DatabaseId=book.Id;
btnFolder.Click += new EventHandler(FolderClicked);
}
}
Lastly you can then do whever you want on the event Click:
void FolderClicked(object sender, EventArgs e)
{
int id = ((FolderButton)sender).DatabaseId;
/// Do something with your Id
}
Let me know if anything is unclear. This is just a quick freehand sample, so forgive any typos or bad practices... code is just for demostration purposes only.
I have an ASP.NET GridView control with two asp:CommandField columns that are both using the Select command to perform different tasks. How do I distinguish which column was selected in the OnRowCommand event when both return "Select" when I check the CommandName property of the GridViewCommandEventArgs object?
Here is my source code:
ASPX page:
<asp:GridView ID="MyGridView" runat="server" AutoGenerateColumns="false" OnRowCommand="MyGridView_OnRowCommand">
<Columns>
<asp:CommandField ButtonType="Link" ShowSelectButton="true" SelectText="Click Me!" />
<asp:CommandField ButtonType="Link" ShowSelectButton="true" SelectText="No Click Me!" />
</Columns>
</asp:GridView>
Code behind:
protected void MyGridView_OnRowCommand(object sender, GridViewCommandEventArgs e)
{
string x = e.CommandName //returns "Select" for both asp:CommandField columns
}
Use a button column instead that way you can specify the specific command names and work with it from there
<asp:ButtonField ButtonType="Link" Text="Click Me" CommandName="MyCommand1" />
<asp:ButtonField ButtonType="Link" Text="No Click Me" CommandName="MyCommand2" />
Then you can take action based on e.CommandName
Use the GridViewCommandEventArgs.CommandArgument property !
Well, first do you HAVE to use SELECt as the command? You could set that to something else that makes more sense.
Secondly, you could set the CommandArgument property to different values so you know which one is being clicked.
use the command argument property.
e.commandargument
Or you can dynamically create the button in the command field and set the commandName to anything you want.
in gridview_Load
for (int i = 0; i <= GridView1.Rows.Count - 1; i++) {
linkbutton btnedit = new linkbutton();
GridView1.Rows(i).Cells(3).Controls.Add(btnedit);
//the above is where you want the button to be on the grid
btndel.CommandName = "Select2";
btndel.CommandArgument = "whatever you want";
}
Protected Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles GridView1.RowCommand
If e.CommandName = "Select1" Then
//do stuff
End Sub
The answer you seek is simple and tricky. I had that problem too in my website project, so I surfed the internet for days and not found what I and you were needed.
One day I just thought about the problem alone and made experiments for hours and finally realized that the only easy way to know the column that the button was clicked is in the RowCommand event of the GridView in the CommandName property of the GridViewCommandEventArgs. More correctly probably and comfortable is to edit columns of GridView through the design mode and replace your Command fields to Button fields.
You can give any string/text you want to each button field in its CommandName, so in the RowCommand event you can know which was clicked. Button1 or Button2, but you don't want to give these strings, because you request something that the buttons should select and give to you, so the CommandName should be the word select, but it can be SELECT too and Select and selecT and etc.
The select command can be mention in the CommandName in many forms, and still the system will recognize it. So for example if you have two Button fields in the GridView, whose first CommandName is simply select and the other is SELECT, then when any of them is clicked the RowCommand event raises and
if (e.CommandName == "select")
{
Label1.Text = "You clicked first button";
}
else
{
Label1.Text = "You clicked second button";
}
and the SelectedDataKey of your GridView will contains the data you requested. What other people offered in their answer to differ in CommandName by setting button one to select_one and setting button two to select_two will help you to know if either select_one was clicked or other, but the SelectedDataKey of your GridView will remain null or DataKey instance that doesn't contain the information you need at all.
In other words, the buttons will not select any necessary information, so it is very important to follow my answer. It is the exact, perfect and great solution to your problem!