Short version:
I want to know how I can change the css style of individual items generated by an autocompleteextender, but the control only allows me to set the styling of all items as a group.
Long version:
I have a search box that utilizes an AutoCompleteExtender. As you type, search results pop up in a box under the search box.
<asp:TextBox ID="txtSearch" OnChange="javascript: Changed(this);" runat="server" style="width:360px;" />
<cc1:AutoCompleteExtender ID="Search_AutoCompleteExtender" runat="server"
BehaviorID="Search_AutoCompleteExtender"
MinimumPrefixLength="3"
DelimiterCharacters=""
Enabled="True"
ServicePath="~/Services/Search.asmx"
ServiceMethod="GetResults"
TargetControlID="txtSearch"
FirstRowSelected="true"
CompletionListItemCssClass="AutoExtenderList"
CompletionListHighlightedItemCssClass="AutoExtenderHighlight"
CompletionInterval="1"
EnableCaching="false"
CompletionSetCount="20"
CompletionListElementID="autocompleteDropDownPanel1" />
<asp:Panel ID="autocompleteDropDownPanel1" runat="server" ScrollBars="Vertical" Height="250" style="overflow-y:scroll;position:absolute;left:0;top:0" /
The AutoCompleteExtender has a property to assign a CSS class to every item, CompletionListItemCssClass and CompletionListHighlightedItemCssClass for the highlighted item. What I want to know is how I can individually style specific items based on certain criteria.
In my service, I'm running a stored procedure, manipulating the results a bit, then returning a string array back to the autocompleteextender. I have that stored procedure set up to include a certain flag with each record.. I want to use that flag to highlight certain rows of the search results (turn the text red, for example).
How can I do this? I can't seem to find any way to access individual items to change their styling.
I've been searching all over for help and am coming up empty handed. Thank you.
Such behavior isn't supported by default but you can tweak AutoCompleteExtender sources to add this feature. Actually there are two ways available: the first one is to extend object, returned to extender from web service with new property for item css class and apply this property value in extender client script. And the second available approach is to introduce new event for item creating, handle it on page and apply custom logic on item depending on it value. Let's implement both approaches here. At the first you need to download AjaxControlToolkit sources if you didn't this yet and open Client/MicrosoftAjax.Extended/AutoComplete/AutoCompleteBehavior.pre.js file. In this file you need to change the _update method as below:
_update: function (prefixText, completionItems, cacheResults) {
/// <summary>
/// Method to update the status of the autocomplete behavior
/// </summary>
/// <param name="prefixText" type="String" DomElement="false" mayBeNull="true" />
/// <param name="completionItems" type="Object" DomElement="false" mayBeNull="true" />
/// <param name="cacheResults" type="Object" DomElement="false" mayBeNull="true" />
/// <returns />
if (cacheResults && this.get_enableCaching()) {
if (!this._cache) {
this._cache = {};
}
this._cache[prefixText] = completionItems;
}
// If the target control loses focus or
// if the value in textbox has changed before the webservice returned
// completion items we don't need to show popup
if ((!this._textBoxHasFocus) || (prefixText != this._currentCompletionWord())) {
this._hideCompletionList();
return;
}
if (completionItems && completionItems.length) {
this._completionListElement.innerHTML = '';
this._selectIndex = -1;
var _firstChild = null;
var text = null;
var value = null;
var cssClass = null;
var dataItem = null;
//remove this line if you don't need to implement itemDataBinding event
var itemDataBindingHandler = this.get_events().getHandler('itemDataBinding');
for (var i = 0; i < completionItems.length; i++) {
// Create the item
var itemElement = null;
if (this._completionListElementID) {
// the completion element has been created by the user and li won't necessarily work
itemElement = document.createElement('div');
} else {
itemElement = document.createElement('li');
}
// set the first child if it is null
if (_firstChild == null) {
_firstChild = itemElement;
}
// Get the text/value for the item
try {
dataItem = Sys.Serialization.JavaScriptSerializer.deserialize( completionItems[i] );
if (dataItem && dataItem.First) {
// Use the text and value pair returned from the web service
text = dataItem.First;
value = dataItem.Second;
if (dataItem.CssClass) {
cssClass = dataItem.CssClass;
}
}
else {
// If the web service only returned a regular string, use it for
// both the text and the value
text = completionItems[i];
value = text;
}
} catch (ex) {
text = completionItems[i];
value = completionItems[i];
}
// Set the text/value for the item
// ShowOnlyCurrentWordInCompletionListItem support
var optionText = this._showOnlyCurrentWordInCompletionListItem ? text : this._getTextWithInsertedWord(text);
itemElement.appendChild(document.createTextNode(optionText));
itemElement._value = value;
itemElement.__item = '';
if (this._completionListItemCssClass) {
Sys.UI.DomElement.addCssClass(itemElement, this._completionListItemCssClass);
} else {
var itemElementStyle = itemElement.style;
itemElementStyle.padding = '0px';
itemElementStyle.textAlign = 'left';
itemElementStyle.textOverflow = 'ellipsis';
// workaround for safari since normal colors do not
// show well there.
if (Sys.Browser.agent === Sys.Browser.Safari) {
itemElementStyle.backgroundColor = 'white';
itemElementStyle.color = 'black';
} else {
itemElementStyle.backgroundColor = this._textBackground;
itemElementStyle.color = this._textColor;
}
}
if (cssClass) {
Sys.UI.DomElement.addCssClass(itemElement, cssClass);
}
//remove this if you don't need to implement itemDataBinding event
if (itemDataBindingHandler) {
itemDataBindingHandler(itemElement, dataItem || completionItems[i]);
}
this._completionListElement.appendChild(itemElement);
}
var elementBounds = $common.getBounds(this.get_element());
this._completionListElement.style.width = Math.max(1, elementBounds.width - 2) + 'px';
this._completionListElement.scrollTop = 0;
this.raisePopulated(Sys.EventArgs.Empty);
var eventArgs = new Sys.CancelEventArgs();
this.raiseShowing(eventArgs);
if (!eventArgs.get_cancel()) {
this.showPopup();
// Check if the first Row is to be selected by default and if yes highlight it and updated selectIndex.
if (this._firstRowSelected && (_firstChild != null)) {
this._highlightItem(_firstChild);
this._selectIndex = 0;
}
}
} else {
this._hideCompletionList();
}
}
If you not intended to implement scenario with new client-side event this is all changes in project you required. You already can pass item css class from web server method along with item's text and value. Only difference is to use custom class instead of the Pair that used in AjaxControlToolkit control by default:
[WebMethod]
public string[] GetCompletionList(string prefixText, int count)
{
var serializer = new JavaScriptSerializer();
var items = Enumerable.Range(1, count)
.Select(id => serializer.Serialize(
new
{
Second = id,
First = prefixText + "_" + Guid.NewGuid().ToString(),
CssClass = id % 2 == 0 ? "even" : "odd"
}));
return items.ToArray();
}
IF you want to implement also custom client event (that give more flexibility indeed) then you need to add some more code to the AutoCompleteBehavior.pre.js file. Add code below somewhere in Sys.Extended.UI.AutoCompleteBehavior.prototype object:
add_itemDataBinding: function (handler) {
this.get_events().addHandler('itemDataBinding', handler);
},
remove_itemDataBinding: function (handler) {
this.get_events().removeHandler('itemDataBinding', handler);
},
Also, open the Server/AjaxControlToolkit/AutoComplete/AutoCompleteExtender.cs file and add this property to the AutoCompleteExtender class:
/// <summary>
/// Handler to attach to the client-side item data binding event
/// </summary>
[DefaultValue("")]
[ExtenderControlEvent]
[ClientPropertyName("itemDataBinding")]
public string OnClientItemDataBinding
{
get { return GetPropertyValue("OnClientItemDataBinding", string.Empty); }
set { SetPropertyValue("OnClientItemDataBinding", value); }
}
After rebuilding project you can use custom AjaxControlToolkit assembly with additional functionality.
<script type="text/javascript">
function itemDataBinding(item, dataItem) {
var value = parseInt(dataItem.Second, 10);
if (!isNaN(value)) {
item.className = value % 2 == 0 ? "even" : "odd";
}
}
</script>
<ajaxToolkit:AutoCompleteExtender runat="server" ID="autoComplete1"
TargetControlID="myTextBox" ServicePath="AutoComplete.asmx" ServiceMethod="GetCompletionList"
OnClientItemDataBinding="itemDataBinding">
Related
I extended the ASP.NET CheckBoxList web control to create a Bootstrap 5 layout version.
The control works fine, but on postback, it loses the checked state. Also, the control's SelectedItem property is null.
I created the same control for the RadioButtonList and it works perfectly. Using DotPeek, I see that both of those inherit the same controls and interfaces, so I can't figure out why the custom RadioButtonList maintains state but the CheckboxList doesn't.
Any ideas? The internet has no useable examples to speak of.
C#
public class Bootstrap5CheckBoxList : CheckBoxList {
protected override void Render(HtmlTextWriter writer) {
try {
var selected = false;
//var webControl = new WebControl(HtmlTextWriterTag.Div);
//webControl.ID = ClientID;
//webControl.RenderBeginTag(writer);
for (int index = 0; index < Items.Count; index++) {
var item = this.Items[index];
//div
writer.Indent++;
writer.WriteBeginTag($"div class='form-check {base.CssClass}'");
writer.Write('>');
writer.WriteLine();
//input
writer.Indent++;
writer.WriteBeginTag("input");
writer.WriteAttribute("id", $"{this.ID}_{index}");
writer.WriteAttribute("type", "checkbox");
writer.WriteAttribute("name", $"{this.UniqueID}_{index}");
var cssClass = "";
if (item.Attributes["class"] != null) {
cssClass = item.Attributes["class"];
}
writer.WriteAttribute("class", $"form-check-input {cssClass}");
writer.WriteAttribute("value", item.Value);
var clientID = this.ClientID;
if (item.Selected) {
if (selected) {
this.VerifyMultiSelect();
}
selected = true;
writer.WriteAttribute("checked", "checked");
}
if (item.Attributes.Count > 0) {
foreach (string key in item.Attributes.Keys) {
if (!"class".Equals(key)) {
writer.WriteAttribute(key, item.Attributes[key]);
}
}
}
if (!item.Enabled)
writer.WriteAttribute("disabled", "disabled");
if (this.Page != null) {
this.Page.ClientScript.RegisterForEventValidation(
this.UniqueID,
item.Value);
}
writer.Write('>');
writer.WriteEndTag("input");
writer.WriteLine();
//label
writer.WriteBeginTag("label");
writer.WriteAttribute("class", "form-check-label");
writer.WriteAttribute("for", $"{this.ID}_{index}");
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag("label");
writer.Indent--;
writer.WriteLine();
//Close Div
writer.WriteEndTag("div");
writer.WriteLine();
writer.Indent--;
}
//webControl.RenderEndTag(writer);
} catch (Exception ex) {
throw new Exception(string.Format("{0}.{1}:{2} {3}", System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName, System.Reflection.MethodBase.GetCurrentMethod().Name, ex.Message, ex.StackTrace));
}
}
}
HTML
<%# Register TagPrefix="BSControls" Namespace="My.App.classes.custom_controls" Assembly="My.App" %>
<BSControls:Bootstrap5CheckBoxList ID="customCheckList" runat="server">
<asp:ListItem Value="1">Check 1</asp:ListItem>
<asp:ListItem Value="2">Check 2</asp:ListItem>
</BSControls:Bootstrap5CheckBoxList>
looks like you now using html controls, and they don't have automatic viewstate. you would be MUCH better to use a CheckBox list, and format that with bootstrap. And it also FAR better to include that checkbox list in the user control markup, and not write code to inject such controls if possible.
So, plain jane check box (input type = checkbox) as a general rule does not have automatic view state like asp.net controls. So, either drop in a check box list into your user control markup, or you may well have to add code to save/restore the values, since it looks much like you are injecting the "input" control as opposed to using a asp.net checkbox list.
After many trials, I was able to get this working and the answer is surprisingly, or maybe not, simple.
The 'name' attribute is the key and must be in the correct format.
Incorrect Format
writer.WriteAttribute("name", $"{this.UniqueID}_{index}");
Correct Format
writer.WriteAttribute("name", $"{this.UniqueID}${index}");
You must use the $ separator and not an underscore. On postback, the LoadPostData method in CheckBoxList iterates through a collection to retrieve the check state.
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!
I am developing an open source project for rendering HTML5 using ASP.NET. Here you can take a look:
http://asphtml5.codeplex.com/
now I have a problem with update panel in posting back the input values that have type other than 'text'. as you might know, html 5 has introduced several input types, for example 'number', 'tel', 'search', etc. Now if I render such controls, everything works fine in normal situations, but if I put them inside an UpdatePanel, no value will be posted back and the value will be reset.
here is a small piece of code that produces the same error:
<asp:UpdatePanel runat="server" ID="UP">
<ContentTemplate>
<p>
Enter A Number:
<asp:TextBox runat="server" ID="Number2" type="number" />
</p>
<asp:Button Text="Submit" runat="server" ID="BtnSubmit" OnClick="BtnSubmit_Click" />
<p>
You entered :
<asp:Label Text="" ID="LblValue" runat="server" />
</p>
</ContentTemplate>
</asp:UpdatePanel>
if you test this code on a browser that supports html 5, lets say Chrome as an example, a Numeric Up-Down field will be shown. but if you click on the submit button, it will lose the value that you have entered.
here is the code for event handler:
protected void BtnSubmit_Click(object sender, EventArgs e)
{
LblValue.Text = Number2.Text;
}
what I have already tried is reading UpdatePanel, ScriptManager and ScriptManagerProxy classes codes, nothing found.
I think I might need to create my own UpdatePanel and/or ScriptManager classes for use.
Could anyone help me, and tell me where to check.
Interestingly enough, the ASP.NET 4.0 AJAX framework does seem to be aware of HTML 5 input types (see code), yet as #TimSchmelter pointed out, this is confirmed by Microsoft as a bug.
This may give you a starting point for debugging the behavior and/or overriding the default behavior and finding a solution.
It could also be an error on the server-side processing code for these input types, although I'm not sure why they should/would care about an async postback versus a normal postback.
this._textTypes = /^(text|password|hidden|search|tel|url|email|number|range|color|datetime|date|month|week|time|datetime-local)$/i;
function Sys$WebForms$PageRequestManager$_onFormSubmit(evt) {
var i, l, continueSubmit = true,
isCrossPost = this._isCrossPost;
this._isCrossPost = false;
if (this._onsubmit) {
continueSubmit = this._onsubmit();
}
if (continueSubmit) {
for (i = 0, l = this._onSubmitStatements.length; i < l; i++) {
if (!this._onSubmitStatements[i]()) {
continueSubmit = false;
break;
}
}
}
if (!continueSubmit) {
if (evt) {
evt.preventDefault();
}
return;
}
var form = this._form;
if (isCrossPost) {
return;
}
if (this._activeDefaultButton && !this._activeDefaultButtonClicked) {
this._onFormElementActive(this._activeDefaultButton, 0, 0);
}
if (!this._postBackSettings || !this._postBackSettings.async) {
return;
}
var formBody = new Sys.StringBuilder(),
count = form.elements.length,
panelID = this._createPanelID(null, this._postBackSettings);
formBody.append(panelID);
for (i = 0; i < count; i++) {
var element = form.elements[i];
var name = element.name;
if (typeof(name) === "undefined" || (name === null) || (name.length === 0) || (name === this._scriptManagerID)) {
continue;
}
var tagName = element.tagName.toUpperCase();
if (tagName === 'INPUT') {
var type = element.type;
if (this._textTypes.test(type)
|| ((type === 'checkbox' || type === 'radio') && element.checked)) {
formBody.append(encodeURIComponent(name));
formBody.append('=');
formBody.append(encodeURIComponent(element.value));
formBody.append('&');
}
}
else if (tagName === 'SELECT') {
var optionCount = element.options.length;
for (var j = 0; j < optionCount; j++) {
var option = element.options[j];
if (option.selected) {
formBody.append(encodeURIComponent(name));
formBody.append('=');
formBody.append(encodeURIComponent(option.value));
formBody.append('&');
}
}
}
else if (tagName === 'TEXTAREA') {
formBody.append(encodeURIComponent(name));
formBody.append('=');
formBody.append(encodeURIComponent(element.value));
formBody.append('&');
}
}
formBody.append("__ASYNCPOST=true&");
if (this._additionalInput) {
formBody.append(this._additionalInput);
this._additionalInput = null;
}
// truncated for length
All right then, I figured out a way to fix that.
First of all I found the problem with the code that Tim Medora provided. It all was the this. modifier's fault. So this JavaScript fixed the problem:
var _textTypes = /^(text|password|hidden|search|tel|url|email|number|range|color|datetime|date|month|week|time|datetime-local)$/i;
function Sys$WebForms$PageRequestManager$_onFormSubmit(evt) {
...
if (_textTypes.test(type) ||
(((type === 'checkbox') || (type === 'radio')) && element.checked)) {
formBody.append(encodeURIComponent(name));
formBody.append('=');
formBody.append(encodeURIComponent(element.value));
formBody.append('&');
...
}
Now I had to inject my function into ScriptResource.axd.
For now I found a way that seems to work:
I created a class ScriptResouceHandler that extends System.Web.Handlers.ScriptResourceHandler in namespace DotM.Html5.Handlers.
in its ProcessRequest I called base.ProcessRequest(context) to do its job. But I wanted to add my function to the one rendering original function. I found out that it was when the encrypted ZSystem.Web.Extensions,4.0.0.0,,31bf3856ad364e35|MicrosoftAjaxWebForms.debug.js| was passed.
Another problem was that in System.Web.Handlers.ScriptResourceHandler, 'Page.DecryptString' method which is internal is called to decrypt the query string param.
so there was no way for me to invoke that method via reflection.
here is the code:
protected sealed override void ProcessRequest(HttpContext context)
{
base.ProcessRequest(context);
if (CypherContainsAjax(context.Request.QueryString["d"]))
context.Response.Write(OnFormSubmit);
}
private bool CypherContainsAjax(string cypher)
{
var text = DecryptString(cypher);
if (text == null)
return true; //Then Add it everywhere. What else could I do? :D
return text.Contains("MicrosoftAjaxWebForms");
}
private string DecryptString(string cypher)
{
if (PageDecryptString == null)
return null;
return (string)PageDecryptString.Invoke(null, new object[] { cypher });
}
private static MethodInfo PageDecryptString;
static ScriptResourceHandler()
{
PageDecryptString = typeof(Page).GetMethod("DecryptString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
}
You may call this some kind of ugly hacking ...
This was already fixed in .Net 4 Reliability Update 1 (there is also a version 2, but it does not contain the first one): http://support.microsoft.com/kb/2533523
But if you are using AjaxControlToolkit, it uses its own internal MicrosoftAjaxWebForms.js which is an older fork, and there is still no official fix for it - you can use mine solution from here: http://ajaxcontroltoolkit.codeplex.com/workitem/27041
So - you can either include the fixed ToolkitScriptManager with your project (a bloat, I know), or you can try to include the new version of MicrosoftAjaxWebForms.js by experimenting with vanilla ScriptManager properties AjaxFrameworkMode="Explicit", Scripts or CompositeScript.
Use the AjaxFrameworkMode property to enable all Microsoft Ajax script files, to disable all Microsoft Ajax script files, or to explicitly include individual script files.
The Scripts collection does not contain the core Microsoft Ajax Library scripts. The scripts in the core library are rendered automatically; they do not have to be registered with the ScriptManager control. However, if you want to override a core script or any control script and substitute a different version of the script, you can add your version to the Scripts collection.
<asp:ScriptManager runat="server">
<Scripts>
<asp:ScriptReference Path="~/MicrosoftAjaxWebForms.js" />
</Scripts>
</asp:ScriptManager>
You can get the new version of MicrosoftAjaxWebForms.js from System.Web.Extensions assembly Resources (with .Net Reflector) on machines with installed Reliability Update 1.
I'm experiencing some problems and right now I don't know how to solve it. The web control simply updates a clock represented by a label every second. My issue is that the web control exposes a property called 'Formato' where the user can select to display in format 12 or 24 hours. This is done with an enum type where in spanish Doce means 12 and Veinticuatro means 24. This is the code for the server control:
namespace Ejercicio2RelojControl
{
public enum _FormatoHora
{
Doce,
Veinticuatro
}
[DefaultProperty("FormatoHora")]
[ToolboxData("<{0}:Ejercicio2RelojControl runat=server></{0}:Ejercicio2RelojControl>")]
[ToolboxBitmap(typeof(Ejercicio2RelojControl), "Ejercicio2RelojControl.Ejercicio2RelojControl.ico")]
//[Designer("Ejercicio2RelojControl.Ejercicio2RelojControlDesigner, Ejercicio2RelojControl")]
public class Ejercicio2RelojControl : WebControl
{
public Ejercicio2RelojControl()
{
}
[
//Bindable(true),
Category("Appearance"),
//DefaultValue(_FormatoHora.Doce),
Description(""),
]
public virtual _FormatoHora FormatoHora
{
get
{
//object t = ViewState["FormatoHora"];
//return (t == null) ? _FormatoHora.Doce : (_FormatoHora)t;
object obj2 = this.ViewState["_FormatoHora"];
if (obj2 != null)
{
return (_FormatoHora)obj2;
}
return _FormatoHora.Doce;
}
set
{
ViewState["_FormatoHora"] = value;
}
}
//Create one TimerControl
Timer timer = new Timer();
private Label clockLabel = new Label();
// Declare one Updatepanel
UpdatePanel updatePanel = new UpdatePanel();
// Now override the Load event of Current Web Control
protected override void OnLoad(EventArgs e)
{
//Text = "hh:mm:ss";
// Create Ids for Control
timer.ID = ID + "_tiker";
clockLabel.ID = ID + "_l";
// get the contentTemplate Control Instance
Control controlContainer = updatePanel.ContentTemplateContainer;
// add Label and timer control in Update Panel
controlContainer.Controls.Add(clockLabel);
controlContainer.Controls.Add(timer);
// Add control Trigger in update panel on Tick Event
updatePanel.Triggers.Add(new AsyncPostBackTrigger() { ControlID = timer.ID, EventName = "Tick" });
updatePanel.ChildrenAsTriggers = true;
// Set default clock time in label
clockLabel.Text = DateTime.Now.ToString("h:mm:ss tt");
//clockLabel.Text = DateTime.Now.ToString("H:mm:ss");
// Set Interval
timer.Interval = 1000;
// Add handler to timer
timer.Tick += new EventHandler<EventArgs>(timer_Tick);
updatePanel.RenderMode = UpdatePanelRenderMode.Block;
//Add update panel to the base control collection.
base.Controls.Add(updatePanel);
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(FormatoHora);
}
void timer_Tick(object sender, EventArgs e)
{
// Set current date time in label to move current at each Tick Event
clockLabel.Text = DateTime.Now.ToString("h:mm:ss tt");
//clockLabel.Text = DateTime.Now.ToString("H:mm:ss");
}
}
}
Now it's time to test the custom control in an asp.net web application.
<cc1:Ejercicio2RelojControl ID="Ejercicio2RelojControl1" runat="server" />
Works great! BUT when I add the property "Formato" fails at compile time:
<cc1:Ejercicio2RelojControl ID="Ejercicio2RelojControl1" runat="server" Formato="Doce" />
Compiler Error Message: CS0117: 'Ejercicio2RelojControl.Ejercicio2RelojControl' does not contain a definition for 'FormatoHora'
Why is the property Formato making the web app crash at compile time?
Thanks a lot.
EDIT:
namespace Ejercicio2RelojControl
{
public enum FormatoHora
{
Doce,
Veinticuatro
}
[DefaultProperty("FormatoHora")]
[ToolboxData("<{0}:Ejercicio2RelojControl runat=server></{0}:Ejercicio2RelojControl>")]
public class Ejercicio2RelojControl : WebControl, INamingContainer
{
public FormatoHora FormatoHora
{
get
{
object obj2 = this.ViewState["FormatoHora"];
if (obj2 != null)
{
return (FormatoHora)obj2;
}
return FormatoHora.Doce;
}
set
{
ViewState["FormatoHora"] = value;
}
}
As you can see I've changed the public property. Now the error has changed. Is the following:
Compiler Error Message: CS0120: An object reference is required for the non-static field, method, or property 'Ejercicio2RelojControl.Ejercicio2RelojControl.FormatoHora.get'
Any help appreciated. Thanks
EDIT 2:
I've discovered that the problem is on the set {}. If I comment it, all is working fine but then I cannot change FormatoHora between 12 and 24 because of is read only due to only get{} is implemented. Any help with the implementation of set{} ?
I am here for giving you the solution:
You are using the same name for the namespace and for the webcontrol (Ejercicio2RelojControl) . Simply change that and your code will work fine.
Hope it helps, despite the fact some years have passed :)
Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();