Get Selected Text asp.net custom server control - asp.net

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!

Related

dynamically created controls within a usercontrol is not recognized

In the ascx.cs file I'm dynamically generating buttons. In .aspx file I add the control to the form. The control itself renders well, but when the buttons are clicked I get this error
An error has occurred because a control with id 'ctl03' could not be
located or a different control is assigned to the same ID after
postback.
DestopControl.ascx.cs
public partial class DesktopControl : PlaceHolder
{
public void Build()
{
for (int i = 0; i < 10; i++)
{
Button button = new Button()
{
Width = 50,
Height = 50,
ID = string.Format("button{0}", i),
Text = i.ToString()
};
button.Click+=new EventHandler(button_Click);
}
}
}
Default.aspx.cs
DesktopControl desktop = new DesktopControl();
desktop.Build();
MainContent.Controls.Add(desktop);
After reading the comments (little hard to read the code-part of the comments) it appears that yes, you are generating your controls inside an if(!isPostBack){}; well, looks like it's in the else part of that if statement.
You have to generate your controls every time the page posts back, as the page_load gets fired before your button click. So once the controls have been re-created the code will continue on to your button click handler, where the controls should be available to handle.
Essentially, take ReloadUI(Session["ui"]); OUT of the if(!isPostBack){}else{} statement. Put it after your if statement.
Like this:
if (!isPostBack){
// my first load code
}else{
// my postback code
}
// load all my dynamic controls here
ReloadUI(Session["ui"]);
Found a solution:
Every time there is a new UI I call this ClearScreen() which does the trick.
The error on 'ctl03' was a menu control which was generating it's own ID and somehow wasn't available on postback. I assigned an ID to it. But I guess all the issue went away with this ClearScreen() method.
private void ClearScreen()
{
try
{
List<Control> controls = new List<Control>();
foreach (Control control in MainContent.Controls)
{
controls.Add(control);
}
for (int i = 0; i < controls.Count; i++)
{
if (!(controls[i].GetType() == typeof(LiteralControl) || controls[i].GetType() == typeof(ScriptManager)))
{
MainContent.Controls.Remove(controls[i]);
}
}
}
catch (Exception ex)
{
}
}

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

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?

How can I populate a drop down box with all the possible SeriesChartType options?

I wish to populate a drop down box with each possible SeriesChartType so that my users may choose an appropriate chart type.
How can I iterate through the SeriesChartType collection (it's in the namespace System.Web.Ui.DataVisualization.Charting) and return each possible option so I can add it to the drop down box?
Thanks.
This worked for me in VB - I had to instantiate a new instance of the SeriesChartType which allowed me to use the [Enum].GetNames Method.
I was then able to add them to the drop down box as shown:
Dim z As New SeriesChartType
For Each charttype As String In [Enum].GetNames(z.GetType)
Dim itm As New ListItem
itm.Text = charttype
ddl_ChartType.Items.Add(itm)
Next
Thanks to everyone for your answers. mrK has a great C alternative to this VB code.
foreach (ChartType in Enum.GetValues(typeof(System.Web.UI.DataVisualization.Charting))
{
//Add an option the the dropdown menu
// Convert.ToString(ChartType) <- Text of Item
// Convert.ToInt32(ChartType) <- Value of Item
}
If this isn't what you're looking for, let me know.
You could bind data in the DataBind event handler:
public override void DataBind()
{
ddlChartType.DataSource =
Enum.GetValues(typeof(SeriesChartType))
.Cast<SeriesChartType>()
.Select(i => new ListItem(i.ToString(), i.ToString()));
ddlChartType.DataBind();
}
and then retrieve the selected value in the SelectedIndexChanged event handler like this:
protected void ddlChartType_SelectedIndexChanged(object sender, EventArgs e)
{
// holds the selected value
SeriesChartType selectedValue =
(SeriesChartType)Enum.Parse(typeof(SeriesChartType),
((DropDownList)sender).SelectedValue);
}
Here's a generic function:
// ---- EnumToListBox ------------------------------------
//
// Fills List controls (ListBox, DropDownList) with the text
// and value of enums
//
// Usage: EnumToListBox(typeof(MyEnum), ListBox1);
static public void EnumToListBox(Type EnumType, ListControl TheListBox)
{
Array Values = System.Enum.GetValues(EnumType);
foreach (int Value in Values)
{
string Display = Enum.GetName(EnumType, Value);
ListItem Item = new ListItem(Display, Value.ToString());
TheListBox.Items.Add(Item);
}
}

Validate AutoCompleteExtender

Hi
i need to validate a textbox value in order to accept only values that are in the completion list of the associated autocompleteextender control.
I'm using ajaxtoolkit (version 20229) on asp.net 2.0.
For now i use the code below to validate the textbox ; as you can see i had a hiddenfield that keep the selected key. The hiddenfield is set to 0 if the user enter a value without selecting it from the list.
Do you have any idea?
Thanks
/**** Javascript code
function AutoCompleteItemPopulated(source, eventArgs)
{
var assocHiddenField = document.getElementById( source.get_element().id+'_hidden');
assocHiddenField.value=0;
}
function AutoCompleteItemSelected(source, eventArgs)
{
var assocHiddenField = document.getElementById( source.get_element().id+'_hidden');
assocHiddenField.value = eventArgs.get_value();
}
/*****CODEBEHIND code used to populate the autocompletion list
[System.Web.Services.WebMethodAttribute(), System.Web.Script.Services.ScriptMethodAttribute()]
public static string[] getStrada(string prefixText, int count, string contextKey)
{
System.Collections.Generic.List<string> items = new System.Collections.Generic.List<string>();
DataSetIncidentiTableAdapters.StradarioTableAdapter adapter = new DataSetIncidentiTableAdapters.StradarioTableAdapter();
DataSetIncidenti.StradarioDataTable dtStrade = adapter.GetStrade(contextKey, prefixText);
foreach (DataSetIncidenti.StradarioRow strada in dtStrade.Rows)
{
items.Add(AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem(strada.DenominazioneCompletaVia, strada.IdStrada.ToString()));
}
return items.ToArray();
}
Yes this can be validated; you need to use a CustomValidator to do this, which you can setup both a client and server validation function, and then check the hidden field for its value.
This works great for us.
HTH.

ASP.Net Custom Control

I am developing a custom control that needs to display a dropdownlist as a composite control.
The drop down list gets populated from a Rest web service. The problem I am facing is that the dropdownlist only has DataTextField and DataValueField but I need a way of storing more values in the control i.e. I have a couple of other properties I need to access for the selected item.
What is the best way of going about this?
Here is the code I have so far:
[ValidationProperty("SelectedValue")]
public class SelectSurveyControl : Panel
{
private DropDownList ddlSurveys;
public string SelectedSurveyId
{
get
{
return SelectedValue;
}
}
public string SelectedSurveyJavascriptEmbedCode
{
get
{
return this.ddlSurveys.SelectedItem.Attributes[""];
}
}
public string SelectedValue
{
get
{
return ddlSurveys.SelectedValue;
}
set
{
if (ddlSurveys == null)
{
ddlSurveys = new DropDownList();
}
ddlSurveys.SelectedValue = value;
}
}
protected override void OnLoad(EventArgs e)
{
base.OnInit(e);
if (ddlSurveys == null)
{
ddlSurveys = new DropDownList();
}
IList<Survey> surveys = GetSurveys();
this.ddlSurveys.DataSource = surveys;
this.ddlSurveys.DataTextField = "title";
this.ddlSurveys.DataValueField = "id";
this.ddlSurveys.DataBind();
ddlSurveys.SelectedValue = this.SelectedValue;
ddlSurveys.CssClass = "umbEditorTextFieldMultiple charlimit";
ddlSurveys.Attributes.Add("SurveyId", SelectedSurveyId);
ddlSurveys.Attributes.Add("JavascriptEmbedingCode", SelectedSurveyId);
this.Controls.Add(ddlSurveys);
}
public IList<Survey> GetSurveys()
{
...
}
}
Try using a string join/split to store and retrieve the various values, then you don't have to customize your dropdown list very much.
For Example:
Text: Some Title
Value: 1|testing test|2/12/2010
This will let you store as many values as you want, so long as you choose an appropriate character to join and split on. I usually use the bar, as in my example above.
Side Note: I was looking at your selected value set handler and it needs some tweaking. You shouldn't check for a null drop down list, instead you should call EnsureChildControls() before each get and set instead. Make sure you override the CreateChildControls() method and create your controls there.
You could use a hidden field and iterate thru a copy of the returned Surveys like this:
foreach(Survey s in Surveys){
string val = s.id + ":" + s.<property1> + ":" + s.<property2>;
hiddenField.Value += val +",";
}
When you need to read from the hidden field, you use String.Split to separate the values into arrays using ',' as the separator and in each array, you split again using ':'.
In the first split Array1[0] who be the survey id and Array1[n!=0] would be the properties of the Survey with the id = Array1[0]. Array[n!=0] would then be split into Array2.
I would suggest handling empty property values with an empty string or something or else you might end up with unequal lengths especially if you specify StringSplitOptions.RemoveEmptyEntries.
Agricfowl

Resources