2010-03-12 Jb Evain <jbevain@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.UI.HtmlControls / HtmlSelect.cs
1 //
2 // System.Web.UI.HtmlControls.HtmlSelect.cs
3 //
4 // Author:
5 //      Dick Porter  <dick@ximian.com>
6 //
7 // Copyright (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 using System.Web.UI.WebControls;
30 using System.Web.Util;
31 using System.ComponentModel;
32 using System.Collections;
33 using System.Collections.Specialized;
34 using System.Globalization;
35 using System.Security.Permissions;
36
37 namespace System.Web.UI.HtmlControls 
38 {
39         // CAS
40         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
41         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
42         // attributes
43         [DefaultEvent ("ServerChange")]
44         [ValidationProperty ("Value")]
45         [ControlBuilder (typeof (HtmlSelectBuilder))]
46 #if NET_2_0
47         [SupportsEventValidation]
48         public class HtmlSelect : HtmlContainerControl, IPostBackDataHandler, IParserAccessor {
49
50                 DataSourceView _boundDataSourceView;
51                 bool requiresDataBinding;
52                 bool _initialized;
53 #else
54         public class HtmlSelect : HtmlContainerControl, IPostBackDataHandler {
55 #endif
56                 public HtmlSelect () : base ("select")
57                 {
58                 }
59
60                 [DefaultValue ("")]
61                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
62                 [WebSysDescription("")]
63                 [WebCategory("Data")]
64                 public virtual string DataMember 
65                 {
66                         get {
67                                 string member = Attributes["datamember"];
68
69                                 if (member == null) {
70                                         return (String.Empty);
71                                 }
72
73                                 return (member);
74                         }
75                         set {
76                                 if (value == null) {
77                                         Attributes.Remove ("datamember");
78                                 } else {
79                                         Attributes["datamember"] = value;
80                                 }
81                         }
82                 }
83
84                 object datasource;
85                 
86                 [DefaultValue (null)]
87                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
88                 [WebSysDescription("")]
89                 [WebCategory("Data")]
90                 public virtual object DataSource 
91                 {
92                         get {
93                                 return (datasource);
94                         }
95                         set {
96                                 if ((value != null) &&
97                                     !(value is IEnumerable) &&
98                                     !(value is IListSource)) {
99                                         throw new ArgumentException ();
100                                 }
101
102                                 datasource = value;
103                         }
104                 }
105
106 #if NET_2_0
107                 [DefaultValue ("")]
108                 public virtual string DataSourceID
109                 {
110                         get {
111                                 return ViewState.GetString ("DataSourceID", "");
112                         }
113                         set {
114                                 if (DataSourceID == value)
115                                         return;
116                                 ViewState ["DataSourceID"] = value;
117                                 if (_boundDataSourceView != null)
118                                         _boundDataSourceView.DataSourceViewChanged -= OnDataSourceViewChanged;
119                                 _boundDataSourceView = null;
120                                 OnDataPropertyChanged ();
121                         }
122                 }
123 #endif
124                                 
125
126                 [DefaultValue ("")]
127                 [WebSysDescription("")]
128                 [WebCategory("Data")]
129                 public virtual string DataTextField 
130                 {
131                         get {
132                                 string text = Attributes["datatextfield"];
133
134                                 if (text == null) {
135                                         return (String.Empty);
136                                 }
137
138                                 return (text);
139                         }
140                         set {
141                                 if (value == null) {
142                                         Attributes.Remove ("datatextfield");
143                                 } else {
144                                         Attributes["datatextfield"] = value;
145                                 }
146                         }
147                 }
148
149                 [DefaultValue ("")]
150                 [WebSysDescription("")]
151                 [WebCategory("Data")]
152                 public virtual string DataValueField 
153                 {
154                         get {
155                                 string value = Attributes["datavaluefield"];
156
157                                 if (value == null) {
158                                         return (String.Empty);
159                                 }
160
161                                 return (value);
162                         }
163                         set {
164                                 if (value == null) {
165                                         Attributes.Remove ("datavaluefield");
166                                 } else {
167                                         Attributes["datavaluefield"] = value;
168                                 }
169                         }
170                 }
171
172                 public override string InnerHtml 
173                 {
174                         get {
175                                 throw new NotSupportedException ();
176                         }
177                         set {
178                                 throw new NotSupportedException ();
179                         }
180                 }
181
182                 public override string InnerText
183                 {
184                         get {
185                                 throw new NotSupportedException ();
186                         }
187                         set {
188                                 throw new NotSupportedException ();
189                         }
190                 }
191
192 #if NET_2_0
193                 protected bool IsBoundUsingDataSourceID 
194                 {
195                         get {
196                                 return (DataSourceID.Length != 0);
197                         }
198                 }
199 #endif          
200
201                 ListItemCollection items;
202                 
203                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
204                 [Browsable (false)]
205                 public ListItemCollection Items 
206                 {
207                         get {
208                                 if (items == null) {
209                                         items = new ListItemCollection ();
210                                         if (IsTrackingViewState)
211                                                 ((IStateManager) items).TrackViewState ();
212                                 }
213
214                                 return (items);
215                         }
216                 }
217
218                 [DefaultValue ("")]
219                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
220                 [WebSysDescription("")]
221                 [WebCategory("Behavior")]
222                 public bool Multiple 
223                 {
224                         get {
225                                 string multi = Attributes["multiple"];
226
227                                 if (multi == null) {
228                                         return (false);
229                                 }
230
231                                 return (true);
232                         }
233                         set {
234                                 if (value == false) {
235                                         Attributes.Remove ("multiple");
236                                 } else {
237                                         Attributes["multiple"] = "multiple";
238                                 }
239                         }
240                 }
241                 
242                 [DefaultValue ("")]
243                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
244                 [WebSysDescription("")]
245                 [WebCategory("Behavior")]
246                 public string Name 
247                 {
248                         get {
249                                 return (UniqueID);
250                         }
251                         set {
252                                 /* Do nothing */
253                         }
254                 }
255
256 #if NET_2_0
257                 protected bool RequiresDataBinding 
258                 {
259                         get { return requiresDataBinding; }
260                         set { requiresDataBinding = value; }
261                 }
262 #endif
263
264                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
265                 [Browsable (false)]
266                 public virtual int SelectedIndex 
267                 {
268                         get {
269                                 /* Make sure Items has been initialised */
270                                 ListItemCollection listitems = Items;
271
272                                 for (int i = 0; i < listitems.Count; i++) {
273                                         if (listitems[i].Selected) {
274                                                 return (i);
275                                         }
276                                 }
277
278                                 /* There is always a selected item in
279                                  * non-multiple mode, if the size is
280                                  * <= 1
281                                  */
282                                 if (!Multiple && Size <= 1) {
283                                         /* Select the first item */
284                                         if (listitems.Count > 0) {
285                                                 /* And make it stick
286                                                  * if there is
287                                                  * anything in the
288                                                  * list
289                                                  */
290                                                 listitems[0].Selected = true;
291                                         }
292                                         
293                                         return (0);
294                                 }
295                                 
296                                 return (-1);
297                         }
298                         set {
299                                 ClearSelection ();
300
301                                 if (value == -1 || items == null) {
302                                         return;
303                                 }
304
305                                 if (value < 0 || value >= items.Count) {
306                                         throw new ArgumentOutOfRangeException ("value");
307                                 }
308
309                                 items[value].Selected = true;
310                         }
311                 }
312
313                 /* "internal infrastructure" according to the docs,
314                  * but has some documentation in 2.0
315                  */
316                 protected virtual int[] SelectedIndices
317                 {
318                         get {
319                                 ArrayList selected = new ArrayList ();
320
321                                 int count = Items.Count;
322
323                                 for (int i = 0; i < count; i++) {
324                                         if (Items [i].Selected) {
325                                                 selected.Add (i);
326                                         }
327                                 }
328                                 
329                                 return ((int[])selected.ToArray (typeof (int)));
330                         }
331                 }
332                 
333
334                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
335                 public int Size 
336                 {
337                         get {
338                                 string size = Attributes["size"];
339
340                                 if (size == null) {
341                                         return (-1);
342                                 }
343
344                                 return (Int32.Parse (size, Helpers.InvariantCulture));
345                         }
346                         set {
347                                 if (value == -1) {
348                                         Attributes.Remove ("size");
349                                 } else {
350                                         Attributes["size"] = value.ToString ();
351                                 }
352                         }
353                 }
354
355                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
356                 public string Value 
357                 {
358                         get {
359                                 int sel = SelectedIndex;
360
361                                 if (sel >= 0 && sel < Items.Count) {
362                                         return (Items[sel].Value);
363                                 }
364                                 
365                                 return (String.Empty);
366                         }
367                         set {
368                                 int sel = Items.IndexOf (value);
369
370                                 if (sel >= 0) {
371                                         SelectedIndex = sel;
372                                 }
373                         }
374                 }
375
376                 static readonly object EventServerChange = new object ();
377                 
378                 [WebSysDescription("")]
379                 [WebCategory("Action")]
380                 public event EventHandler ServerChange
381                 {
382                         add {
383                                 Events.AddHandler (EventServerChange, value);
384                         }
385                         remove {
386                                 Events.RemoveHandler (EventServerChange, value);
387                         }
388                 }
389
390                 protected override void AddParsedSubObject (object obj)
391                 {
392                         if (!(obj is ListItem)) {
393                                 throw new HttpException ("HtmlSelect can only contain ListItem");
394                         }
395
396                         Items.Add ((ListItem)obj);
397                         
398                         base.AddParsedSubObject (obj);
399                 }
400
401                 /* "internal infrastructure" according to the docs,
402                  * but has some documentation in 2.0
403                  */
404                 protected virtual void ClearSelection ()
405                 {
406                         if (items == null) {
407                                 return;
408                         }
409
410                         int count = items.Count;
411                         for (int i = 0; i < count; i++) {
412                                 items[i].Selected = false;
413                         }
414                 }
415                 
416                 protected override ControlCollection CreateControlCollection ()
417                 {
418                         return (base.CreateControlCollection ());
419                 }
420
421 #if NET_2_0
422                 protected void EnsureDataBound ()
423                 {
424                         if (IsBoundUsingDataSourceID && RequiresDataBinding)
425                                 DataBind ();
426                 }
427
428                 protected virtual IEnumerable GetData ()
429                 {
430                         if (DataSource != null && IsBoundUsingDataSourceID)
431                                 throw new HttpException ("Control bound using both DataSourceID and DataSource properties.");
432
433                         if (DataSource != null)
434                                 return DataSourceResolver.ResolveDataSource (DataSource, DataMember);
435
436                         if (!IsBoundUsingDataSourceID)
437                                 return null;
438
439                         IEnumerable result = null;
440
441                         DataSourceView boundDataSourceView = ConnectToDataSource ();
442                         boundDataSourceView.Select (DataSourceSelectArguments.Empty, delegate (IEnumerable data) { result = data; });
443
444                         return result;
445                 }
446 #endif          
447
448                 protected override void LoadViewState (object savedState)
449                 {
450                         object first = null;
451                         object second = null;
452
453                         Pair pair = savedState as Pair;
454                         if (pair != null) {
455                                 first = pair.First;
456                                 second = pair.Second;
457                         }
458
459                         base.LoadViewState (first);
460
461                         if (second != null) {
462                                 IStateManager manager = Items as IStateManager;
463                                 manager.LoadViewState (second);
464                         }
465                 }
466
467                 protected override void OnDataBinding (EventArgs e)
468                 {
469                         base.OnDataBinding (e);
470
471                         /* Make sure Items has been initialised */
472                         ListItemCollection listitems = Items;
473
474                         listitems.Clear ();
475                         
476                         IEnumerable list;
477
478 #if NET_2_0
479                                 list = GetData ();
480 #else
481                                 list = DataSourceResolver.ResolveDataSource (DataSource, DataMember);
482 #endif
483
484                         if (list == null) {
485                                 return;
486                         }
487                         
488                         foreach (object container in list) {
489                                 string text = null;
490                                 string value = null;
491
492                                 if (DataTextField == String.Empty &&
493                                     DataValueField == String.Empty) {
494                                         text = container.ToString ();
495                                         value = text;
496                                 } else {
497                                         if (DataTextField != String.Empty) {
498                                                 text = DataBinder.Eval (container, DataTextField).ToString ();
499                                         }
500
501                                         if (DataValueField != String.Empty) {
502                                                 value = DataBinder.Eval (container, DataValueField).ToString ();
503                                         } else {
504                                                 value = text;
505                                         }
506
507                                         if (text == null &&
508                                             value != null) {
509                                                 text = value;
510                                         }
511                                 }
512
513                                 if (text == null) {
514                                         text = String.Empty;
515                                 }
516                                 if (value == null) {
517                                         value = String.Empty;
518                                 }
519
520                                 ListItem item = new ListItem (text, value);
521                                 listitems.Add (item);
522                         }
523 #if NET_2_0
524                         RequiresDataBinding = false;
525                         IsDataBound = true;
526 #endif
527                 }
528
529 #if NET_2_0
530                 protected virtual void OnDataPropertyChanged ()
531                 {
532                         if (_initialized)
533                         RequiresDataBinding = true;
534                 }
535
536                 protected virtual void OnDataSourceViewChanged (object sender,
537                                                                 EventArgs e)
538                 {
539                         RequiresDataBinding = true;
540                 }
541
542                 protected internal override void OnInit (EventArgs e)
543                 {
544                         base.OnInit (e);
545
546                         Page.PreLoad += new EventHandler (OnPagePreLoad);
547                 }
548
549                 protected virtual void OnPagePreLoad (object sender, EventArgs e) {
550                         Initialize ();
551                 }
552
553                 protected internal override void OnLoad (EventArgs e)
554                 {
555                         if (!_initialized)
556                                 Initialize ();
557
558                         base.OnLoad (e);
559                 }
560
561                 void Initialize () {
562                         _initialized = true;
563
564                         if (!IsDataBound)
565                                 RequiresDataBinding = true;
566
567                         if (IsBoundUsingDataSourceID)
568                                 ConnectToDataSource ();
569                 }
570
571                 bool IsDataBound {
572                         get {
573                                 return ViewState.GetBool ("_DataBound", false);
574                         }
575                         set {
576                                 ViewState ["_DataBound"] = value;
577                         }
578                 }
579
580                 DataSourceView ConnectToDataSource ()
581                 {
582                         if (_boundDataSourceView != null)
583                                 return _boundDataSourceView;
584
585                         /* verify that the data source exists and is an IDataSource */
586                         object ctrl = null;
587                         if (Page != null)
588                                 ctrl = Page.FindControl (DataSourceID);
589
590                         if (ctrl == null || !(ctrl is IDataSource)) {
591                                 string format;
592
593                                 if (ctrl == null)
594                                         format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource.  A control with ID '{1}' could not be found.";
595                                 else
596                                         format = "DataSourceID of '{0}' must be the ID of a control of type IDataSource.  '{1}' is not an IDataSource.";
597
598                                 throw new HttpException (String.Format (format, ID, DataSourceID));
599                         }
600
601                         _boundDataSourceView = ((IDataSource)ctrl).GetView (String.Empty);
602                         _boundDataSourceView.DataSourceViewChanged += OnDataSourceViewChanged;
603                         return _boundDataSourceView;
604                 }
605 #endif
606
607 #if NET_2_0
608                 protected internal
609 #else           
610                 protected
611 #endif          
612                 override void OnPreRender (EventArgs e)
613                 {
614 #if NET_2_0
615                         EnsureDataBound ();
616 #endif
617
618                         base.OnPreRender (e);
619
620                         if (Page != null && !Disabled) {
621                                 Page.RegisterRequiresPostBack (this);
622 #if NET_2_0
623                                 Page.RegisterEnabledControl (this);
624 #endif
625                         }
626                 }
627
628                 protected virtual void OnServerChange (EventArgs e)
629                 {
630                         EventHandler handler = (EventHandler)Events[EventServerChange];
631
632                         if (handler != null) {
633                                 handler (this, e);
634                         }
635                 }
636                 
637                 protected override void RenderAttributes (HtmlTextWriter w)
638                 {
639 #if NET_2_0
640                         if (Page != null)
641                                 Page.ClientScript.RegisterForEventValidation (UniqueID);
642 #endif
643                         /* If there is no "name" attribute,
644                          * LoadPostData doesn't work...
645                          */
646                         w.WriteAttribute ("name", Name);
647                         Attributes.Remove ("name");
648                         
649                         /* Don't render the databinding attributes */
650                         Attributes.Remove ("datamember");
651                         Attributes.Remove ("datatextfield");
652                         Attributes.Remove ("datavaluefield");
653                         
654                         base.RenderAttributes (w);
655                 }
656                 
657 #if NET_2_0
658                 protected internal
659 #else
660                 protected
661 #endif          
662                 override void RenderChildren (HtmlTextWriter w)
663                 {
664                         base.RenderChildren (w);
665
666                         if (items == null) {
667                                 return;
668                         }
669                         
670                         w.WriteLine ();
671
672                         bool done_sel = false;
673                         
674                         int count = items.Count;
675                         for (int i = 0; i < count; i++) {
676                                 ListItem item = items[i];
677                                 w.Indent++;
678                                 
679                                 /* Write the <option> elements this
680                                  * way so that the output HTML matches
681                                  * the ms version (can't make
682                                  * HtmlTextWriterTag.Option an inline
683                                  * element, cos that breaks other
684                                  * stuff.)
685                                  */
686                                 w.WriteBeginTag ("option");
687                                 if (item.Selected && !done_sel) {
688
689                                         w.WriteAttribute ("selected", "selected");
690
691                                         if (!Multiple) {
692                                                 done_sel = true;
693                                         }
694                                 }
695                                 
696                                 w.WriteAttribute ("value", item.Value, true);
697                                 w.Write (HtmlTextWriter.TagRightChar);
698                                 
699                                 w.Write (HttpUtility.HtmlEncode(item.Text));
700                                 w.WriteEndTag ("option");
701                                 w.WriteLine ();
702
703                                 w.Indent--;
704                         }
705                 }
706
707                 protected override object SaveViewState ()
708                 {
709                         object first = null;
710                         object second = null;
711
712                         first = base.SaveViewState ();
713
714                         IStateManager manager = items as IStateManager;
715                         if (manager != null) {
716                                 second = manager.SaveViewState ();
717                         }
718
719                         if (first == null && second == null)
720                                 return (null);
721
722                         return new Pair (first, second);
723                 }
724
725                 /* "internal infrastructure" according to the docs,
726                  * but has some documentation in 2.0
727                  */
728                 protected virtual void Select (int[] selectedIndices)
729                 {
730                         if (items == null) {
731                                 return;
732                         }
733
734                         ClearSelection ();
735                         
736                         int count = items.Count;
737                         foreach (int i in selectedIndices) {
738                                 if (i >= 0 && i < count) {
739                                         items[i].Selected = true;
740                                 }
741                         }
742                 }
743
744                 protected override void TrackViewState ()
745                 {
746                         base.TrackViewState ();
747
748                         IStateManager manager = items as IStateManager;
749                         if (manager != null) {
750                                 manager.TrackViewState ();
751                         }
752                 }
753
754 #if NET_2_0
755                 protected virtual
756 #endif
757                 void RaisePostDataChangedEvent ()
758                 {
759                         OnServerChange (EventArgs.Empty);
760                 }
761
762 #if NET_2_0
763                 protected virtual
764 #endif
765                 bool LoadPostData (string postDataKey, NameValueCollection postCollection)
766                 {
767                         /* postCollection contains the values that are
768                          * selected
769                          */
770
771                         string[] values = postCollection.GetValues (postDataKey);
772                         bool changed = false;
773
774                         if (values != null) {
775                                 if (Multiple) {
776                                         /* We have a set of
777                                          * selections.  We can't just
778                                          * set the new list, because
779                                          * we need to know if the set
780                                          * has changed from last time
781                                          */
782                                         int value_len = values.Length;
783                                         int[] old_sel = SelectedIndices;
784                                         int[] new_sel = new int[value_len];
785                                         int old_sel_len = old_sel.Length;
786                                         
787                                         for (int i = 0; i < value_len; i++) {
788                                                 new_sel[i] = Items.IndexOf (values[i]);
789                                                 if (old_sel_len != value_len ||
790                                                     old_sel[i] != new_sel[i]) {
791                                                         changed = true;
792                                                 }
793                                         }
794
795                                         if (changed) {
796                                                 Select (new_sel);
797                                         }
798                                 } else {
799                                         /* Just take the first one */
800                                         int sel = Items.IndexOf (values[0]);
801
802                                         if (sel != SelectedIndex) {
803                                                 SelectedIndex = sel;
804                                                 changed = true;
805                                         }
806                                 }
807                         }
808
809 #if NET_2_0
810                         if (changed)
811                                 ValidateEvent (postDataKey, String.Empty);
812 #endif
813                         return (changed);
814                 }
815
816                 bool IPostBackDataHandler.LoadPostData (string postDataKey, NameValueCollection postCollection)
817                 {
818                         return LoadPostData (postDataKey, postCollection);
819                 }
820
821                 void IPostBackDataHandler.RaisePostDataChangedEvent ()
822                 {
823                         RaisePostDataChangedEvent ();
824                 }
825         }
826 }