#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.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace MagicAjax.UI
{
#region Public Class RenderStartEventArgs
/// <summary>
/// It contains the HtmlTextWriter writer event argument to be used during a
/// RenderStart event.
/// </summary>
public class RenderStartEventArgs : EventArgs
{
private bool abortRendering = false;
private HtmlTextWriter writer;
public bool AbortRendering
{
get { return abortRendering; }
set { abortRendering = value; }
}
public HtmlTextWriter Writer
{
get { return writer; }
}
public RenderStartEventArgs(HtmlTextWriter writer)
{
this.writer = writer;
}
}
#endregion
/// <summary>
/// The event handler for a RenderStart event.
/// </summary>
public delegate void RenderStartEventHandler(object sender, RenderStartEventArgs e);
/// <summary>
/// This control manages its appearance on the page using javascript that sends
/// to the client during an AjaxCall.
/// </summary>
/// <remarks>
/// It provides the basic functionality for controls like AjaxPanel. It manages
/// its tag attributes using javascript.
/// </remarks>
public abstract class RenderedByScriptControl : AjaxControl, IScriptWriter, INonHtmlHolder
{
protected abstract void RenderByScript();
private bool _isRenderedOnPage = false;
private bool _monitorVisibilityState = true;
private string _currentTagHtml = null;
private bool _isHtmlRendered = false;
private bool _skipRendering = false;
public event RenderStartEventHandler RenderStart;
public event EventHandler RenderEnd;
public override bool Visible
{
get
{
return base.Visible;
}
set
{
if (IsAjaxCall
&& IsRenderedOnPage
&& MonitorVisibilityState
&& value != base.Visible)
AjaxCallHelper.WriteSetVisibilityOfElementScript(ClientID, value);
base.Visible = value;
}
}
/// <summary>
/// Determines whether the RenderedByScriptControl control has been rendered
/// on the page, either by normal rendering or by script.
/// </summary>
/// <remarks>
/// IsRenderedOnPage is set to true during a normal rendering or a rendering
/// by script. It is set to false at WriteScript method when the Visible property
/// is false.
/// </remarks>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual bool IsRenderedOnPage
{
get { return _isRenderedOnPage; }
}
/// <summary>
/// Gets or sets whether every time "Visible" property is changed
/// AjaxCallHelper.WriteSetVisibilityOfElementScript method should be called.
/// Default is true.
/// </summary>
/// <remarks>
/// If this property is true, every time you change the "Visible" property during a
/// AjaxCall, the display attribute of the style of the control on the page will
/// be set to "" or "none" by calling the AjaxCallHelper.WriteSetVisibilityOfElementScript
/// method.
/// </remarks>
[Bindable(false), Category("Behavior"), DefaultValue(true)]
public bool MonitorVisibilityState
{
get { return _monitorVisibilityState; }
set { _monitorVisibilityState = value; }
}
/// <summary>
/// Returns the tag name of the control.
/// </summary>
/// <returns></returns>
public string GetTagName()
{
return TagKey.ToString();
}
public RenderedByScriptControl()
{
this.ID = "";
}
public RenderedByScriptControl(HtmlTextWriterTag tag)
: base(tag)
{
this.ID = "";
}
/// <summary>
/// Defines wether the Render method will skip rendering completely.
/// </summary>
/// <remarks>
/// Mainly used by AjaxPanel for RenderedByScriptControls that have Visible
/// property set to false.
/// </remarks>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
internal bool SkipRendering
{
get { return _skipRendering; }
set { _skipRendering = value; }
}
/// <summary>
/// It defines whether the RenderedByScriptControl was already rendered during
/// a normal rendering.
/// </summary>
/// <remarks>
/// The RenderedByScriptControl can be normally rendered by the Render method
/// even during an AjaxCall (i.e when all the controls of an AjaxPanel is new
/// and the AjaxPanel renders all of them in a single html rendering).
/// In this case it's not necessary to send any javascript to the client.
/// </remarks>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
protected bool IsHtmlRendered
{
get { return _isHtmlRendered; }
}
protected override void OnPreRender(EventArgs e)
{
if (this.MagicAjaxContext.IsBrowserSupported && !IsChildOfRenderedByScriptControl(this))
{
string hiddenStore;
if (IsPageNoStoreMode)
{
// Firefox, when performing a refresh, loads the page but keeps any INPUT
// field values. Thus, the html of a RenderedByScriptControl is restored
// as if the page was loaded because of the browser's "Back Button".
// We cannot avoid this because Firefox also keeps any changes that occured
// to ViewState, so we need the controls to keep their changes.
hiddenStore = "__" + this.ClientID + "$RBS_Store";
}
else
{
// At storing modes we don't mess with the ViewState field, so use
// a different name for the hidden field at each page request so that
// it can be reset for Firefox's "Refresh".
hiddenStore = "__" + this.ClientID + "$RBS_Store" + DateTime.Now.Ticks;
}
Page.RegisterHiddenField(hiddenStore, null);
Page.RegisterArrayDeclaration("RBS_Controls", String.Format("document.getElementById(\"{0}$RBS_Holder\")", this.ClientID));
Page.RegisterArrayDeclaration("RBS_Controls_Store", String.Format("document.{0}[\"{1}\"]", Util.GetPageFormID(this.Page), hiddenStore));
// Keep track of last Ajax control, so we can run some additional code after rendering the last Ajax control
if (!HttpContext.Current.Items.Contains("__LAST_AJAX_CONTROL"))
HttpContext.Current.Items.Add("__LAST_AJAX_CONTROL", this.ClientID);
else
HttpContext.Current.Items["__LAST_AJAX_CONTROL"] = this.ClientID;
}
base.OnPreRender(e);
}
protected bool IsChildOfRenderedByScriptControl(Control control)
{
if (control.Parent == null || control.Parent == control.Page)
return false;
else if (control.Parent is RenderedByScriptControl)
return true;
else
return IsChildOfRenderedByScriptControl(control.Parent);
}
protected override void OnAjaxCallStart(EventArgs e)
{
_isHtmlRendered = false;
base.OnAjaxCallStart(e);
}
/// <summary>
/// Normal rendering.
/// </summary>
/// <remarks>
/// It raises the RenderStart and RenderEnd events. If the rendering is not
/// aborted during the RenderStart event, it renders the control and sets
/// the _isHtmlRendered variable to true.
/// </remarks>
/// <param name="writer"></param>
protected override void Render(HtmlTextWriter writer)
{
RenderStartEventArgs eventArgs = new RenderStartEventArgs(writer);
OnRenderStart(eventArgs);
if (!eventArgs.AbortRendering && !SkipRendering)
{
if (!IsAjaxCall || (writer.InnerWriter is IScriptRenderingDisabler && (writer.InnerWriter as IScriptRenderingDisabler).DisableScriptRendering))
{
if (!IsChildOfRenderedByScriptControl(this))
{
// Put the html of this control inside a SPAN tag so that it can
// be saved and restored when the page is loaded from the Browser's cache,
// i.e. when the back button is pressed.
writer.Write("<span id='{0}$RBS_Holder'>", this.ClientID);
base.Render(writer);
writer.Write("</span>");
}
else
base.Render(writer);
_isHtmlRendered = true;
_isRenderedOnPage = true;
}
else
{
WriteScript();
}
}
#if !MEDIUM_TRUST
if (this.MagicAjaxContext.IsBrowserSupported && !IsChildOfRenderedByScriptControl(this))
{
if ((string)HttpContext.Current.Items["__LAST_AJAX_CONTROL"] == this.ClientID)
{
// This is the last Ajax control.
// Reflect added startup-scripts and arrays AFTER reflecting the controls HTML
if (MagicAjaxContext.Current.IsAjaxCall)
{
AjaxCallHelper.HandleArrayDeclares(this.Page, true);
AjaxCallHelper.HandleClientStartupScripts(this.Page, true);
bool reflectFingerprints = false;
// See if one of the _currentScriptFPs is not in _previousScriptFPs
for (int i = 0; i < AjaxCallHelper._currentScriptFPs.Count && !reflectFingerprints; i++)
{
if (!AjaxCallHelper._previousScriptFPs.Contains(AjaxCallHelper._currentScriptFPs[i]))
{
// Store scriptblock fingerprints in hidden field
string allScriptFPs = string.Join(";", (string[])AjaxCallHelper._currentScriptFPs.ToArray(typeof(string)));
AjaxCallHelper.WriteSetFieldScript("__MAGICAJAX_SCRIPT_FINGERPRINTS", allScriptFPs);
break;
}
}
}
else
{
// Normal request (i.e. first page request)
AjaxCallHelper.HandleArrayDeclares(this.Page, false);
AjaxCallHelper.HandleClientStartupScripts(this.Page, false);
// Fill hidden field __MAGICAJAX_SCRIPT_FINGERPRINTS using script
writer.WriteLine("<script type=\"text/javascript\">document.{0}[\"__MAGICAJAX_SCRIPT_FINGERPRINTS\"].value={1};</script>", Util.GetPageFormID(this.Page), AjaxCallHelper.EncodeString(string.Join(";", (string[])AjaxCallHelper._currentScriptFPs.ToArray(typeof(string)))));
}
}
}
#endif
OnRenderEnd(EventArgs.Empty);
}
/// <summary>
/// It stores the tag html for later checking.
/// </summary>
/// <param name="writer"></param>
public override void RenderBeginTag(HtmlTextWriter writer)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HtmlTextWriter strwriter = new HtmlTextWriter(new System.IO.StringWriter(sb));
base.RenderBeginTag(strwriter);
_currentTagHtml = sb.ToString();
base.RenderBeginTag(writer);
}
/// <summary>
/// If the tag html of the control is changed, send the attributes using javascript.
/// </summary>
public virtual void WriteScript()
{
if (!this.IsInAjaxScope)
return;
if (!this.Visible)
{
if (_isRenderedOnPage)
{
// Verify that it is indeed rendered on page
_isRenderedOnPage = (GetTopInvisibleControl(this) is RenderedByScriptControl);
}
return;
}
// If there was a normal rendering, javascript is not needed
if (IsHtmlRendered)
return;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
HtmlTextWriter strwriter = new HtmlTextWriter(new System.IO.StringWriter(sb));
if (!IsRenderedOnPage)
{
// Put the control's tag on page
PutTagOnPageForAjaxCall();
}
else
{
// Take care of the tag of the control
// TODO: Take care of the tag for 'NoStore' mode too.
if (!IsPageNoStoreMode)
{
base.RenderBeginTag(strwriter);
string html = sb.ToString();
sb.Length = 0;
if (_currentTagHtml != html)
{
AjaxCallHelper.WriteSetAttributesOfControl(ClientID, FormatAttributes(html));
_currentTagHtml = html;
}
}
}
this.RenderByScript();
_isRenderedOnPage = true;
}
/// <summary>
/// Called when the control gets visible during AjaxCall and is not already
/// rendered on page.
/// </summary>
/// <remarks>
/// RenderedByScriptControl's method throws an exception because it doesn't know
/// where to put the tag. An inheriting control should override this method
/// to provide the appropriate functionality.
/// </remarks>
protected virtual void PutTagOnPageForAjaxCall()
{
throw new MagicAjaxException(String.Format("RenderByScript control '{0}' cannot get visible during an AjaxCall.", this.ClientID));
}
/// <summary>
/// Gets the top control that has its Visible property set to false.
/// </summary>
/// <remarks>
/// It's used to determine whether the control is rendered on page or not.
/// </remarks>
/// <param name="control"></param>
/// <returns></returns>
protected Control GetTopInvisibleControl(Control control)
{
if (control.Parent == null || control.Parent.Visible)
return control;
else
return GetTopInvisibleControl(control.Parent);
}
protected virtual void OnRenderStart(RenderStartEventArgs eventArgs)
{
if (RenderStart != null)
RenderStart(this, eventArgs);
}
protected virtual void OnRenderEnd(EventArgs e)
{
if (RenderEnd != null)
RenderEnd(this, e);
}
/// <summary>
/// Finds the next visible control of the control collection of this
/// control's parent that has its ID attribute set.
/// </summary>
/// <returns></returns>
private Control FindNextVisibleSibling()
{
for (int i = Parent.Controls.IndexOf(this) + 1; i < Parent.Controls.Count; i++)
{
Control con = Parent.Controls[i];
if (con.Visible && con.ID != null)
return con;
}
return null;
}
/// <summary>
/// It finds the attributes from the html of the control's tag, and formats them
/// so that they are send by AjaxCallHelper.WriteSetAttributesOfControl method.
/// </summary>
/// <param name="html">The html tag of the control</param>
/// <returns></returns>
private string FormatAttributes(string html)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(html);
System.Text.StringBuilder attribs = new System.Text.StringBuilder();
int mode = 0;
for (int i = 0; i < sb.Length; i++)
{
char ch = sb[i];
if (ch == '>') break;
switch (mode)
{
case 0:
if (ch == ' ') mode++;
break;
case 1:
switch (ch)
{
case '\"':
mode++;
break;
case ' ':
attribs.Append('|');
break;
default:
attribs.Append(ch);
break;
}
break;
case 2:
if (ch == '\"')
mode--;
else
attribs.Append(ch);
break;
}
}
return attribs.ToString();
}
/// <summary>
/// Interface used with a HtmlTextWriter to define whether to disable
/// script rendering and do a normal rendering or not.
/// </summary>
protected interface IScriptRenderingDisabler
{
bool DisableScriptRendering
{
get;
}
}
}
}