Need help removing this fudged code... Binding user control on postback - asp.net

As per the title, I know the code I've posted below is utter poo, this is why I need your help!
I've put way too many hours into this, and it's either down to inexperience, a bug or I've screwed up somewhere.
I have a user control with a view properties that access the ViewState and two user controls within that display the properties.
Within the page_load of the user control, depending on the value of some of the properties, it will toggle the visibility of the controls within:
public partial class PatientStatus : System.Web.UI.UserControl
{
public string PatientName { get { return ViewState["PatientName"] as string; } set { ViewState["PatientName"] = value; } }
public bool ClinicianView { get { return Convert.ToBoolean(ViewState["ClinicianView"]); } set { ViewState["ClinicianView"] = value; } }
public string RangeTitle { get { return ViewState["RangeTitle"] as string; } set { ViewState["RangeTitle"] = value; } }
public int? RangeLimitNormSys { get { return ViewState["RangeLimitNormSys"] as int?; } set { ViewState["RangeLimitNormSys"] = value; } }
public int? RangeLimitNormDia { get { return ViewState["RangeLimitNormDia"] as int?; } set { ViewState["RangeLimitNormDia"] = value; } }
protected void Page_Load(object sender, EventArgs e)
{
bool ispostback = IsPostBack;
if (ispostback && ((System.Web.UI.WebControls.Repeater)(this.Parent.Parent)).DataSource != null)
{
object itm = ((RepeaterItem)this.Parent).DataItem;
if (itm is AppointmentRow)
{
AppointmentRow row = itm as AppointmentRow;
PatientName = row.Name;
RangeTitle = row.Range;
RangeLimitNormDia = row.RangeLimitNormDia;
RangeLimitNormSys = row.RangeLimitNormSys;
ispostback = false;
}
else if (itm is ReadingRow)
{
ReadingRow row = itm as ReadingRow;
PatientName = row.Name;
RangeTitle = row.Range;
RangeLimitNormDia = row.RangeLimitNormDia;
RangeLimitNormSys = row.RangeLimitNormSys;
ispostback = false;
}
else if (itm is PatientRow)
{
PatientRow row = itm as PatientRow;
PatientName = row.Name;
RangeTitle = row.Range;
RangeLimitNormDia = row.RangeLimitNormDia;
RangeLimitNormSys = row.RangeLimitNormSys;
ispostback = false;
}
}
if (!ispostback)
{
if (!string.IsNullOrWhiteSpace(RangeTitle))
{
placeHolder.Visible = true;
literalNA.Visible = false;
}
}
}
}
Previously the Page_Load event simply contained:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (!string.IsNullOrWhiteSpace(RangeTitle))
{
placeHolder.Visible = true;
literalNA.Visible = false;
}
}
}
But on postback, the RangeTitle property was always null, so even when removing the isPostBack statement, it didn't work property.
The only way to resolve it, was to really fudge it by the first block of code.
On the Page_Load of the parent page, and on !isPostBack I'm calling a method that gets and binds data to the repeater. This works fine. But on a postback, i'm calling the same method and for some reason, the user control isn't populating.
Any idea's?
Merry Christmas
Gav
Edit
In response to #jwiscarson
I have a table that is generated via an ASP:Repeater and within the ItemTemplate, I have added a User Control which contains a PlaceHolder and a Literal. Also within the User Control is a number of Properties.
Then on databind I pass across a number of values to the user control (I've tried both OnItemDataBound and inline using Eval). Then on the User Control's Page_Load event, as per the second block of code above, I first check if it's a postback, if not, I then check to see if the Property RangeTitle has a value.
If RangeTitle does not have a value, I then hide the placeholder that contains HTML that would display the RangeTitle and show a literal that displays N/A.
When loading the page for the first time, (!isPostBack) it works fine. But as soon as I create a postback, the User Controls within the repeater all revert to N/A even when their RangeTitle properties had a value.
On debugging, I set a breakpoint in the Page_Load of the User Control. When I first load the page, I can see that my properties have been populated correctly. Then on postback, Page_Load is called on the UserControl and the properties are populated correctly, then Page_Load is called again, but this time, the properties are empty.
i.e.
!isPostBack
UserControl::Page_Load < Correct data
isPostBack
UserControl::Page_Load < Correct data
myButton_Click (bind new data)
UserControl::Page_Load < No data
To make things even more confusing. The method called within myButton_Click to bind the data, is the exact same method called in the Page_Load of the Page to populate the repeater on !isPostBack
Thanks ;)

I would check to make sure that DataItem is accessible in this function. I think you need to listen to the ItemDataBound event and perform this work in that event.
Beyond that, it's difficult to suggest anything else concrete. I don't really understand why you're doing what you're doing (if you have this information in a Repeater, why does it also need to be in ViewState?). If you could explain your rationale for doing this, it might help me and anyone else who visits this question. You say that you're just trying to show/hide some specific items on the page. This is pretty complicated code without a lot of justification for that complication.
As an aside: you really, really need to break this code down and think about what you're trying to accomplish. Here are a few suggestions:
Separate the scopes inside your if and else if statements into functions that return the data you need.
Do not include lines like ((System.Web.UI.WebControls.Repeater)(this.Parent.Parent)).DataSource != null in an if statement. Perform this cast separately and store it in a variable, or write a small function that checks this.
Statements like this.Parent.Parent and other references to Parent controls are code smells, in my opinion. Even on a normal page, this would be a code smell, but what exactly is this.Parent going to reference when you include it in a UserControl?

Related

Disable/Enable save button based on the mandatory field being null or empty using Behaviors

I created a behavior 'RequiredValidationBehavior' and applied to one of the entry fields in XAML page.
It works as, if the entry field is empty, the placeholder color becomes red, thus indicating the mandatory field. This works fine.
The issue I am facing is with the button on the page, where it should to be disabled if this entry field is empty and enabled if the entry field has some value.
I want to achieve this using the behavior I created.
Thanks for the responses in advance.
You can try with below code:
public void entryTextChanged(object obj, EventArgs args)
{
if (entry.Text.Length > 0)
button.IsEnable= true;
else
button.IsEnable = false;
}
And just assign this event to Entry on "TextChanged" Event.
private void EntryMessage(object sender, EventArgs e)
{
var keyword = Message.Text;//your entry
if (string.IsNullOrEmpty(keyword))
{
OnAddBT.IsEnabled = false;//your button
}
else
{
OnAddBT.IsEnabled = true;//your button
}
}

Get Selected Text asp.net custom server control

I need to get the latest text set in the custom control by javascript. When i tried to get the selected text from server control, it is always returning the default text & not the modified text. How to retain the latest value set by the javascript in servercontrol? Below is the complete code for your reference..
ServerControl1.cs
[assembly: WebResource("ServerControl1.Scripts.JScript1.js", "text/javascript")]
namespace ServerControl1
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
public class ServerControl1 : WebControl
{
public List<string> ListItems
{
get
{
return ViewState["items"] as List<string>;
}
set
{
ViewState["items"] = value;
}
}
public string Text
{
get
{
return (FindControl("middleDiv").FindControl("anchorID") as HtmlAnchor).InnerText;
}
set
{
((FindControl("middleDiv").FindControl("anchorID") as HtmlAnchor)).InnerText = value;
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
HtmlGenericControl selectedTextContainer = new HtmlGenericControl("div");
selectedTextContainer.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextContainer.ID = "middleDiv";
HtmlAnchor selectedTextAnchor = new HtmlAnchor();
selectedTextAnchor.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextAnchor.ID = "anchorID";
selectedTextAnchor.HRef = "";
selectedTextContainer.Controls.Add(selectedTextAnchor);
HtmlGenericControl unList = new HtmlGenericControl("ul");
foreach (string item in ListItems)
{
HtmlGenericControl li = new HtmlGenericControl("li");
HtmlAnchor anchor = new HtmlAnchor();
anchor.HRef = "";
anchor.Attributes.Add("onclick", "updateData()");
anchor.InnerText = item;
li.Controls.Add(anchor);
unList.Controls.Add(li);
}
selectedTextContainer.Controls.Add(unList);
Controls.Add(selectedTextContainer);
ChildControlsCreated = true;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string resourceName = "ServerControl1.Scripts.JScript1.js";
ClientScriptManager cs = this.Page.ClientScript;
cs.RegisterClientScriptResource(typeof(ServerControl1), resourceName);
}
}
}
JScript1.js
function updateData() {
var evt = window.event || arguments.callee.caller.arguments[0];
var target = evt.target || evt.srcElement;
var anchor = document.getElementById("anchorID");
anchor.innerText = target.innerText;
return false;
}
TestPage Codebehind
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
List<string> items = GetDataSource();
ServerControl1.ListItems = items;
ServerControl1.Text = "Select ..";
}
}
protected void ClientButton_Click(object sender, EventArgs e)
{
string selectedText = ServerControl1.Text;
}
The server won't get your client changes unless you POST the changes to him. Your HtmlAnchors are being rendered in HTML as <a> controls, and these type of controls won't POST anything to the server.
You're going to need an <input> control to input the changes into the server (that's why they're called input controls after all). I suggest an <input type=hidden> to hold the value of the anchor.innerText and keeps its state.
Your Javascript function needs to be modified so it updates the anchor.innerText AND updates the hidden input value as well. This way when the page gets posted back to the server you can retrieve the updated and client-modified value from the hidden field.
First you need to define as private fields your selectedTextAnchor and the hiddenField you are going to insert. This is because you need to access them in your CreateChildControls method as well as in the getter and setter of yout Text property. Much in the way the partial designer classes define the controls you want to have available in code-behind.
ServerControl.cs
private HtmlAnchor selectedTextAnchor;
private HtmlInputHidden hiddenField;
In the CreateChildControls method you need to insert the hidden field.
You'll notice I removed the use of ClientIDMode.Static. Using that mode would make your client controls to have the same fixed IDs and Javascript might get confused when you have multiple copies of your ServerControl in a page, and thus losing the reusable purpose of a custom control.
Instead, you need to provide your Javascript function with the ClientID's of the controls it needs to modify. The key here is that you need to attach your controls to the Control's hierarchy BEFORE you try to get their ClientID's.
As soon as you do this.Controls.Add(dummyControl), you're making dummyControl to become a part of the Page and its dummyControl.ClientID will be suddenly changed to reflect the hierarchy of the page you're attaching it into.
I changed the order at which your controls are attached to the Control's collection so we can grab their ClientID's at the time we build the onclick attribute and pass the parameters so your Javascript function knows which anchor and hiddenField to affect.
ServerControl.cs
protected override void CreateChildControls()
{
base.CreateChildControls();
// Instantiate the hidden input field to include
hiddenField = new HtmlInputHidden();
hiddenField.ID = "ANCHORSTATE";
// Insert the hiddenfield into the Control's Collection hierarchy
// to ensure that hiddenField.ClientID contains all parent's NamingContainers
Controls.Add(hiddenField);
HtmlGenericControl selectedTextContainer = new HtmlGenericControl("div");
// REMOVED: selectedTextContainer.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextContainer.ID = "middleDiv";
selectedTextAnchor = new HtmlAnchor();
// REMOVED: selectedTextAnchor.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextAnchor.ID = "anchorID";
selectedTextAnchor.HRef = "";
selectedTextContainer.Controls.Add(selectedTextAnchor);
// Insert the selectedTextContainer (and its already attached selectedTextAnchor child)
// into the Control's Collection hierarchy
// to ensure that selectedTextAnchor.ClientID contains all parent's NamingContainers
Controls.Add(selectedTextContainer);
HtmlGenericControl unList = new HtmlGenericControl("ul");
foreach (string item in ListItems)
{
HtmlGenericControl li = new HtmlGenericControl("li");
HtmlAnchor anchor = new HtmlAnchor();
anchor.HRef = "";
// The updateData function is provided with parameters that will help
// to know who's triggering and to find the anchor and the hidden field.
// ClientID's are now all set and resolved at this point.
anchor.Attributes.Add("onclick", "updateData(this, '" + selectedTextAnchor.ClientID + "', '" + hiddenField.ClientID + "')");
anchor.InnerText = item;
li.Controls.Add(anchor);
unList.Controls.Add(li);
}
selectedTextContainer.Controls.Add(unList);
}
Note the use of the keyword this in the updateData function, it'll help us to grab the object that is triggering the action. Also note that both Id's are passed as strings (with single quotes)
The Javascript function would need to be modified so it updates the anchor and the hidden input field.
JScript1.js
function updateData(sender, anchorId, hidFieldId) {
// Update the anchor
var anchor = document.getElementById(anchorId);
anchor.innerText = sender.innerText;
// Update the hidden Input Field
var hidField = document.getElementById(hidFieldId);
hidField.value = sender.innerText;
return false;
}
The last thing to do is change the way you are setting and getting your Text property.
When you GET the property you need to check if it's a Postback, and if it is, then you want to check if among all the info that comes from the browser there is your HiddenInputField. You can grab all the info coming from the client right at the Request object, more specifically, in the Request.Form.
All enabled input controls on your page will be part of the Request.Form collection, and you can get their values by using Request.Form[anyInputControl.UniqueID]. Note that the key used for this object is the UniqueID, NOT ClientID.
Once you get your client-modified value from the hidden input, you assign its value to the selectedTextAnchor, otherwise it'll go back to the original "Select..." text.
When you SET the property, you just need to assign it to the selectedTextAnchor.
In both GET and SET you need to call EnsureChildControls(), which will actually call your CreateChildControls() to make sure that your selectedTextAnchor and hiddenField controls are instantiated before you try to get some of their properties. Pretty much the same way that it's done in Composite Controls.
ServerControl.cs
public string Text
{
get
{
EnsureChildControls();
if (this.Page.IsPostBack)
{
string HiddenFieldPostedValue = Context.Request.Form[hiddenField.UniqueID];
// Assign the value recovered from hidden field to the Anchor
selectedTextAnchor.InnerText = HiddenFieldPostedValue;
return HiddenFieldPostedValue;
}
else
{
return selectedTextAnchor.InnerText;
}
}
set
{
EnsureChildControls();
selectedTextAnchor.InnerText = value;
}
}
This way you can have a control that recognizes the changes made in client. Remember that server won't know any change in client unless you notice him.
Another approach would be to notice the server everytime you click a link through an ajax request, but this would require a whole new different code.
Good luck!

show popupcontrol in rowupdating event of devexpress gridview, if a certain criteria fails

I have a Devexpress Gridview where item and it's price is displayed.
Editing is enabled.
I use rowupdating event so inorder to check if the price updated is higher than a normal value.
if so, i cancel edit by
e.Cancel = true;
ASPxGridView1.CancelEdit();
the next thing i want is to popup a aspx popupcontrol requesting a password inorder to proceed with higher amount within the rowupdating event.
the popcontrol will contain a password textbox and a button.The remaining procces will be carried out by button click function
eventhough i called popcontrol
ASPxPopupControl2.ShowOnPageLoad = true;
the pop doesn't show up......why is this so..
here is my over all code..
protected void ASPxGridView1_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{
string msg;
double new_amt = double.Parse(e.NewValues["Amount"].ToString());//-->gets new amount
string type= e.OldValues["Type"].ToString();//-->gets the item
double refer_amt=Misc_functions.Get_Item_Amount(type,out msg);//--this function fetches the normal amount for a particular item
if (new_amt > refer_amt)
{
e.Cancel = true;
ASPxGridView1.CancelEdit();
ASPxPopupControl2.ShowOnPageLoad = true;
}
}
Basically i need a password authentication if an amount edited is a higher than a normal value.
any ideas??
This cannot be done using the server code. The best solution is to create a custom java script variable within the RowUpdating event handler and check its value in the ASPxGridView's client side EndCallback event handler. I.e.
protected void ASPxGridView1_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{ ...
gridView.JSProperties["cpShowPopup"] = true;
...
}
EndCallback = function(s,e) {
if(typeof(s.cpShowPopup) != 'undefined') {
popup.Show();
}
}
Hope, this helps.

How can I dynamically clear all controls in a user control?

Is it possible to dynamically (and generically) clear the state of all of a user control's child controls? (e.g., all of its TextBoxes, DropDrownLists, RadioButtons, DataGrids, Repeaters, etc -- basically anything that has ViewState)
I'm trying to avoid doing something like this:
foreach (Control c in myUserControl.Controls)
{
if (c is TextBox)
{
TextBox tb = (TextBox)c;
tb.Text = "";
}
else if (c is DropDownList)
{
DropDownList ddl = (DropDownList)c;
ddl.SelectedIndex = -1;
}
else if (c is DataGrid)
{
DataGrid dg = (DataGrid)c;
dg.Controls.Clear();
}
// etc.
}
I'm looking for something like this:
foreach (Control c in myUserControl.Controls)
c.Clear();
...but obviously that doesn't exist. Is there any easy way to accomplish this dynamically/generically?
I was going to suggest a solution similar to Task's except (as sixlettervariables points out) we need to implement it as 1 extension method and essentailly switch on the precise type of the control passed in (i.e. copy your logic that you posted in your question).
public static class ControlExtensions
{
public static void Clear( this Control c )
{
if(c == null) {
throw new ArgumentNullException("c");
}
if (c is TextBox)
{
TextBox tb = (TextBox)c;
tb.Text = "";
}
else if (c is DropDownList)
{
DropDownList ddl = (DropDownList)c;
ddl.SelectedIndex = -1;
}
else if (c is DataGrid)
{
DataGrid dg = (DataGrid)c;
dg.Controls.Clear();
}
// etc....
}
}
It is not particularly elegent looking method but your code in your page/control is now the more succinct
foreach (Control c in myUserControl.Controls) {
c.Clear();
}
and you can of course now call control.Clear() anywhere else in you code.
You can do
foreach (Control c in myUserControl.Controls) {
myUserControl.Controls.Remove(c);
}
Because Controls is just a list, you can call Remove() on it, passing it what you want to remove.
EDIT: Oh I'm sorry, I didn't read it correctly. I don't know of a way to do this, maybe someone here who is good with Reflection could make it where you could do like
foreach (Control c in myUserControl.Controls) {
c = new c.Type.GetConstructor().Invoke();
}
or something, to turn it into a freshly made component.
I haven't tested it, but clearing viewstate for the usercontrol may work. You could expose a custom method on the user control as well:
usercontrol:
public void Clear()
{
this.ViewState.Clear();
}
page:
myUserControlInstance.Clear();
Now again I haven't tested. It's possible this will only clear the StateBag for the UserControl container, and not its nested/child controls.. if the above doesn't work you could try using recursion to walk down the control tree to clear viewstate for all children:
usercontrol:
public void Clear()
{
ClearViewState(this.Controls);
}
private void ClearViewState(ControlCollection cc)
{
foreach(Control c in cc)
{
if(c.HasControls())
{
//clear the child controls first
ClearViewState(c.Controls);
}
//then clear the control itself
c.ViewState.Clear();
}
}
page:
myUserControlInstance.Clear();
Just an idea. I haven't tested it but I think in theory it could work. One implication would be to call Clear at the correct point in the page/controls lifecycle, otherwise it may not work.
Hope this helps!
myUserControl.Controls.ToList().ForEach(c => myUserControl.Controls.Remove(c));
However, be careful, because you modify the iterating list. This could lead to some strange behaviour.
Setting EnableViewState="false" on the individual controls might save you the work, if it doesn't cause other problems for you in this instance.
What about the Control.ClearChildViewState method?
MSDN states
Deletes the view-state information for all the server control's child controls.
I have never used this though. So I am unsure if it will help you. Sounds good though, I think :)
Why not do as you suggest:
foreach (Control c in myUserControl.Controls)
c.Clear();
And then implement Clear:
public static class UserController
{
public static void Clear( this Control c )
{
c.Controls.Clear();
}
public static void Clear( this TextBox c )
{
c.Text = String.Empty;
}
}
That should do it.

Editable GridView that stores changes in viewstate until save

I know how to have an editable GridView along with a SqlDataSource in which each edit (update/insert/delete) is immediately persisted to the database (using the SqlDataSource's UpdateCommand, Insertcommand, etc).
What I need now is to have an editable GridView that maintains all edits in viewstate until the user presses a "Save" button elsewhere on the form.
In other words:
On first load, populate the GridView from DB data
User makes various edits to the data, which are not persisted to the DB yet, but which survive through any number of postbacks.
User presses Save, and all the changes are persisted to the DB
I assume I'll need to write custom code to persist the data in step 3, but is there a straightforward, out-of-the-box approach to step 2?
You want to use a DataSet or DataTable and use the following:
myDataSet.AcceptChanges();
This commits the changes when you call that method on the DataSet, DataTable, or DataRow. Think of this almost like a SqlTransaction where you need to commit or rollback. Hope this helps!
see link: Accept Changes
I can suggest to do the following:
1) create a custom list object that stores your data. Upload DB data to that object and save it to session state.
Object:
public class InvestorClaim
{
public InvestorClaim()
{
}
private int? _record_id;
private int? _ic_record_id;
private Int64? _lh_record_id;
public int? record_id
{
get { return _record_id; }
set { _record_id = value; }
}
public int? ic_record_id
{
get { return _ic_record_id; }
set { _ic_record_id = value; }
}
public Int64? lh_record_id
{
get { return _lh_record_id; }
set { _lh_record_id = value; }
}
}
Upload data to the list :
List<InvestorClaim> inv_claim = new List<InvestorClaim>();
inv_clai= dataFromDB
Save to session :
HttpContext.Current.Session[ "InvestorClaimsObject" ] = inv_claim;
2) bind gridview to the object and manipulate data as you need.
protected void yourGridView_Bind()
{
inv_claim = HttpContext.Current.Session[ "InvestorClaimsObject" ] as List<InvestorClaim>;
yourGridView.DataSource = inv_claim;
BindData();
}
protected void yourGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
inv_claim = HttpContext.Current.Session[ "InvestorClaimsObject" ] as List<InvestorClaim>;
//Update the values.
GridViewRow row = yourGridView.Rows[e.RowIndex];
inv_claim[row.DataItemIndex].lh_record_id = ((TextBox)(row.Cells[1].Controls[0])).Text;
inv_claim[row.DataItemIndex].ic_record_id = ((TextBox)(row.Cells[2].Controls[0])).Text;
//Reset the edit index.
yourGridView.EditIndex = -1;
//Bind data to the GridView control.
yourGridView.DataSource = inv_claim;
BindData();
}
3) save data from session list object to DB when ready.

Resources