Launching a Edit Form from a GridButtonColumn - c#

I have a class that defines a Hierarchical RadGrid that I will be using application wide. This grid has many column so this is the best implementation for me, as I will be overriding specific characteristics of the grid based om implementation.
The grid will function in a different manner based on the access level of the user. On a 'basic user level' they will have a Add New Item/Edit Item on the parent grid and Edit, Reject(delete), Approve(Update) on the Child Grid
The next level will be a 'Approver' role. They will NOT have Add New Item/Edit Item on the parent grid and will only have Reject(Edit) on the child. The edit action that the user will take in this role when rejecting an item is that they will be required to enter a comment through a user control that will be launched when the click the reject button. The problem that I am having is that the custom user control is not displaying for a DetailTableView.EditFormSettings when using a GridButtonColumn as the firing event. Any thoughts? TIA
private void SubmittedBatchesRadGrid_ItemDataBound(object sender, GridItemEventArgs e)
{
GridDataItem _dataItem = e.Item as GridDataItem;
if (_dataItem == null) return;
if (e.Item.OwnerTableView.Name == "SubmittedBatchesRadGrid_ChildGrid")
{
SetChildGridCommandColumns(sender, e);
return;
}
if (_dataItem.KeyValues == "{}") { return; }
SetMasterGridCommandColumns(sender, e, _dataItem);
}
private static void SetChildGridCommandColumns(object sender, GridItemEventArgs e)
{
const string _jqueryCode = "if(!$find('{0}').confirm('{1}', event, '{2}'))return false;";
const string _confirmText = "<p>Rejecting this adjustment will mean that you will have to also reject the batch when you are done processing these items.</p><p>Are you sure you want to reject this adjustment?</p>";
((ImageButton)(((GridEditableItem)e.Item)["PolicyEditRecord"].Controls[0])).ImageUrl = "/controls/styles/images/editpencil.png";
ImageButton _btnReject = (ImageButton)((GridDataItem)e.Item)["DeleteTransaction"].Controls[0];
_btnReject.CommandName = "Update";
_btnReject.ImageUrl = "/controls/styles/images/decline.png";
_btnReject.ToolTip = "Reject this item";
//_btnReject.Attributes["onclick"] = string.Format(_jqueryCode, ((Control)sender).ClientID, _confirmText, "Reject Adjustment");
}
private void SubmittedBatchesRadGrid_DetailTableDataBind(object sender, GridDetailTableDataBindEventArgs e)
{
e.DetailTableView.EditFormSettings.EditFormType = GridEditFormType.WebUserControl;
e.DetailTableView.EditFormSettings.UserControlName = "/Controls/RejectedAdjustmentComment.ascx";
e.DetailTableView.EditMode = GridEditMode.PopUp;
e.DetailTableView.CommandItemSettings.ShowAddNewRecordButton = false;
GridDataItem _dataItem = e.DetailTableView.ParentItem;
e.DetailTableView.DataSource = AdjustmentAPI.GetAdjustmentsByBatch(Convert.ToInt32(_dataItem.GetDataKeyValue("BatchID").ToString()), PolicyClaimManualAdjustmentCode);
}

It looks like you just need to use OnClientClick instead, and return the value of the confirm dialog.
_btnReject.OnClientClick = "return confirm(\"Are you sure you?\");"
RadAjax has a little quirk when it comes to confirm dialogs, so you may need to use this instead:
_btnReject.OnClientClick = "if (!confirm(\"Are you sure?\")) return false;"

So I thought I would share my solution in case anyone else needs it.
I was barking up the wrong tree with the edit control. Even though a comment is part of the dataset in the RadGrid I don't want to edit the existing record. I decided to create a usercontrol to handle the process. The RadWindow does not take .ascx pages directly so I started with a .aspx wrapper page and inserted the control there. Then I changed the OnClientClick event to launch the RadWindow loading the new aspx file passing the parameters I needed to the usercontrol. The usercontrol saves the comment to the database and updates the record status and then closes.
I modified this section from above:
private static void SetChildGridCommandColumns(object sender, GridItemEventArgs e)
{
((ImageButton)(((GridEditableItem)e.Item)["PolicyEditRecord"].Controls[0])).ImageUrl = "/controls/styles/images/editpencil.png";
ImageButton _btnReject = (ImageButton)((GridDataItem)e.Item)["DeleteTransaction"].Controls[0];
int _manualAdjustmentId = Convert.ToInt32(((GridDataItem)e.Item)["ManualAdjustmentId"].Text);
int _manualAdjustmentBatchId = Convert.ToInt32(((GridDataItem)e.Item)["ManualAdjustmentBatchId"].Text);
_btnReject.ImageUrl = "/controls/styles/images/decline.png";
_btnReject.ToolTip = "Reject this item";
_btnReject.OnClientClick = String.Format("OpenRadWindow('/controls/RejectedAdjustmentComment.aspx?manualAdjustmentId={0}&manualAdjustmentBatchId={1}', 'CommentDialog');return false;", _manualAdjustmentId, _manualAdjustmentBatchId);
}
private void SubmittedBatchesRadGrid_DetailTableDataBind(object sender, GridDetailTableDataBindEventArgs e)
{
//I deleted this section
e.DetailTableView.EditFormSettings.EditFormType = GridEditFormType.WebUserControl;
e.DetailTableView.EditFormSettings.UserControlName = "/Controls/RejectedAdjustmentComment.ascx";
e.DetailTableView.EditMode = GridEditMode.PopUp;
//
e.DetailTableView.CommandItemSettings.ShowAddNewRecordButton = false;
GridDataItem _dataItem = e.DetailTableView.ParentItem;
e.DetailTableView.DataSource = AdjustmentAPI.GetAdjustmentsByBatch(Convert.ToInt32(_dataItem.GetDataKeyValue("BatchID").ToString()), PolicyClaimManualAdjustmentCode);
}
I added this to the page with the datagrid:
<telerik:RadWindowManager ID="SubmittedBatchesWindow" runat="server">
<windows>
<telerik:RadWindow ID="CommentDialog" runat="server" Title="Rejected Agjustment Comment Dialog"
Height="350px" Width="440" Left="250px" ReloadOnShow="false" ShowContentDuringLoad="false"
Modal="true" VisibleStatusbar="false" />
</windows>
</telerik:RadWindowManager>
I created a new aspx file and inserted the new ascx control inside
<form id="form1" runat="server">
<telerik:RadScriptManager ID="RadScriptManager1" runat="server">
</telerik:RadScriptManager>
<uc:RejectedComment id="RejectionComment1" runat="server" />
</form>
I added my code behind for the update in the ascx file, the javascript for the front end
<script language ="javascript" type ="text/javascript" >
//<![CDATA[
function GetRadWindow() {
var oWindow = null;
if (window.radWindow) oWindow = window.radWindow; //Will work in Moz in all cases, including clasic dialog
else if (window.frameElement.radWindow) oWindow = window.frameElement.radWindow; //IE (and Moz as well)
return oWindow;
}
function CancelEdit() {
GetRadWindow().close();
}
//]]>
</script>
and last but not least closing the window after a successful update in the button click event;
Page.ClientScript.RegisterStartupScript(Page.GetType(), "", "CancelEdit();", true);
I hope someone else finds this useful. It took me several hours hunting the telerik site to find this piece by piece.

Related

Make radio button list clickable without js in browser, used asp webforms

I have function that hides or displays divs, depending on radio button list value. My problem is that, it won't work without javascript enabled in browser. How could I get it work on server side, because SelectedIndexChanged can't get postback in this case.
I tried to add some click events to buttons, also tried prerender radio button list but no luck.
HTML:
<script>
function HideOrDisplayDisvsWithRadioButton(radioButtonList) {
var rblValue = $('#RblParticipantsType input:checked').val();
var divInsertPerson = $('div#DivInsertPerson');
var divInsertCompany = $('div#DivInsertCompany');
if (rblValue === '1') {
divInsertPerson.css('display', 'inherit');
divInsertCompany.css('display', 'none');
}
if (rblValue === '2') {
divInsertPerson.css('display', 'none');
divInsertCompany.css('display', 'inherit');
}
return false;
}
<script>
<asp:RadioButtonList ID="RadioButtonList" OnSelectedIndexChanged="RadioButtonList_SelectedIndexChanged"
onchange="javscript: HideOrDisplayDisvsWithRadioButton(this)"
AutoPostBack="true"
ClientIDMode="Static"
RepeatColumns="2"
runat="server"
<asp:ListItem Value="1" Selected="True">Eraisik</asp:ListItem>
<asp:ListItem Value="2">Ettevõte</asp:ListItem>
</asp:RadioButtonList>
And code behind:
protected void RadioButtonList_SelectedIndexChanged(object sender, EventArgs e)
{
if (RadioButtonList.SelectedValue == "1")
{
DivInsertPerson.Visible = true;
DivInsertCompany.Visible = false;
}
if (RadioButtonList.SelectedValue == "2")
{
DivInsertCompany.Visible = true;
DivInsertPerson.Visible = false;
}
}
I got it work with using CSS, I do not know is it wrong or right, but it works. I did something like here. link . These hidden buttons helped me out. Some configurations for CSS, HTML and C#.

How to store/restore table with dynamic elements in view state in c# asp web aplication

I am new to concept of web app with dynamic content.
I am creating simple app that take information (steps for a test) from database and adds rows to table (every row is containing, two labels, two radio buttons and text field).
It works like this:
1. I have a page with text box and button,
2. I put test ID to retrieve test steps, then click submit button
3. Based on number of steps I add a row to table for every step, so I have table that looks like this:
[Label.text="Step1"][RadioButtonPass][RadioButtonFail][Label.Text="Comment:"][TextBox]
[Label.text="Step2"][RadioButtonPass][RadioButtonFail][Label.Text="Comment:"][TextBox]
[Label.text="Step3"][RadioButtonPass][RadioButtonFail][Label.Text="Comment:"][TextBox]
etc.
When user press every radio button he can click submitResult button and data are send to db.
Page is genereting correctly but I am having trouble with dynamic content because when I hit submitResult button table is empty again (at this point submitResult button do nothing). I read about it and I think I need to store table into View State. How can I do it?
I tried to save table to
ViewState[table.ID] = table;
at the end of PopulateTable method and then restore it in
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
if (ViewState[TableForMethods.ID] != null)
{
TableForMethods = (Table)ViewState[TableForMethods.ID];
}
}
}
but that doesn't work.
My code looks like this:
*.aspx
<body style="height: 510px">
<form id="form1" runat="server">
<p>
Put test case ID and submit
</p>
<asp:TextBox ID="TextBoxId" runat="server">1804673290</asp:TextBox>
<asp:Button ID="ButtonRetriveId" runat="server" OnClick="ButtonSubmitId_Click" Text="Submit" Width="81px" />
<p>
</p>
<p>
<asp:Label ID="LabelMethods" runat="server"></asp:Label>
</p>
<p>
<asp:Table ID="TableForMethods" runat="server">
</asp:Table>
</p>
<div style="text-align: right">
<asp:Button ID="ButtonSubmitResults" runat="server" Text="Submit result" OnClick="ButtonSubmitResults_Click" Visible="False" />
</div>
<div style="text-align: right; position: absolute; bottom: 0px">
<asp:Label ID="LabelStatus" runat="server"></asp:Label>
</div>
</form>
<script>
var trPassArray = $("tr input[id*='RadioButtonPass']").click(function () {
this.closest("tr").setAttribute("bgcolor", "yellowgreen");
console.log("zmien na green");
console.log(closest("tr"));
});
var trFailArray = $("tr input[id*='RadioButtonFail']").click(function() {
this.closest("tr").setAttribute("bgcolor", "orangered");
console.log("zmien na red");
console.log(this.closest("tr"));
});
console.log(trPassArray);
console.log(trFailArray);
</script>
</body>
*.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
}
protected void ButtonSubmitId_Click(object sender, EventArgs e)
{
this.PopulateTable(TableForMethods, value);
ButtonSubmitResults.Visible = true;
}
protected void ButtonSubmitResults_Click(object sender, EventArgs e)
{
}
private void PopulateTable(Table table, string value)
{
string[] sep = { "<br>" };
var words = value.Split(sep, StringSplitOptions.RemoveEmptyEntries);
for (int iterator = 1; iterator <= words.Count(); iterator++)
{
var tRow = new TableRow { ID = "Row" + iterator };
table.Rows.Add(tRow);
var tCell = new TableCell();
var myLabel = new Label
{
Text = words[iterator - 1],
ID = "Label " + iterator
};
var radiobuttonPass = new RadioButton
{
Text = "Pass ",
ID = "RadioButtonPass " + iterator,
GroupName = "passFailGroup" + iterator,
};
radiobuttonPass.CheckedChanged += passRadioButton_CheckedChanged;
var radiobuttonFail = new RadioButton
{
Text = "Fail ",
ID = "RadioButtonFail " + iterator,
GroupName = "passFailGroup" + iterator,
};
radiobuttonFail.CheckedChanged += failRadioButton_CheckedChanged;
var upPassFail = new UpdatePanel { UpdateMode = UpdatePanelUpdateMode.Conditional };
upPassFail.ContentTemplateContainer.Controls.Add(radiobuttonPass);
upPassFail.ContentTemplateContainer.Controls.Add(radiobuttonFail);
var passTrigger = new AsyncPostBackTrigger
{
ControlID = radiobuttonPass.ID,
EventName = "CheckedChanged"
};
upPassFail.Triggers.Add(passTrigger);
var failTrigger = new AsyncPostBackTrigger
{
ControlID = radiobuttonFail.ID,
EventName = "CheckedChanged"
};
upPassFail.Triggers.Add(failTrigger);
var labelComment = new Label
{
Text = " Comment:",
ID = "LabelComment " + iterator.ToString()
};
TextBox textBoxComment = new TextBox { ID = "TextBoxComment " + iterator.ToString() };
tCell.Controls.Add(myLabel);
tCell.Controls.Add(radiobuttonPass);
tCell.Controls.Add(radiobuttonFail);
tCell.Controls.Add(labelComment);
tCell.Controls.Add(textBoxComment);
tRow.Cells.Add(tCell);
}
}
What you're trying to do won't work. ViewState is used by the server application to store information between requests.
During the course of page load or a postback the server modifies the state of the page or its controls
When you post back, without some way to "remember" what was done, all of those changes would be lost. It would be like loading the page for the first time again.
ASP.NET solves this by writing details about the controls in a form field (ViewState.) That way every time there's postback you're sending data back to the server telling it what the state of the page and controls are, so it can recreate it.
You're making changes to the HTML on the client. At that point the page has been rendered, including ViewState. It only keeps track of the server's changes, not anything that happens on the client. So the server will never know that those changes happened.
There are a few ways to solve this (more than two, but these are the obvious ones.)
Whatever changes have to happen to modify that table, do it on the server. If the user interacts with something, do a postback to the server. The server modifies the table, and assuming that ViewState is enabled for the table, those changes are already written to ViewState and persisted.
Avoid postbacks. If there aren't server controls then after the page loads it behaves just like a normal HTML page, and you can do whatever you want on the client.
This gets a little messy: store client-side changes on the client. When you update the table on the client then you could store it in localStorage. When the page refreshes you could then have a client script that checks localStorage and restores whatever client-side stuff you stored there. Think of it as just like ViewState, but the opposite. ViewState stores server-side data between roundtrips. localStorage stores client-side data between roundtrips. The catch is that maybe on some postback you'll completely change the table (like new data, something else) and you'll have to have a way to distinguish that so that in that scenario you don't refresh it from localStorage.
Mixing WebForms with client-side code can be a little frustrating. We start learning about all of these neat client-side tools, but they don't always play well with the behavior of WebForms. Client-side code wants to change things on the page. WebForms wants to refresh the whole page.
Personally I would go with option one or two if possible vs trying to cross-breed the behavior of the two.

How to loop through the div to reset the delegate of the links in?

If i have method like this to Draw my side Menu Dynamically :
private void DrawSideMenu()
{
LinkButton x;
TaskDTO TaskList = new TaskDTO();
List<TaskDTO> List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
HtmlGenericControl myDIV = new HtmlGenericControl("div");
myDIV.ID = "menu8";
HtmlGenericControl myOrderedList = new HtmlGenericControl("ul");//css clss for <ul>
myOrderedList.ID = "orderedList";
myOrderedList.Attributes.Add("class", "task");
HtmlGenericControl listItem1;
string count = "";
foreach (TaskDTO i in List)
{
count = AdjustMenuCount1(i.TaskCode);
x = new LinkButton();
x.ID = i.TaskCode.ToString();
x.Text = i.TaskName + " " + count;
x.Click += new EventHandler(TaskC);
x.Style["FONT-FAMILY"] = "tahoma";
listItem1 = new HtmlGenericControl("li");
listItem1.Attributes.Add("class", "normal");
if (count != "0")
{
listItem1.Controls.Add(x);
myOrderedList.Controls.Add(listItem1);
}
}
myDIV.Controls.Add(myOrderedList);
MenuTD.Controls.Add(myDIV);
Session["SideMenu"] = myDIV;//Save to redraw when page postbacks
}
This Method takes long time to draw my menu.so i call it one time in (!IsPostBack) and save it in session so that i could redraw it like that :
MenuTD.Controls.Add( ((System.Web.UI.Control)(Session["SideMenu"])));
It redraws it successfully but when i click on any link it doesn't hit the event because i thought it's not possible to save the x.Click += new EventHandler(TaskC); in the session ,so i want to know how to loop through my session content to resetting the delegate of my link ?
That idea won't work because if you're not wiring up the Event Handler every time the page is loaded, it won't run.
If we come back to the original issue, you said it's slow. Creating controls at runtime cannot be slow and it's most likely the way you create your list of items:
List<TaskDTO> List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
Instead of storing complete menu, try to store in the Session only List and create all controls as usual. If menu is required on one page only, then use ViewState instead of Session.
Also it makes sense to change the entire code as currently you hardcode all style and layout settings in the code. Create all layout (div, ul, li) in aspx, move all styles in css (for example, you use "task" class but still set "tahoma" in the code). This would simplify the code and bring more flexibility.
List<TaskDTO> List = null;
void Page_Load(object sender, EventArgs e)
{
if (ViewState["List"] != null) {
List = (List<TaskDTO>)ViewState["List"];
} else {
// ArrayList isn't in view state, so we need to load it from scratch.
List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
}
// Code to create menu, e.g.
if (!Page.IsPosBack) {
Repeater1.DataSource = List;
Repeater1.DataBind();
}
}
void Page_PreRender(object sender, EventArgs e)
{
// Save PageArrayList before the page is rendered.
ViewState.Add("List", List);
}
...
<ul id="orderedList">
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<li><%# Eval("TaskName") %></li>
</ItemTemplate>
</asp:Repeater>
</ul>
Maybe save it in application level so it only gets built once, then just put the menu into an object and loop through it to re-add the clicks.
I'm afraid that in order for it to work you are going to have to rebind the Click handler on every Page_Load.
Based on your code, and assuming your TaskC is available, you can make this method:
private void RebindMenuHandlers() {
if(Session["SideMenu"] == null)
return; // Your menu has not been built yet
var menu = ((System.Web.UI.Control)(Session["SideMenu"]));
var orderedList = menu.Controls[0];
foreach(var listItem in orderedList){
foreach(var control in listItem){
var linkButton = control as LinkButton;
if(linkButton != null){
linkButton.Click += new EventHandler(TaskC);
}
}
}
}
Then call it on your Page_Load event:
void Page_Load(object sender, EventArgs e)
{
RebindMenuHandlers();
// .... etc
}
I just typed this directly here, so please forgive any silly compilation mistakes, this should be enough to give you the general idea. Hope that helps.

AJAXFileUpload with Title Textbox

Fairly straightforward. I'm just looking for users to be able to add a title to the file before uploading. (Yes, I encourage proper filenames, but that's not the point.)
<asp:TextBox runat="server" ID="txtDocumentTitle" />
<ajaxToolkit:AjaxFileUpload runat="server" ID="ajxUploadNDA" OnUploadComplete="ajxUpload_Complete" Width="400px" /><br />
protected void ajxUpload_Complete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
{
MyFile f = new MyFile();
f.DocumentType = e.ContentType;
f.FileBytes = e.GetContents();
f.FileName = e.FileName;
f.DocumentCategory = "Package Files";
f.FileUploaded = DateTime.Now;
f.DocumentTitle = txtDocumentTitle.Text;
f.Save();
DataBind();
}
However when setting a breakpoint, txtDocumentTitle.Text is always blank. I can't seem to force a full postback or find any other way to get the current value of that textbox. I can allow the user to edit those properties after the file is uploaded, but that is not the design I'd prefer for a few reasons. (It encourages leaving values at default.)
I've tried:
protected void Page_Init(object sender, EventArgs e)
{
ScriptManager.GetCurrent(Page).RegisterPostBackControl(ajxUploadNDA);
ScriptManager.GetCurrent(Page).SupportsPartialRendering = false;
ScriptManager.GetCurrent(Page).EnablePartialRendering = false;
}
and I've tried
<ajaxToolkit:AjaxFileUpload runat="server" ID="ajxUploadNDA" OnUploadComplete="ajxUpload_Complete" Width="400px" onchange="if (confirm('Upload ' + this.value + '?')) this.form.submit();" />
Any suggestions would be more than welcome.
I've sort of solved it by adding a button to "Set Document Title" which adds the value of the textbox to session. The ajxUpload_Complete function then uses this Session variable to set the title to that session value on upload.
It's sloppy for a couple reasons, but it's the best I could do.
On Page_Load:
if (!Page.IsPostBack && !ajxUploadNDA.IsInFileUploadPostBack)
{
Session.Remove("DefaultDocumentCategory");
lblDocumentCategory.Text = "Data Package Files";
Session.Remove("DefaultDocumentTitle");
lblDocumentTitle.Text = "Data Package File";
}
protected void btnChangeDocumentAttributes_Click(object sender, EventArgs e)
{
lblDocumentCategory.Text = cboDocumentCategory.SelectedValue;
lblDocumentTitle.Text = txtDocumentTitle.Text;
Session["DefaultDocumentCategory"] = lblDocumentCategory.Text;
Session["DefaultDocumentTitle"] = lblDocumentTitle.Text;
}
I also added a dummy button to the page to force a postback refreshing my gridview that shows all the files uploaded.
<asp:Button ID="btnForcePostBack" runat="server" Text="" Style="background-color: Transparent; color: inherit; border-style: none;" />
protected void ajxUpload_Complete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
{
MyFile f = new MyFile();
f.DocumentType = e.ContentType;
f.FileBytes = e.GetContents();
f.FileName = e.FileName;
f.FileUploaded = DateTime.Now;
if (Session["DefaultDocumentCategory"] == null || Session["DefaultDocumentCategory"].ToString() == string.Empty) f.DocumentCategory = "Data Package Files";
else f.DocumentCategory = Session["DefaultDocumentCategory"].ToString();
if (Session["DefaultDocumentTitle"] == null || Session["DefaultDocumentTitle"].ToString() == string.Empty) f.DocumentTitle = "Data Package File";
else f.DocumentTitle = Session["DefaultDocumentTitle"].ToString();
f.Save();
ajxUploadNDA.Page.ClientScript.RegisterStartupScript(this.GetType(), "RefreshParent", "<script type='text/javascript'>var btn = window.parent.document.getElementById('btnForcePostBack');if (btn) btn.click();</script>");
}
I couldn't get any of these other answers to work. I ended up putting the textbox on an ajax update panel. Then I created an event for the textbox OnTextboxChanged that stored the value in the session. Then I could grab the value in the UploadComplete right from the session.
When using ajax upload, you can only save right away, then second step would be to have separate call to get file from saved location and operate on it. I was having same issue with multiple asynchronous upload using Uploadify and Uploadifive. My first step when uploading multiple files is to save to temp location, then have second call to retrieve it, resize it and save it to cloud (Azure Storage). Its impossible to put a break point, since the threads are all over the place. Its a strange behavior, specially when uploading a single file, but that's the best solution to first save then retrieve using separate call.
The problem is that AjaxFleUpload control use hidden frame for submitting file content. You may use script below to pass textbox value to server:
Sys.Application.add_load(applicationLoadHandler);
function applicationLoadHandler() {
var originalCreateForm = Sys.Extended.UI.AjaxFileUpload.prototype._createVForm;
Sys.Extended.UI.AjaxFileUpload.prototype._createVForm = function () {
originalCreateForm.call(this);
var textBox = document.createElement("INPUT");
textBox.setAttribute("type", "text");
textBox.setAttribute("name", "<%= txtDocumentTitle.UniqueID %>");
textBox.setAttribute("value", document.getElementById("<%= txtDocumentTitle.ClientID %>").value);
this._vForm.appendChild(textBox);
}
}
On server you can get user input from Request.Form collection:
var title = Request.Form[txtDocumentTitle.UniqueID];

ASP.Net double-click problem

having a slight problem with an ASP.net page of mine. If a user were to double click on a "submit" button it will write to the database twice (i.e. carry out the 'onclick' method on the imagebutton twice)
How can I make it so that if a user clicks on the imagebutton, just the imagebutton is disabled?
I've tried:
<asp:ImageButton
runat="server"
ID="VerifyStepContinue"
ImageUrl=image src
ToolTip="Go"
TabIndex="98"
CausesValidation="true"
OnClick="methodName"
OnClientClick="this.disabled = true;" />
But this OnClientClick property completely stops the page from being submitted! Any help?
Sorry, yes, I do have Validation controls... hence the icky problem.
Working on this still, up to this point now:
ASP code:
<asp:TextBox ID="hidToken" runat="server" Visible="False" Enabled="False"></asp:TextBox>
...
<asp:ImageButton runat="server" ID="InputStepContinue" Name="InputStepContinue" ImageUrl="imagesrc" ToolTip="Go" TabIndex="98" CausesValidation="true" OnClick="SubmitMethod" OnClientClick="document.getElementById('InputStepContinue').style.visibility='hidden';" />
C# code:
private Random
random = new Random();
protected void Page_Load(object sender, EventArgs e)
{
//Use a Token to make sure it has only been clicked once.
if (Page.IsPostBack)
{
if (double.Parse(hidToken.Text) == ((double)Session["NextToken"]))
{
InputMethod();
}
else
{
// double click
}
}
double next = random.Next();
hidToken.Text = next + "";
Session["NextToken"] = next;
Actually... this nearly works. The double click problem is pretty much fixed (yay!) The image still isn't hidden though.
The general approach is twofold.
Serverside:
On load of the page, generate a token (using System.Random), save it in the session, and write it to a hidden form field
On submit, check that the hidden form field equals the session variable (before setting it again)
Do work
Clientside:
Similar to what you have, but probably just hide the button, and replace it with some text like 'submitting'.
The important thing to note, client side, is that the user may cancel the post by hitting 'escape', so you should consider what to do here (depending on how far along they are the token won't be used, so you'll need to bring the button back from being disabled/hidden).
Complete example follows:
C# (includes code to see it in action):
<html>
<head runat="server">
<title>double-click test</title>
<script language="c#" runat="server">
private Random
random = new Random();
private static int
TEST = 0;
public void Page_Load (object sender, EventArgs ea)
{
SetToken();
}
private void btnTest_Click (object sender, EventArgs ea)
{
if( IsTokenValid() ){
DoWork();
} else {
// double click
ltlResult.Text = "double click!";
}
}
private bool IsTokenValid ()
{
bool result = double.Parse(hidToken.Value) == ((double) Session["NextToken"]);
SetToken();
return result;
}
private void SetToken ()
{
double next = random.Next();
hidToken.Value = next + "";
Session["NextToken"] = next;
}
private void DoWork ()
{
TEST++;
ltlResult.Text = "DoWork(): " + TEST + ".";
}
</script>
</head>
<body>
<script language="javascript">
var last = null;
function f (obj)
{
obj.src = "http://www.gravatar.com/avatar/4659883ec420f39723c3df6ed99971b9?s=32&d=identicon&r=PG";
// Note: Disabling it here produced strange results. More investigation required.
last = obj;
setTimeout("reset()", 1 * 1000);
return true;
}
function reset ()
{
last.src = "http://www.gravatar.com/avatar/495ce8981a5127a9fd24bd72e7e3664a?s=32&d=identicon&r=PG";
last.disabled = "false";
}
</script>
<form id="form1" runat="server">
<asp:HiddenField runat="server" ID="hidToken" />
<asp:ImageButton runat="server" ID="btnTest"
OnClientClick="return f(this);"
ImageUrl="http://www.gravatar.com/avatar/495ce8981a5127a9fd24bd72e7e3664a?s=32&d=identicon&r=PG" OnClick="btnTest_Click" />
<pre>Result: <asp:Literal runat="server" ID="ltlResult" /></pre>
</form>
</body>
</html>
If you have validation on the page, disabling the button client side gets a little tricky. If validation fails, you don't want to disable the button. Here's a snippet that adds the client side event handler:
private void BuildClickOnceButton(WebControl ctl)
{
System.Text.StringBuilder sbValid = new System.Text.StringBuilder();
sbValid.Append("if (typeof(Page_ClientValidate) == 'function') { ");
sbValid.Append("if (Page_ClientValidate() == false) { return false; }} ");
sbValid.Append(ctl.ClientID + ".value = 'Please wait...';");
sbValid.Append(ctl.ClientID + ".disabled = true;");
// GetPostBackEventReference obtains a reference to a client-side script
// function that causes the server to post back to the page.
sbValid.Append(ClientScript.GetPostBackEventReference(ctl, ""));
sbValid.Append(";");
ctl.Attributes.Add("onclick", sbValid.ToString());
}
See this asp.net thread for more info.
Update: the above code would be used to add the OnClientClick handler in code behind. You could also write the javascript in your aspx markup like so:
<script type="text/javascript">
function disableButton(button)
{
// if there are client validators on the page
if (typeof(Page_ClientValidate) == 'function')
{
// if validation failed return false
// this will cancel the click event
if (Page_ClientValidate() == false)
{
return false;
}
}
// change the button text (does not apply to an ImageButton)
//button.value = "Please wait ...";
// disable the button
button.disabled = true;
// fire postback
__doPostBack(button.id, '');
}
</script>
<asp:ImageButton runat="server" ID="VerifyStepContinue" ImageUrl="button.png"
ToolTip="Go" TabIndex="98" CausesValidation="true" OnClick="methodName"
OnClientClick="return disableButton(this);" />
I have solved this by setting a hidden field on the client click before hitting the server.
Then in the server I check the hidden field and if the value is for example something 'FALSE' that might mean I can or cannot of the action.
Similar to Silky's client-side response, I usually make two buttons that look alike except that the second button is disabled and hidden. OnClientClick of the normal button swaps the display styles of the two buttons so that the normal button is hidden and the disabled button is shown.
The double-click feature is a server-side implementation to prevent processing that same request which can be implemented on the client side through JavaScript. The main purpose of the feature is to prevent processing the same request twice. The server-side implementation does this by identifying the repeated request; however, the ideal solution is to prevent this from occurring on the client side.
In the HTML content sent to the client that allows them to submit requests, a small validation JavaScript can be used to check whether the request has already been submitted and if so, prevent the online shopper from submitting the request again. This JavaScript validation function will check the global flag to see if the request has been submitted and, if so; does not resubmit the request. If the double-click feature is disabled on the server, it is highly recommended that the JSP and HTML pages implement this JavaScript prevention.
The following example prevents the form from being submitted more then once by using the onSubmit() action of the form object:
...
<script>
var requestSubmitted = false;
function submitRequest() {
if (!requestSubmitted ) {
requestSubmitted = true;
return true;
}
return false;
}
</script>
...
<FORM method="POST" action="Logon" onSubmit="javascript:submitRequest()">
......
</FORM>
for those who just want to do a quick fix , just hide it and show another button that has no events
<asp:Button ID="RedeemSubmitButton" runat="server" Text="Submit to Redeem" OnClick="RedeemSubmitButton_Click" OnClientClick="hideit();" />
<asp:Button ID="RedeemSubmitButtonDisabled" style="display:none;" runat="server" Text="please wait" OnClientClick="javascript:alert('please wait, processing');" />
<script>
function hideit() {
var btn = $get('<%= this.RedeemSubmitButton.ClientID %>');
var btn2 = $get('<%= this.RedeemSubmitButtonDisabled.ClientID %>');
if (btn != null)
{
btn.style.display = 'none';
btn2.style.display = 'block'
}
}
</script>

Categories