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