Have to click button twice in asp.net (after autopostback textbox) - asp.net

On a page I have:
<asp:TextBox runat="server" ID="EmailTextBox" AutoPostBack="true" OnTextChanged="EmailTextBox_Changed" />
<asp:Button runat="server" ID="SearchButton" OnClick="AddButton_Click" Text="add" />
In EmailTextBox_Changed, it counts up how many emails can be found, before running the search.
The problem is, when you type something in EmailTextBox, and click on the button, you have to click twice to get the actual results up. This is because the first click is doing the "AutoPostBack" part from the text box, and then you have to click again to make the actual click postback to happen.
Without removing the "AutoPostBack=true", how can I stop it needing two clicks in these circumstances?

I was looking for an answer to this issue as well. I ended up removing all autopostback=true and doing all the actions with JavaScript, same as you.
However, one of the things I experimented with before the JavaScript was something to maintain control focus after a postback. I noticed the hidden field I used to store the name of the control that had the last focus DID have the name of the search button (mine is a save button). So, while I'm still not sure how to get the 'search' function to fire 'automatically' like it should, which is basically to chain the postback events from both the textbox AND the button together one after another, I CAN know that the user clicked that save button before the postback happened (or tried to).
So, what you have on postback is your textbox event firing, and then the Page_Load method, or whatever page cycle method you want to use, where you can check to see what the last control to have focus was. With this, there are several ways you could implement a work around.
Off hand, you could add code in every event that fires from a control autopostback, like the textbox and the search button, to check the name of the focus control. If the control that had focus last is NOT the control's autopostback function we are running, we can set a page level bool called 'Run_Controls_Method' to TRUE, else, set it to false. This way we know we should run the control that had last focus postback method.
On page load, you could do something like:
if (Run_Controls_Method && hdfFocusControl.Value != "")
{
switch(hdfFocusControl.Value)
{
case "btnSearch":
btnSearch_OnClick(null, null);
break;
case etc.
}
}
The way I implement the hdfHasFocus is:
HTML:
<input id="hdfHasFocus" runat="server" type="hidden" />
HTML code behind:
protected void Page_PreRender(object sender,EventArgs e)
{
if (IsPostBack != true)
{
//Add the OnFocus event to all appropriate controls on the panel1 panel.
ControlManager.AddOnFocus(this.Controls,hdfHasFocus,true);
//other code...
}
ControlManager.SetFocus(this.Controls,hdfHasFocus.Value,true);
}
ControlManager.cs related code:
/// <summary>
/// Adds the onfocus event to the UI controls on the controls in the passed in control list.
/// </summary>
/// <param name="controls">The list of controls to apply this event.</param>
/// <param name="saveControl">The control whose .value will be set to the control.ID of the control which had focus before postback.</param>
/// <param name="Recurse">Should this method apply onfocus recursively to all child controls?</param>
public static void AddOnFocus(ControlCollection controls, Control saveControl, bool Recurse)
{
foreach (Control control in controls)
{
//To make the .Add a bit easier to see/read.
string action = "";
//Only apply this change to valid control types.
if ((control is Button) ||
(control is DropDownList) ||
(control is ListBox) ||
(control is TextBox) ||
(control is RadDateInput) ||
(control is RadDatePicker) ||
(control is RadNumericTextBox))
{
//This version ignores errors. This results in a 'worse case' scenario of having the hdfHasFocus field not getting a
// value but also avoids bothering the user with an error. So the user would call with a tweak request instead of
// and error complaint.
action = "try{document.getElementById(\"" + saveControl.ClientID + "\").value=\"" + control.ClientID + "\"} catch(e) {}";
//Now, add the 'onfocus' attribute and the built action string.
(control as WebControl).Attributes.Add("onfocus", action);
}
//The 'onfocus' event doesn't seem to work for checkbox...use below.
if (control is CheckBox)
{
//This version ignores errors. This results in a 'worse case' scenario of having the hdfHasFocus field not getting a
// value but also avoids bothering the user with an error. So the user would call with a tweak request instead of
// and error complaint.
action = "try{document.getElementById(\"" + saveControl.ClientID + "\").value=\"" + control.ClientID + "\"} catch(e) {}";
//In case there is already an attribute here for 'onclick' then we will simply try to add to it.
action = action + (control as WebControl).Attributes["onclick"];
//Now, add the event attribute and the built action string.
(control as WebControl).Attributes.Add("onclick", action);
}
//You don't seem to be able to easily work the calendar button wiht the keyboard, and it seems made for
// mouse interaction, so lets set the tab index to -1 to avoid focus with tab.
if (control is CalendarPopupButton)
{
(control as WebControl).Attributes.Add("tabindex", "-1");
}
//We also want to avoid user tab to the up and down spinner buttons on any RadNumericTextBox controls.
if (control is RadNumericTextBox)
{
(control as RadNumericTextBox).ButtonDownContainer.Attributes.Add("tabindex", "-1");
(control as RadNumericTextBox).ButtonUpContainer.Attributes.Add("tabindex", "-1");
}
//Recursively call this method if the control in question has children controls and we are told to recurse.
if ((Recurse) && (control.HasControls()))
{
AddOnFocus(control.Controls, saveControl, Recurse);
}
}
}
/// <summary>
/// Searches the ControlCollection passed in for a match on the ID name string passed in and sets focus on that control if it is found.
/// </summary>
/// <param name="controls">The collection of controls to search.</param>
/// <param name="FocusToID">The ID of the control to set focus on.</param>
/// <param name="recurse">Recursively search sub-controls in the passed in control collection?</param>
/// <returns>True means keep processing the control list. False means stop processing the control list.</returns>
public static bool SetFocus(ControlCollection controls, string FocusToID, bool recurse)
{
//Return if no control ID to work with.
if (string.IsNullOrEmpty(FocusToID) == true)
{ return false; }
//If we get here and don't have controls, return and continue the other controls if applicable.
if (controls.Count <= 0)
{ return true; }
foreach (Control control in controls)
{
//If this is the control we need AND it is Enabled, set focus on it.
if (((control is GridTableRow) != true) && //GridTableRow.ClientID throws an error. We don't set focus on a 'Row' anyway.
(control.ClientID == FocusToID) &&
((control as WebControl).Enabled))
{
control.Focus();
//return to caller. If we were recursing then we can stop now.
return false;
}
else
{
//Otherwise, see if this control has children controls to process, if we are told to recurse.
if ((recurse) && (control.HasControls()))
{
bool _continue = SetFocus(control.Controls, FocusToID, recurse);
//If the recursive call sends back false, that means stop.
if (_continue != true)
{ return _continue; }
}
}
}
//We are done processing all the controls in the list we were given...
// If we get here, then return True to the caller. If this was a recursive call, then
// the SetFocus in the call stack above will be told to continue looking since we
// didn't find the control in question in the list we were given.
return true;
}

In fact, you don't have to click on the button to make the first event happen.
Just 'leave' the textbox, i.e. with 'tabbing' out of it to make the AutoPostBack happen.
If you want to do both in a single postback just remove the Button and do the things you do in AddButton_Click also in the Textbox_Change event.

Making it a client side check was the solution to this...there doesn't seem to be a way to prevent it otherwise

Write below code in Page_Load event to prevent twice click
BtnSaveAndPrint.Attributes.Add("onclick", "return confirm('Are you sure you Want to Save & Print?');")

You could avoid this by not doing it server side and using Javascript. You also didn't post your page load event. Are you checking if it post back or not ?
Another way you could do this is the event that happens on the click of the button can be called from the TextChanged event and get rid of the button all together.

I had the same problem, I decided to move the click event code to the page load event and execute it in case of postback. And not to use a click event at all.
protected void Page_Load(object sender, System.EventArgs e)
{
if (IsPostBack)
{
// put code here
}
}
instead of :
public void ButtonClick(object sender, EventArgs e)
{
//...
}

Related

ListView without items on first pass

I'm building an ASP.NET application. I'm using a ListView to show some Entities however my listview doesn't have items on the first pass. I mean, they show up on the page, but this code only works when I refresh the page:
protected void Page_Load(object sender, EventArgs e)
{
fillFeatures();
}
private void fillFeatures()
{
using (Entities myEntities = new Entities())
{
System.Diagnostics.Debug.Write("Filling features.. \n");
foreach (ListViewItem item in ListView1.Items)
{
System.Diagnostics.Debug.Write("FOR \n");
CheckBox checkbox = (CheckBox)item.FindControl("Checkbox");
TextBox description = (TextBox)item.FindControl("descriptionTextbox");
//Try to get an existing relation
int featureId = Int32.Parse(((Label)item.FindControl("idLabel")).Text);
PlaceHasFeature phf = (from p in myEntities.PlaceHasFeature
where p.place_id == placeId && p.feature_id == featureId
select p).SingleOrDefault();
if (phf != null)
{
System.Diagnostics.Debug.Write("Checking " + phf.Feature.name + "\n");
//Relation exists
checkbox.Checked = true;
description.Text = phf.description;
}
else
{
System.Diagnostics.Debug.Write("Didn't find relation for " + featureId + "\n");
}
}
}
}
Console output:
When I open the link: Filling features...
After refresh: Filling features... FOR FOR FOR (...)
Anyone knows the cause of this?
I suspect the issue is due to the ASP.NET Page Life Cycle, where the page load event occurs before the individual controls load event:
The Page object calls the OnLoad method on the Page object, and then
recursively does the same for each child control until the page and
all controls are loaded. The Load event of individual controls occurs
after the Load event of the page.
I believe you have a couple of options. Move the fillFeatures method to the Page.LoadComplete Event:
The LoadComplete event occurs after all postback data and view-state
data is loaded into the page and after the OnLoad method has been
called for all controls on the page.
Or move the fillFeatures method to the ListBox's DataBound Event. Though I would suspect that the Page.LoadComplete Event is really the better of the two options.

How to check whether UpdatePanel is posting back?

Is there a way to determine if an <asp:UpdatePanel /> has performed an Ajax postback similar to how we can use...
if(!Page.IsPostBack) { ...snip }
... to determine if a postback from a button submit is taking place.
I'm trying to detect Ajax requests from jQuery, but it's picking up UpdatePanel requests as well which I want to exclude eg...
if (Request.IsAjaxRequest() && !Page.IsUpdatePanelPostback)
{
// Deal with jQuery Ajax
}
You can check whether the postback was asynchronous and whether it was issued by an update panel looking at these properties:
ScriptManager.GetCurrent(Page).IsInAsyncPostback
ScriptManager.GetCurrent(Page).AsyncPostbackSourceElementID
I don't know if this will work any better than your solution, but have you tried?:
if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack)
{
Control ctrl = GetControlThatCausedPostBack(Page);
if (ctrl is UpdatePanel)
{
//handle updatepanel postback
}
}
private Control GetControlThatCausedPostBack(Page page)
{
//initialize a control and set it to null
Control ctrl = null;
//get the event target name and find the control
string ctrlName = Page.Request.Params.Get("__EVENTTARGET");
if (!String.IsNullOrEmpty(ctrlName))
ctrl = page.FindControl(ctrlName);
//return the control to the calling method
return ctrl;
}
Try out following:
var controlName = Page.Request.Params.Get("__EVENTTARGET");
if (!String.IsNullOrEmpty(controlName))
{
// Use FindControl(controlName) to see whether
// control is of UpdatePanel type
}
Helpful links:
ASP.NET: Recursive FindControl & Extension Methods

How to get the latest selected value from a checkbox list?

I am currently facing a problem. How to get the latest selected value from a asp.net checkbox list?
From looping through the Items of a checkbox list, I can get the highest selected index and its value, but it is not expected that the user will select the checkbox sequentially from lower to higher index. So, how to handle that?
Is there any event capturing system that will help me to identify the exact list item which generates the event?
If I understood it right, this is the code I'd use:
protected void CheckBoxList1_SelectedIndexChanged(object sender, EventArgs e)
{
int lastSelectedIndex = 0;
string lastSelectedValue = string.Empty;
foreach (ListItem listitem in CheckBoxList1.Items)
{
if (listitem.Selected)
{
int thisIndex = CheckBoxList1.Items.IndexOf(listitem);
if (lastSelectedIndex < thisIndex)
{
lastSelectedIndex = thisIndex;
lastSelectedValue = listitem.Value;
}
}
}
}
Is there any event capturing system that will help me to identify the exact list item which generates the event?
You use the event CheckBoxList1_SelectedIndexChanged of the CheckBoxList. When a CheckBox of the list is clicked this event is called and then you can check whatever condition you want.
Edit:
The following code allows you to get the last checkbox index that the user selected. With this data you can get the last selected value by the user.
protected void CheckBoxList1_SelectedIndexChanged(object sender, EventArgs e)
{
string value = string.Empty;
string result = Request.Form["__EVENTTARGET"];
string[] checkedBox = result.Split('$'); ;
int index = int.Parse(checkedBox[checkedBox.Length - 1]);
if (CheckBoxList1.Items[index].Selected)
{
value = CheckBoxList1.Items[index].Value;
}
else
{
}
}
Below is the code which gives you the Latest selected CheckBoxList Item.
string result = Request.Form["__EVENTTARGET"];
string [] checkedBox = result.Split('$'); ;
int index = int.Parse(checkedBox[checkedBox.Length - 1]);
if (cbYears.Items[index].Selected)
{
//your logic
}
else
{
//your logic
}
Hope this helps.
Don't know about you, but as a user i wouldn't want the page to post back every time a checkbox item was checked.
This is the solution i would go with (jQuery):
Declare a server-side hidden field on your form:
<asp:HiddenField ID="HiddenField1" runat="server" EnableViewState="true" />
Then wire up client-side event handlers for the checkboxes to store checkbox clicked:
$('.someclassforyourcheckboxes').click(function() {
$('#HiddenField1').val($(this).attr('id'));
This is a lightweight mechanism for storing the ID of the "latest" checkbox clicked. And you won't have to set autopostback=true for the checkboxes and do an unecessary postback.
You dont HAVE to use jQuery - you can use regular Javascript, but, why do more work? =)
Then when you actually do the postback (on a submit button click i assume), just check the value of the hidden field.
Unless of course you WANT to postback on every checkbox click, but i can't envision a scenario in which you'd want this (maybe you're using UpdatePanel).
EDIT
The HTML of a checkbox list looks like this:
<input type="checkbox" name="vehicle" value="Bike" /> I have a bike
So, you can access three things:
Vehicle = $(this).attr('name');
Bike = $(this).attr('value');
I have a bike = $(this).html();
If you're trying to access the databound value, try the second technique.
Give that a try.

LoadViewState not fired on my user control

I have a user control nested in a repeater.
Inside my user control I have another repeater and in that I have a panel.
I am trying to override the LoadViewState event of my user control and dynamically add controls to the panel. I want to do it in the LoadViewState so that the dynamic controls get added before the viewstate gets loaded, so they retain their values after post backs.
For some reason the LoadViewState event on the user control (ascx) is not firing. Is there some way to force it to fire, or is there another method I could use? I have ruled out the user controls repeater databind event, because I need it to work even if data binding isn't happening and I can't do it on the repeaters item created event either because the child panel and inner html doesn't exist yet.
LoadViewState isn't the appropriate place for adding child controls. For dynamically adding controls within a user control, you'll want to look at the CreateChildControls method.
It's not firing a LoadViewState event because you need to save at least one value in the ViewState to have the event fire.
I think I had a similar problem with some dynamically created children user controls. LoadViewState wasn't called in postbacks even if I was able to access their ViewState when creating them first. SaveViewState seemed to be also called correctly. It ended that the children ViewState was not really usable (without this resulting in an exception) in the page Init event before they were fully initializated, and this happened only when the controls were added to the parent. After having ensured this, the children ViewState was correctly persisted across postbacks.
// Belongs to a Page. If you create the children control in the
// Load event in you can also access the page ViewState
protected void Page_Init(object sender, EventArgs e)
{
if (!IsPostBack)
{
for (int it = 0; it < 5; it++)
{
ChildControl child = LoadControl("ChildControl.ascx")
as ChildControl;
child.ParentPage = this;
TabPanel tab = tabContainer.FindControl("TabPanel" + it)
as TabPanel;
// Ensure to add the child control to its parent before
// accessing its ViewState!
tab.Controls.Add(child); // <---
string caption = "Ciao" + it;
child.Caption = caption; // Here we access the ViewState
tab.HeaderText = caption;
tab.Visible = true;
_Children.Add(child);
}
}
[...]
}
// Belongs to ChildControl
public string Caption
{
get { return ViewState["Caption"] as string; }
internal set { this.ViewState["Caption"] = value; }
}

ASP.Net CustomValidator in a CompositeControl

I present to you a little mystery... the following control is intended to fail validation every time, no matter what, but it does not:
public class Test : CompositeControl
{
protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierachy();
ClearChildViewState();
}
void CreateControlHierachy()
{
var validator = new CustomValidator
{
ErrorMessage = "Can't do that!"
};
validator.ServerValidate += (sender, e) =>
{
e.IsValid = false;
};
Controls.Add(validator);
}
}
To "fix" the issue, add the following line to CreateControlHierachyand all works as expected:
Controls.Add(new TextBox());
The control is registered in the web.config and placed on a simple page like this:
<uc:Test runat="server" />
Using the debugger on a post back event reveals the following:
The validator is in the control hierachy on the page, as expected.
The validator is not registered in Page.Validators.
Both Page.IsValid and validator.IsValid are still true.
What effect is the TextBox having on the validator and what is the correct way to fix this?
I found a possible explanation for this. The presence of the TextBox adds a child control to your control that is an IPostbackDataHandler. In order to load the post data the page must first find the control which of course it does by calling FindControl. As FindControl does its thing it eventually accesses the Controls collection of your control. Because your control is a CompositeControl this calls EnsureChildControls which call CreateChildControls.
All of this happens before Validation. Take out the TextBox and the Controls collection is no longer accessed before validation and therefore the validator is not created until after validation (most likely during prerender)
Since your validator doesn't exist at the validation stage it doesn't get called. I recommend adding a call to EnsureChildControls before validation occurs.
protected override void OnLoad(EventArgs e)
{
EnsureChildControls();
base.OnLoad(e);
}

Resources