Click here to Skip to main content
15,896,432 members
Articles / Web Development / ASP.NET

ASP.NET Ajax Controls to Simulate IFrame

Rate me:
Please Sign up or sign in to vote.
4.55/5 (7 votes)
8 Nov 2009CPOL2 min read 50.2K   2.1K   44  
An Ajax control to simulate IFrame. It works like Microsoft MSDN Library site.
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Collections;
using System.Threading;
using System.ComponentModel;
using System.Web;
using System.Globalization;
using System.Diagnostics;

namespace Symber.Web.APX
{
	[ParseChildren(true)]
	[PersistChildren(true)]
	public class APXAccordion : WebControl
	{
		#region [ Const Fields ]

		internal const string ItemCountViewStateKey = "_!ItemCount";

		#endregion

		#region [ Fields ]

		private APXAccordionBehavior _behavior;
		private APXAccordionPaneCollection _panes;

		#endregion

		#region [ DataBinding Fields ]

		private object _dataSource;
		private ITemplate _headerTemplate;
		private ITemplate _contentTemplate;
		private bool _initialized;
		private bool _pagePreLoadFired;
		private bool _requiresDataBinding;
		private bool _throwOnDataPropertyChange;
		private DataSourceView _currentView;
		private bool _currentViewIsFromDataSourceID;
		private bool _currentViewValid;
		private DataSourceSelectArguments _arguments;
		IEnumerable _selectResult;
		EventWaitHandle _selectWait;

		#endregion

		#region [ Constructors ]

		public APXAccordion()
			: base(HtmlTextWriterTag.Div)
		{
		}

		#endregion

		#region [ Properties ]

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Length of the transition animation in milliseconds")]
		[DefaultValue(500)]
		public int TransitionDuration
		{
			get { return AccordionBehavior.TransitionDuration; }
			set { AccordionBehavior.TransitionDuration = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Number of frames per second used in the transition animation")]
		[DefaultValue(15)]
		public int FramesPerSecond
		{
			get { return AccordionBehavior.FramesPerSecond; }
			set { AccordionBehavior.FramesPerSecond = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Whether or not to use a fade effect in the transition animations")]
		[DefaultValue(false)]
		public bool FadeTransitions
		{
			get { return AccordionBehavior.FadeTransitions; }
			set { AccordionBehavior.FadeTransitions = value; }
		}

		[Browsable(true)]
		[Category("Appearance")]
		[Description("Default CSS class for Accordion Pane Headers")]
		public string HeaderCssClass
		{
			get { return AccordionBehavior.HeaderCssClass; }
			set { AccordionBehavior.HeaderCssClass = value; }
		}

		[Browsable(true)]
		[Category("Appearance")]
		[Description("Default CSS class for the selected Accordion Pane Headers")]
		public string HeaderSelectedCssClass
		{
			get { return AccordionBehavior.HeaderSelectedCssClass; }
			set { AccordionBehavior.HeaderSelectedCssClass = value; }
		}

		[Browsable(true)]
		[Category("Appearance")]
		[Description("Default CSS class for Accordion Pane Content")]
		public string ContentCssClass
		{
			get { return AccordionBehavior.ContentCssClass; }
			set { AccordionBehavior.ContentCssClass = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Determine how the growth of the Accordion will be controlled")]
		[DefaultValue(APXAccordionAutoSize.None)]
		public APXAccordionAutoSize AutoSize
		{
			get { return AccordionBehavior.AutoSize; }
			set { AccordionBehavior.AutoSize = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Index of the AccordionPane to be displayed")]
		[DefaultValue(0)]
		public int SelectedIndex
		{
			get { return AccordionBehavior.SelectedIndex; }
			set { AccordionBehavior.SelectedIndex = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Whether or not clicking the header will close the currently opened pane (leaving all the Accordion's panes closed)")]
		[DefaultValue(true)]
		public bool RequireOpenedPane
		{
			get { return AccordionBehavior.RequireOpenedPane; }
			set { AccordionBehavior.RequireOpenedPane = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Whether or not we suppress the client-side click handlers of any elements in the header sections")]
		[DefaultValue(false)]
		public bool SuppressHeaderPostbacks
		{
			get { return AccordionBehavior.SuppressHeaderPostbacks; }
			set { AccordionBehavior.SuppressHeaderPostbacks = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Cookie name of select index")]
		public string CookieName
		{
			get { return AccordionBehavior.CookieName; }
			set { AccordionBehavior.CookieName = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Cookie Path")]
		public string CookiePath
		{
			get { return AccordionBehavior.CookiePath; }
			set { AccordionBehavior.CookiePath = value; }
		}

		[Browsable(true)]
		[Category("Behavior")]
		[Description("Cookie enabled")]
		public bool CookieEnabled
		{
			get { return AccordionBehavior.CookieEnabled; }
			set { AccordionBehavior.CookieEnabled = value; }
		}

		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public APXAccordionPaneCollection Panes
		{
			get
			{
				if (_panes == null)
					_panes = new APXAccordionPaneCollection(this);
				return _panes;
			}
		}

		#endregion

		#region [ DataBinding Properties ]

		[Browsable(false)]
		[DefaultValue(null)]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[TemplateContainer(typeof(APXAccordionContentPanel))]
		public virtual ITemplate HeaderTemplate
		{
			get { return _headerTemplate; }
			set { _headerTemplate = value; }
		}

		[Browsable(false)]
		[DefaultValue(null)]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[TemplateContainer(typeof(APXAccordionContentPanel))]
		public virtual ITemplate ContentTemplate
		{
			get { return _contentTemplate; }
			set { _contentTemplate = value; }
		}

		[Bindable(true)]
		[Category("Data")]
		[DefaultValue(null)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual object DataSource
		{
			get { return _dataSource; }
			set
			{
				if ((value == null) || (value is IListSource) || (value is IEnumerable))
				{
					_dataSource = value;
					OnDataPropertyChanged();
				}
				else
				{
					throw new ArgumentException("Can't bind to value that is not an IListSource or an IEnumerable.");
				}
			}
		}

		[DefaultValue("")]
		[IDReferenceProperty(typeof(DataSourceControl))]
		[Category("Data")]
		public virtual string DataSourceID
		{
			get { return ViewState["DataSourceID"] as string ?? string.Empty; }
			set
			{
				ViewState["DataSourceID"] = value;
				OnDataPropertyChanged();
			}
		}

		[DefaultValue("")]
		[Category("Data")]
		public virtual string DataMember
		{
			get { return ViewState["DataMember"] as string ?? string.Empty; }
			set
			{
				ViewState["DataMember"] = value;
				OnDataPropertyChanged();
			}
		}

		protected bool IsBoundUsingDataSourceID
		{
			get { return !string.IsNullOrEmpty(DataSourceID); }
		}

		protected bool RequiresDataBinding
		{
			get { return _requiresDataBinding; }
			set { _requiresDataBinding = value; }
		}

		protected DataSourceSelectArguments SelectArguments
		{
			get
			{
				if (_arguments == null)
					_arguments = CreateDataSourceSelectArguments();
				return _arguments;
			}
		}

		#endregion

		#region [ Events ]

		public event EventHandler<APXAccordionItemEventArgs> ItemCreated;

		public event EventHandler<APXAccordionItemEventArgs> ItemDataBound;

		public event CommandEventHandler ItemCommand;

		#endregion

		#region [ DataBinding Methods ]

		private DataSourceView ConnectToDataSourceView()
		{
			// If the current view is correct, there is no need to reconnect
			if (_currentViewValid && !DesignMode)
				return _currentView;

			// Disconnect from old view, if necessary
			if ((_currentView != null) && (_currentViewIsFromDataSourceID))
			{
				// We only care about this event if we are bound through the DataSourceID property
				_currentView.DataSourceViewChanged -= new EventHandler(OnDataSourceViewChanged);
			}

			// Connect to new view
			IDataSource ds = null;
			string dataSourceID = DataSourceID;

			if (!string.IsNullOrEmpty(dataSourceID))
			{
				// Try to find a DataSource control with the ID specified in DataSourceID
				Control control = NamingContainer.FindControl(dataSourceID);
				if (control == null)
					throw new HttpException(String.Format(CultureInfo.CurrentCulture, "DataSource '{1}' for control '{0}' doesn't exist", ID, dataSourceID));
				ds = control as IDataSource;
				if (ds == null)
					throw new HttpException(String.Format(CultureInfo.CurrentCulture, "'{1}' is not a data source for control '{0}'.", ID, dataSourceID));
			}

			if (ds == null)
			{
				// DataSource control was not found, construct a temporary data source to wrap the data
				return null;
			}
			else
			{
				// Ensure that both DataSourceID as well as DataSource are not set at the same time
				if (DataSource != null)
					throw new InvalidOperationException("DataSourceID and DataSource can't be set at the same time.");
			}

			// IDataSource was found, extract the appropriate view and return it
			DataSourceView newView = ds.GetView(DataMember);
			if (newView == null)
				throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "DataSourceView not found for control '{0}'", ID));

			_currentViewIsFromDataSourceID = IsBoundUsingDataSourceID;
			_currentView = newView;
			// If we're bound through the DataSourceID proeprty, then we care about this event
			if ((_currentView != null) && (_currentViewIsFromDataSourceID))
				_currentView.DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);
			_currentViewValid = true;

			return _currentView;
		}

		public override void DataBind()
		{
			// Don't databind to a data source control when the control is in the designer but not top-level
			if (IsBoundUsingDataSourceID && DesignMode && (Site == null))
				return;

			// do our own databinding
			RequiresDataBinding = false;
			OnDataBinding(EventArgs.Empty);
		}

		protected override void OnDataBinding(EventArgs e)
		{
			base.OnDataBinding(e);

			//Only bind if the control has the DataSource or DataSourceID set
			if (this.DataSource != null || IsBoundUsingDataSourceID)
			{
				// reset the control state
				ClearPanes();
				ClearChildViewState();

				// and then create the control hierarchy using the datasource
				CreateControlHierarchy(true);
				ChildControlsCreated = true;
			}
		}

		protected virtual void CreateControlHierarchy(bool useDataSource)
		{
			int count = -1;
			IEnumerable dataSource = null;
			List<APXAccordionPane> itemsArray = new List<APXAccordionPane>();

			if (!useDataSource)
			{
				object viewCount = ViewState[ItemCountViewStateKey];

				// ViewState must have a non-null value for ItemCount because we check for
				// this in CreateChildControls
				if (viewCount != null)
				{
					count = (int)viewCount;
					if (count != -1)
					{
						List<object> dummyList = new List<object>(count);
						for (int i = 0; i < count; i++)
							dummyList.Add(null);
						dataSource = dummyList;
						itemsArray.Capacity = count;
					}
				}
			}
			else
			{
				dataSource = GetData();
				count = 0;
				ICollection collection = dataSource as ICollection;
				if (collection != null)
					itemsArray.Capacity = collection.Count;
			}

			if (dataSource != null)
			{
				int index = 0;
				foreach (object dataItem in dataSource)
				{
					APXAccordionPane ap = new APXAccordionPane();
					Controls.Add(ap);

					CreateItem(dataItem, index, APXAccordionItemType.Header, ap.HeaderContainer, HeaderTemplate, useDataSource);
					CreateItem(dataItem, index, APXAccordionItemType.Content, ap.ContentContainer, ContentTemplate, useDataSource);

					itemsArray.Add(ap);

					count++;
					index++;
				}
			}

			// If we're binding, save the number of items contained in the repeater for use in round-trips
			if (useDataSource)
				ViewState[ItemCountViewStateKey] = ((dataSource != null) ? count : -1);
		}

		private void CreateItem(object dataItem, int index, APXAccordionItemType itemType, APXAccordionContentPanel container, ITemplate template, bool dataBind)
		{

			if (template == null)
				return;

			APXAccordionItemEventArgs itemArgs = new APXAccordionItemEventArgs(container, itemType);
			OnItemCreated(itemArgs);

			container.SetDataItemProperties(dataItem, index, itemType);
			template.InstantiateIn(container);

			if (dataBind)
			{
				container.DataBind();
				OnItemDataBound(itemArgs);
			}
		}

		protected void EnsureDataBound()
		{
			try
			{
				_throwOnDataPropertyChange = true;
				if (RequiresDataBinding && !string.IsNullOrEmpty(DataSourceID))
					DataBind();
			}
			finally
			{
				_throwOnDataPropertyChange = false;
			}
		}

		protected virtual IEnumerable GetData()
		{
			_selectResult = null;
			DataSourceView view = ConnectToDataSourceView();
			if (view != null)
			{
				Debug.Assert(_currentViewValid);

				// create a handle here to make sure this is a synchronous operation.
				_selectWait = new EventWaitHandle(false, EventResetMode.AutoReset);
				view.Select(SelectArguments, new DataSourceViewSelectCallback(DoSelect));
				_selectWait.WaitOne();

			}
			else if (DataSource != null)
			{
				_selectResult = DataSource as IEnumerable;
			}
			return _selectResult;
		}

		protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments()
		{
			return DataSourceSelectArguments.Empty;
		}

		private void DoSelect(IEnumerable data)
		{
			_selectResult = data;
			_selectWait.Set();
		}

		protected override bool OnBubbleEvent(object source, EventArgs args)
		{
			bool handled = false;
			APXAccordionCommandEventArgs accordionArgs = args as APXAccordionCommandEventArgs;
			if (accordionArgs != null)
			{
				OnItemCommand(accordionArgs);
				handled = true;
			}
			return handled;
		}

		protected virtual void OnDataPropertyChanged()
		{
			if (_throwOnDataPropertyChange)
				throw new HttpException("Invalid data property change");
			if (_initialized)
				RequiresDataBinding = true;
			_currentViewValid = false;
		}

		protected virtual void OnDataSourceViewChanged(object sender, EventArgs args)
		{
			RequiresDataBinding = true;
		}

		protected virtual void OnItemCommand(APXAccordionCommandEventArgs args)
		{
			if (ItemCommand != null)
				ItemCommand(this, args);
		}

		protected virtual void OnItemCreated(APXAccordionItemEventArgs args)
		{
			if (ItemCreated != null)
				ItemCreated(this, args);
		}

		protected virtual void OnItemDataBound(APXAccordionItemEventArgs args)
		{
			if (ItemDataBound != null)
				ItemDataBound(this, args);
		}

		#endregion

		#region [ Internal Methods ]

		internal void ClearPanes()
		{
			for (int i = Controls.Count - 1; i >= 0; i--)
				if (Controls[i] is APXAccordionPane)
					Controls.RemoveAt(i);
		}

		#endregion

		#region [ Private Properties ]

		private APXAccordionBehavior AccordionBehavior
		{
			get
			{
				if (_behavior == null)
				{
					// Create the extender
					_behavior = new APXAccordionBehavior();
					_behavior.ID = ID + "_AccordionBehavior";
					_behavior.TargetControlID = ID;

					if (!this.DesignMode)
						Controls.AddAt(0, _behavior);
				}
				return _behavior;
			}
		}

		#endregion

		#region [ Private Methods ]

		private void OnPagePreLoad(object sender, EventArgs e)
		{
			_initialized = true;

			if (Page != null)
			{
				Page.PreLoad -= new EventHandler(this.OnPagePreLoad);

				// Setting RequiresDataBinding to true in OnLoad is too late because the OnLoad page event
				// happens before the control.OnLoad method gets called.  So a page_load handler on the page
				// that calls DataBind won't prevent DataBind from getting called again in PreRender.
				if (!Page.IsPostBack)
					RequiresDataBinding = true;

				// If this is a postback and viewstate is enabled, but we have never bound the control
				// before, it is probably because its visibility was changed in the postback.  In this
				// case, we need to bind the control or it will never appear.  This is a common scenario
				// for Wizard and MultiView.
				if (Page.IsPostBack && IsViewStateEnabled && ViewState[ItemCountViewStateKey] == null)
					RequiresDataBinding = true;

				_pagePreLoadFired = true;
			}
		}

		#endregion

		#region [ Override Implementation of WebControl ]

		[EditorBrowsable(EditorBrowsableState.Never)]
		public override ControlCollection Controls
		{
			get { return base.Controls; }
		}

		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);

			if (Page != null)
			{
				Page.PreLoad += new EventHandler(this.OnPagePreLoad);
				if (!IsViewStateEnabled && Page.IsPostBack)
					RequiresDataBinding = true;
			}
		}

		protected override void OnLoad(EventArgs e)
		{
			_initialized = true;
			ConnectToDataSourceView();
			if (Page != null && !_pagePreLoadFired && ViewState[ItemCountViewStateKey] == null)
			{
				// If the control was added after PagePreLoad, we still need to databind it because it missed its
				// first change in PagePreLoad.  If this control was created by a call to a parent control's DataBind
				// in Page_Load (with is relatively common), this control will already have been databound even
				// though pagePreLoad never fired and the page isn't a postback.
				if (!Page.IsPostBack)
				{
					RequiresDataBinding = true;
				}
				// If the control was added to the page after page.PreLoad, we'll never get the event and we'll
				// never databind the control.  So if we're catching up and Load happens but PreLoad never happened,
				// call DataBind.  This may make the control get databound twice if the user called DataBind on the control
				// directly in Page.OnLoad, but better to bind twice than never to bind at all.
				else if (IsViewStateEnabled)
				{
					RequiresDataBinding = true;
				}
			}

			base.OnLoad(e);
		}

		protected override void CreateChildControls()
		{
			base.CreateChildControls();

			// If we already have items in the ViewState, create the control
			// hierarchy using the view state (and not the datasource)
			if (AccordionBehavior != null && ViewState[ItemCountViewStateKey] != null)
				CreateControlHierarchy(false);

			ClearChildViewState();
		}

		protected override void OnPreRender(EventArgs e)
		{
			EnsureDataBound();
			base.OnPreRender(e);

			// Set the overflow to hidden to prevent any growth from
			// showing initially before it is hidden by the script if
			// we are controlling the height
			if (AutoSize != APXAccordionAutoSize.None)
			{
				Style[HtmlTextWriterStyle.Overflow] = "hidden";
				Style[HtmlTextWriterStyle.OverflowX] = "auto";
			}

			// Apply the standard header/content styles, but allow the
			// pane's styles to take precedent            
			foreach (APXAccordionPane pane in Panes)
			{
				if (!string.IsNullOrEmpty(HeaderCssClass) && string.IsNullOrEmpty(pane.HeaderCssClass))
					pane.HeaderCssClass = HeaderCssClass;
				if (!string.IsNullOrEmpty(ContentCssClass) && string.IsNullOrEmpty(pane.ContentCssClass))
					pane.ContentCssClass = ContentCssClass;
			}

			// Get the index of the selected pane, or use the first pane if we don't
			// have a valid index and require one.  (Note: We don't reset the SelectedIndex
			// property because it may refer to a pane that will be added dynamically on the
			// client.  If we need to start with a pane visible, then we'll open the first
			// pane because that's the default value used on the client as the SelectedIndex
			// in this scenario.)
			int index = AccordionBehavior.SelectedIndex;
			index = ((index < 0 || index >= Panes.Count) && AccordionBehavior.RequireOpenedPane) ? 0 : index;


			// Make sure the selected pane is displayed
			if (index >= 0 && index < Panes.Count)
			{
				APXAccordionContentPanel content = Panes[index].ContentContainer;
				if (content != null)
					content.Collapsed = false;

				// Set the CSS class for the open panes header
				if (!string.IsNullOrEmpty(HeaderSelectedCssClass))
					Panes[index].HeaderCssClass = HeaderSelectedCssClass;
			}
		}

		public override Control FindControl(string id)
		{
			Control ctrl = base.FindControl(id);
			if (ctrl == null)
				foreach (APXAccordionPane pane in Panes)
				{
					ctrl = pane.FindControl(id);
					if (ctrl != null)
						break;
				}
			return ctrl;
		}

		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer APEnnead.net Term
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions