2 // System.Web.UI.WebControls.Repeater.cs
5 // Ben Maurer (bmaurer@novell.com)
7 // (C) 2005 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 // Helpful resources while implementing this class:
31 // _Developing Microsoft ASP.NET Server Controls and Components_ (Kothari, Datye)
32 // Chapters 16 and 20 (especially listing 20-3 on page 559)
34 // "Building DataBound Templated Custom ASP.NET Server Controls" (Mitchell) on MSDN
35 // Right now, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/databoundtemplatedcontrols.asp
36 // works, but with msdn we all know that urls have a very short lifetime :-)
39 using System.Collections;
40 using System.ComponentModel;
41 using System.Security.Permissions;
42 using System.Web.Util;
44 namespace System.Web.UI.WebControls {
47 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48 [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50 [DefaultEvent ("ItemCommand")]
51 [DefaultProperty ("DataSource")]
52 [Designer ("System.Web.UI.Design.WebControls.RepeaterDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
53 [ParseChildren (true)]
54 [PersistChildren (false)]
55 public class Repeater : Control, INamingContainer {
59 IDataSource boundDataSource;
60 private bool initialized;
61 private bool preRendered = false;
62 private bool requiresDataBinding;
63 private DataSourceSelectArguments selectArguments;
64 private IEnumerable data;
67 // See Kothari, listing 20-3
73 override void CreateChildControls ()
75 // We are recreating the children from viewstate
78 // Build the children from the viewstate
79 if (ViewState ["Items"] != null)
80 CreateControlHierarchy (false);
83 // See Kothari, listing 20-3
84 protected override void OnDataBinding (EventArgs e)
86 base.OnDataBinding (EventArgs.Empty);
89 ClearChildViewState ();
92 CreateControlHierarchy (true);
94 ChildControlsCreated = true;
97 void DoItem (int i, ListItemType t, object d, bool databind)
99 RepeaterItem itm = CreateItem (i, t);
101 if (t == ListItemType.Item || t == ListItemType.AlternatingItem)
105 RepeaterItemEventArgs e = new RepeaterItemEventArgs (itm);
106 InitializeItem (itm);
109 // It is very important that this be called *before* data
110 // binding. Otherwise, we won't save our state in the viewstate.
121 protected virtual void CreateControlHierarchy (bool useDataSource)
124 items = new ArrayList ();
131 // Optimize (shouldn't need all this memory ;-)
132 ds = new object [(int) ViewState ["Items"]];
135 // If there is no datasource, then we don't show anything. the "Items"
136 // viewstate won't get set, so on postback, we won't get here
140 if (HeaderTemplate != null)
141 DoItem (-1, ListItemType.Header, null, useDataSource);
144 foreach (object o in ds) {
145 if (idx != 0 && SeparatorTemplate != null)
146 DoItem (idx - 1, ListItemType.Separator, null, useDataSource);
148 DoItem (idx, idx % 2 == 0 ? ListItemType.Item : ListItemType.AlternatingItem, o, useDataSource);
152 if (FooterTemplate != null)
153 DoItem (-1, ListItemType.Footer, null, useDataSource);
155 ViewState ["Items"] = idx;
158 // Why does this get overriden?
159 public override void DataBind ()
161 // In all the examples I've seen online, this does base.OnDataBinding and
162 // then does all the create child controls stuff. But from stack traces on
163 // windows, this doesn't seem to be the case here.
164 OnDataBinding (EventArgs.Empty);
167 RequiresDataBinding = false;
171 protected virtual RepeaterItem CreateItem (int itemIndex, ListItemType itemType)
173 return new RepeaterItem (itemIndex, itemType);
176 protected virtual void InitializeItem (RepeaterItem item)
180 switch (item.ItemType) {
181 case ListItemType.Header:
184 case ListItemType.Footer:
187 case ListItemType.Item:
190 case ListItemType.AlternatingItem:
191 t = AlternatingItemTemplate;
195 case ListItemType.Separator:
196 t = SeparatorTemplate;
201 t.InstantiateIn (item);
205 protected override bool OnBubbleEvent (object sender, EventArgs e)
207 RepeaterCommandEventArgs rcea = e as RepeaterCommandEventArgs;
209 OnItemCommand (rcea);
217 public override ControlCollection Controls {
219 EnsureChildControls ();
220 return base.Controls;
225 RepeaterItemCollection itemscol;
228 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
229 [WebSysDescription ("")]
230 public virtual RepeaterItemCollection Items {
232 if (itemscol == null) {
234 items = new ArrayList ();
236 itemscol = new RepeaterItemCollection (items);
243 [WebSysDescription ("")]
244 [WebCategory ("Data")]
245 public virtual string DataMember {
247 return ViewState.GetString ("DataMember", "");
251 ViewState.Remove ("DataMember");
253 ViewState ["DataMember"] = value;
257 OnDataPropertyChanged ();
264 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
265 [WebSysDescription ("")]
266 [WebCategory ("Data")]
267 public virtual object DataSource {
273 if (value == null || value is IListSource || value is IEnumerable) {
275 // FIXME - can't duplicate in a test case ? LAMESPEC ?
276 // can't duplicate in a test case
277 // if ((dataSourceId != null) && (dataSourceId.Length != 0))
278 // throw new HttpException (Locale.GetText ("DataSourceID is already set."));
283 OnDataPropertyChanged ();
288 throw new ArgumentException (String.Format (
289 "An invalid data source is being used for {0}. A valid data source must implement either IListSource or IEnumerable",
296 [IDReferenceProperty (typeof (DataSourceControl))]
297 public virtual string DataSourceID
300 return ViewState.GetString ("DataSourceID", "");
303 if (dataSource != null)
304 throw new HttpException ("Only one of DataSource and DataSourceID can be specified.");
305 ViewState ["DataSourceID"] = value;
308 OnDataPropertyChanged ();
313 [DefaultValue(false)]
314 public override bool EnableTheming {
315 get { return base.EnableTheming; }
316 set { base.EnableTheming = value; }
320 ITemplate alt_itm_tmpl;
323 [PersistenceMode(PersistenceMode.InnerProperty)]
324 [TemplateContainer (typeof (RepeaterItem))]
325 [WebSysDescription ("")]
326 public virtual ITemplate AlternatingItemTemplate {
331 alt_itm_tmpl = value;
335 ITemplate footer_tmpl;
338 [PersistenceMode(PersistenceMode.InnerProperty)]
339 [TemplateContainer (typeof (RepeaterItem))]
340 [WebSysDescription ("")]
341 public virtual ITemplate FooterTemplate {
350 ITemplate header_tmpl;
353 [PersistenceMode(PersistenceMode.InnerProperty)]
354 [TemplateContainer (typeof (RepeaterItem))]
355 [WebSysDescription ("")]
356 public virtual ITemplate HeaderTemplate {
368 [PersistenceMode(PersistenceMode.InnerProperty)]
369 [TemplateContainer (typeof (RepeaterItem))]
370 [WebSysDescription ("")]
371 public virtual ITemplate ItemTemplate {
380 ITemplate separator_tmpl;
383 [PersistenceMode(PersistenceMode.InnerProperty)]
384 [TemplateContainer (typeof (RepeaterItem))]
385 [WebSysDescription ("")]
386 public virtual ITemplate SeparatorTemplate {
388 return separator_tmpl;
391 separator_tmpl = value;
396 protected virtual void OnItemCommand (RepeaterCommandEventArgs e)
398 RepeaterCommandEventHandler h = (RepeaterCommandEventHandler) Events [ItemCommandEvent];
403 static readonly object ItemCommandEvent = new object ();
405 [WebSysDescription ("")]
406 [WebCategory ("Action")]
407 public event RepeaterCommandEventHandler ItemCommand {
408 add { Events.AddHandler (ItemCommandEvent, value); }
409 remove { Events.RemoveHandler (ItemCommandEvent, value); }
413 protected virtual void OnItemCreated (RepeaterItemEventArgs e)
415 RepeaterItemEventHandler h = (RepeaterItemEventHandler) Events [ItemCreatedEvent];
420 static readonly object ItemCreatedEvent = new object ();
422 [WebSysDescription ("")]
423 [WebCategory ("Behavior")]
424 public event RepeaterItemEventHandler ItemCreated {
425 add { Events.AddHandler (ItemCreatedEvent, value); }
426 remove { Events.RemoveHandler (ItemCreatedEvent, value); }
429 protected virtual void OnItemDataBound (RepeaterItemEventArgs e)
431 RepeaterItemEventHandler h = (RepeaterItemEventHandler) Events [ItemDataBoundEvent];
436 static readonly object ItemDataBoundEvent = new object ();
438 [WebSysDescription ("")]
439 [WebCategory ("Behavior")]
440 public event RepeaterItemEventHandler ItemDataBound {
441 add { Events.AddHandler (ItemDataBoundEvent, value); }
442 remove { Events.RemoveHandler (ItemDataBoundEvent, value); }
446 protected bool Initialized {
447 get { return initialized; }
450 protected bool IsBoundUsingDataSourceID
452 get { return (DataSourceID.Length != 0); }
455 protected bool RequiresDataBinding
457 get { return requiresDataBinding; }
459 requiresDataBinding = value;
460 if (value && preRendered && IsBoundUsingDataSourceID && Page != null && !Page.IsCallback)
465 protected DataSourceSelectArguments SelectArguments
468 // MSDN: The first call to the SelectArguments property calls the
469 // CreateDataSourceSelectArguments method to return the Empty value.
470 if (selectArguments == null)
471 selectArguments = CreateDataSourceSelectArguments();
472 return selectArguments;
476 protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments ()
478 // MSDN: Returns the Empty value.
479 return DataSourceSelectArguments.Empty;
482 protected void EnsureDataBound ()
484 if (IsBoundUsingDataSourceID && RequiresDataBinding)
488 private void SelectCallback (IEnumerable data)
497 IEnumerable GetData ()
501 if (IsBoundUsingDataSourceID) {
502 if (DataSourceID.Length == 0)
505 if (boundDataSource == null)
508 DataSourceView dsv = boundDataSource.GetView (String.Empty);
509 dsv.Select (SelectArguments, new DataSourceViewSelectCallback (SelectCallback));
516 result = DataSourceResolver.ResolveDataSource (DataSource, DataMember);
522 protected virtual void OnDataPropertyChanged ()
525 RequiresDataBinding = true;
528 protected virtual void OnDataSourceViewChanged (object sender, EventArgs e)
530 RequiresDataBinding = true;
533 protected internal override void OnInit (EventArgs e)
537 Page.PreLoad += new EventHandler (OnPagePreLoad);
539 if (!IsViewStateEnabled && Page.IsPostBack)
540 RequiresDataBinding = true;
544 protected virtual void OnPagePreLoad (object sender, EventArgs e)
549 protected internal override void OnLoad (EventArgs e)
557 private void Initialize ()
560 if (!Page.IsPostBack || (IsViewStateEnabled && (ViewState ["Items"] == null)))
561 RequiresDataBinding = true;
564 if (IsBoundUsingDataSourceID)
565 ConnectToDataSource ();
570 protected internal override void OnPreRender (EventArgs e)
574 base.OnPreRender (e);
577 void ConnectToDataSource ()
579 /* verify that the data source exists and is an IDataSource */
582 ctrl = Parent.FindControl (DataSourceID);
584 if (ctrl == null || !(ctrl is IDataSource)) {
588 format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource. A control with ID '{1}' could not be found.";
590 format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource. '{1}' is not an IDataSource.";
592 throw new HttpException (String.Format (format, ID, DataSourceID));
595 boundDataSource = (IDataSource)ctrl;
596 boundDataSource.GetView (String.Empty).DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);