Merge pull request #2020 from tomjepp/master
[mono.git] / mcs / class / System.Web / System.Web.UI.WebControls / Repeater.cs
1 //
2 // System.Web.UI.WebControls.Repeater.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@novell.com)
6 //
7 // (C) 2005 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 // Helpful resources while implementing this class:
30 //
31 // _Developing Microsoft ASP.NET Server Controls and Components_ (Kothari, Datye)
32 //    Chapters 16 and 20 (especially listing 20-3 on page 559)
33 //
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 :-)
37 //
38
39 using System.Collections;
40 using System.ComponentModel;
41 using System.Security.Permissions;
42 using System.Web.Util;
43
44 namespace System.Web.UI.WebControls {
45
46         // CAS
47         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
49         // attributes
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
56         {
57                 object dataSource;
58                 IDataSource boundDataSource;
59                 bool initialized;
60                 bool preRendered = false;
61                 bool requiresDataBinding;
62                 DataSourceSelectArguments selectArguments;
63                 IEnumerable data;
64
65                 // See Kothari, listing 20-3
66                 protected internal
67                 override void CreateChildControls ()
68                 {
69                         // We are recreating the children from viewstate
70                         Controls.Clear();
71
72                         // Build the children from the viewstate
73                         if (ViewState ["Items"] != null)
74                                 CreateControlHierarchy (false);
75                 }
76                 
77                 // See Kothari, listing 20-3
78                 protected override void OnDataBinding (EventArgs e)
79                 {
80                         base.OnDataBinding (EventArgs.Empty);
81
82                         Controls.Clear ();
83                         ClearChildViewState ();
84                         TrackViewState ();
85
86                         CreateControlHierarchy (true);
87
88                         ChildControlsCreated = true;
89                 }
90
91                 void DoItem (int i, ListItemType t, object d, bool databind)
92                 {
93                         RepeaterItem itm = CreateItem (i, t);
94
95                         if (t == ListItemType.Item || t == ListItemType.AlternatingItem)
96                                 items.Add (itm);
97                         
98                         itm.DataItem = d;
99                         RepeaterItemEventArgs e = new RepeaterItemEventArgs (itm);
100                         InitializeItem (itm);
101                         
102                         //
103                         // It is very important that this be called *before* data
104                         // binding. Otherwise, we won't save our state in the viewstate.
105                         //
106                         Controls.Add (itm);
107                         OnItemCreated (e);
108
109                         if (databind) {
110                                 itm.DataBind ();
111                                 OnItemDataBound (e);
112                         }
113                 }
114                 
115                 protected virtual void CreateControlHierarchy (bool useDataSource)
116                 {
117                         IEnumerable ds;
118                         items = new ArrayList ();
119                         itemscol = null;
120                         
121                         if (useDataSource) {
122                                 ds = GetData ();
123                         }
124                         else {
125                                 // Optimize (shouldn't need all this memory ;-)
126                                 ds = new object [(int) ViewState ["Items"]];
127                         }
128
129                         // If there is no datasource, then we don't show anything. the "Items"
130                         // viewstate won't get set, so on postback, we won't get here
131                         if (ds == null)
132                                 return;
133
134                         if (HeaderTemplate != null)
135                                 DoItem (-1, ListItemType.Header, null, useDataSource);
136
137                         int idx = 0;
138                         foreach (object o in ds) {
139                                 if (idx != 0 && SeparatorTemplate != null)
140                                         DoItem (idx - 1, ListItemType.Separator, null, useDataSource);
141
142                                 DoItem (idx, idx % 2 == 0 ? ListItemType.Item : ListItemType.AlternatingItem, o, useDataSource);
143                                 idx ++;
144                         }
145                         
146                         if (FooterTemplate != null)
147                                 DoItem (-1, ListItemType.Footer, null, useDataSource);
148
149                         ViewState ["Items"] = idx;
150                 }
151                 
152                 // Why does this get overriden?
153                 public override void DataBind ()
154                 {
155                         // In all the examples I've seen online, this does base.OnDataBinding and
156                         // then does all the create child controls stuff. But from stack traces on
157                         // windows, this doesn't seem to be the case here.
158                         OnDataBinding (EventArgs.Empty);
159
160                         RequiresDataBinding = false;
161                 }
162                 
163                 protected virtual RepeaterItem CreateItem (int itemIndex, ListItemType itemType)
164                 {
165                         return new RepeaterItem (itemIndex, itemType);
166                 }
167                 
168                 protected virtual void InitializeItem (RepeaterItem item)
169                 {
170                         ITemplate t = null;
171                         
172                         switch (item.ItemType) {
173                         case ListItemType.Header:
174                                 t = HeaderTemplate;
175                                 break;
176                         case ListItemType.Footer:
177                                 t = FooterTemplate;
178                                 break;  
179                         case ListItemType.Item:
180                                 t = ItemTemplate;
181                                 break;
182                         case ListItemType.AlternatingItem:
183                                 t = AlternatingItemTemplate;
184                                 if (t == null)
185                                         t = ItemTemplate;
186                                 break;
187                         case ListItemType.Separator:
188                                 t = SeparatorTemplate;
189                                 break;
190                         }
191
192                         if (t != null)
193                                 t.InstantiateIn (item);                 
194                 }
195                 
196
197                 protected override bool OnBubbleEvent (object sender, EventArgs e)
198                 {
199                         RepeaterCommandEventArgs rcea = e as RepeaterCommandEventArgs;
200                         if (rcea != null) {
201                                 OnItemCommand (rcea);
202                                 return true;
203                         }
204
205                         return false;
206                 }
207
208         
209                 public override ControlCollection Controls {
210                         get {
211                                 EnsureChildControls ();
212                                 return base.Controls;
213                         }
214                         
215                 }
216
217                 RepeaterItemCollection itemscol;
218                 ArrayList items;
219                 [Browsable(false)]
220                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
221                 [WebSysDescription ("")]
222                 public virtual RepeaterItemCollection Items {
223                         get {
224                                 if (itemscol == null) {
225                                         if (items == null)
226                                                 items = new ArrayList ();
227
228                                         itemscol = new RepeaterItemCollection (items);
229                                 }
230                                 return itemscol;
231                         }
232                 }
233                 
234                 [DefaultValue("")]
235                 [WebSysDescription ("")]
236                 [WebCategory ("Data")]
237                 public virtual string DataMember {
238                         get {
239                                 return ViewState.GetString ("DataMember", "");
240                         }
241                         set {
242                                 if (value == null)
243                                         ViewState.Remove ("DataMember");
244                                 else
245                                         ViewState ["DataMember"] = value;
246
247                                 if (!Initialized)
248                                         OnDataPropertyChanged ();
249                         }
250                 }
251
252                 [Bindable(true)]
253                 [DefaultValue(null)]
254                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
255                 [WebSysDescription ("")]
256                 [WebCategory ("Data")]
257                 public virtual object DataSource {
258                         get {
259                                 return dataSource;
260                         }
261                         
262                         set {
263                                 if (value == null || value is IListSource || value is IEnumerable) {
264 // FIXME - can't duplicate in a test case ? LAMESPEC ?
265 // can't duplicate in a test case
266 //                                      if ((dataSourceId != null) && (dataSourceId.Length != 0))
267 //                                              throw new HttpException (Locale.GetText ("DataSourceID is already set."));
268
269                                         dataSource = value;
270
271                                         if (!Initialized)
272                                                 OnDataPropertyChanged ();
273                                 } else
274                                         throw new ArgumentException (String.Format (
275                                             "An invalid data source is being used for {0}. A valid data source must implement either IListSource or IEnumerable",
276                                             ID));
277                         }
278                 }
279
280                 [DefaultValue ("")]
281                 [IDReferenceProperty (typeof (DataSourceControl))]
282                 public virtual string DataSourceID
283                 {
284                         get {
285                                 return ViewState.GetString ("DataSourceID", "");
286                         }
287                         set {
288                                 if (dataSource != null)
289                                         throw new HttpException ("Only one of DataSource and DataSourceID can be specified.");
290                                 ViewState ["DataSourceID"] = value;
291
292                                 if (!Initialized)
293                                         OnDataPropertyChanged ();
294                         }
295                 }
296
297                 [Browsable (true)]
298                 public override bool EnableTheming {
299                         get { return base.EnableTheming; }
300                         set { base.EnableTheming = value; }
301                 }
302
303                 ITemplate alt_itm_tmpl;
304                 [Browsable(false)]
305                 [DefaultValue(null)]
306                 [PersistenceMode(PersistenceMode.InnerProperty)]
307                 [TemplateContainer (typeof (RepeaterItem))]
308                 [WebSysDescription ("")]
309                 public virtual ITemplate AlternatingItemTemplate {
310                         get {
311                                 return alt_itm_tmpl;
312                         }
313                         set {
314                                 alt_itm_tmpl = value;
315                         }
316                 }               
317                 
318                 ITemplate footer_tmpl;
319                 [Browsable(false)]
320                 [DefaultValue(null)]
321                 [PersistenceMode(PersistenceMode.InnerProperty)]
322                 [TemplateContainer (typeof (RepeaterItem))]
323                 [WebSysDescription ("")]
324                 public virtual ITemplate FooterTemplate {
325                         get {
326                                 return footer_tmpl;
327                         }
328                         set {
329                                 footer_tmpl = value;
330                         }
331                 }
332
333                 ITemplate header_tmpl;
334                 [Browsable(false)]
335                 [DefaultValue(null)]
336                 [PersistenceMode(PersistenceMode.InnerProperty)]
337                 [TemplateContainer (typeof (RepeaterItem))]
338                 [WebSysDescription ("")]
339                 public virtual ITemplate HeaderTemplate {
340                         get {
341                                 return header_tmpl;
342                         }
343                         set {
344                                 header_tmpl = value;
345                         }
346                 }
347
348                 ITemplate item_tmpl;
349                 [Browsable(false)]
350                 [DefaultValue(null)]
351                 [PersistenceMode(PersistenceMode.InnerProperty)]
352                 [TemplateContainer (typeof (RepeaterItem))]
353                 [WebSysDescription ("")]
354                 public virtual ITemplate ItemTemplate {
355                         get {
356                                 return item_tmpl;
357                         }
358                         set {
359                                 item_tmpl = value;
360                         }
361                 }
362
363                 ITemplate separator_tmpl;
364                 [Browsable(false)]
365                 [DefaultValue(null)]
366                 [PersistenceMode(PersistenceMode.InnerProperty)]
367                 [TemplateContainer (typeof (RepeaterItem))]
368                 [WebSysDescription ("")]
369                 public virtual ITemplate SeparatorTemplate {
370                         get {
371                                 return separator_tmpl;
372                         }
373                         set {
374                                 separator_tmpl = value;
375                         }
376                 }
377
378                 
379                 protected virtual void OnItemCommand (RepeaterCommandEventArgs e)
380                 {
381                         RepeaterCommandEventHandler h = (RepeaterCommandEventHandler) Events [ItemCommandEvent];
382                         if (h != null)
383                                 h (this, e);
384                 }
385
386                 static readonly object ItemCommandEvent = new object ();
387
388                 [WebSysDescription ("")]
389                 [WebCategory ("Action")]
390                 public event RepeaterCommandEventHandler ItemCommand {
391                         add { Events.AddHandler (ItemCommandEvent, value); }
392                         remove { Events.RemoveHandler (ItemCommandEvent, value); }
393                 }
394
395                 
396                 protected virtual void OnItemCreated (RepeaterItemEventArgs e)
397                 {
398                         RepeaterItemEventHandler h = (RepeaterItemEventHandler) Events [ItemCreatedEvent];
399                         if (h != null)
400                                 h (this, e);
401                 }
402
403                 static readonly object ItemCreatedEvent = new object ();
404
405                 [WebSysDescription ("")]
406                 [WebCategory ("Behavior")]
407                 public event RepeaterItemEventHandler ItemCreated {
408                         add { Events.AddHandler (ItemCreatedEvent, value); }
409                         remove { Events.RemoveHandler (ItemCreatedEvent, value); }
410                 }
411                 
412                 protected virtual void OnItemDataBound (RepeaterItemEventArgs e) 
413                 {
414                         RepeaterItemEventHandler h = (RepeaterItemEventHandler) Events [ItemDataBoundEvent];
415                         if (h != null)
416                                 h (this, e);
417                 }
418                 
419                 static readonly object ItemDataBoundEvent = new object ();
420
421                 [WebSysDescription ("")]
422                 [WebCategory ("Behavior")]
423                 public event RepeaterItemEventHandler ItemDataBound {
424                         add { Events.AddHandler (ItemDataBoundEvent, value); }
425                         remove { Events.RemoveHandler (ItemDataBoundEvent, value); }
426                 }
427
428                 protected bool Initialized {
429                         get { return initialized; }
430                 }
431
432                 protected bool IsBoundUsingDataSourceID
433                 {
434                         get { return (DataSourceID.Length != 0); }
435                 }
436
437                 protected bool RequiresDataBinding
438                 {
439                         get { return requiresDataBinding; }
440                         set { 
441                                 requiresDataBinding = value;
442                                 if (value && preRendered && IsBoundUsingDataSourceID && Page != null && !Page.IsCallback)
443                                         EnsureDataBound ();
444                         }
445                 }
446
447                 protected DataSourceSelectArguments SelectArguments
448                 {
449                         get {
450                                 // MSDN: The first call to the SelectArguments property calls the 
451                                 // CreateDataSourceSelectArguments method to return the Empty value.
452                                 if (selectArguments == null)
453                                         selectArguments = CreateDataSourceSelectArguments();
454                                 return selectArguments;
455                         }
456                 }
457
458                 protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments ()
459                 {
460                         // MSDN: Returns the Empty value. 
461                         return DataSourceSelectArguments.Empty;
462                 }
463
464                 protected void EnsureDataBound ()
465                 {
466                         if (IsBoundUsingDataSourceID && RequiresDataBinding)
467                                 DataBind ();
468                 }
469
470                 void SelectCallback (IEnumerable data)
471                 {
472                         this.data = data;
473                 }
474
475                 protected virtual 
476                 IEnumerable GetData ()
477                 {
478                         IEnumerable result;
479                         if (IsBoundUsingDataSourceID) {
480                                 if (DataSourceID.Length == 0)
481                                         return null;
482
483                                 if (boundDataSource == null)
484                                         return null;
485
486                                 DataSourceView dsv = boundDataSource.GetView (String.Empty);
487                                 dsv.Select (SelectArguments, new DataSourceViewSelectCallback (SelectCallback));
488
489                                 result = data;
490                                 data = null;
491                         }
492                         else
493                                 result = DataSourceResolver.ResolveDataSource (DataSource, DataMember);
494
495                         return result;
496                 }
497
498                 protected virtual void OnDataPropertyChanged ()
499                 {
500                         if (Initialized)
501                                 RequiresDataBinding = true;
502                 }
503
504                 protected virtual void OnDataSourceViewChanged (object sender, EventArgs e)
505                 {
506                         RequiresDataBinding = true;
507                 }
508
509                 protected internal override void OnInit (EventArgs e)
510                 {
511                         base.OnInit (e);
512                         Page page = Page;
513                         if (page != null) {
514                                 page.PreLoad += new EventHandler (OnPagePreLoad);
515
516                                 if (!IsViewStateEnabled && page.IsPostBack)
517                                         RequiresDataBinding = true;
518                         }
519                 }
520
521                 void OnPagePreLoad (object sender, EventArgs e) 
522                 {
523                         Initialize ();
524                 }
525
526                 protected internal override void OnLoad (EventArgs e)
527                 {
528                         if (!Initialized)
529                                 Initialize ();
530
531                         base.OnLoad (e);
532                 }
533
534                 void Initialize () 
535                 {
536                         Page page = Page;
537                         if (page != null) {
538                                 if (!page.IsPostBack || (IsViewStateEnabled && (ViewState ["Items"] == null)))
539                                         RequiresDataBinding = true;
540                         }
541                         
542                         if (IsBoundUsingDataSourceID)
543                                 ConnectToDataSource ();
544                 
545                         initialized = true;
546                 }
547
548                 protected internal override void OnPreRender (EventArgs e)
549                 {
550                         preRendered = true;
551                         EnsureDataBound ();
552                         base.OnPreRender (e);
553                 }
554
555                 void ConnectToDataSource ()
556                 {
557                         /* verify that the data source exists and is an IDataSource */
558                         object ctrl = null;
559                         if (Parent != null)
560                                 ctrl = Parent.FindControl (DataSourceID);
561
562                         if (ctrl == null || !(ctrl is IDataSource)) {
563                                 string format;
564
565                                 if (ctrl == null)
566                                         format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource.  A control with ID '{1}' could not be found.";
567                                 else
568                                         format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource.  '{1}' is not an IDataSource.";
569
570                                 throw new HttpException (String.Format (format, ID, DataSourceID));
571                         }
572
573                         boundDataSource = (IDataSource)ctrl;
574                         boundDataSource.GetView (String.Empty).DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);
575                 }
576         }
577 }