1 //------------------------------------------------------------------------------
2 // <copyright file="HtmlSelect.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web.UI.HtmlControls {
8     using System.Runtime.Serialization.Formatters;
9     using System.Text;
10     using System.ComponentModel;
11     using System;
12     using System.Collections;
13     using System.Collections.Specialized;
14     using System.Data;
15     using System.Web;
16     using System.Web.Util;
17     using System.Web.UI;
18     using System.Web.UI.WebControls;
19     using System.Globalization;
20     using Debug=System.Web.Util.Debug;
21     using System.Security.Permissions;
22 
23     public class HtmlSelectBuilder : ControlBuilder {
24 
25 
GetChildControlType(string tagName, IDictionary attribs)26         public override Type GetChildControlType(string tagName, IDictionary attribs) {
27             if (StringUtil.EqualsIgnoreCase(tagName, "option"))
28                 return typeof(ListItem);
29 
30             return null;
31         }
32 
33 
AllowWhitespaceLiterals()34         public override bool AllowWhitespaceLiterals() {
35             return false;
36         }
37     }
38 
39 
40     /// <devdoc>
41     ///    <para>
42     ///       The <see langword='HtmlSelect'/>
43     ///       class defines the methods, properties, and events for the
44     ///       HtmlSelect control. This class allows programmatic access to the HTML
45     ///       &lt;select&gt; element on the server.
46     ///    </para>
47     /// </devdoc>
48     [
49     DefaultEvent("ServerChange"),
50     ValidationProperty("Value"),
51     ControlBuilderAttribute(typeof(HtmlSelectBuilder)),
52     SupportsEventValidation,
53     ]
54     public class HtmlSelect : HtmlContainerControl, IPostBackDataHandler, IParserAccessor {
55 
56         private static readonly object EventServerChange = new object();
57 
58         internal const string DataBoundViewStateKey = "_!DataBound";
59 
60         private object dataSource;
61         private ListItemCollection items;
62         private int cachedSelectedIndex;
63 
64         private bool _requiresDataBinding;
65         private bool _inited;
66         private bool _throwOnDataPropertyChange;
67 
68         private DataSourceView _currentView;
69         private bool _currentViewIsFromDataSourceID;
70         private bool _currentViewValid;
71         private bool _pagePreLoadFired;
72 
73         /*
74          * Creates an intrinsic Html SELECT control.
75          */
76 
HtmlSelect()77         public HtmlSelect() : base("select") {
78             cachedSelectedIndex = -1;
79         }
80 
81 
82         /// <devdoc>
83         ///    <para>[To be supplied.]</para>
84         /// </devdoc>
85         [
86         DefaultValue(""),
87         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
88         WebCategory("Data"),
89         WebSysDescription(SR.HtmlSelect_DataMember)
90         ]
91         public virtual string DataMember {
92             get {
93                 object o = ViewState["DataMember"];
94                 if (o != null)
95                     return (string)o;
96                 return String.Empty;
97             }
98             set {
99                 Attributes["DataMember"] = MapStringAttributeToString(value);
100                 OnDataPropertyChanged();
101             }
102         }
103 
104 
105         /// <devdoc>
106         ///    Gets or sets the data source to databind the list values
107         ///    in the <see langword='HtmlSelect'/> control against. This provides data to
108         ///    populate the select list with items.
109         /// </devdoc>
110         [
111         WebCategory("Data"),
112         DefaultValue(null),
113         WebSysDescription(SR.BaseDataBoundControl_DataSource),
114         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
115         ]
116         public virtual object DataSource {
117             get {
118                 return dataSource;
119             }
120             set {
121                 if ((value == null) || (value is IListSource) || (value is IEnumerable)) {
122                     dataSource = value;
123                     OnDataPropertyChanged();
124                 }
125                 else {
126                     throw new ArgumentException(SR.GetString(SR.Invalid_DataSource_Type, ID));
127                 }
128             }
129         }
130 
131 
132         /// <summary>
133         /// The ID of the DataControl that this control should use to retrieve
134         /// its data source. When the control is bound to a DataControl, it
135         /// can retrieve a data source instance on-demand, and thereby attempt
136         /// to work in auto-DataBind mode.
137         /// </summary>
138         [
139         DefaultValue(""),
140         WebCategory("Data"),
141         WebSysDescription(SR.BaseDataBoundControl_DataSourceID),
142         ]
143         public virtual string DataSourceID {
144             get {
145                 object o = ViewState["DataSourceID"];
146                 if (o != null) {
147                     return (string)o;
148                 }
149                 return String.Empty;
150             }
151             set {
152                 ViewState["DataSourceID"] = value;
153                 OnDataPropertyChanged();
154             }
155         }
156 
157 
158         /// <devdoc>
159         ///    <para>
160         ///       Gets or sets the field in the data source that provides
161         ///       the text for an option entry in the HtmlSelect control.
162         ///    </para>
163         /// </devdoc>
164         [
165         WebCategory("Data"),
166         DefaultValue(""),
167         WebSysDescription(SR.HtmlSelect_DataTextField)
168         ]
169         public virtual string DataTextField {
170             get {
171                 string s = Attributes["DataTextField"];
172                 return((s == null) ? String.Empty : s);
173             }
174             set {
175                 Attributes["DataTextField"] = MapStringAttributeToString(value);
176                 if (_inited) {
177                     RequiresDataBinding = true;
178                 }
179             }
180         }
181 
182 
183         /// <devdoc>
184         ///    <para>
185         ///       Gets or sets the field in the data source that provides
186         ///       the option item value for the <see langword='HtmlSelect'/>
187         ///       control.
188         ///    </para>
189         /// </devdoc>
190         [
191         WebCategory("Data"),
192         DefaultValue(""),
193         WebSysDescription(SR.HtmlSelect_DataValueField)
194         ]
195         public virtual string DataValueField {
196             get {
197                 string s = Attributes["DataValueField"];
198                 return((s == null) ? String.Empty : s);
199             }
200             set {
201                 Attributes["DataValueField"] = MapStringAttributeToString(value);
202                 if (_inited) {
203                     RequiresDataBinding = true;
204                 }
205             }
206         }
207 
208 
209         /// <devdoc>
210         ///    <para>[To be supplied.]</para>
211         /// </devdoc>
212         public override string InnerHtml {
213             get {
214                 throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name));
215             }
216             set {
217                 throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name));
218             }
219         }
220 
221 
222         /// <devdoc>
223         ///    <para>[To be supplied.]</para>
224         /// </devdoc>
225         public override string InnerText {
226             get {
227                 throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name));
228             }
229             set {
230                 throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name));
231             }
232         }
233 
234 
235         protected bool IsBoundUsingDataSourceID {
236             get {
237                 return (DataSourceID.Length > 0);
238             }
239         }
240 
241         /*
242          * A collection containing the list of items.
243          */
244 
245         /// <devdoc>
246         ///    <para>
247         ///       Gets the list of option items in an <see langword='HtmlSelect'/> control.
248         ///    </para>
249         /// </devdoc>
250         [
251         Browsable(false),
252         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
253         ]
254         public ListItemCollection Items {
255             get {
256                 if (items == null) {
257                     items = new ListItemCollection();
258                     if (IsTrackingViewState)
259                         ((IStateManager)items).TrackViewState();
260                 }
261                 return items;
262             }
263         }
264 
265         /*
266          * Multi-select property.
267          */
268 
269         /// <devdoc>
270         ///    <para>
271         ///       Gets or sets a value indicating whether multiple option items can be selected
272         ///       from the list.
273         ///    </para>
274         /// </devdoc>
275         [
276         WebCategory("Behavior"),
277         DefaultValue(""),
278         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
279         ]
280         public bool Multiple {
281             get {
282                 string s = Attributes["multiple"];
283                 return((s != null) ? (s.Equals("multiple")) : false);
284             }
285 
286             set {
287                 if (value)
288                     Attributes["multiple"] = "multiple";
289                 else
290                     Attributes["multiple"] = null;
291             }
292         }
293 
294         /*
295          * Name property.
296          */
297 
298         [
299         WebCategory("Behavior"),
300         DefaultValue(""),
301         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
302         ]
303         public string Name {
304             get {
305                 return UniqueID;
306                 //string s = Attributes["name"];
307                 //return ((s != null) ? s : "");
308             }
309             set {
310                 //Attributes["name"] = MapStringAttributeToString(value);
311             }
312         }
313 
314         // Value that gets rendered for the Name attribute
315         internal string RenderedNameAttribute {
316             get {
317                 return Name;
318                 //string name = Name;
319                 //if (name.Length == 0)
320                 //    return UniqueID;
321 
322                 //return name;
323             }
324         }
325 
326 
327         protected bool RequiresDataBinding {
328             get {
329                 return _requiresDataBinding;
330             }
331             set {
332                 _requiresDataBinding = value;
333             }
334         }
335 
336         /*
337          * The index of the selected item.
338          * Returns the first selected item if list is multi-select.
339          * Returns -1 if there is no selected item.
340          */
341 
342         /// <devdoc>
343         ///    <para>
344         ///       Gets or sets the ordinal index of the selected option item in an
345         ///    <see langword='HtmlSelect'/> control. If multiple items are selected, this
346         ///       property holds the index of the first item selected in the list.
347         ///    </para>
348         /// </devdoc>
349         [
350         Browsable(false),
351         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
352         HtmlControlPersistable(false),
353         ]
354         public virtual int SelectedIndex {
355             get {
356                 for (int i=0; i < Items.Count; i++) {
357                     if (Items[i].Selected)
358                         return i;
359                 }
360                 if (Size <= 1 && !Multiple) {
361                     // SELECT as a dropdown must have a selection
362                     if (Items.Count > 0)
363                         Items[0].Selected = true;
364                     return 0;
365                 }
366                 return -1;
367             }
368             set {
369                 // if we have no items, save the selectedindex
370                 // for later databinding
371                 if (Items.Count == 0) {
372                     cachedSelectedIndex = value;
373                 }
374                 else {
375                     if (value < -1 || value >= Items.Count) {
376                         throw new ArgumentOutOfRangeException("value");
377                     }
378                     ClearSelection();
379                     if (value >= 0)
380                         Items[value].Selected = true;
381                 }
382             }
383         }
384 
385         /*
386          *  SelectedIndices property.
387          *  Protected property for getting array of selected indices.
388          */
389 
390         /// <internalonly/>
391         /// <devdoc>
392         /// </devdoc>
393         protected virtual int[] SelectedIndices {
394             get {
395                 int n = 0;
396                 int[] temp = new int[3];
397                 for (int i=0; i < Items.Count; i++) {
398                     if (Items[i].Selected == true) {
399                         if (n == temp.Length) {
400                             int[] t = new int[n+n];
401                             temp.CopyTo(t,0);
402                             temp = t;
403                         }
404                         temp[n++] = i;
405                     }
406                 }
407                 int[] selectedIndices = new int[n];
408                 Array.Copy(temp,0,selectedIndices,0,n);
409                 return selectedIndices;
410             }
411         }
412 
413         /*
414          * The size of the list.
415          * A size of 1 displays a dropdown list.
416          */
417 
418         /// <devdoc>
419         ///    <para>
420         ///       Gets or sets the number of option items visible in the browser at a time. A
421         ///       value greater that one will typically cause browsers to display a scrolling
422         ///       list.
423         ///    </para>
424         /// </devdoc>
425         [
426         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
427         ]
428         public int Size {
429             get {
430                 string s = Attributes["size"];
431                 return((s != null) ? Int32.Parse(s, CultureInfo.InvariantCulture) : -1);
432             }
433 
434             set {
435                 Attributes["size"] = MapIntegerAttributeToString(value);
436             }
437         }
438 
439         /*
440          * Value property.
441          */
442 
443         /// <devdoc>
444         ///    <para>
445         ///       Gets or sets the current item selected in the <see langword='HtmlSelect'/>
446         ///       control.
447         ///    </para>
448         /// </devdoc>
449         [
450         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
451         ]
452         public string Value {
453             get {
454                 int i = SelectedIndex;
455                 return(i < 0 || i >= Items.Count) ? String.Empty : Items[i].Value;
456             }
457 
458             set {
459                 int i = Items.FindByValueInternal(value, true);
460                 if (i >= 0)
461                     SelectedIndex = i;
462             }
463         }
464 
465 
466         /// <devdoc>
467         ///    <para>
468         ///       Occurs when an <see langword='HtmlSelect'/> control is changed on the
469         ///       server.
470         ///    </para>
471         /// </devdoc>
472         [
473         WebCategory("Action"),
474         WebSysDescription(SR.HtmlSelect_OnServerChange)
475         ]
476         public event EventHandler ServerChange {
477             add {
478                 Events.AddHandler(EventServerChange, value);
479             }
480             remove {
481                 Events.RemoveHandler(EventServerChange, value);
482             }
483         }
484 
485 
486         /// <internalonly/>
AddParsedSubObject(object obj)487         protected override void AddParsedSubObject(object obj) {
488             if (obj is ListItem)
489                 Items.Add((ListItem)obj);
490             else
491                 throw new HttpException(SR.GetString(SR.Cannot_Have_Children_Of_Type, "HtmlSelect", obj.GetType().Name));
492         }
493 
494 
495         /// <internalonly/>
496         /// <devdoc>
497         /// </devdoc>
ClearSelection()498         protected virtual void ClearSelection() {
499             for (int i=0; i < Items.Count; i++)
500                 Items[i].Selected = false;
501         }
502 
503         /// <devdoc>
504         /// Connects this data bound control to the appropriate DataSourceView
505         /// and hooks up the appropriate event listener for the
506         /// DataSourceViewChanged event. The return value is the new view (if
507         /// any) that was connected to. An exception is thrown if there is
508         /// a problem finding the requested view or data source.
509         /// </devdoc>
ConnectToDataSourceView()510         private DataSourceView ConnectToDataSourceView() {
511             if (_currentViewValid && !DesignMode) {
512                 // If the current view is correct, there is no need to reconnect
513                 return _currentView;
514             }
515 
516             // Disconnect from old view, if necessary
517             if ((_currentView != null) && (_currentViewIsFromDataSourceID)) {
518                 // We only care about this event if we are bound through the DataSourceID property
519                 _currentView.DataSourceViewChanged -= new EventHandler(OnDataSourceViewChanged);
520             }
521 
522             // Connect to new view
523             IDataSource ds = null;
524             string dataSourceID = DataSourceID;
525 
526             if (dataSourceID.Length != 0) {
527                 // Try to find a DataSource control with the ID specified in DataSourceID
528                 Control control = DataBoundControlHelper.FindControl(this, dataSourceID);
529                 if (control == null) {
530                     throw new HttpException(SR.GetString(SR.DataControl_DataSourceDoesntExist, ID, dataSourceID));
531                 }
532                 ds = control as IDataSource;
533                 if (ds == null) {
534                     throw new HttpException(SR.GetString(SR.DataControl_DataSourceIDMustBeDataControl, ID, dataSourceID));
535                 }
536             }
537 
538             if (ds == null) {
539                 // DataSource control was not found, construct a temporary data source to wrap the data
540                 ds = new ReadOnlyDataSource(DataSource, DataMember);
541             }
542             else {
543                 // Ensure that both DataSourceID as well as DataSource are not set at the same time
544                 if (DataSource != null) {
545                     throw new InvalidOperationException(SR.GetString(SR.DataControl_MultipleDataSources, ID));
546                 }
547             }
548 
549             // IDataSource was found, extract the appropriate view and return it
550             DataSourceView newView = ds.GetView(DataMember);
551             if (newView == null) {
552                 throw new InvalidOperationException(SR.GetString(SR.DataControl_ViewNotFound, ID));
553             }
554 
555             _currentViewIsFromDataSourceID = IsBoundUsingDataSourceID;
556             _currentView = newView;
557             if ((_currentView != null) && (_currentViewIsFromDataSourceID)) {
558                 // We only care about this event if we are bound through the DataSourceID property
559                 _currentView.DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);
560             }
561             _currentViewValid = true;
562 
563             return _currentView;
564         }
565 
566 
CreateControlCollection()567         protected override ControlCollection CreateControlCollection() {
568             return new EmptyControlCollection(this);
569         }
570 
571 
EnsureDataBound()572         protected void EnsureDataBound() {
573             try {
574                 _throwOnDataPropertyChange = true;
575                 if (RequiresDataBinding && DataSourceID.Length > 0) {
576                     DataBind();
577                 }
578             }
579             finally{
580                 _throwOnDataPropertyChange = false;
581             }
582         }
583 
584 
585         /// <devdoc>
586         /// Returns an IEnumerable that is the DataSource, which either came
587         /// from the DataSource property or from the control bound via the
588         /// DataSourceID property.
589         /// </devdoc>
GetData()590         protected virtual IEnumerable GetData() {
591             DataSourceView view = ConnectToDataSourceView();
592 
593             Debug.Assert(_currentViewValid);
594 
595             if (view != null) {
596                 return view.ExecuteSelect(DataSourceSelectArguments.Empty);
597             }
598             return null;
599         }
600 
601         /*
602          * Override to load items and selected indices.
603          */
604 
605         /// <internalonly/>
606         /// <devdoc>
607         /// </devdoc>
LoadViewState(object savedState)608         protected override void LoadViewState(object savedState) {
609             if (savedState != null) {
610                 Triplet statetriplet = (Triplet)savedState;
611                 base.LoadViewState(statetriplet.First);
612 
613                 // restore state of items
614                 ((IStateManager)Items).LoadViewState(statetriplet.Second);
615 
616                 // restore selected indices
617                 object selectedIndices = statetriplet.Third;
618                 if (selectedIndices != null)
619                     Select((int[])selectedIndices);
620             }
621         }
622 
623 
624         /// <internalonly/>
625         /// <devdoc>
626         /// </devdoc>
OnDataBinding(EventArgs e)627         protected override void OnDataBinding(EventArgs e) {
628             base.OnDataBinding(e);
629 
630             // create items using the datasource
631             IEnumerable dataSource = GetData();
632 
633             // create items using the datasource
634             if (dataSource != null) {
635                 bool fieldsSpecified = false;
636                 string textField = DataTextField;
637                 string valueField = DataValueField;
638 
639                 Items.Clear();
640                 ICollection collection = dataSource as ICollection;
641                 if (collection != null) {
642                     Items.Capacity = collection.Count;
643                 }
644 
645                 if ((textField.Length != 0) || (valueField.Length != 0))
646                     fieldsSpecified = true;
647 
648                 foreach (object dataItem in dataSource) {
649                     ListItem item = new ListItem();
650 
651                     if (fieldsSpecified) {
652                         if (textField.Length > 0) {
653                             item.Text = DataBinder.GetPropertyValue(dataItem,textField,null);
654                         }
655                         if (valueField.Length > 0) {
656                             item.Value = DataBinder.GetPropertyValue(dataItem,valueField,null);
657                         }
658                     }
659                     else {
660                         item.Text = item.Value = dataItem.ToString();
661                     }
662 
663                     Items.Add(item);
664                 }
665             }
666             // try to apply the cached SelectedIndex now
667             if (cachedSelectedIndex != -1) {
668                 SelectedIndex = cachedSelectedIndex;
669                 cachedSelectedIndex = -1;
670             }
671             ViewState[DataBoundViewStateKey] = true;
672             RequiresDataBinding = false;
673         }
674 
675 
676         /// <devdoc>
677         ///  This method is called when DataMember, DataSource, or DataSourceID is changed.
678         /// </devdoc>
OnDataPropertyChanged()679         protected virtual void OnDataPropertyChanged() {
680             if (_throwOnDataPropertyChange) {
681                 throw new HttpException(SR.GetString(SR.DataBoundControl_InvalidDataPropertyChange, ID));
682             }
683 
684             if (_inited) {
685                 RequiresDataBinding = true;
686             }
687             _currentViewValid = false;
688         }
689 
690 
OnDataSourceViewChanged(object sender, EventArgs e)691         protected virtual void OnDataSourceViewChanged(object sender, EventArgs e) {
692             RequiresDataBinding = true;
693         }
694 
695 
OnInit(EventArgs e)696         protected internal override void OnInit(EventArgs e) {
697             base.OnInit(e);
698 
699             if (Page != null) {
700                 Page.PreLoad += new EventHandler(this.OnPagePreLoad);
701                 if (!IsViewStateEnabled && Page.IsPostBack) {
702                     RequiresDataBinding = true;
703                 }
704             }
705         }
706 
707 
OnLoad(EventArgs e)708         protected internal override void OnLoad(EventArgs e) {
709             _inited = true; // just in case we were added to the page after PreLoad
710             ConnectToDataSourceView();
711             if (Page != null && !_pagePreLoadFired && ViewState[DataBoundViewStateKey] == null) {
712                 // If the control was added after PagePreLoad, we still need to databind it because it missed its
713                 // first change in PagePreLoad.  If this control was created by a call to a parent control's DataBind
714                 // in Page_Load (with is relatively common), this control will already have been databound even
715                 // though pagePreLoad never fired and the page isn't a postback.
716                 if (!Page.IsPostBack) {
717                     RequiresDataBinding = true;
718                 }
719 
720                 // If the control was added to the page after page.PreLoad, we'll never get the event and we'll
721                 // never databind the control.  So if we're catching up and Load happens but PreLoad never happened,
722                 // call DataBind.  This may make the control get databound twice if the user called DataBind on the control
723                 // directly in Page.OnLoad, but better to bind twice than never to bind at all.
724                 else if (IsViewStateEnabled) {
725                     RequiresDataBinding = true;
726                 }
727             }
728 
729             base.OnLoad(e);
730         }
731 
OnPagePreLoad(object sender, EventArgs e)732         private void OnPagePreLoad(object sender, EventArgs e) {
733             _inited = true;
734 
735             if (Page != null) {
736                 Page.PreLoad -= new EventHandler(this.OnPagePreLoad);
737 
738                 // Setting RequiresDataBinding to true in OnLoad is too late because the OnLoad page event
739                 // happens before the control.OnLoad method gets called.  So a page_load handler on the page
740                 // that calls DataBind won't prevent DataBind from getting called again in PreRender.
741                 if (!Page.IsPostBack) {
742                     RequiresDataBinding = true;
743                 }
744                 // If this is a postback and viewstate is enabled, but we have never bound the control
745                 // before, it is probably because its visibility was changed in the postback.  In this
746                 // case, we need to bind the control or it will never appear.  This is a common scenario
747                 // for Wizard and MultiView.
748                 if (Page.IsPostBack && IsViewStateEnabled && ViewState[DataBoundViewStateKey] == null) {
749                     RequiresDataBinding = true;
750                 }
751             }
752             _pagePreLoadFired = true;
753         }
754 
755         /*
756          * This method is invoked just prior to rendering.
757          */
758 
759         /// <internalonly/>
760         /// <devdoc>
761         /// </devdoc>
OnPreRender(EventArgs e)762         protected internal override void OnPreRender(EventArgs e) {
763             base.OnPreRender(e);
764 
765             // An Html SELECT does not post when nothing is selected.
766             if (Page != null && !Disabled) {
767                 if (Size > 1) {
768                     Page.RegisterRequiresPostBack(this);
769                 }
770 
771                 Page.RegisterEnabledControl(this);
772             }
773 
774             EnsureDataBound();
775         }
776 
777         /*
778          * Method used to raise the OnServerChange event.
779          */
780 
781         /// <devdoc>
782         ///    <para>
783         ///       Raised
784         ///       on the server when the <see langword='HtmlSelect'/> control list values
785         ///       change between postback requests.
786         ///    </para>
787         /// </devdoc>
OnServerChange(EventArgs e)788         protected virtual void OnServerChange(EventArgs e) {
789             EventHandler handler = (EventHandler)Events[EventServerChange];
790             if (handler != null) handler(this, e);
791         }
792 
793         /*
794          * Override to prevent SelectedIndex from being rendered as an attribute.
795          */
796 
797         /// <internalonly/>
798         /// <devdoc>
799         /// </devdoc>
RenderAttributes(HtmlTextWriter writer)800         protected override void RenderAttributes(HtmlTextWriter writer) {
801             if (Page != null) {
802                 Page.ClientScript.RegisterForEventValidation(RenderedNameAttribute);
803             }
804 
805             writer.WriteAttribute("name", RenderedNameAttribute);
806             Attributes.Remove("name");
807 
808             Attributes.Remove("DataValueField");
809             Attributes.Remove("DataTextField");
810             Attributes.Remove("DataMember");
811             Attributes.Remove("DataSourceID");
812             base.RenderAttributes(writer);
813         }
814 
815         /*
816          * Render the Items in the list.
817          */
818 
819         /// <internalonly/>
820         /// <devdoc>
821         /// </devdoc>
RenderChildren(HtmlTextWriter writer)822         protected internal override void RenderChildren(HtmlTextWriter writer) {
823             bool selected = false;
824             bool isSingle = !Multiple;
825 
826             writer.WriteLine();
827             writer.Indent++;
828             ListItemCollection liCollection = Items;
829             int n = liCollection.Count;
830             if (n > 0) {
831                 for (int i=0; i < n; i++) {
832                     ListItem li = liCollection[i];
833                     writer.WriteBeginTag("option");
834                     if (li.Selected) {
835                         if (isSingle)
836                         {
837                             if (selected)
838                                 throw new HttpException(SR.GetString(SR.HtmlSelect_Cant_Multiselect_In_Single_Mode));
839                             selected=true;
840                         }
841                         writer.WriteAttribute("selected", "selected");
842                     }
843 
844                     writer.WriteAttribute("value", li.Value, true /*fEncode*/);
845 
846                     // This is to fix the case where the user puts one of these
847                     // three values in the AttributeCollection.  Removing them
848                     // at least is better than rendering them twice.
849                     li.Attributes.Remove("text");
850                     li.Attributes.Remove("value");
851                     li.Attributes.Remove("selected");
852 
853                     li.Attributes.Render(writer);
854                     writer.Write(HtmlTextWriter.TagRightChar);
855                     HttpUtility.HtmlEncode(li.Text, writer);
856                     writer.WriteEndTag("option");
857                     writer.WriteLine();
858                 }
859             }
860             writer.Indent--;
861         }
862 
863         /*
864          * Save selected indices and modified Items.
865          */
866 
867         /// <internalonly/>
868         /// <devdoc>
869         /// </devdoc>
SaveViewState()870         protected override object SaveViewState() {
871 
872             object baseState = base.SaveViewState();
873             object items = ((IStateManager)Items).SaveViewState();
874             object selectedindices = null;
875 
876             // only save selection if handler is registered,
877             // we are disabled, or we are not visible
878             // since selection is always posted back otherwise
879             if (Events[EventServerChange] != null || Disabled || !Visible)
880                 selectedindices = SelectedIndices;
881 
882             if (selectedindices  != null || items != null || baseState != null)
883                 return new Triplet(baseState, items, selectedindices);
884 
885             return null;
886         }
887 
888 
889         /// <internalonly/>
890         /// <devdoc>
891         /// </devdoc>
Select(int[] selectedIndices)892         protected virtual void Select(int[] selectedIndices) {
893             ClearSelection();
894             for (int i=0; i < selectedIndices.Length; i++) {
895                 int n = selectedIndices[i];
896                 if (n >= 0 && n < Items.Count)
897                     Items[n].Selected = true;
898             }
899         }
900 
901         /*
902          * TrackState
903          */
904 
905         /// <internalonly/>
906         /// <devdoc>
907         /// </devdoc>
TrackViewState()908         protected override void TrackViewState() {
909             base.TrackViewState();
910             ((IStateManager)Items).TrackViewState();
911         }
912 
913 
914         /*
915          * Method of IPostBackDataHandler interface to process posted data.
916          * SelectList processes a newly posted value.
917          */
918 
919         /// <internalonly/>
920         /// <devdoc>
921         /// </devdoc>
IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)922         bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
923             return LoadPostData(postDataKey, postCollection);
924         }
925 
926 
927         /// <internalonly/>
928         /// <devdoc>
929         /// </devdoc>
LoadPostData(string postDataKey, NameValueCollection postCollection)930         protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
931             string[] selectedItems = postCollection.GetValues(postDataKey);
932             bool selectionChanged = false;
933 
934             if (selectedItems != null) {
935                 if (!Multiple) {
936                     int n = Items.FindByValueInternal(selectedItems[0], false);
937                     if (SelectedIndex != n) {
938                         SelectedIndex = n;
939                         selectionChanged = true;
940                     }
941                 }
942                 else { // multiple selection
943                     int count = selectedItems.Length;
944                     int[] oldSelectedIndices = SelectedIndices;
945                     int[] newSelectedIndices = new int[count];
946                     for (int i=0; i < count; i++) {
947                         // create array of new indices from posted values
948                         newSelectedIndices[i] = Items.FindByValueInternal(selectedItems[i], false);
949                     }
950 
951                     if (oldSelectedIndices.Length == count) {
952                         // check new indices against old indices
953                         // assumes selected values are posted in order
954                         for (int i=0; i < count; i++) {
955                             if (newSelectedIndices[i] != oldSelectedIndices[i]) {
956                                 selectionChanged = true;
957                                 break;
958                             }
959                         }
960                     }
961                     else {
962                         // indices must have changed if count is different
963                         selectionChanged = true;
964                     }
965 
966                     if (selectionChanged) {
967                         // select new indices
968                         Select(newSelectedIndices);
969                     }
970                 }
971             }
972             else { // no items selected
973                 if (SelectedIndex != -1) {
974                     SelectedIndex = -1;
975                     selectionChanged = true;
976                 }
977             }
978 
979             if (selectionChanged) {
980                 ValidateEvent(postDataKey);
981             }
982 
983             return selectionChanged;
984         }
985 
986         /*
987          * Method of IPostBackDataHandler interface which is invoked whenever posted data
988          * for a control has changed.  SelectList fires an OnServerChange event.
989          */
990 
991         /// <internalonly/>
992         /// <devdoc>
993         /// </devdoc>
IPostBackDataHandler.RaisePostDataChangedEvent()994         void IPostBackDataHandler.RaisePostDataChangedEvent() {
995             RaisePostDataChangedEvent();
996         }
997 
998 
999         /// <internalonly/>
1000         /// <devdoc>
1001         /// </devdoc>
RaisePostDataChangedEvent()1002         protected virtual void RaisePostDataChangedEvent() {
1003             OnServerChange(EventArgs.Empty);
1004         }
1005     }
1006 }
1007