Merge pull request #757 from mlintner/master
[mono.git] / mcs / class / System.Web / System.Web.UI.WebControls / BaseDataList.cs
1 //
2 // System.Web.UI.WebControls.BaseDataList.cs
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // Copyright (C) 2005-2010 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 using System.Collections;
30 using System.ComponentModel;
31 using System.Security.Permissions;
32
33 namespace System.Web.UI.WebControls
34 {
35         // CAS
36         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
37         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
38         // attributes
39         [DefaultEvent ("SelectedIndexChanged")]
40         [DefaultProperty ("DataSource")]
41         [Designer ("System.Web.UI.Design.WebControls.BaseDataListDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
42         public abstract class BaseDataList : WebControl 
43         {
44                 static readonly object selectedIndexChangedEvent = new object ();
45
46                 DataKeyCollection keycoll;
47                 object source;
48
49                 //string dataSourceId;
50                 IDataSource boundDataSource = null;
51                 bool initialized;
52                 bool requiresDataBinding;
53                 DataSourceSelectArguments selectArguments;
54                 IEnumerable data;
55
56                 protected BaseDataList ()
57                 {
58                 }
59
60
61                 [DefaultValue ("")]
62                 [Localizable (true)]
63                 [WebSysDescription ("")]
64                 [WebCategory ("Accessibility")]
65                 public virtual string Caption {
66                         get { return ViewState.GetString ("Caption", String.Empty); }
67                         set {
68                                 if (value == null)
69                                         ViewState.Remove ("Caption");
70                                 else
71                                         ViewState ["Caption"] = value;
72                         }
73                 }
74
75                 [DefaultValue (TableCaptionAlign.NotSet)]
76                 public virtual TableCaptionAlign CaptionAlign {
77                         get { return (TableCaptionAlign) ViewState.GetInt ("CaptionAlign", (int)TableCaptionAlign.NotSet); }
78                         set {
79                                 if ((value < TableCaptionAlign.NotSet) || (value > TableCaptionAlign.Right))
80                                         throw new ArgumentOutOfRangeException (Locale.GetText ("Invalid TableCaptionAlign value."));
81
82                                 ViewState ["CaptionAlign"] = value;
83                         }
84                 }
85
86                 [DefaultValue (-1)]
87                 [WebSysDescription("")]
88                 [WebCategory("Layout")]
89                 public virtual int CellPadding {
90                         get {
91                                 if (!ControlStyleCreated)
92                                         return -1; // default value
93                                 return TableStyle.CellPadding;
94                         }
95                         set { TableStyle.CellPadding = value; }
96                 }
97
98                 [DefaultValue (0)]
99                 [WebSysDescription("")]
100                 [WebCategory("Layout")]
101                 public virtual int CellSpacing {
102                         get {
103                                 if (!ControlStyleCreated)
104                                         return 0; // default value
105                                 return TableStyle.CellSpacing;
106                         }
107                         set { TableStyle.CellSpacing = value; }
108                 }
109
110                 public override ControlCollection Controls {
111                         get {
112                                 EnsureChildControls ();
113                                 return base.Controls;
114                         }
115                 }
116
117                 [DefaultValue ("")]
118                 [Themeable (false)]
119                 [MonoTODO ("incomplete")]
120                 [WebSysDescription("")]
121                 [WebCategory("Data")]
122                 public virtual string DataKeyField {
123                         get { return ViewState.GetString ("DataKeyField", String.Empty); }
124                         set {
125                                 if (value == null)
126                                         ViewState.Remove ("DataKeyField");
127                                 else
128                                         ViewState ["DataKeyField"] = value;
129                         }
130                 }
131
132                 [Browsable (false)]
133                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
134                 [WebSysDescription("")]
135                 [WebCategory("Data")]
136                 public DataKeyCollection DataKeys {
137                         get {
138                                 if (keycoll == null)
139                                         keycoll = new DataKeyCollection (DataKeysArray);
140                                 return keycoll;
141                         }
142                 }
143
144                 protected ArrayList DataKeysArray {
145                         get {
146                                 ArrayList keys = (ArrayList) ViewState ["DataKeys"];
147                                 if (keys == null) {
148                                         keys = new ArrayList ();
149                                         ViewState ["DataKeys"] = keys;
150                                 }
151                                 return keys;
152                         }
153                 }
154
155                 [DefaultValue ("")]
156                 [Themeable (false)]
157                 [WebSysDescription("")]
158                 [WebCategory("Data")]
159                 public string DataMember {
160                         get { return ViewState.GetString ("DataMember", String.Empty); }
161                         set {
162                                 if (value == null)
163                                         ViewState.Remove ("DataMember");
164                                 else
165                                         ViewState ["DataMember"] = value;
166                                 OnDataPropertyChanged ();
167                         }
168                 }
169
170                 [Bindable (true)]
171                 [DefaultValue (null)]
172                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
173                 [Themeable (false)]
174                 [WebSysDescription("")]
175                 [WebCategory("Data")]
176                         public virtual object DataSource {
177                         get { return source; }
178                         set {
179                                 if ((value == null) || (value is IEnumerable) || (value is IListSource)) {
180                                         // FIXME - can't duplicate in a test case ? LAMESPEC ?
181                                         // can't duplicate in a test case
182                                         // if ((dataSourceId != null) && (dataSourceId.Length != 0))
183                                         //      throw new HttpException (Locale.GetText ("DataSourceID is already set."));
184
185                                         source = value;
186
187                                         OnDataPropertyChanged ();
188
189                                 } else {
190                                         string msg = Locale.GetText ("Invalid data source. This requires an object implementing {0} or {1}.",
191                                                                      "IEnumerable", "IListSource");
192                                         throw new ArgumentException (msg);
193                                 }
194                         }
195                 }
196
197                 [DefaultValue (GridLines.Both)]
198                 [WebSysDescription("")]
199                 [WebCategory("Appearance")]
200                 public virtual GridLines GridLines {
201                         get {
202                                 if (!ControlStyleCreated)
203                                         return GridLines.Both; // default value
204                                 return TableStyle.GridLines;
205                         }
206                         set { TableStyle.GridLines = value; }
207                 }
208
209                 [Category ("Layout")]
210                 [DefaultValue (HorizontalAlign.NotSet)]
211                 [WebSysDescription("")]
212                 public virtual HorizontalAlign HorizontalAlign {
213                         get {
214                                 if (!ControlStyleCreated)
215                                         return HorizontalAlign.NotSet; // default value
216                                 return TableStyle.HorizontalAlign;
217                         }
218                         set { TableStyle.HorizontalAlign = value; }
219                 }
220
221                 [DefaultValue (false)]
222                 public virtual bool UseAccessibleHeader {
223                         get { return ViewState.GetBool ("UseAccessibleHeader", false); }
224                         set { ViewState ["UseAccessibleHeader"] = value; }
225                 }
226
227                 [DefaultValue ("")]
228                 [IDReferenceProperty (typeof (DataSourceControl))]
229                 [Themeable (false)]
230                 public virtual string DataSourceID {
231                         get { return ViewState.GetString ("DataSourceID", String.Empty); }
232                         set {
233                                 // LAMESPEC ? this is documented as an HttpException in beta2
234                                 if (source != null)
235                                         throw new InvalidOperationException (Locale.GetText ("DataSource is already set."));
236
237                                 ViewState ["DataSourceID"] = value;
238
239                                 OnDataPropertyChanged ();
240                         }
241                 }
242
243                 protected bool Initialized {
244                         get { return initialized; }
245                 }
246
247                 // as documented in BaseDataBoundControl
248                 protected bool IsBoundUsingDataSourceID {
249                         get { return (DataSourceID.Length != 0); }
250                 }
251
252                 // doc says ?automatically? called by ASP.NET
253                 protected bool RequiresDataBinding {
254                         get { return requiresDataBinding; }
255                         set { requiresDataBinding = value; }
256                 }
257
258                 protected DataSourceSelectArguments SelectArguments {
259                         get {
260                                 if (selectArguments == null)
261                                         selectArguments = CreateDataSourceSelectArguments ();
262                                 return selectArguments;
263                         }
264                 }
265                 public override bool SupportsDisabledAttribute {
266                         get { return RenderingCompatibilityLessThan40; }
267                 }
268                 TableStyle TableStyle {
269                         // this will throw an InvalidCasException just like we need
270                         get { return (TableStyle) ControlStyle; }
271                 }
272
273
274                 protected override void AddParsedSubObject (object obj)
275                 {
276                         // don't accept controls
277                 }
278
279                 // see Kothari, page 435
280                 protected internal override void CreateChildControls ()
281                 {
282                         // We are recreating the children from viewstate
283                         if (HasControls ())
284                                 base.Controls.Clear();
285
286                         if (IsDataBound)
287                                 CreateControlHierarchy (false);
288                         else if (RequiresDataBinding)
289                                 EnsureDataBound ();
290                 }
291
292                 protected abstract void CreateControlHierarchy (bool useDataSource);
293
294                 // see Kothari, page 434
295                 // see also: Control.DataBind on Fx 2.0 beta2 documentation
296                 public override void DataBind ()
297                 {
298                         // unlike most samples we don't call base.OnDataBinding
299                         // because we override it in this class
300                         OnDataBinding (EventArgs.Empty);
301
302                         // Clear, if required, then recreate the control hierarchy
303                         if (HasControls ())
304                                 Controls.Clear ();
305                         if (HasChildViewState)
306                                 ClearChildViewState ();
307                         if (!IsTrackingViewState)
308                                 TrackViewState ();
309                         CreateControlHierarchy (true);
310
311                         // Indicate that child controls have been created, preventing
312                         // CreateChildControls from getting called.
313                         ChildControlsCreated = true;    
314                         RequiresDataBinding = false;
315                         IsDataBound = true;
316                 }
317
318                 protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments ()
319                 {
320                         return DataSourceSelectArguments.Empty;
321                 }
322
323                 // best documentation is (again) in BaseDataBoundControl
324                 protected void EnsureDataBound ()
325                 {
326                         if (IsBoundUsingDataSourceID && RequiresDataBinding)
327                                 DataBind ();
328                 }
329
330                 void SelectCallback (IEnumerable data)
331                 {
332                         this.data = data;
333                 }
334
335                 protected virtual IEnumerable GetData ()
336                 {
337                         if (DataSourceID.Length == 0)
338                                 return null;
339
340                         if (boundDataSource == null)
341                                 ConnectToDataSource ();
342
343                         DataSourceView dsv = boundDataSource.GetView (String.Empty);
344                         dsv.Select (SelectArguments, new DataSourceViewSelectCallback (SelectCallback));
345                         return data;
346                 }
347
348                 bool IsDataBound {
349                         get {
350                                 return ViewState.GetBool ("_DataBound", false);
351                         }
352                         set {
353                                 ViewState ["_DataBound"] = value;
354                         }
355                 }
356
357                 protected override void OnDataBinding (EventArgs e)
358                 {
359                         base.OnDataBinding (e);
360                 }
361
362                 protected virtual void OnDataPropertyChanged ()
363                 {
364                         if (Initialized)
365                                 RequiresDataBinding = true;
366                 }
367
368                 protected virtual void OnDataSourceViewChanged (object sender, EventArgs e)
369                 {
370                         RequiresDataBinding = true;
371                 }
372
373                 protected internal override void OnInit (EventArgs e)
374                 {
375                         base.OnInit (e);
376                         Page page = Page;
377                         if (page != null) {
378                                 page.PreLoad += new EventHandler (OnPagePreLoad);
379
380                                 if (!IsViewStateEnabled && page.IsPostBack)
381                                         RequiresDataBinding = true;
382                         }
383                 }
384                 
385                 void OnPagePreLoad (object sender, EventArgs e)
386                 {
387                         if (!Initialized)
388                                 Initialize ();
389                 }
390                 
391                 protected internal override void OnLoad (EventArgs e)
392                 {
393                         if (!Initialized)
394                                 Initialize ();
395
396                         base.OnLoad (e);
397                 }
398                 
399                 void Initialize ()
400                 {
401                         Page page = Page;
402                         if (page != null) {
403                                 if (!page.IsPostBack || (IsViewStateEnabled && !IsDataBound))
404                                         RequiresDataBinding = true;
405                         }
406
407                         if (IsBoundUsingDataSourceID)
408                                 ConnectToDataSource ();
409
410                         initialized = true;
411                 }
412
413                 protected internal override void OnPreRender (EventArgs e)
414                 {
415                         EnsureDataBound ();
416                         base.OnPreRender (e);
417                 }
418
419                 protected virtual void OnSelectedIndexChanged (EventArgs e)
420                 {
421                         EventHandler selectedIndexChanged = (EventHandler) Events [selectedIndexChangedEvent];
422                         if (selectedIndexChanged != null)
423                                 selectedIndexChanged (this, e);
424                 }
425
426                 protected abstract void PrepareControlHierarchy ();
427
428                 protected internal override void Render (HtmlTextWriter writer)
429                 {
430                         PrepareControlHierarchy ();
431                         // don't call base class or RenderBegin|EndTag
432                         // or we'll get an extra <span></span>
433                         RenderContents (writer);
434                 }
435
436                 [WebSysDescription("")]
437                 [WebCategory("Action")]
438                 public event EventHandler SelectedIndexChanged {
439                         add { Events.AddHandler (selectedIndexChangedEvent, value); }
440                         remove { Events.RemoveHandler (selectedIndexChangedEvent, value); }
441                 }
442
443                 static public bool IsBindableType (Type type)
444                 {
445                         // I can't believe how many NRE are possible in System.Web
446                         if (type == null) // Type.GetTypeCode no longer throws when a null is passed.
447                                 throw new NullReferenceException ();
448
449                         switch (Type.GetTypeCode (type)) {
450                                 case TypeCode.Boolean:
451                                 case TypeCode.Byte:
452                                 case TypeCode.SByte:
453                                 case TypeCode.Int16:
454                                 case TypeCode.UInt16:
455                                 case TypeCode.Int32:
456                                 case TypeCode.UInt32:
457                                 case TypeCode.Int64:
458                                 case TypeCode.UInt64:
459                                 case TypeCode.Char:
460                                 case TypeCode.Double:
461                                 case TypeCode.Single:
462                                 case TypeCode.DateTime:
463                                 case TypeCode.Decimal:
464                                 case TypeCode.String:
465                                         return true;
466                                 default:
467                                         return false;
468                         }
469                 }
470
471                 void ConnectToDataSource ()
472                 {
473                         if (NamingContainer != null)
474                                 boundDataSource = (NamingContainer.FindControl (DataSourceID) as IDataSource);
475
476                         if (boundDataSource == null) {
477                                 if (Parent != null)
478                                         boundDataSource = (Parent.FindControl (DataSourceID) as IDataSource);
479
480                                 if (boundDataSource == null)
481                                         throw new HttpException (Locale.GetText ("Coulnd't find a DataSource named '{0}'.", DataSourceID));
482                         }
483                         DataSourceView dsv = boundDataSource.GetView (String.Empty);
484                         dsv.DataSourceViewChanged += new EventHandler (OnDataSourceViewChanged);
485                 }
486         }
487 }