#region LGPL License
/*
MagicAjax.NET Framework
Copyright (C) 2005 MagicAjax Project Team
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#endregion
using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace MagicAjax.UI.Controls
{
/// <summary>
/// Defines the AjaxCall connection types.
/// </summary>
public enum AjaxCallConnectionType
{
/// <summary>
/// Client will not invoke an AjaxCall
/// </summary>
None,
/// <summary>
/// Asynchronous AjaxCall
/// </summary>
Asynchronous,
/// <summary>
/// Synchronous AjaxCall
/// </summary>
Synchronous
}
/// <summary>
/// Defines what form elements should be excluded from the post data.
/// </summary>
[Flags]
public enum ExcludeFromPostFlag
{
/// <summary>
/// Post all elements
/// </summary>
None = 0,
/// <summary>
/// Do not post ViewState
/// </summary>
ViewState = 1,
/// <summary>
/// Do not post the control fingerprints hidden fields
/// </summary>
Fingerprints = 2,
/// <summary>
/// Do not post the custom hidden fields of the user
/// </summary>
UserHidden = 4,
/// <summary>
/// Do not post any hidden field
/// </summary>
AllHidden = ViewState | Fingerprints | UserHidden,
/// <summary>
/// Do not post any (non-hidden) form element
/// </summary>
FormElements = 8,
/// <summary>
/// Do not post any element
/// </summary>
AllElements = AllHidden | FormElements
}
#region AjaxPanelUpdated event Handler & Args
public class AjaxPanelUpdatedEventArgs
{
private Control _updatedControl;
public Control UpdatedControl
{
get { return _updatedControl; }
}
public AjaxPanelUpdatedEventArgs(Control updatedControl)
{
this._updatedControl = updatedControl;
}
}
public delegate void AjaxPanelUpdatedEventHandler(object sender, AjaxPanelUpdatedEventArgs e);
#endregion
/// <summary>
/// Works like Panel but the controls it contains are rendered on the page by sending
/// javascript to the client.
/// </summary>
/// <remarks>
/// The main control that makes all the hard work for seamless AJAXing. It spots
/// controls that are added, removed or altered, and sends the appropriate javascript
/// for the refreshing of the page. In case it contains a RenderedByScriptControl, it
/// ignores it and lets the RenderedByScriptControl to produce the appropriate javascript
/// for its appearance.
///
/// If an AjaxPanel (parent) contains another AjaxPanel (child), and a control
/// of the child-AjaxPanel is altered, parent-AjaxPanel won't send the entire html
/// rendering of the child-AjaxPanel, but instead the child-AjaxPanel will send only
/// the html of the altered control. Thus, the size of javascript code that the client
/// gets as a response of an AjaxCall, is greatly reduced.
///
/// It is not necessary to put all the controls of the page inside an AjaxPanel. Only
/// the controls that are going to be refreshed on the client using javascript are
/// required to be contained within an AjaxPanel.
/// </remarks>
[Designer("MagicAjax.UI.Design.AjaxPanelDesigner, MagicAjax"),
ParseChildrenAttribute(false),
PersistChildren(true),
ToolboxData("<{0}:AjaxPanel runat=server>AjaxPanel</{0}:AjaxPanel>")]
public class AjaxPanel : RenderedByScriptControl
{
#region Fields
private AjaxCallConnectionType _ajaxCallConnection = AjaxCallConnectionType.Asynchronous;
private ExcludeFromPostFlag _excludeFlags = ExcludeFromPostFlag.None;
private ArrayList _addedControls = new ArrayList();
private ArrayList _removedControls = new ArrayList();
private Hashtable _controlHtmlFingerprints = new Hashtable();
private ControlCollectionState _controlState;
private Hashtable _controlUpdatedHandlerLists = new Hashtable();
#endregion
#region Constructor
/// <summary>
/// Creates an instance of a RenderedByScriptControl with the Span tagName.
/// </summary>
/// <remarks>
/// AjaxPanel uses Span instead of Div, because ASP.NET translates Div to a
/// table if the browser is Firefox.
/// </remarks>
public AjaxPanel()
: base(HtmlTextWriterTag.Span)
{
_controlState = new ControlCollectionState(this);
}
#endregion
#region Properties
#region AjaxCallConnection
/// <summary>
/// Defines the connection type (a/synchronous) that will be utilized for an AjaxCall.
/// Default is AjaxCallConnectionType.Asynchronous.
/// </summary>
[Bindable(false),
Category("Behaviour"),
DefaultValue(AjaxCallConnectionType.Asynchronous)]
public AjaxCallConnectionType AjaxCallConnection
{
get { return _ajaxCallConnection; }
set { _ajaxCallConnection = value; }
}
#endregion
#region ExcludeFlags
/// <summary>
/// Defines the form elements that will be excluded from the POST data.
/// Default is ExcludeFromPostFlag.None.
/// </summary>
[Bindable(false),
Category("Behaviour"),
DefaultValue(ExcludeFromPostFlag.None)]
public ExcludeFromPostFlag ExcludeFlags
{
get { return _excludeFlags; }
set { _excludeFlags = value; }
}
#endregion
#endregion
#region Events
public event AjaxPanelUpdatedEventHandler ContentUpdated;
#endregion
#region Public Methods
#region Clear
/// <summary>
/// It removes the child controls of the AjaxPanel without producing the
/// appropriate javascript to refresh the page at the client.
/// </summary>
public void Clear()
{
Controls.Clear();
_addedControls.Clear();
_removedControls.Clear();
_controlHtmlFingerprints.Clear();
}
#endregion
#region override IsRenderedOnPage
/// <summary>
/// If it's in 'NoStore' page mode, returns true if the AjaxPanel's fingerprint
/// is not empty, false if it is empty.
/// </summary>
public override bool IsRenderedOnPage
{
get
{
if (IsPageNoStoreMode)
{
return (Context.Request.Form[ControlCollectionState.GetControlFingerprintsField(this.ClientID)] != String.Empty);
}
else
return base.IsRenderedOnPage;
}
}
#endregion
public virtual void AddControlUpdatedEventHandler(Control controlToMonitor, EventHandler controlUpdatedHandler)
{
ArrayList handlerList = _controlUpdatedHandlerLists[controlToMonitor] as ArrayList;
if (handlerList == null)
{
handlerList = new ArrayList();
_controlUpdatedHandlerLists.Add(controlToMonitor, handlerList);
}
if (!handlerList.Contains(controlUpdatedHandler))
handlerList.Add(controlUpdatedHandler);
}
public virtual void RemoveControlUpdatedEventHandler(Control controlToMonitor, EventHandler controlUpdatedHandler)
{
ArrayList handlerList = _controlUpdatedHandlerLists[controlToMonitor] as ArrayList;
if (handlerList == null)
{
handlerList = new ArrayList();
_controlUpdatedHandlerLists.Add(controlToMonitor, handlerList);
}
handlerList.Add(controlUpdatedHandler);
}
#endregion
#region Protected Methods
#region override OnLoad
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.MagicAjaxContext.IsBrowserSupported)
{
Page.RegisterHiddenField(ControlCollectionState.GetControlFingerprintsField(this.ClientID), String.Empty);
}
#if !MEDIUM_TRUST
// Attach to prerendercomplete event so we can do
// some extra processing before rendering of the AjaxPanels.
// Also some processing is done after rendering of the AjaxPanels,
// see Render method in RenderedByScriptControl.cs.
// This extra processing involves reflecting hidden fields, scripts and
// stylesheets that were added/changed on a callback.
if (Context != null && !Context.Items.Contains("__ATTACHED_PAGE_HANDLERS"))
{
Context.Items.Add("__ATTACHED_PAGE_HANDLERS", String.Empty);
#if NET_2_0
this.Page.PreRenderComplete += new EventHandler(AjaxCallHelper.Page_PreRender);
#else
this.Page.PreRender += new EventHandler(AjaxCallHelper.Page_PreRender);
#endif
}
#endif
}
#endregion
#region override OnAjaxCallEnd
protected override void OnAjaxCallEnd(EventArgs e)
{
if (IsPageNoStoreMode && IsRenderedOnPage)
{
// Verify that it is indeed rendered on page. If it's not, set its
// fingerprint to empty string.
if (!(GetTopInvisibleControl(this) is RenderedByScriptControl))
AjaxCallHelper.WriteSetFieldScript(ControlCollectionState.GetControlFingerprintsField(this.ClientID), String.Empty);
}
base.OnAjaxCallEnd(e);
}
#endregion
#region override AddedControl
/// <summary>
/// Adds the control to the added controls collection.
/// </summary>
/// <param name="control"></param>
/// <param name="index"></param>
protected override void AddedControl(Control control, int index)
{
if (IsHtmlHolder(control))
{
if (IsPageNoStoreMode)
{
// If the ID of the added control is null, find a unique id manually,
// or else the default naming order may cause mismatches.
if (control.ID == null || control.ID == String.Empty)
control.ID = FindUniqueID();
}
_addedControls.Add(control);
}
base.AddedControl(control, index);
}
#endregion
#region override RemovedControl
/// <summary>
/// Adds the control to the removed controls collection.
/// </summary>
/// <param name="control"></param>
/// <param name="index"></param>
protected override void RemovedControl(Control control)
{
if (IsHtmlHolder(control))
{
if (_addedControls.Contains(control))
_addedControls.Remove(control);
else
_removedControls.Add(control);
}
base.RemovedControl(control);
}
#endregion
#region override RenderChildren
/// <summary>
/// Does a normal rendering of the child controls.
/// </summary>
/// <remarks>
/// Each child control is contained inside a Span tag with unique id so that
/// it can be easily manipulated apart from the other child controls.
/// </remarks>
/// <param name="writer"></param>
protected override void RenderChildren(HtmlTextWriter writer)
{
if (MagicAjaxContext == null)
{
// It's in VS Designer, do the default render
base.RenderChildren(writer);
return;
}
System.Text.StringBuilder sbFull = new System.Text.StringBuilder();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HtmlTextWriter fullwriter = new HtmlTextWriter(new System.IO.StringWriter(sbFull));
HtmlTextWriter litewriter = new HtmlTextWriter(new System.IO.StringWriter(sb));
StringBuilder sbLiteral = new StringBuilder();
HtmlTextWriter literalwriter = new HtmlTextWriter(new System.IO.StringWriter(sbLiteral));
for (int i = 0; i < Controls.Count; i++)
{
Control con = Controls[i];
// Put inside span tag only html holder controls
bool isHtmlHolder = IsHtmlHolder(con);
if (isHtmlHolder)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("id", GetAjaxElemID(con));
writer.WriteAttribute("name", "__ajaxmark");
writer.Write(HtmlTextWriter.TagRightChar);
literalwriter.WriteBeginTag("span");
literalwriter.WriteAttribute("id", GetAjaxElemID(con));
literalwriter.WriteAttribute("name", "__ajaxmark");
literalwriter.Write(HtmlTextWriter.TagRightChar);
}
ExtendedRenderControl(con, fullwriter, litewriter);
writer.Write(sbFull.ToString());
string html = sb.ToString();
if (isHtmlHolder)
{
_controlHtmlFingerprints[con] = Util.GetFingerprint(html);
writer.WriteEndTag("span");
literalwriter.WriteEndTag("span");
}
else
{
literalwriter.Write(html);
}
sbFull.Length = 0;
sb.Length = 0;
}
_controlState.LiteralFingerprint = Util.GetFingerprint(sbLiteral.ToString());
_addedControls.Clear();
_removedControls.Clear();
if (IsPageNoStoreMode)
SaveControlState();
}
#endregion
#region override AddAjaxAttributesToRender
protected override void AddAjaxAttributesToRender(HtmlTextWriter writer)
{
base.AddAjaxAttributesToRender(writer);
switch (_ajaxCallConnection)
{
case AjaxCallConnectionType.Asynchronous:
writer.AddAttribute("AjaxCall", "async");
break;
case AjaxCallConnectionType.Synchronous:
writer.AddAttribute("AjaxCall", "sync");
break;
}
if (_excludeFlags != ExcludeFromPostFlag.None)
{
writer.AddAttribute("ExcludeFlags", ((int)_excludeFlags).ToString());
}
}
#endregion
protected virtual void SaveControlState()
{
//note:only for NoStore mode
if (_controlState != null)
{
_controlState.SetControlIDs(_controlHtmlFingerprints);
_controlState.Save(this.ClientID, this.Page);
}
}
protected virtual void LoadControlState()
{
_controlState = ControlCollectionState.LoadState(this.ClientID, this);
if (_controlState == null)
{
// The control fingerprints were excluded from the post data.
// There will be no evaluation of 'html holders'; all the contents
// of the AjaxPanel will be sent to client in one javascript command.
return;
}
// Find new and previous controls
_addedControls.Clear();
_controlHtmlFingerprints.Clear();
foreach (Control con in this.Controls)
{
if (IsHtmlHolder(con))
{
if (_controlState.ControlHtmlFingerprints.ContainsKey(con.ClientID))
{
_controlHtmlFingerprints[con] = _controlState.ControlHtmlFingerprints[con.ClientID];
}
else
{
_addedControls.Add(con);
}
}
}
// Removed controls do not render their 'marker tags' and will be removed from
// page when literal content is rendered. So there's no need to
// explicitly send javascript command to remove a control.
_removedControls.Clear();
}
/// <summary>
/// Parses all form input controls from the html, and checks if
/// their values were updated (i.e. different from the current Request.Form values)
/// and sends javascript commands to update the client's form values if necessary.
/// </summary>
/// <param name="html"></param>
protected void ReflectUpdatedFormValues(string html)
{
NameValueCollection form = Context.Request.Form;
Regex regEx = Util.FormElementRegEx;
MatchCollection matches = regEx.Matches(html);
for (int i = 0; i < matches.Count; i++)
{
Match match = matches[i];
CaptureCollection attrnames = match.Groups["attrname"].Captures;
CaptureCollection attrvalues = match.Groups["attrvalue"].Captures;
Hashtable attrNameValues = new Hashtable(attrnames.Count);
for (int j = 0; j < attrnames.Count; j++)
{
attrNameValues[attrnames[j].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture)] = attrvalues[j].Value;
}
// If the form element has the MagicAjax 'ExcludeFromPost' attribute
// set to 'true', ignore it.
if (attrNameValues.ContainsKey("excludefrompost")
&& (attrNameValues["excludefrompost"] as String).ToLower(System.Globalization.CultureInfo.InvariantCulture) == "true")
continue;
string tag = match.Groups["tag"].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
string name = (string)attrNameValues["name"];
string clientID = (string)attrNameValues["id"];
if (name == null || clientID == null)
continue;
switch (tag)
{
#region <input> tags
case "input":
string type = (string)attrNameValues["type"];
if (type != null)
{
string value = attrNameValues.ContainsKey("value") ? (string)attrNameValues["value"] : String.Empty;
bool isChecked = attrNameValues.ContainsKey("checked");
switch (type)
{
case "text":
if (value != form[name])
AjaxCallHelper.WriteSetFieldScript(clientID, value);
break;
case "checkbox":
if (isChecked != (form[name] != null))
AjaxCallHelper.WriteFormat("__PageForm[\"{0}\"].checked={1};\r\n", clientID, (isChecked) ? "true" : "false");
break;
case "radio":
if (isChecked && form[name] != value)
{
AjaxCallHelper.WriteFormat("__PageForm[\"{0}\"].checked=true;\r\n", clientID);
}
else if (!isChecked && form[name] == value)
{
AjaxCallHelper.WriteFormat("__PageForm[\"{0}\"].checked=false;\r\n", clientID);
}
break;
}
}
break;
#endregion
#region <textarea> tags
case "textarea":
string text = match.Groups["inner"].Value;
if (text != form[name])
AjaxCallHelper.WriteSetFieldScript(clientID, text);
break;
#endregion
#region <select> tags
case "select":
Regex regExOpt = Util.FormElementOptionsRegEx;
bool multiple = attrNameValues.ContainsKey("multiple");
ArrayList serverOptions = new ArrayList();
ArrayList serverSelected = new ArrayList();
string oneSelection = null;
//now match the options within this <select> tag
MatchCollection matchesOptions = regExOpt.Matches(match.Groups["inner"].Value);
for (int j = 0; j < matchesOptions.Count; j++)
{
Match matchOption = matchesOptions[j];
CaptureCollection attrnamesOption = matchOption.Groups["attrname"].Captures;
CaptureCollection attrvaluesOption = matchOption.Groups["attrvalue"].Captures;
Hashtable attrNameValuesOption = new Hashtable();
for (int k = 0; k < attrnamesOption.Count; k++)
{
attrNameValuesOption[attrnamesOption[k].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture)] = attrvaluesOption[k].Value;
}
string optValue = (string)attrNameValuesOption["value"]; //TODO: check value encodings etc.
bool optSelected = attrNameValuesOption.ContainsKey("selected");
if (optSelected && optValue != null)
{
if (multiple)
{
serverSelected.Add(optValue);
}
else
{
oneSelection = optValue;
break;
}
}
serverOptions.Add(optValue);
}
if (!multiple)
{
if (oneSelection != form[name])
{
if (oneSelection == null)
AjaxCallHelper.WriteFormat("__PageForm[\"{0}\"].selectedIndex=-1;\r\n", clientID);
else
AjaxCallHelper.WriteSetFieldScript(clientID, oneSelection);
}
}
else
{
string[] selections = form.GetValues(name);
if (selections == null)
selections = new string[0];
bool elemWritten = false;
// Make selections
for (int j = 0; j < serverSelected.Count; j++)
{
if (Array.IndexOf(selections, serverSelected[j]) == -1)
{
if (!elemWritten)
{
AjaxCallHelper.WriteFormat("o=__PageForm[\"{0}\"].options;\r\n", clientID);
elemWritten = true;
}
AjaxCallHelper.WriteFormat("o[{0}].selected=true;\r\n", serverOptions.IndexOf(serverSelected[j]));
}
}
// Make unselections
for (int j = 0; j < selections.Length; j++)
{
if (!serverSelected.Contains(selections[j]))
{
if (!elemWritten)
{
AjaxCallHelper.WriteFormat("o=__PageForm[\"{0}\"];\r\n", clientID);
elemWritten = true;
}
AjaxCallHelper.WriteFormat("o[{0}].selected=false;\r\n", serverOptions.IndexOf(selections[j]));
}
}
}
break;
#endregion
}
}
}
/// <summary>
/// Determines whether the AjaxPanel should treat the given control as a separate
/// 'html holder' to compare and 'reflect' its html rendering.
/// </summary>
/// <remarks>
/// Only WebControls, excluding RenderedByScriptControls, are considered html holders.
/// </remarks>
/// <param name="control"></param>
/// <returns></returns>
protected virtual bool IsHtmlHolder(Control control)
{
return (control is WebControl && !(control is INonHtmlHolder));
}
#region override PutTagOnPageForAjaxCall
/// <summary>
/// If this AjaxPanel is inside another AjaxPanel it puts its empty tag on page
/// using "clientID$rbs" ID, otherwise it throws an exception.
/// </summary>
protected override void PutTagOnPageForAjaxCall()
{
if (!IsChildOfAjaxPanel(this))
throw new MagicAjaxException(String.Format("AjaxPanel '{0}' cannot get visible during an AjaxCall. It must be inside an AjaxPanel that is visible for the initial page request.", this.ClientID));
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HtmlTextWriter strwriter = new HtmlTextWriter(new System.IO.StringWriter(sb));
this.RenderBeginTag(strwriter);
this.RenderEndTag(strwriter);
string html = sb.ToString();
AjaxCallHelper.WriteSetHtmlOfElementScript(html, this.ClientID + "$rbs");
}
#endregion
#region RenderByScript
/// <summary>
/// It scans child controls for added, removed or altered controls and sends
/// the appropriate javascript to the client.
/// </summary>
protected override void RenderByScript()
{
if (IsPageNoStoreMode)
{
LoadControlState();
}
else
{
InitValidators();
}
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HtmlTextWriter litewriter = new HtmlTextWriter(new System.IO.StringWriter(sb));
System.Text.StringBuilder sbFull = new System.Text.StringBuilder();
HtmlTextWriter fullwriter = new HtmlTextWriter(new System.IO.StringWriter(sbFull));
StringBuilder sbLiteral = new StringBuilder();
HtmlTextWriter literalwriter = new HtmlTextWriter(new System.IO.StringWriter(sbLiteral));
// To be used in 'NoStore' mode to continue the rendering of control tree.
HtmlTextWriter nullWriter = new HtmlTextWriter(System.IO.TextWriter.Null);
bool allControlsAreNew = (Controls.Count == _addedControls.Count);
if (!this.IsRenderedOnPage || allControlsAreNew || _controlState == null)
{
// Render all the controls in a single html rendering.
for (int i = 0; i < Controls.Count; i++)
{
Control con = Controls[i];
// Put inside span tag only html holder controls
bool isHtmlHolder = IsHtmlHolder(con);
if (isHtmlHolder)
{
fullwriter.WriteBeginTag("span");
fullwriter.WriteAttribute("id", GetAjaxElemID(con));
fullwriter.WriteAttribute("name", "__ajaxmark");
fullwriter.Write(HtmlTextWriter.TagRightChar);
literalwriter.WriteBeginTag("span");
literalwriter.WriteAttribute("id", GetAjaxElemID(con));
literalwriter.WriteAttribute("name", "__ajaxmark");
literalwriter.Write(HtmlTextWriter.TagRightChar);
}
ExtendedRenderControl(con, fullwriter, litewriter, true);
string html = sb.ToString();
if (isHtmlHolder)
{
fullwriter.WriteEndTag("span");
literalwriter.WriteEndTag("span");
_controlHtmlFingerprints[con] = Util.GetFingerprint(html);
}
else
{
literalwriter.Write(html);
}
sb.Length = 0;
}
if (sbFull.Length > 0)
{
string html = sbFull.ToString();
AjaxCallHelper.WriteSetHtmlOfElementScript(html, ClientID);
}
if (_controlState != null)
_controlState.LiteralFingerprint = Util.GetFingerprint(sbLiteral.ToString());
}
else
{
// Increase script writing level because we want literal content to
// be rendered before the child controls renderings
AjaxCallHelper.IncreaseWritingLevel();
foreach (Control con in _removedControls)
{
_controlHtmlFingerprints.Remove(con);
}
for (int i = 0; i < Controls.Count; i++)
{
Control con = Controls[i];
// Only html holder controls are inside a span tag
bool isHtmlHolder = IsHtmlHolder(con);
if (isHtmlHolder)
{
literalwriter.WriteBeginTag("span");
literalwriter.WriteAttribute("id", GetAjaxElemID(con));
literalwriter.WriteAttribute("name", "__ajaxmark");
literalwriter.Write(HtmlTextWriter.TagRightChar);
literalwriter.WriteEndTag("span");
}
string html;
sbFull.Length = sb.Length = 0;
if (isHtmlHolder && _addedControls.Contains(con))
{
// It's a new control, create it on the client at the appropriate place.
ExtendedRenderControl(con, fullwriter, litewriter, true);
html = sbFull.ToString();
AjaxCallHelper.WriteSetHtmlOfElementScript(html, GetAjaxElemID(con));
_controlHtmlFingerprints[con] = Util.GetFingerprint(sb.ToString());
}
else
{
if (IsPageNoStoreMode)
{
// Any scripts of child controls should be after the scripts
// of parent controls, so increase the script writing level.
AjaxCallHelper.IncreaseWritingLevel();
ExtendedRenderControl(con, nullWriter, litewriter);
AjaxCallHelper.DecreaseWritingLevel();
}
else
ExtendedRenderControl(con, litewriter);
html = sb.ToString();
if (isHtmlHolder)
{
string htmlFingerprint = Util.GetFingerprint(html);
if (htmlFingerprint == (string)_controlHtmlFingerprints[con])
{
// Its html rendering fingerprint is the same, ignore the
// html and "reflect" the form elements that it contains
// on client's page if they're different.
ReflectUpdatedFormValues(html);
}
else
{
// Its html rendering fingerprint has changed,
// "reflect" its html on client.
ExtendedWriteSetHtmlOfElementScript(html, GetAjaxElemID(con));
_controlHtmlFingerprints[con] = htmlFingerprint;
}
}
else
{
literalwriter.Write(html);
}
if (IsPageNoStoreMode)
{
AjaxCallHelper.MergeUpperWritingLevelsWithCurrent();
}
}
}
AjaxCallHelper.DecreaseWritingLevel();
string literalhtml = sbLiteral.ToString();
string literalFingerprint = Util.GetFingerprint(literalhtml);
if (literalFingerprint == _controlState.LiteralFingerprint)
{
// Its html rendering fingerprint is the same, ignore the
// html and "reflect" the form elements that it contains
// on client's page if they're different.
ReflectUpdatedFormValues(literalhtml);
}
else
{
ExtendedWriteSetHtmlOfElementScript(literalhtml, this.ClientID);
_controlState.LiteralFingerprint = Util.GetFingerprint(literalhtml);
}
AjaxCallHelper.MergeUpperWritingLevelsWithCurrent();
}
_addedControls.Clear();
_removedControls.Clear();
if (IsPageNoStoreMode)
SaveControlState();
}
#endregion
#region OnContentUpdated
protected virtual void OnContentUpdated(AjaxPanelUpdatedEventArgs e)
{
if (ContentUpdated != null)
ContentUpdated(this, e);
}
#endregion
#endregion
#region Private Methods
#region ExtendedWriteSetHtmlOfElementScript
/// <summary>
/// Calls the AJAXCbo.ExtendedSetHtmlOfElement function on the client.
/// </summary>
/// <remarks>
/// It's used by RenderByScript method.
///
/// AjaxPanel doesn't include RenderedByScriptControl controls in the html rendering,
/// to reduce the size of the javascript script sent to clients.
/// AJAXCbo.ExtendedSetHtmlOfElement finds the RenderedByScriptControl controls
/// that are missing from the html rendering, gets them from the page of the client
/// and adds them in the appropriate place in the html rendering.
/// </remarks>
/// <param name="html">The html rendering of the control</param>
/// <param name="elementID">The span element id that the control belongs to.</param>
private void ExtendedWriteSetHtmlOfElementScript(string html, string elementID)
{
AjaxCallHelper.Write(String.Format("AJAXCbo.ExtendedSetHtmlOfElement({0},\"{1}\");\r\n", AjaxCallHelper.EncodeString(html), elementID));
}
#endregion
#region ExtendedRenderControl
/// <summary>
/// It renders the control but without including the html rendering of
/// any RenderedByScriptControl controls.
/// </summary>
/// <remarks>
/// It's used by RenderByScript method.
/// </remarks>
/// <param name="control"></param>
/// <param name="litewriter"></param>
private void ExtendedRenderControl(Control control, HtmlTextWriter litewriter)
{
RenderStartEventHandler renderStart = new RenderStartEventHandler(control_RenderStart_Abort);
ArrayList list = FindRenderedByScriptControls(control);
bool[] visibleProps = new bool[list.Count];
bool[] monitorProps = new bool[list.Count];
for (int i = 0; i < list.Count; i++)
{
RenderedByScriptControl con = list[i] as RenderedByScriptControl;
con.RenderStart += renderStart;
visibleProps[i] = con.Visible;
monitorProps[i] = con.MonitorVisibilityState;
con.MonitorVisibilityState = false;
con.Visible = true;
}
control.RenderControl(litewriter);
for (int i = 0; i < list.Count; i++)
{
RenderedByScriptControl con = list[i] as RenderedByScriptControl;
con.RenderStart -= renderStart;
con.Visible = visibleProps[i];
con.MonitorVisibilityState = monitorProps[i];
}
list.Clear();
}
#endregion
#region control_RenderStart_Abort
/// <summary>
/// It's part of the ExtendedRenderControl(Control control) method. It replaces the html rendering
/// of a RenderedByScriptControl with a Span tag named "__ajaxmark".
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void control_RenderStart_Abort(object sender, RenderStartEventArgs e)
{
RenderedByScriptControl con = (RenderedByScriptControl)sender;
e.Writer.Write("<span id=\"{0}$rbs\" name=\"__ajaxmark\"></span>", con.ClientID);
e.AbortRendering = true;
}
#endregion
#region ExtendedRenderControl
private void ExtendedRenderControl(Control control, HtmlTextWriter fullwriter, HtmlTextWriter litewriter)
{
ExtendedRenderControl(control, fullwriter, litewriter, false);
}
#endregion
#region ExtendedRenderControl
/// <summary>
/// It produces the full rendering of the control and the rendering without
/// including the html rendering of any RenderedByScriptControl controls.
/// </summary>
/// <remarks>
/// It's used by RenderByScript method.
/// </remarks>
/// <param name="control"></param>
/// <param name="fullwriter">The writer to use for the full rendering</param>
/// <param name="litewriter">The writer to use for the rendering without any RenderedByScriptControl controls</param>
private void ExtendedRenderControl(Control control, HtmlTextWriter fullwriter, HtmlTextWriter litewriter, bool disableScriptRendering)
{
ExtendedTextWriter extTextWriter = new ExtendedTextWriter(fullwriter, litewriter, disableScriptRendering);
RenderStartEventHandler renderStart = new RenderStartEventHandler(extTextWriter.IScriptWriter_RenderStart);
EventHandler renderEnd = new EventHandler(extTextWriter.IScriptWriter_RenderEnd);
ArrayList list = FindRenderedByScriptControls(control);
bool[] visibleProps = new bool[list.Count];
bool[] monitorProps = new bool[list.Count];
for (int i = 0; i < list.Count; i++)
{
RenderedByScriptControl con = list[i] as RenderedByScriptControl;
con.RenderStart += renderStart;
con.RenderEnd += renderEnd;
if (!con.Visible)
con.SkipRendering = true;
visibleProps[i] = con.Visible;
monitorProps[i] = con.MonitorVisibilityState;
con.MonitorVisibilityState = false;
con.Visible = true;
}
HtmlTextWriter extwriter = new HtmlTextWriter(extTextWriter);
control.RenderControl(extwriter);
for (int i = 0; i < list.Count; i++)
{
RenderedByScriptControl con = list[i] as RenderedByScriptControl;
con.RenderStart -= renderStart;
con.RenderEnd -= renderEnd;
con.Visible = visibleProps[i];
con.MonitorVisibilityState = monitorProps[i];
con.SkipRendering = false;
}
list.Clear();
}
#endregion
#region Validators
/// <summary>
/// Clears 'display' and 'visibility' styles from all the validators.
/// </summary>
private void InitValidators()
{
ArrayList validators = Util.GetChildControlsOfType(this, typeof(BaseValidator), typeof(RenderedByScriptControl), true);
foreach (BaseValidator validator in validators)
{
validator.Style.Remove("display");
validator.Style.Remove("visibility");
}
}
private void DisableClientValidators()
{
ArrayList validators = Util.GetChildControlsOfType(this, typeof(BaseValidator), typeof(RenderedByScriptControl), true);
foreach (BaseValidator validator in validators)
{
validator.EnableClientScript = false;
}
}
#endregion
#region FindRenderedByScriptControls
/// <summary>
/// It returns an ArrayList of all the RenderedByScriptControl controls that
/// the supplied control contains.
/// </summary>
/// <remarks>
/// It's used by the ExtendedRenderControl methods.
/// </remarks>
/// <param name="control"></param>
/// <returns></returns>
private ArrayList FindRenderedByScriptControls(Control control)
{
ArrayList list = new ArrayList();
if (control is RenderedByScriptControl)
{
list.Add(control);
}
else if (control.Visible)
{
for (int i = 0; i < control.Controls.Count; i++)
list.AddRange(FindRenderedByScriptControls(control.Controls[i]));
}
return list;
}
#endregion
#region GetNextExistingElement
/// <summary>
/// It returns the next control that is available at the page of the client
/// so that a new control can be added before it.
/// </summary>
/// <remarks>
/// It's used by the RenderByScript method.
///
/// It checks to see if the html rendering of the control is stored to determine
/// whether the control exists on the page or not.
/// </remarks>
/// <param name="start">The index position to start searching</param>
/// <returns></returns>
private string GetNextExistingElement(int start)
{
for (int i = start + 1; i < Controls.Count; i++)
{
Control con = Controls[i];
if (!_addedControls.Contains(con) && _controlHtmlFingerprints[con] != null)
return GetAjaxElemID(con);
}
return null;
}
#endregion
#region IsChildOfAjaxPanel
/// <summary>
/// Returns true if the given control is contained inside an AjaxPanel
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
private bool IsChildOfAjaxPanel(Control control)
{
if (control.Parent == null || control.Parent == control.Page)
return false;
if (control.Parent is AjaxPanel)
return true;
else
return IsChildOfAjaxPanel(control.Parent);
}
#endregion
#region FindUniqueID
/// <summary>
/// Finds an ID that no other AjaxPanel's child control has.
/// </summary>
/// <returns></returns>
private string FindUniqueID()
{
string prefix = "_ajaxctl";
string id;
for (int num = 0; ; num++)
{
id = prefix + num;
int i;
for (i = 0; i < this.Controls.Count; i++)
if (this.Controls[i].ID == id)
break;
if (i == this.Controls.Count)
break;
}
return id;
}
#endregion
#region GetAjaxElemID
/// <summary>
/// It returns the Span tag id that contains the supplied control.
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
private string GetAjaxElemID(Control control)
{
return control.ClientID + "$ajaxdest";
}
private string GetAjaxElemID(string clientID)
{
return clientID + "$ajaxdest";
}
#endregion
#endregion
#region Private Class ControlCollectionState
private class ControlCollectionState
{
private string _literalFingerprint;
private SortedList _controlHtmlFingerprints;
private AjaxPanel _owner;
/// <summary>
/// Default ctor
/// </summary>
public ControlCollectionState(AjaxPanel owner)
{
_owner = owner;
_controlHtmlFingerprints = new SortedList();
}
/// <summary>
/// Constructs a new ControlCollectionState with a list of controlHtmlFingerprints
/// </summary>
/// <param name="controlHtmlFingerprints"></param>
public ControlCollectionState(string literalFingerprint, SortedList controlHtmlFingerprints, AjaxPanel owner)
{
_owner = owner;
_literalFingerprint = literalFingerprint;
_controlHtmlFingerprints = controlHtmlFingerprints;
}
/// <summary>
/// Loads the ControlCollectionState from the hidden form element
/// </summary>
/// <param name="panelClientID"></param>
/// <returns></returns>
public static ControlCollectionState LoadState(string panelClientID, AjaxPanel owner)
{
string panelKey = GetControlFingerprintsField(panelClientID);
string panelControlFingerprints = HttpContext.Current.Request.Form[panelKey];
if (panelControlFingerprints != null)
{
SortedList controlHtmlFingerprints = new SortedList();
string literalFingerprint = null;
if (panelControlFingerprints != String.Empty)
{
string[] namevaluepairs = panelControlFingerprints.Split(';');
if (owner.MagicAjaxContext.Configuration.CompareMode == MagicAjax.Configuration.OutputCompareMode.FullHtml)
{
literalFingerprint = UnicodeEncoding.Default.GetString(Convert.FromBase64String(namevaluepairs[0]));
}
else
{
literalFingerprint = namevaluepairs[0];
}
for (int i = 1; i < namevaluepairs.Length; i++)
{
string namevaluepair = namevaluepairs[i];
string[] namevalue = namevaluepair.Split('#');
if (owner.MagicAjaxContext.Configuration.CompareMode == MagicAjax.Configuration.OutputCompareMode.FullHtml)
{
controlHtmlFingerprints.Add(namevalue[0], UnicodeEncoding.Default.GetString(Convert.FromBase64String(namevalue[1])));
}
else
{
controlHtmlFingerprints.Add(namevalue[0], namevalue[1]);
}
}
}
return new ControlCollectionState(literalFingerprint, controlHtmlFingerprints, owner);
}
else
{
return null;
}
}
/// <summary>
/// Returns the ClientID of the hidden field containing the Control fingerprints for given panel
/// </summary>
/// <param name="panelClientID"></param>
/// <returns></returns>
public static string GetControlFingerprintsField(string panelClientID)
{
return string.Format("__CONTROL_FINGERPRINTS_{0}", panelClientID);
}
public string LiteralFingerprint
{
get { return _literalFingerprint; }
set { _literalFingerprint = value; }
}
public System.Collections.SortedList ControlHtmlFingerprints
{
get { return _controlHtmlFingerprints; }
}
public void SetControlIDs(Hashtable controlHtmlFingerprints)
{
_controlHtmlFingerprints.Clear();
foreach (Control con in controlHtmlFingerprints.Keys)
_controlHtmlFingerprints.Add(con.ClientID, (string)controlHtmlFingerprints[con]);
}
/// <summary>
/// Saves the ControlState fingerprints.
/// When in AjaxCall mode, creates SetFieldScript to set hidden field when last Control fingerprints were changed.
/// </summary>
/// <param name="panelClientID"></param>
/// <param name="page"></param>
public void Save(string panelClientID, Page page)
{
System.Text.StringBuilder sbuilder = new System.Text.StringBuilder();
// Put the literal content first, before the control/fingerprint pairs
if (_owner.MagicAjaxContext.Configuration.CompareMode == MagicAjax.Configuration.OutputCompareMode.FullHtml)
{
sbuilder.Append(Convert.ToBase64String(UnicodeEncoding.Default.GetBytes(LiteralFingerprint)));
}
else
{
sbuilder.Append(LiteralFingerprint);
}
foreach (string key in _controlHtmlFingerprints.Keys)
{
sbuilder.Append(';');
if (_owner.MagicAjaxContext.Configuration.CompareMode == MagicAjax.Configuration.OutputCompareMode.FullHtml)
{
sbuilder.AppendFormat("{0}#{1}", key, Convert.ToBase64String(UnicodeEncoding.Default.GetBytes((string)_controlHtmlFingerprints[key])));
}
else
{
sbuilder.AppendFormat("{0}#{1}", key, _controlHtmlFingerprints[key]);
}
}
string serializedPanelFingerprints = sbuilder.ToString();
string panelKey = GetControlFingerprintsField(panelClientID);
if (_owner.MagicAjaxContext.IsAjaxCallForPage(_owner.Page))
{
//if ajax callback, generate javascript to set panelfingerprints's hidden field value
if (HttpContext.Current.Request.Form[panelKey] != serializedPanelFingerprints)
{
AjaxCallHelper.WriteSetFieldScript(panelKey, serializedPanelFingerprints);
}
}
else if (_owner.MagicAjaxContext.IsBrowserSupported)
{
// The hidden field is already registered at OnLoad event.
// Set its value by javascript.
page.RegisterStartupScript(panelKey + "VALUESET", String.Format("<script type='text/javascript'>AJAXCbo.SetFieldIfEmpty(\"{0}\",{1});</script>", panelKey, AjaxCallHelper.EncodeString(serializedPanelFingerprints)));
}
}
}
#endregion
#region Private Class ExtendedTextWriter
/// <summary>
/// It enables simultaneous rendering of full rendering and rendering without
/// including the html rendering of any RenderedByScriptControl controls.
/// </summary>
/// <remarks>
/// It's used by ExtendedRenderControl(Control, HtmlTextWriter, HtmlTextWriter).
/// </remarks>
private class ExtendedTextWriter : System.IO.TextWriter, IScriptRenderingDisabler
{
private HtmlTextWriter fullwriter, litewriter;
private bool onlyFullRender = false;
private bool _disableScriptRendering = false;
public override System.Text.Encoding Encoding
{
get { return null; }
}
public void IScriptWriter_RenderStart(object sender, RenderStartEventArgs e)
{
RenderedByScriptControl con = (RenderedByScriptControl)sender;
Write("<span id=\"{0}$rbs\" name=\"__ajaxmark\">", con.ClientID);
onlyFullRender = true;
}
public void IScriptWriter_RenderEnd(object sender, System.EventArgs e)
{
onlyFullRender = false;
Write("</span>");
}
public ExtendedTextWriter(HtmlTextWriter fullwriter, HtmlTextWriter litewriter, bool disableScriptRendering)
: base(null)
{
this.fullwriter = fullwriter;
this.litewriter = litewriter;
_disableScriptRendering = disableScriptRendering;
}
#region Overrides of TextWriter
public override void Write(bool value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(char value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(char[] buffer)
{
fullwriter.Write(buffer);
if (!onlyFullRender)
litewriter.Write(buffer);
}
public override void Write(char[] buffer, int index, int count)
{
fullwriter.Write(buffer, index, count);
if (!onlyFullRender)
litewriter.Write(buffer, index, count);
}
public override void Write(decimal value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(double value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(float value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(int value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(long value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(object value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(string format, object arg0)
{
fullwriter.Write(format, arg0);
if (!onlyFullRender)
litewriter.Write(format, arg0);
}
public override void Write(string format, object arg0, object arg1)
{
fullwriter.Write(format, arg0, arg1);
if (!onlyFullRender)
litewriter.Write(format, arg0, arg1);
}
public override void Write(string format, object arg0, object arg1, object arg2)
{
fullwriter.Write(format, arg0, arg1, arg2);
if (!onlyFullRender)
litewriter.Write(format, arg0, arg1, arg2);
}
public override void Write(string format, params object[] arg)
{
fullwriter.Write(format, arg);
if (!onlyFullRender)
litewriter.Write(format, arg);
}
public override void Write(string value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(uint value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
public override void Write(ulong value)
{
fullwriter.Write(value);
if (!onlyFullRender)
litewriter.Write(value);
}
#endregion
#region IScriptRenderingDisabler Members
public bool DisableScriptRendering
{
get
{
return _disableScriptRendering;
}
}
#endregion
}
#endregion
}
}