In one of my ASP.NET Web Applications, I am using a BulkEditGridView (a GridView which allows all rows to be edited at the same time) to implement an order form. In my grid, I have a column which calculates the total for each item (cost x quantity) and a grand total field at the bottom of the page. Currently, however, these fields are only refreshed on every post-back. I need to have these fields updated dynamically so that as users change quantities, the totals and grand total update to reflect the new values. I have attempted to use AJAX solutions to accomplish this, but the asynchronous post-backs interfere with the focus on the page. I imagine that a purely client-side solution exists, and I'm hopeful that someone in the community can share.
If your calculations can be reproduced in JavaScript the easiest method would be using jQuery to get all the items like this:
$("#myGridView input[type='text']").each(function(){
this.change(function(){
updateTotal(this.value);
});
});
Or if your calculations are way too complex to be done in JavaScript (or time restraints prevent it) then an AJAX call to a web service is the best way. Lets say we've got our webservice like this:
[WebMethod, ScriptMethod]
public int UpdateTotal(int currTotal, int changedValue){
// do stuff, then return
}
You'll need some JavaScript to invoke the webservice, you can do it either with jQuery or MS AJAX. I'll show a combo of both, just for fun:
$("#myGridView input[type='text']").each(function(){
this.change(function(){
Sys.Net.WebServiceProxy.invoke(
"/Helpers.asmx",
"UpdateTotal",
false,
{ currTotal: $get('totalField').innerHTML, changedValue: this.value },
showNewTotal
);
});
});
function showNewTotal(res){
$get('totalField').innerHTML = res;
}
Check out this link for full info on the Sys.Net.WebServiceProxy.invoke method: http://www.asp.net/AJAX/Documentation/Live/ClientReference/Sys.Net/WebServiceProxyClass/WebServiceProxyInvokeMethod.aspx
One solution is to build some javascript in you RowDataBound method to constantly update those totals when the textboxes change.
So during the RowDataBound, start building a javascript string in memory that will add up the textboxes you need added. What's nice in RowDataBound is you can get the Client Side id's of these textboxes by calling TextBox.ClientId.
Add this javascript to the page, then also add an onkeyup event to each textbox you require to call this script.
So something like (this is a row bound event from a gridview)
private string _jscript;
protected void gridview_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//Get your textbox
Textbox tb = e.Row.FindControl("tbAddUp");
//Add the event that you're going to call to this textbox's attributes
tb.Attributes.Add("onkeyup", "MyAddUpJavaScriptMethod();");
//Build the javascript for the MyAddUpJavaScriptMethod
jscript += "document.getElementById('" + tb.ClientId + '").value + ";
}
}
Then once you've built that whole script, use your Page.ClientScript class to add a method to you page which will be called by your onkeyup in your textboxes "MyAddUpJavaScriptMethod"
Hope that makes sense and helps
Related
I have some updatepanels in asp.net page. I want updatepanel 2 get refreshed when updatepanel 1 does, but UpdatePanel 2 has his UpdateMode attributed set to Conditional and ChildrenAsTrigger=False, because it has another updatePanels inside and i need to control the way it is refreshed. I was trying to make a trigger for the other updatepanel but this seems not work, maybe i am missing something
Is there a way to make this happen?
If you can't figure a valid example, imagine product category list and product list, when selected category change (linkbutton inside updatepanel) product list is refreshed which is inside another updatepanel, product list change for example when you order by price (order is inside other updatepanel) and i don't want category list gets refreshed.
thanks in advance.
You can call updatePanelProdList.Update() inside the code-behind file of your page. The place to call this method in your scenario would be the event handler for the category change LinkButton.
Thank you, that's what i am looking for! I have 2 "Update Panel" on my page, one keeping my form and other one keeping flexiGrid (non .net version,pure jQuery).
I do manual AsyncPostBack from my grid to get in edit or delete mode.
function doPostBackAsync(eventName, eventArgs) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (!Array.contains(prm._asyncPostBackControlIDs, eventName)) {
prm._asyncPostBackControlIDs.push(eventName);
}
if (!Array.contains(prm._asyncPostBackControlClientIDs, eventName)) {
prm._asyncPostBackControlClientIDs.push(eventName);
}
__doPostBack(eventName, eventArgs);
}
<a title="Edit" href="javascript:doPostBackAsync('Edit','2');">Edit</a>
and then i handle it like
string EventName = Request.Form["__EVENTTARGET"].ToString();
int EventValue = Request.Form["__EVENTARGUMENT"].ToString();
after determinate EventName and EventValue calling the method below
protected void Edit(int id)
{
//load the form here
UpdatePanel1.Update(); //do the trick right, without this line unable to show form fields and other stuff with newly loaded data
}
regards.
(sorry for my English)
I have a GridView with dynamically created image buttons that should fire command events when clicked. The event handling basically works, except for the very first time a button is clicked. Then, the postback is processed, but the event is not fired.
I have tried to debug this, and it seems to me, that the code executed before and after the first click is exactly the same as for any other clicks. (With the exception that in the first click, the event handler is not called.)
There is some peculiarity in that: The buttons which fire the event are created dynamically through databinding, i.e. databinding must be carried out twice in the page lifecycle: Once on load, in order to make the buttons exist (otherwise, events could not be handled at all), and once before rendering in order to display the new data after the events have been processed.
I have read these posts but they wouldn't match my situation:
ASP.NET LinkButton OnClick Event Is Not Working On Home Page,
LinkButton not firing on production server,
ASP.NET Click() event doesn't fire on second postback
To the details:
The GridView contains image buttons in each row. The images of the buttons are databound. The rows are generated by GridView.DataBind(). To achieve this, I have used the TemplateField with a custom ItemTemplate implementation. The ItemTemplate's InstantiateIn method creates the ImageButton and assigns it the according event handler. Further, the image's DataBinding event is assigned a handler that retrieves the appropriate image based on the respective row's data.
The GridView is placed on a UserControl. The UserControl defines the event handlers for the GridView's events. The code roughly looks as follows:
private DataTable dataTable = new DataTable();
protected SPGridView grid;
protected override void OnLoad(EventArgs e)
{
DoDataBind(); // Creates the grid. This is essential in order for postback events to work.
}
protected override void Render(HtmlTextWriter writer)
{
DoDataBind();
base.Render(writer); // Renews the grid according to the latest changes
}
void ReadButton_Command(object sender, CommandEventArgs e)
{
ImageButton button = (ImageButton)sender;
GridViewRow viewRow = (GridViewRow)button.NamingContainer;
int rowIndex = viewRow.RowIndex;
// rowIndex is used to identify the row in which the button was clicked,
// since the control.ID is equal for all rows.
// [... some code to process the event ...]
}
private void DoDataBind()
{
// [... Some code to fill the dataTable ...]
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
TemplateField templateField = new TemplateField();
templateField.HeaderText = "";
templateField.ItemTemplate = new MyItemTemplate(new CommandEventHandler(ReadButton_Command));
grid.Columns.Add(templateField);
grid.DataSource = this.dataTable.DefaultView;
grid.DataBind();
}
private class MyItemTemplate : ITemplate
{
private CommandEventHandler commandEventHandler;
public MyItemTemplate(CommandEventHandler commandEventHandler)
{
this.commandEventHandler = commandEventHandler;
}
public void InstantiateIn(Control container)
{
ImageButton imageButton = new ImageButton();
imageButton.ID = "btnRead";
imageButton.Command += commandEventHandler;
imageButton.DataBinding += new EventHandler(imageButton_DataBinding);
container.Controls.Add(imageButton);
}
void imageButton_DataBinding(object sender, EventArgs e)
{
// Code to get image URL
}
}
Just to repeat: At each lifecycle, first the OnLoad is executed, which generates the Grid with the ImageButtons. Then, the events are processed. Since the buttons are there, the events usually work. Afterwards, Render is called, which generates the Grid from scratch based upon the new data. This always works, except for the very first time the user clicks on an image button, although I have asserted that the grid and image buttons are also generated when the page is sent to the user for the first time.
Hope that someone can help me understand this or tell me a better solution for my situation.
A couple problems here. Number one, there is no IsPostBack check, which means you're databinding on every load... this is bound to cause some problems, including events not firing. Second, you are calling DoDataBind() twice on every load because you're calling it in OnLoad and Render. Why?
Bind the data ONCE... and then again in reaction to events (if needed).
Other issue... don't bind events to ImageButton in the template fields. This is generally not going to work. Use the ItemCommand event and CommandName/CommandArgument values.
Finally... one last question for you... have you done a comparison (windiff or other tool) on the HTML rendered by the entire page on the first load, and then subsequent loads? Are they EXACTLY the same? Or is there a slight difference... in a control name or PostBack reference?
Well I think the event dispatching happens after page load. In this case, its going to try to run against the controls created by your first data-binding attempt. This controls will have different IDs than when they are recreated later. I'd guess ASP.NET is trying to map the incoming events to a control, not finding a control, and then thats it.
I recommend taking captures of what is in the actual post.
ASP.NET is pretty crummy when it comes to event binding and dynamically created controls. Have fun.
Since in my opinion this is a partial answer, I re-post it this way:
If I use normal Buttons instead of ImageButtons (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", it works fine.
If I assert that DoDataBind() is always executed twice before sending the content to the client, it works fine with ImageButtons.
Still puzzled, but whatever...
I have the following requirement for creating a user profile in my application:
User should be able to enter multiple phone numbers/email addresses in his profile.
The screen looks somewhat like this:
- By default, on page load a single textbox for phone and email are shown.
- User can click a "+" button to add additional numbers/addresses.
- On clicking the "+" button we need to add another textbox just below the first one. User can add as many numbers/addresses as he wants. On submit, the server should collect all numbers/emails and save it in DB.
I tried using the Repeater control to do this. On page_load I bind the repeater to a "new arraylist" object of size 1. So, this renders fine - user sees a single textbox with no value in it.
When he clicks the "+" button, I ideally want to use javascript to create more textboxes with similar mark-up as the first.
My questions are these:
Can I render the new textboxes anyway using js? I notice that the HTML rendered by the repeater control is somewhat complex (names/ids) etc. and it might not be possible to correctly create those controls on client-side.
If there is a way to do #1, will the server understand that these additional inputs are items in the repeater control? Say, I want to get all the phone numbers that the user entered by iterating over Repeater.DataItems.
Conceptually, is my approach correct or is it wrong to use the Repeater for this? Would you suggest any other approach that might handle this requirement?
Coming from a Struts/JSP background, I am still struggling to get a grip on the .NET way of doing things - so any help would be appreciated.
The repeater control may be a bit of overkill for what you're trying to accomplish. It is mainly meant as a databound control for presenting rows of data.
What you can do is to dynamically create the boxes as part of the Page_Load event (C#):
TestInput.aspx :
<form id="form1" runat="server">
<asp:HiddenField ID="hdnAddInput" runat="server" />
<asp:Button ID="btnPlus" OnClientClick="setAdd()" Text="Plus" runat="server" />
<asp:PlaceHolder ID="phInputs" runat="server" />
</form>
<script type="text/javascript">
function setAdd() {
var add = document.getElementById('<%=hdnAddInput.ClientID%>');
add.value = '1';
return true;
}
</script>
TestInput.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["inputs"] == null)
ViewState["inputs"] = 1;
if (hdnAddInput.Value == "1")
{
ViewState["inputs"] = int.Parse(ViewState["inputs"].ToString()) + 1;
hdnAddInput.Value = "";
}
for (int loop = 0; loop < int.Parse(ViewState["inputs"].ToString()); loop++)
phInputs.Controls.Add(new TextBox() { ID = "phone" + loop });
}
I ended up using a PlaceHolder to dynamically add the text boxes and a HiddenField to flag when another TextBox needed to be added. Since the IDs were matching, it maintains the ViewState of the controls during each postback.
Welcome to the hairball that is dynamically-added controls in ASP.NET. It's not pretty but it can be done.
You cannot add new fields dynamically using javascript because the new field would have no representation in the server-side controls collection of the page.
Given that the requirements are that there is no limit to the number of addresses a user can add to the page, your only option is to do "traditional" dynamic ASP.NET controls. This means that you must handle the adding of the control server-side by new-ing a new object to represent the control:
private ArrayList _dynamicControls = new ArrayList();
public void Page_Init()
{
foreach (string c in _dynamicControls)
{
TextBox txtDynamicBox = new TextBox();
txtDynamicBox.ID = c;
Controls.Add(txtDynamicBox);
}
}
public void AddNewTextBox()
{
TextBox txtNewBox = new TextBox();
txtNewBox.ID = [uniqueID] // Give the textbox a unique name
Controls.Add(txtNewBox);
_dynamicControls.Add([uniqueID]);
}
You can see here that the object that backs each dynamically-added field has to be added back to the Controls collection of the Page on each postback. If you don't do this, data POSTed back from the field has nowhere to go.
If you want to user the repeater, I think the easiest way is to put the repeater in a ASP.Net AJAX update panel, add the extra textbox on the sever side.
There are definitely other way to implement this without using repeater, and it maybe much easier to add the textbox using js.
No, but you can create input elements similar to what TextBox controls would render.
No. ASP.NET protects itself from phony data posted to the server. You can't make the server code think that it created a TextBox earlier by just adding data that it would return.
The approach is wrong. You are trying to go a middle way that doesn't work. You have to go all the way in either direction. Either you make a postback and add the TextBox on the server side, or you do it completely on the client side and use the Request.Form collection to receive the data on the server side.
I have a gridview button that I programmatically created and I want to load an update panel on the client side with the sent data. I have a hidden value field that gets its data on the click of the gridview button and the dropdownlist in my updatepanel depends on that value.
while calling __doPostBack directly will work, it's not a perfect solution because the name of that function is strictly speaking an implementation detail of the .Net framework.
A better solution is to use ClientScriptManager.GetPostBackEventReference, which gives you a more resilient interface to the same functionality. Do note that GetPostBackEventReference and GetCallBackEventReference are not the same thing - the former causes a page reload (partial or full, depending on how your UpdatePanels are set up), while the latter doesn't.
The easiest way to do this is to call __doPostBack from client side.
On client side button1_onclick method, calls:
__doPostBack('<%=UpdatePanel1.ClientID %>','Refresh:0,1,2'); //refresh update panel
On page behind add the following event handler to capture the post back call:
protected void UpdatePanel1_Load(object sender, EventArgs e)
{
string arg = Request.Form["__EVENTARGUMENT"];
if (string.IsNullOrEmpty(arg)) return;
if (arg.StartWith("Refresh")
{
//parse data first then do your thing here...
}
}
And of course don't forget to wire event to the above method:
protected void Page_Init(object sender, EventArgs e)
{
UpdatePanel1.Load += new EventHandler(UpdatePanel1_Load);
}
we use the __dopostback() method which simulates a postback and causes the updatepanel to refresh
__doPostBack('controlName','');
Don't forget that the control name is it's HTML ID (which may well contain dollars etc) and not just it's ASP.NET ID.
As far as I know you can either call this method and pass in the hidden value field, or the div that it is in.
I have the following situation:
A user will define a certain filter on a page, and on postback I will query the database using that filter and return a bunch of matching records to the user, each with a checkbox next to it, so he can choose whether to act on each of those records.
In Classic ASP / PHP I can generate a lot of controls named "chk__*" and then on postback go through all the $POST entries looking for the ones prefixed "chk".
What is the best way to do this in ASP.Net 2.0?
I can do it easily by implementing a Repeater with a Template containing the checkbox, bind the Repeater to a Dataset, and then on the second Postback, I just do:
For Each it As RepeaterItem In repContacts.Items
Dim chkTemp As CheckBox = DirectCast(it.FindControl("cbSelect"), CheckBox)
If chkTemp.Checked Then
End If
Next
However this has the slight disadvantage of giving me a HUGE Viewstate, which is really bad because the client will need to re-upload the whole viewstate to the server, and these people will probably be using my site over a crappy connection.
Any other ideas?
(I can also create the controls dynamically and iterate through Request.Form as in the old days, however, I was looking for a cleaner
Have you looked at the CheckBoxList control? You can bind it to your data set, provide text member and value member items, and it will also allow you to easily see which items are checked. There is also the ability to dynamically add more checkbox items if needed.
Do it the same way you did it in classic ASP. Use <input type="checkbox"> instead of <asp:checkbox>. You can access the raw post paramaters using Request.Form
I recommend the classic ASP solution when faced with absurd Viewstate conditions. It is sad to lose the nice features it provides, but combining some Viewstate enabled controls (asp:*) with some classic techniques (input type="...") has saved me a lot of headaches in the past.
Sometimes you just want to do something simple, and the simple solution beats "WYSIWYG" form editing.
One of the things that I have done is to record the state of a check via AJAX in the session, then on Postback (full or partial via AJAX), look in the session for the items to perform the selected action on.
The basic idea is to add an onclick handler to the checkbox that knows the id of the associated item. In the on click handler communicate this id back to the server via AJAX and record it in the session -- you'll need to communicate checkbox status as well so you can uncheck items. Have the handler for the submit control use the data about which items were selected from the session.
This way allows you to handle paged data as well, since you can set the initial value of the checkbox from the session when rendering (either full or partial) a page with checked items on it.
It might look something like this. Assuming ASP.NET AJAX with PageMethods (and ScriptManager, of course).
<script type='text/javascript'>
function record(checkbox,item)
{
var context = { ctl : checkbox };
PageMethods.Record(item,checkbox.checked,onSuccess,onFailure,context);
}
function onSuccess(result,context)
{
// do something, maybe highlight the row, maybe nothing
}
function onFailure(error,context)
{
context.ctl.checked = false;
alert(error.get_Message());
}
</script>
...
<tr><td><input type='checkbox' onclick='record(this,"item_1");'></td><td>Item 1</td></tr>
...
Codebehind
[WebMethod(EnableSessionState=true)]
public static void Record( string itemName, bool value )
{
List<string> itemList = (List<string>)Session["Items"];
if (itemList == null)
{
itemList = new List<string>();
Session["Items"] = itemList;
}
if (itemList.Contains(itemName) && !value)
{
itemList.Remove(itemName);
}
else if (!itemList.Contains(itemName) && value)
{
itemList.Add(itemName);
}
}
protected void button_OnClick( object sender, EventArgs e )
{
List<string> itemList = (List<string>)Session["Items"];
if (itemList != null)
{
foreach (string item in itemList)
{
// do something with the selected item
}
}
}
Disable the ViewState. In case it cannot be done try using Session to store the view state