Ewl » Canonical The canonical version of EWL Read More
Clone URL:  
Pushed to 2 repositories · View In Graph Contained in tip and canonical

Created Checkbox and FlowCheckbox to replace EwfCheckBox and BlockCheckBox, and
reimplemented RadioButtonGroup and FreeFormRadioList to use them.

Changeset 34407856c3a8

Parent 98cea5697926

by Profile picture of William GrossWilliam Gross

Changes to 13 files · Browse files at 34407856c3a8 Showing diff from parent 98cea5697926 Diff from another changeset...

Change 1 of 3 Show Entire File Core/​Core.csproj Stacked
 
379
380
381
 
 
 
 
 
 
382
383
384
 
399
400
401
 
 
402
403
404
 
739
740
741
742
743
744
745
 
379
380
381
382
383
384
385
386
387
388
389
390
 
405
406
407
408
409
410
411
412
 
747
748
749
 
750
751
752
@@ -379,6 +379,12 @@
  <Compile Include="EnterpriseWebFramework\EwfInitializationOps.cs" />   <Compile Include="EnterpriseWebFramework\FontAwesomeIcon.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Check Boxes\BlockCheckBoxSetup.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\Checkbox.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\CheckboxSetup.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\FlowCheckbox.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\FlowCheckboxSetup.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\FlowRadioButtonSetup.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Checkboxes\RadioButtonSetup.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Date and Time\DateAndTimeControl.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Date and Time\DateAndTimeControlSetup.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Date and Time\DateControl.cs" /> @@ -399,6 +405,8 @@
  <Compile Include="EnterpriseWebFramework\Form Controls\Imprecise Number Control\ImpreciseNumberControl.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Imprecise Number Control\ImpreciseNumberControlSetup.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Lists\ChangeBasedCheckBoxList.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Lists\Free-Form Radio List\FreeFormRadioList.cs" /> + <Compile Include="EnterpriseWebFramework\Form Controls\Lists\Free-Form Radio List\FreeFormRadioListSetup.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Lists\List Items\ChangeBasedListItem.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Lists\List Items\ChangeBasedListItemWithSelectionState.cs" />   <Compile Include="EnterpriseWebFramework\Form Controls\Lists\EwfCheckBoxList.cs" /> @@ -739,7 +747,6 @@
  <Compile Include="EnterpriseWebFramework\UserManagement\FormsAuthCapableUserManagementProvider.cs" />   <Compile Include="EnterpriseWebFramework\UserManagement\User.cs" />   <Compile Include="EnterpriseWebFramework\UserManagement\SystemUserManagementProvider.cs" /> - <Compile Include="EnterpriseWebFramework\Form Controls\Lists\FreeFormRadioList.cs" />   <Compile Include="Tools\CollectionTools.cs" />   <Compile Include="Collections\ListSet.cs" />   <Compile Include="FormattingMethods.cs" />
 
26
27
28
 
29
30
31
32
33
34
 
35
36
37
 
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@@ -26,12 +26,14 @@
  <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cerror_0020display_0020styles/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cerror_0020display_005Cstyles/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=EnterpriseWebFramework_005CForm_0020Controls/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Ccheckboxes/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=EnterpriseWebFramework_005CForm_0020Controls_005CCheck_0020Boxes/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Cdate_0020and_0020time/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Cemail_0020address_0020control/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Chtml_0020editor/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Cimprecise_0020number_0020control/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=EnterpriseWebFramework_005CForm_0020Controls_005CLists/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Clists_005Cfree_002Dform_0020radio_0020list/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=EnterpriseWebFramework_005CForm_0020Controls_005CLists_005CList_0020Items/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=EnterpriseWebFramework_005CForm_0020Controls_005CLists_005CSelect_0020List/@EntryIndexedValue">True</s:Boolean>   <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enterprisewebframework_005Cform_0020controls_005Cnavigational_0020form_0020control/@EntryIndexedValue">True</s:Boolean>
 
10
11
12
13
14
15
 
16
17
18
 
10
11
12
 
 
 
13
14
15
16
@@ -10,9 +10,7 @@
 using EnterpriseWebLibrary.InputValidation;    namespace EnterpriseWebLibrary.EnterpriseWebFramework { - /// <summary> - /// A block-level check box with the label vertically centered on the box. - /// </summary> + [ Obsolete( "Guaranteed through 28 Feb 2019. Use Checkbox instead." ) ]   [ ParseChildren( ChildrenAsProperties = true, DefaultProperty = "NestedControls" ) ]   public class BlockCheckBox: WebControl, CommonCheckBox, ControlTreeDataLoader, FormValueControl, ControlWithJsInitLogic, FormControl<FlowComponent> {   private readonly FormValue<bool> checkBoxFormValue;
 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
23
24
25
 
38
39
40
41
42
43
44
45
 
 
46
47
48
 
6
7
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
10
11
12
 
25
26
27
 
 
 
 
 
28
29
30
31
32
@@ -6,20 +6,7 @@
 using EnterpriseWebLibrary.JavaScriptWriting;    namespace EnterpriseWebLibrary.EnterpriseWebFramework { - /* NOTE: This should be named InlineCheckBox. When we do this, rename CommonCheckBox to EwfCheckBox. - * - * InlineCheckBox is a check box that can be centered with text-align or used within a paragraph of text. This cannot be done with BlockCheckBox for two reasons: - * - * 1. BlockCheckBox needs to have 100% width in order for nested controls to work properly in Chrome and Safari. Specifically, a two-row shrink wrap table with wide content - * in the second column of the second row did not work properly in these browsers under certain circumstances (Sam may have details). Inline elements cannot specify width, - * so the BlockCheckBox must be non-inline, and non-inline elements cannot be centered with text-align or used within a paragraph of text. - * - * 2. We could not find a way to get Chrome and Safari to center a table-based check box control using text-align regardless of what CSS display value we used on the element. - * */ - - /// <summary> - /// An in-line check box with the label vertically centered on the box. - /// </summary> + [ Obsolete( "Guaranteed through 28 Feb 2019. Use Checkbox instead." ) ]   public class EwfCheckBox: WebControl, CommonCheckBox, ControlTreeDataLoader, FormValueControl {   private static readonly ElementClass elementClass = new ElementClass( "ewfCheckBox" );   @@ -38,11 +25,8 @@
  () => isChecked,   () => checkBox.IsOnPage() ? checkBox.UniqueID : "",   v => v.ToString(), - rawValue => rawValue == null - ? PostBackValueValidationResult<bool>.CreateValid( false ) - : rawValue == "on" - ? PostBackValueValidationResult<bool>.CreateValid( true ) - : PostBackValueValidationResult<bool>.CreateInvalid() ); + rawValue => rawValue == null ? PostBackValueValidationResult<bool>.CreateValid( false ) : + rawValue == "on" ? PostBackValueValidationResult<bool>.CreateValid( true ) : PostBackValueValidationResult<bool>.CreateInvalid() );   }     internal static void AddCheckBoxAttributes(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
 
@@ -0,0 +1,151 @@
+using System; +using System.Collections.Generic; +using System.Linq; +using EnterpriseWebLibrary.InputValidation; +using Humanizer; +using JetBrains.Annotations; + +namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// A two-state control (i.e. checkbox). + /// </summary> + public class Checkbox: FormControl<PhrasingComponent> { + private static readonly ElementClass elementClass = new ElementClass( "ewfCb" ); + + [ UsedImplicitly ] + private class CssElementCreator: ControlCssElementCreator { + IReadOnlyCollection<CssElement> ControlCssElementCreator.CreateCssElements() => + new CssElement( "Checkbox", "span.{0}".FormatWith( elementClass.ClassName ) ).ToCollection() + .Append( new CssElement( "CheckboxControl", "input.{0}".FormatWith( elementClass.ClassName ) ) ) + .Append( new CssElement( "CheckboxLabel", "label.{0}".FormatWith( elementClass.ClassName ) ) ) + .Materialize(); + } + + public PhrasingComponent PageComponent { get; } + public EwfValidation Validation { get; } + + /// <summary> + /// Creates a checkbox. + /// </summary> + /// <param name="value"></param> + /// <param name="label">The checkbox label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the checkbox.</param> + /// <param name="validationMethod">The validation method. Pass null if you’re only using this control for page modification.</param> + public Checkbox( + bool value, IReadOnlyCollection<PhrasingComponent> label, CheckboxSetup setup = null, Action<PostBackValue<bool>, Validator> validationMethod = null ) { + setup = setup ?? CheckboxSetup.Create(); + + var id = new ElementId(); + var formValue = new FormValue<bool>( + () => value, + () => setup.IsReadOnly ? "" : id.Id, + v => v.ToString(), + rawValue => rawValue == null ? PostBackValueValidationResult<bool>.CreateValid( false ) : + rawValue == "on" ? PostBackValueValidationResult<bool>.CreateValid( true ) : PostBackValueValidationResult<bool>.CreateInvalid() ); + + PageComponent = getComponent( + formValue, + id, + null, + setup.DisplaySetup, + setup.IsReadOnly, + setup.Classes, + setup.PageModificationValue, + label, + setup.Action, + setup.ValueChangedAction, + () => ( setup.ValueChangedAction?.GetJsStatements() ?? "" ).ConcatenateWithSpace( + setup.PageModificationValue.GetJsModificationStatements( "this.checked" ) ) ); + + formValue.AddPageModificationValue( setup.PageModificationValue, v => v ); + + if( validationMethod != null ) + Validation = formValue.CreateValidation( validationMethod ); + } + + /// <summary> + /// Creates a radio button. + /// </summary> + internal Checkbox( + FormValue<ElementId> formValue, ElementId id, RadioButtonSetup setup, IReadOnlyCollection<PhrasingComponent> label, FormAction selectionChangedAction, + Func<string> jsClickStatementGetter, EwfValidation validation, string listItemId = null ) { + PageComponent = getComponent( + formValue, + id, + listItemId, + setup.DisplaySetup, + setup.IsReadOnly, + setup.Classes, + setup.PageModificationValue, + label, + setup.Action, + selectionChangedAction, + () => setup.IsReadOnly + ? "" + : ( setup.PageModificationValue.Value ? "" : selectionChangedAction?.GetJsStatements() ?? "" ) + .ConcatenateWithSpace( jsClickStatementGetter() ) ); + Validation = validation; + } + + private PhrasingComponent getComponent( + FormValue formValue, ElementId id, string radioButtonListItemId, DisplaySetup displaySetup, bool isReadOnly, ElementClassSet classes, + PageModificationValue<bool> pageModificationValue, IReadOnlyCollection<PhrasingComponent> label, FormAction action, FormAction valueChangedAction, + Func<string> jsClickStatementGetter ) { + var labeler = new FormControlLabeler(); + return new GenericPhrasingContainer( + new CustomPhrasingComponent( + new DisplayableElement( + context => { + id.AddId( context.Id ); + labeler.AddControlId( context.Id ); + + if( !isReadOnly ) { + action.AddToPageIfNecessary(); + valueChangedAction?.AddToPageIfNecessary(); + } + + return new DisplayableElementData( + null, + () => { + var attributes = new List<Tuple<string, string>>(); + var radioButtonFormValue = formValue as FormValue<ElementId>; + attributes.Add( Tuple.Create( "type", radioButtonFormValue != null ? "radio" : "checkbox" ) ); + if( radioButtonFormValue != null || !isReadOnly ) + attributes.Add( + Tuple.Create( "name", radioButtonFormValue != null ? ( (FormValue)radioButtonFormValue ).GetPostBackValueKey() : context.Id ) ); + if( radioButtonFormValue != null ) + attributes.Add( Tuple.Create( "value", radioButtonListItemId ?? context.Id ) ); + if( pageModificationValue.Value ) + attributes.Add( Tuple.Create( "checked", "checked" ) ); + if( isReadOnly ) + attributes.Add( Tuple.Create( "disabled", "disabled" ) ); + + var jsInitStatements = StringTools.ConcatenateWithDelimiter( + " ", + !isReadOnly + ? SubmitButton.GetImplicitSubmissionKeyPressStatements( action, false ) + .Surround( "$( '#{0}' ).keypress( function( e ) {{ ".FormatWith( context.Id ), " } );" ) + : "", + jsClickStatementGetter().Surround( "$( '#{0}' ).click( function() {{ ".FormatWith( context.Id ), " } );" ) ); + + return new DisplayableElementLocalData( + "input", + new FocusabilityCondition( true ), + isFocused => { + if( isFocused ) + attributes.Add( Tuple.Create( "autofocus", "autofocus" ) ); + return new DisplayableElementFocusDependentData( attributes: attributes, includeIdAttribute: true, jsInitStatements: jsInitStatements ); + } ); + }, + classes: elementClass ); + }, + formValue: formValue ).ToCollection() ).ToCollection() + .Concat( label.Any() ? labeler.CreateLabel( label, classes: elementClass ) : Enumerable.Empty<PhrasingComponent>() ) + .Materialize(), + displaySetup: displaySetup, + classes: elementClass.Add( classes ?? ElementClassSet.Empty ) ); + } + + FormControlLabeler FormControl<PhrasingComponent>.Labeler => null; + } +} \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
@@ -0,0 +1,47 @@
+namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// The configuration for a checkbox. + /// </summary> + public class CheckboxSetup { + /// <summary> + /// Creates a setup object for a standard checkbox. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the control.</param> + /// <param name="action">The action that will occur when the user hits Enter on the control. Pass null to use the current default action.</param> + /// <param name="valueChangedAction">The action that will occur when the value is changed. Pass null for no action.</param> + /// <param name="pageModificationValue"></param> + public static CheckboxSetup Create( + DisplaySetup displaySetup = null, ElementClassSet classes = null, FormAction action = null, FormAction valueChangedAction = null, + PageModificationValue<bool> pageModificationValue = null ) { + return new CheckboxSetup( displaySetup, false, classes, action, valueChangedAction, pageModificationValue ); + } + + /// <summary> + /// Creates a setup object for a read-only checkbox. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the control.</param> + public static CheckboxSetup CreateReadOnly( DisplaySetup displaySetup = null, ElementClassSet classes = null ) { + return new CheckboxSetup( displaySetup, true, classes, null, null, null ); + } + + internal readonly DisplaySetup DisplaySetup; + internal readonly bool IsReadOnly; + internal readonly ElementClassSet Classes; + internal readonly FormAction Action; + internal readonly FormAction ValueChangedAction; + internal readonly PageModificationValue<bool> PageModificationValue; + + private CheckboxSetup( + DisplaySetup displaySetup, bool isReadOnly, ElementClassSet classes, FormAction action, FormAction valueChangedAction, + PageModificationValue<bool> pageModificationValue ) { + DisplaySetup = displaySetup; + IsReadOnly = isReadOnly; + Classes = classes; + Action = action ?? FormState.Current.DefaultAction; + ValueChangedAction = valueChangedAction; + PageModificationValue = pageModificationValue ?? new PageModificationValue<bool>(); + } + } +} \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
 
@@ -0,0 +1,100 @@
+using System; +using System.Collections.Generic; +using System.Linq; +using EnterpriseWebLibrary.InputValidation; +using Humanizer; +using JetBrains.Annotations; + +namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// A checkbox that supports nested components. + /// </summary> + public class FlowCheckbox: FormControl<FlowComponent> { + private static readonly ElementClass unhighlightedClass = new ElementClass( "ewfFcu" ); + private static readonly ElementClass highlightedClass = new ElementClass( "ewfFch" ); + private static readonly ElementClass nestedContentClass = new ElementClass( "ewfFcc" ); + + [ UsedImplicitly ] + private class CssElementCreator: ControlCssElementCreator { + IReadOnlyCollection<CssElement> ControlCssElementCreator.CreateCssElements() => + new[] + { + new CssElement( "FlowCheckboxAllStates", new[] { unhighlightedClass, highlightedClass }.Select( getSelector ).ToArray() ), + new CssElement( "FlowCheckboxUnhighlightedState", getSelector( unhighlightedClass ) ), + new CssElement( "FlowCheckboxHighlightedState", getSelector( highlightedClass ) ), + new CssElement( "FlowCheckboxNestedContentContainer", "div.{0}".FormatWith( nestedContentClass ) ) + }; + + private string getSelector( ElementClass elementClass ) => "div.{0}".FormatWith( elementClass.ClassName ); + } + + public FlowComponent PageComponent { get; } + public EwfValidation Validation { get; } + + /// <summary> + /// Creates a checkbox. + /// </summary> + /// <param name="value"></param> + /// <param name="label">The checkbox label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the flow checkbox.</param> + /// <param name="validationMethod">The validation method. Pass null if you’re only using the checkbox for page modification.</param> + public FlowCheckbox( + bool value, IReadOnlyCollection<PhrasingComponent> label, FlowCheckboxSetup setup = null, + Action<PostBackValue<bool>, Validator> validationMethod = null ) { + setup = setup ?? FlowCheckboxSetup.Create(); + + var checkbox = new Checkbox( value, label, setup: setup.CheckboxSetup, validationMethod: validationMethod ); + + PageComponent = getComponent( + setup.DisplaySetup, + setup.Classes, + setup.CheckboxSetup.PageModificationValue, + checkbox, + setup.HighlightedWhenChecked, + setup.NestedContentGetter, + setup.NestedContentAlwaysDisplayed ); + + Validation = checkbox.Validation; + } + + /// <summary> + /// Creates a radio button. + /// </summary> + internal FlowCheckbox( FlowRadioButtonSetup setup, Checkbox checkbox ) { + PageComponent = getComponent( + setup.DisplaySetup, + setup.Classes, + setup.RadioButtonSetup.PageModificationValue, + checkbox, + setup.HighlightedWhenSelected, + setup.NestedContentGetter, + setup.NestedContentAlwaysDisplayed ); + + Validation = checkbox.Validation; + } + + private FlowComponent getComponent( + DisplaySetup displaySetup, ElementClassSet classes, PageModificationValue<bool> pageModificationValue, Checkbox checkbox, bool highlightedWhenChecked, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter, bool nestedContentAlwaysDisplayed ) { + var nestedContent = nestedContentGetter?.Invoke() ?? Enumerable.Empty<FlowComponent>().Materialize(); + return new GenericFlowContainer( + checkbox.PageComponent.ToCollection() + .Concat( + nestedContent.Any() + ? new GenericFlowContainer( + nestedContent, + displaySetup: nestedContentAlwaysDisplayed ? null : pageModificationValue.ToCondition().ToDisplaySetup(), + classes: nestedContentClass ).ToCollection() + : Enumerable.Empty<FlowComponent>() ) + .Materialize(), + displaySetup: displaySetup, + classes: ( highlightedWhenChecked + ? pageModificationValue.ToCondition( isTrueWhenValueSet: false ) + .ToElementClassSet( unhighlightedClass ) + .Add( pageModificationValue.ToCondition().ToElementClassSet( highlightedClass ) ) + : unhighlightedClass ).Add( classes ?? ElementClassSet.Empty ) ); + } + + FormControlLabeler FormControl<FlowComponent>.Labeler => null; + } +} \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 
@@ -0,0 +1,71 @@
+using System; +using System.Collections.Generic; + +namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// The configuration for a flow checkbox. + /// </summary> + public class FlowCheckboxSetup { + /// <summary> + /// Creates a setup object for a standard checkbox. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the container.</param> + /// <param name="highlightedWhenChecked"></param> + /// <param name="action">The action that will occur when the user hits Enter on the checkbox. Pass null to use the current default action.</param> + /// <param name="valueChangedAction">The action that will occur when the checkbox value is changed. Pass null for no action.</param> + /// <param name="pageModificationValue"></param> + /// <param name="nestedContentGetter">A function that gets the content that will appear beneath the checkbox.</param> + /// <param name="nestedContentAlwaysDisplayed">Pass true to force the nested content to always be displayed instead of only when the box is checked.</param> + public static FlowCheckboxSetup Create( + DisplaySetup displaySetup = null, ElementClassSet classes = null, bool highlightedWhenChecked = false, FormAction action = null, + FormAction valueChangedAction = null, PageModificationValue<bool> pageModificationValue = null, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter = null, bool nestedContentAlwaysDisplayed = false ) { + return new FlowCheckboxSetup( + displaySetup, + classes, + CheckboxSetup.Create( action: action, valueChangedAction: valueChangedAction, pageModificationValue: pageModificationValue ), + highlightedWhenChecked, + nestedContentGetter, + nestedContentAlwaysDisplayed ); + } + + /// <summary> + /// Creates a setup object for a read-only checkbox. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the container.</param> + /// <param name="highlightedWhenChecked"></param> + /// <param name="nestedContentGetter">A function that gets the content that will appear beneath the checkbox.</param> + /// <param name="nestedContentAlwaysDisplayed">Pass true to force the nested content to always be displayed instead of only when the box is checked.</param> + public static FlowCheckboxSetup CreateReadOnly( + DisplaySetup displaySetup = null, ElementClassSet classes = null, bool highlightedWhenChecked = false, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter = null, bool nestedContentAlwaysDisplayed = false ) { + return new FlowCheckboxSetup( + displaySetup, + classes, + CheckboxSetup.CreateReadOnly(), + highlightedWhenChecked, + nestedContentGetter, + nestedContentAlwaysDisplayed ); + } + + internal readonly DisplaySetup DisplaySetup; + internal readonly ElementClassSet Classes; + internal readonly CheckboxSetup CheckboxSetup; + internal readonly bool HighlightedWhenChecked; + internal readonly Func<IReadOnlyCollection<FlowComponent>> NestedContentGetter; + internal readonly bool NestedContentAlwaysDisplayed; + + private FlowCheckboxSetup( + DisplaySetup displaySetup, ElementClassSet classes, CheckboxSetup checkboxSetup, bool highlightedWhenChecked, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter, bool nestedContentAlwaysDisplayed ) { + DisplaySetup = displaySetup; + Classes = classes; + CheckboxSetup = checkboxSetup; + HighlightedWhenChecked = highlightedWhenChecked; + NestedContentGetter = nestedContentGetter; + NestedContentAlwaysDisplayed = nestedContentAlwaysDisplayed; + } + } +} \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
 
@@ -0,0 +1,72 @@
+using System; +using System.Collections.Generic; + +namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// The configuration for a flow radio button. + /// </summary> + public class FlowRadioButtonSetup { + /// <summary> + /// Creates a setup object for a standard radio button. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the container.</param> + /// <param name="highlightedWhenSelected"></param> + /// <param name="action">The action that will occur when the user hits Enter on the radio button. Pass null to use the current default action.</param> + /// <param name="pageModificationValue"></param> + /// <param name="nestedContentGetter">A function that gets the content that will appear beneath the radio button.</param> + /// <param name="nestedContentAlwaysDisplayed">Pass true to force the nested content to always be displayed instead of only when the button is selected. + /// </param> + public static FlowRadioButtonSetup Create( + DisplaySetup displaySetup = null, ElementClassSet classes = null, bool highlightedWhenSelected = false, FormAction action = null, + PageModificationValue<bool> pageModificationValue = null, Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter = null, + bool nestedContentAlwaysDisplayed = false ) { + return new FlowRadioButtonSetup( + displaySetup, + classes, + RadioButtonSetup.Create( action: action, pageModificationValue: pageModificationValue ), + highlightedWhenSelected, + nestedContentGetter, + nestedContentAlwaysDisplayed ); + } + + /// <summary> + /// Creates a setup object for a read-only radio button. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the container.</param> + /// <param name="highlightedWhenSelected"></param> + /// <param name="nestedContentGetter">A function that gets the content that will appear beneath the radio button.</param> + /// <param name="nestedContentAlwaysDisplayed">Pass true to force the nested content to always be displayed instead of only when the button is selected. + /// </param> + public static FlowRadioButtonSetup CreateReadOnly( + DisplaySetup displaySetup = null, ElementClassSet classes = null, bool highlightedWhenSelected = false, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter = null, bool nestedContentAlwaysDisplayed = false ) { + return new FlowRadioButtonSetup( + displaySetup, + classes, + RadioButtonSetup.CreateReadOnly(), + highlightedWhenSelected, + nestedContentGetter, + nestedContentAlwaysDisplayed ); + } + + internal readonly DisplaySetup DisplaySetup; + internal readonly ElementClassSet Classes; + internal readonly RadioButtonSetup RadioButtonSetup; + internal readonly bool HighlightedWhenSelected; + internal readonly Func<IReadOnlyCollection<FlowComponent>> NestedContentGetter; + internal readonly bool NestedContentAlwaysDisplayed; + + private FlowRadioButtonSetup( + DisplaySetup displaySetup, ElementClassSet classes, RadioButtonSetup radioButtonSetup, bool highlightedWhenSelected, + Func<IReadOnlyCollection<FlowComponent>> nestedContentGetter, bool nestedContentAlwaysDisplayed ) { + DisplaySetup = displaySetup; + Classes = classes; + RadioButtonSetup = radioButtonSetup; + HighlightedWhenSelected = highlightedWhenSelected; + NestedContentGetter = nestedContentGetter; + NestedContentAlwaysDisplayed = nestedContentAlwaysDisplayed; + } + } +} \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
@@ -0,0 +1,42 @@
+namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// The configuration for a radio button. + /// </summary> + public class RadioButtonSetup { + /// <summary> + /// Creates a setup object for a standard radio button. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the control.</param> + /// <param name="action">The action that will occur when the user hits Enter on the control. Pass null to use the current default action.</param> + /// <param name="pageModificationValue"></param> + public static RadioButtonSetup Create( + DisplaySetup displaySetup = null, ElementClassSet classes = null, FormAction action = null, PageModificationValue<bool> pageModificationValue = null ) { + return new RadioButtonSetup( displaySetup, false, classes, action, pageModificationValue ); + } + + /// <summary> + /// Creates a setup object for a read-only radio button. + /// </summary> + /// <param name="displaySetup"></param> + /// <param name="classes">The classes on the control.</param> + public static RadioButtonSetup CreateReadOnly( DisplaySetup displaySetup = null, ElementClassSet classes = null ) { + return new RadioButtonSetup( displaySetup, true, classes, null, null ); + } + + internal readonly DisplaySetup DisplaySetup; + internal readonly bool IsReadOnly; + internal readonly ElementClassSet Classes; + internal readonly FormAction Action; + internal readonly PageModificationValue<bool> PageModificationValue; + + private RadioButtonSetup( + DisplaySetup displaySetup, bool isReadOnly, ElementClassSet classes, FormAction action, PageModificationValue<bool> pageModificationValue ) { + DisplaySetup = displaySetup; + IsReadOnly = isReadOnly; + Classes = classes; + Action = action ?? FormState.Current.DefaultAction; + PageModificationValue = pageModificationValue ?? new PageModificationValue<bool>(); + } + } +} \ No newline at end of file
Change 1 of 1 Show Entire File Core/​EnterpriseWebFramework/​Form Controls/​Lists/​Free-Form Radio List/​FreeFormRadioList.cs Stacked
renamed from Core/EnterpriseWebFramework/Form Controls/Lists/FreeFormRadioList.cs
 
1
2
3
4
5
6
7
 
8
 
9
10
11
12
 
13
14
15
16
17
18
19
20
21
 
 
 
 
22
23
24
25
26
 
 
27
28
29
30
31
32
33
34
35
36
 
 
 
37
38
39
40
41
 
42
43
44
45
46
47
48
49
 
 
 
 
 
 
 
 
 
50
51
52
53
54
 
 
55
56
57
58
59
60
61
62
63
64
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
67
68
69
 
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
 
 
 
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
114
115
116
 
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
 
 
 
 
 
 
141
142
143
144
 
145
146
 
147
148
149
150
151
152
 
153
154
155
156
157
 
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
 
186
187
 
 
1
2
 
3
 
 
 
4
5
6
7
8
9
 
10
11
12
13
14
15
16
 
 
 
17
18
19
20
21
 
 
 
 
22
23
24
 
 
 
 
 
 
 
 
 
25
26
27
28
29
30
31
 
32
33
34
 
 
 
 
 
 
35
36
37
38
39
40
41
42
43
44
45
 
 
 
46
47
48
 
49
 
 
 
 
 
 
 
 
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 
86
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
89
90
91
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
 
130
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
133
134
135
136
137
138
139
140
 
141
142
 
143
144
145
146
 
 
 
147
148
 
 
 
 
149
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
152
153
 
@@ -1,187 +1,153 @@
 using System;  using System.Collections.Generic; -using System.Collections.Immutable;  using System.Linq; -using System.Web.UI; -using System.Web.UI.WebControls; -using EnterpriseWebLibrary.EnterpriseWebFramework.DisplayLinking; +using EnterpriseWebLibrary.InputValidation;  using Humanizer; +using MoreLinq;    namespace EnterpriseWebLibrary.EnterpriseWebFramework {   /// <summary> - /// A radio button list that allows you to arrange the buttons on the page however you wish. If you want access to the individual selection state of each + /// A radio-button list that allows you to arrange the buttons on the page however you wish. If you want access to the individual selection state of each   /// radio button and do not need the concept of a selected item ID for the group, use RadioButtonGroup instead.   /// </summary>   public static class FreeFormRadioList {   /// <summary>   /// Creates a free-form radio button list.   /// </summary> - /// <param name="allowNoSelection">Pass true to cause a selected item ID with the default value (or the empty string when the item ID type is string) to - /// represent the state in which none of the radio buttons are selected. Note that this is not recommended by the Nielsen Norman Group; see - /// http://www.nngroup.com/articles/checkboxes-vs-radio-buttons/ for more information.</param> + /// <param name="noSelectionIsValid">Pass a value to cause a selected item ID with the default value (or the empty string when the item ID type is string) + /// to represent the state in which none of the radio buttons are selected. Note that this is not recommended by the Nielsen Norman Group; see + /// http://www.nngroup.com/articles/checkboxes-vs-radio-buttons/ for more information. If you do pass a value, passing true will cause this no-selection + /// state to be valid.</param>   /// <param name="selectedItemId"></param> - /// <param name="disableSingleButtonDetection">Pass true to allow just a single radio button to be displayed for this list. Use with caution, as this - /// violates the HTML specification.</param> - /// <param name="itemIdPageModificationValue"></param> - /// <param name="itemMatchPageModificationSetups"></param> + /// <param name="setup">The setup object for the free-form radio list.</param> + /// <param name="validationMethod">The validation method. Pass null if you�re only using this radio-button list for page modification.</param>   public static FreeFormRadioList<ItemIdType> Create<ItemIdType>( - bool allowNoSelection, ItemIdType selectedItemId, bool disableSingleButtonDetection = false, - PageModificationValue<ItemIdType> itemIdPageModificationValue = null, - IEnumerable<ListItemMatchPageModificationSetup<ItemIdType>> itemMatchPageModificationSetups = null ) { - return new FreeFormRadioList<ItemIdType>( - allowNoSelection, - disableSingleButtonDetection, - selectedItemId, - itemIdPageModificationValue, - itemMatchPageModificationSetups ); + bool? noSelectionIsValid, ItemIdType selectedItemId, FreeFormRadioListSetup<ItemIdType> setup = null, + Action<ItemIdType, Validator> validationMethod = null ) { + return new FreeFormRadioList<ItemIdType>( noSelectionIsValid, setup, selectedItemId, validationMethod );   }   }     /// <summary> - /// A radio button list that allows you to arrange the buttons on the page however you wish. If you want access to the individual selection state of each + /// A radio-button list that allows you to arrange the buttons on the page however you wish. If you want access to the individual selection state of each   /// radio button and do not need the concept of a selected item ID for the group, use RadioButtonGroup instead.   /// </summary> - public class FreeFormRadioList<ItemIdType>: DisplayLink { - private readonly bool allowNoSelection; - private readonly FormValue<CommonCheckBox> formValue; - private readonly List<Action<PostBackValueDictionary>> displayLinkingSetInitialDisplayMethods = new List<Action<PostBackValueDictionary>>(); - private readonly List<Action> displayLinkingAddJavaScriptMethods = new List<Action>(); - private readonly List<Tuple<ItemIdType, CommonCheckBox>> itemIdsAndCheckBoxes = new List<Tuple<ItemIdType, CommonCheckBox>>(); + public class FreeFormRadioList<ItemIdType> { + private readonly FormValue<ElementId> formValue; + private readonly bool? noSelectionIsValid; + + private readonly List<( ItemIdType itemId, ElementId buttonId, PageModificationValue<bool> pmv )> itemIdAndButtonIdAndPmvTriples = + new List<( ItemIdType, ElementId, PageModificationValue<bool> )>(); + + private readonly FreeFormRadioListSetup<ItemIdType> listSetup; + private readonly EwfValidation validation;     internal FreeFormRadioList( - bool allowNoSelection, bool disableSingleButtonDetection, ItemIdType selectedItemId, PageModificationValue<ItemIdType> itemIdPageModificationValue, - IEnumerable<ListItemMatchPageModificationSetup<ItemIdType>> itemMatchPageModificationSetups ) { - itemMatchPageModificationSetups = itemMatchPageModificationSetups ?? ImmutableArray<ListItemMatchPageModificationSetup<ItemIdType>>.Empty; + bool? noSelectionIsValid, FreeFormRadioListSetup<ItemIdType> setup, ItemIdType selectedItemId, Action<ItemIdType, Validator> validationMethod ) { + setup = setup ?? FreeFormRadioListSetup.Create<ItemIdType>();   - this.allowNoSelection = allowNoSelection;   formValue = RadioButtonGroup.GetFormValue( - allowNoSelection, - () => from i in itemIdsAndCheckBoxes select i.Item2, - () => from i in itemIdsAndCheckBoxes where EwlStatics.AreEqual( i.Item1, selectedItemId ) select i.Item2, - v => getStringId( v != null ? itemIdsAndCheckBoxes.Single( i => i.Item2 == v ).Item1 : getNoSelectionItemId() ), - rawValue => from itemIdAndCheckBox in itemIdsAndCheckBoxes - let control = (Control)itemIdAndCheckBox.Item2 - where control.IsOnPage() && getStringId( itemIdAndCheckBox.Item1 ) == rawValue - select itemIdAndCheckBox.Item2 ); + noSelectionIsValid.HasValue, + () => from i in itemIdAndButtonIdAndPmvTriples select i.buttonId, + () => from i in itemIdAndButtonIdAndPmvTriples where EwlStatics.AreEqual( i.itemId, selectedItemId ) select i.buttonId, + v => getStringId( v != null ? itemIdAndButtonIdAndPmvTriples.Single( i => i.buttonId == v ).itemId : getNoSelectionItemId() ), + rawValue => from itemIdAndButtonIdAndPmv in itemIdAndButtonIdAndPmvTriples + let buttonId = itemIdAndButtonIdAndPmv.buttonId + where buttonId.Id.Any() && getStringId( itemIdAndButtonIdAndPmv.itemId ) == rawValue + select buttonId ); + + this.noSelectionIsValid = noSelectionIsValid; + listSetup = setup; + + if( setup.ItemIdPageModificationValue != null ) + formValue.AddPageModificationValue( setup.ItemIdPageModificationValue, getItemIdFromButtonId ); + + foreach( var i in setup.ItemMatchPageModificationSetups ) + formValue.AddPageModificationValue( i.PageModificationValue, v => i.ItemIds.Contains( getItemIdFromButtonId( v ) ) ); + + if( validationMethod != null ) + validation = formValue.CreateValidation( + ( postBackValue, validator ) => { + if( setup.ValidationPredicate != null && !setup.ValidationPredicate( postBackValue.ChangedOnPostBack ) ) + return; + + var postBackItemId = getItemIdFromButtonId( postBackValue.Value ); + if( noSelectionIsValid == false && EwlStatics.AreEqual( postBackItemId, getNoSelectionItemId() ) ) { + validator.NoteErrorAndAddMessage( "Please make a selection." ); + setup.ValidationErrorNotifier?.Invoke(); + return; + } + + validationMethod( postBackItemId, validator ); + } );     EwfPage.Instance.AddControlTreeValidation(   () => RadioButtonGroup.ValidateControls( - allowNoSelection, + noSelectionIsValid.HasValue,   EwlStatics.AreEqual( getNoSelectionItemId(), selectedItemId ), - itemIdsAndCheckBoxes.Select( i => i.Item2 ), - disableSingleButtonDetection ) ); - - if( itemIdPageModificationValue != null ) { - formValue.AddPageModificationValue( itemIdPageModificationValue, getItemIdFromCheckBox ); - displayLinkingAddJavaScriptMethods.Add( - () => { - foreach( var pair in itemIdsAndCheckBoxes ) - pair.Item2.AddOnClickJsMethod( itemIdPageModificationValue.GetJsModificationStatements( "'{0}'".FormatWith( pair.Item1.ObjectToString( true ) ) ) ); - } ); - } - foreach( var setup in itemMatchPageModificationSetups ) { - formValue.AddPageModificationValue( setup.PageModificationValue, checkBox => setup.ItemIds.Contains( getItemIdFromCheckBox( checkBox ) ) ); - displayLinkingAddJavaScriptMethods.Add( - () => { - foreach( var pair in itemIdsAndCheckBoxes ) - pair.Item2.AddOnClickJsMethod( setup.PageModificationValue.GetJsModificationStatements( setup.ItemIds.Contains( pair.Item1 ) ? "true" : "false" ) ); - } ); - } - - EwfPage.Instance.AddDisplayLink( this ); + from i in itemIdAndButtonIdAndPmvTriples where EwlStatics.AreEqual( i.itemId, selectedItemId ) select i.buttonId, + from i in itemIdAndButtonIdAndPmvTriples select i.buttonId, + setup.DisableSingleButtonDetection ) );   }   - public void AddDisplayLink( IEnumerable<ItemIdType> itemIds, bool controlsVisibleOnMatch, IEnumerable<WebControl> controls ) { - itemIds = itemIds.ToArray(); - controls = controls.ToArray(); - displayLinkingSetInitialDisplayMethods.Add( - formControlValues => { - var match = itemIds.Contains( GetSelectedItemIdInPostBack( formControlValues ) ); - var visible = ( controlsVisibleOnMatch && match ) || ( !controlsVisibleOnMatch && !match ); - foreach( var i in controls ) - DisplayLinkingOps.SetControlDisplay( i, visible ); - } ); - displayLinkingAddJavaScriptMethods.Add( - () => { - foreach( var pair in itemIdsAndCheckBoxes ) { - DisplayLinkingOps.AddDisplayJavaScriptToCheckBox( - pair.Item2, - itemIds.Contains( pair.Item1 ) ? controlsVisibleOnMatch : !controlsVisibleOnMatch, - controls.ToArray() ); - } - } ); + private ItemIdType getItemIdFromButtonId( ElementId buttonId ) => + itemIdAndButtonIdAndPmvTriples.Where( i => i.buttonId == buttonId ).Select( i => i.itemId ).FallbackIfEmpty( getNoSelectionItemId() ).Single(); + + /// <summary> + /// Creates a radio button that is part of the list. + /// </summary> + /// <param name="listItemId"></param> + /// <param name="label">The radio button label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the radio button.</param> + public Checkbox CreateRadioButton( ItemIdType listItemId, IReadOnlyCollection<PhrasingComponent> label, RadioButtonSetup setup = null ) { + setup = setup ?? RadioButtonSetup.Create(); + + validateListItem( listItemId ); + + var id = new ElementId(); + formValue.AddPageModificationValue( setup.PageModificationValue, v => v == id ); + itemIdAndButtonIdAndPmvTriples.Add( ( listItemId, id, setup.PageModificationValue ) ); + + return new Checkbox( + formValue, + id, + setup, + label, + listSetup.SelectionChangedAction, + () => StringTools.ConcatenateWithDelimiter( + " ", + ( listSetup.ItemIdPageModificationValue?.GetJsModificationStatements( "'{0}'".FormatWith( getStringId( listItemId ) ) ) ?? "" ).ToCollection() + .Concat( + listSetup.ItemMatchPageModificationSetups.Select( + i => i.PageModificationValue.GetJsModificationStatements( i.ItemIds.Contains( listItemId ) ? "true" : "false" ) ) ) + .Concat( itemIdAndButtonIdAndPmvTriples.Select( i => i.pmv.GetJsModificationStatements( i.buttonId == id ? "true" : "false" ) ) ) + .ToArray() ), + null, + listItemId: getStringId( listItemId ) );   }     /// <summary> - /// Creates an in-line radio button that is part of the list. + /// Creates a flow radio button that is part of the list.   /// </summary> - public EwfCheckBox CreateInlineRadioButton( ItemIdType listItemId, string label = "", FormAction action = null, bool autoPostBack = false ) { - validateListItem( listItemId ); - var checkBox = - new EwfCheckBox( formValue, label, action, () => ImmutableArray<string>.Empty, listItemId: getStringId( listItemId ) ) { AutoPostBack = autoPostBack }; - itemIdsAndCheckBoxes.Add( Tuple.Create<ItemIdType, CommonCheckBox>( listItemId, checkBox ) ); - return checkBox; - } - - /// <summary> - /// Creates a block-level radio button that is part of the list. - /// </summary> - public BlockCheckBox CreateBlockRadioButton( - ItemIdType listItemId, string label = "", FormAction action = null, bool autoPostBack = false, Func<IEnumerable<Control>> nestedControlListGetter = null ) { - validateListItem( listItemId ); - var checkBox = new BlockCheckBox( - formValue, - new BlockCheckBoxSetup( action: action, triggersActionWhenCheckedOrUnchecked: autoPostBack, nestedControlListGetter: nestedControlListGetter ), - label.ToComponents(), - () => ImmutableArray<string>.Empty, - null, - listItemId: getStringId( listItemId ) ); - itemIdsAndCheckBoxes.Add( Tuple.Create<ItemIdType, CommonCheckBox>( listItemId, checkBox ) ); - return checkBox; + /// <param name="listItemId"></param> + /// <param name="label">The radio button label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the flow radio button.</param> + public FlowCheckbox CreateBlockRadioButton( ItemIdType listItemId, IReadOnlyCollection<PhrasingComponent> label, FlowRadioButtonSetup setup = null ) { + setup = setup ?? FlowRadioButtonSetup.Create(); + return new FlowCheckbox( setup, CreateRadioButton( listItemId, label, setup: setup.RadioButtonSetup ) );   }     private void validateListItem( ItemIdType listItemId ) { - if( allowNoSelection && EwlStatics.AreEqual( listItemId, getNoSelectionItemId() ) ) + if( noSelectionIsValid.HasValue && EwlStatics.AreEqual( listItemId, getNoSelectionItemId() ) )   throw new ApplicationException( "You cannot create a radio button with the ID that represents no selection." ); - if( itemIdsAndCheckBoxes.Any( i => getStringId( i.Item1 ) == getStringId( listItemId ) ) ) + if( itemIdAndButtonIdAndPmvTriples.Any( i => getStringId( i.itemId ) == getStringId( listItemId ) ) )   throw new ApplicationException( "Item IDs, when converted to strings, must be unique." );   }   - private string getStringId( ItemIdType id ) { - return id.ObjectToString( true ); - } + private ItemIdType getNoSelectionItemId() => EwlStatics.GetDefaultValue<ItemIdType>( true );   - void DisplayLink.SetInitialDisplay( PostBackValueDictionary formControlValues ) { - foreach( var i in displayLinkingSetInitialDisplayMethods ) - i( formControlValues ); - } + private string getStringId( ItemIdType id ) => id.ToString();   - void DisplayLink.AddJavaScript() { - foreach( var i in displayLinkingAddJavaScriptMethods ) - i(); - } - - /// <summary> - /// Gets the selected item ID in the post back. - /// </summary> - public ItemIdType GetSelectedItemIdInPostBack( PostBackValueDictionary postBackValues ) { - return getItemIdFromCheckBox( formValue.GetValue( postBackValues ) ); - } - - private ItemIdType getItemIdFromCheckBox( CommonCheckBox checkBox ) { - var pair = itemIdsAndCheckBoxes.SingleOrDefault( i => i.Item2 == checkBox ); - return pair != null ? pair.Item1 : getNoSelectionItemId(); - } - - private ItemIdType getNoSelectionItemId() { - return EwlStatics.GetDefaultValue<ItemIdType>( true ); - } - - /// <summary> - /// Returns true if the selection changed on this post back. - /// </summary> - public bool SelectionChangedOnPostBack( PostBackValueDictionary postBackValues ) { - return formValue.ValueChangedOnPostBack( postBackValues ); - } + public EwfValidation Validation => validation;   }  } \ No newline at end of file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 
@@ -0,0 +1,60 @@
+using System; +using System.Collections.Generic; +using System.Linq; + +namespace EnterpriseWebLibrary.EnterpriseWebFramework { + /// <summary> + /// The configuration for a free-form radio list. + /// </summary> + public static class FreeFormRadioListSetup { + /// <summary> + /// Creates a setup object for a free-form radio list. + /// </summary> + /// <param name="disableSingleButtonDetection">Pass true to allow just a single radio button to be displayed for this list. Use with caution, as this + /// violates the HTML specification.</param> + /// <param name="selectionChangedAction">The action that will occur when the selection is changed. Pass null for no action.</param> + /// <param name="itemIdPageModificationValue"></param> + /// <param name="itemMatchPageModificationSetups"></param> + /// <param name="validationPredicate"></param> + /// <param name="validationErrorNotifier"></param> + public static FreeFormRadioListSetup<ItemIdType> Create<ItemIdType>( + bool disableSingleButtonDetection = false, FormAction selectionChangedAction = null, PageModificationValue<ItemIdType> itemIdPageModificationValue = null, + IReadOnlyCollection<ListItemMatchPageModificationSetup<ItemIdType>> itemMatchPageModificationSetups = null, Func<bool, bool> validationPredicate = null, + Action validationErrorNotifier = null ) { + return new FreeFormRadioListSetup<ItemIdType>( + disableSingleButtonDetection, + selectionChangedAction, + itemIdPageModificationValue, + itemMatchPageModificationSetups, + validationPredicate, + validationErrorNotifier ); + } + } + + /// <summary> + /// The configuration for a free-form radio list. + /// </summary> + public class FreeFormRadioListSetup<ItemIdType> { + internal readonly bool DisableSingleButtonDetection; + internal readonly FormAction SelectionChangedAction; + internal readonly PageModificationValue<ItemIdType> ItemIdPageModificationValue; + internal readonly IReadOnlyCollection<ListItemMatchPageModificationSetup<ItemIdType>> ItemMatchPageModificationSetups; + internal readonly Func<bool, bool> ValidationPredicate; + internal readonly Action ValidationErrorNotifier; + + /// <summary> + /// Creates a setup object for a free-form radio list. + /// </summary> + internal FreeFormRadioListSetup( + bool disableSingleButtonDetection, FormAction selectionChangedAction, PageModificationValue<ItemIdType> itemIdPageModificationValue, + IReadOnlyCollection<ListItemMatchPageModificationSetup<ItemIdType>> itemMatchPageModificationSetups, Func<bool, bool> validationPredicate, + Action validationErrorNotifier ) { + DisableSingleButtonDetection = disableSingleButtonDetection; + SelectionChangedAction = selectionChangedAction; + ItemIdPageModificationValue = itemIdPageModificationValue; + ItemMatchPageModificationSetups = itemMatchPageModificationSetups ?? Enumerable.Empty<ListItemMatchPageModificationSetup<ItemIdType>>().Materialize(); + ValidationPredicate = validationPredicate; + ValidationErrorNotifier = validationErrorNotifier; + } + } +} \ No newline at end of file
 
1
2
3
4
5
6
7
 
10
11
12
13
14
15
16
17
18
19
20
21
 
 
 
 
 
 
22
23
24
25
26
27
28
 
 
 
 
29
30
31
32
 
33
34
35
36
37
38
 
 
 
39
40
41
 
 
42
43
 
44
45
46
47
48
 
 
 
49
50
 
51
52
53
54
55
56
57
 
58
59
60
 
 
 
 
61
62
63
 
66
67
68
69
 
 
70
71
72
73
74
75
76
77
78
 
 
 
 
 
 
 
 
 
79
80
81
82
83
84
 
 
 
85
86
87
88
89
 
90
91
92
93
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
 
96
97
98
99
100
101
102
103
104
105
106
 
 
 
 
 
 
 
 
 
 
107
108
109
110
 
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
 
 
 
 
 
 
 
 
 
138
139
140
 
 
1
2
3
 
4
5
6
 
9
10
11
 
 
 
 
 
 
 
 
 
12
13
14
15
16
17
18
19
20
 
 
 
 
21
22
23
24
25
 
 
 
26
27
28
29
30
 
 
31
32
33
34
 
 
35
36
37
 
38
39
40
 
 
 
41
42
43
44
 
45
46
47
48
49
50
51
 
52
53
 
 
54
55
56
57
58
59
60
 
63
64
65
 
66
67
68
69
 
 
 
 
 
 
 
70
71
72
73
74
75
76
77
78
79
80
81
82
 
 
83
84
85
86
87
88
89
 
90
91
 
 
 
 
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
 
 
 
 
 
 
 
 
 
 
110
111
112
113
114
115
116
117
118
119
120
121
122
 
123
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
126
127
128
129
130
131
132
133
134
135
136
 
@@ -1,7 +1,6 @@
 using System;  using System.Collections.Generic;  using System.Linq; -using System.Web.UI;  using EnterpriseWebLibrary.InputValidation;    namespace EnterpriseWebLibrary.EnterpriseWebFramework { @@ -10,54 +9,52 @@
  /// ID for the group. Otherwise use FreeFormRadioList.   /// </summary>   public class RadioButtonGroup { - internal static FormValue<CommonCheckBox> GetFormValue( - bool allowsNoSelection, Func<IEnumerable<CommonCheckBox>> allCheckBoxesGetter, Func<IEnumerable<CommonCheckBox>> checkedCheckBoxesGetter, - Func<CommonCheckBox, string> stringValueSelector, Func<string, IEnumerable<CommonCheckBox>> checkedCheckBoxesInPostBackGetter ) { - return new FormValue<CommonCheckBox>( - () => checkedCheckBoxesGetter().FirstOrDefault(), - () => { - var firstCheckBoxOnPage = allCheckBoxesGetter().Select( i => (Control)i ).FirstOrDefault( i => i.IsOnPage() ); - return firstCheckBoxOnPage != null ? firstCheckBoxOnPage.UniqueID : ""; - }, + internal static FormValue<ElementId> GetFormValue( + bool allowsNoSelection, Func<IEnumerable<ElementId>> buttonIdGetter, Func<IEnumerable<ElementId>> selectedButtonIdGetter, + Func<ElementId, string> stringValueSelector, Func<string, IEnumerable<ElementId>> selectedButtonIdInPostBackGetter ) { + return new FormValue<ElementId>( + () => selectedButtonIdGetter().FirstOrDefault(), + () => buttonIdGetter().Select( i => i.Id ).FirstOrDefault( i => i.Any() ) ?? "",   stringValueSelector,   rawValue => {   if( rawValue != null ) { - var selectedButton = checkedCheckBoxesInPostBackGetter( rawValue ).SingleOrDefault(); - return selectedButton != null - ? PostBackValueValidationResult<CommonCheckBox>.CreateValid( selectedButton ) - : PostBackValueValidationResult<CommonCheckBox>.CreateInvalid(); + var selectedButtonId = selectedButtonIdInPostBackGetter( rawValue ).SingleOrDefault(); + return selectedButtonId != null + ? PostBackValueValidationResult<ElementId>.CreateValid( selectedButtonId ) + : PostBackValueValidationResult<ElementId>.CreateInvalid();   } - return allowsNoSelection - ? PostBackValueValidationResult<CommonCheckBox>.CreateValid( null ) - : PostBackValueValidationResult<CommonCheckBox>.CreateInvalid(); + return allowsNoSelection ? PostBackValueValidationResult<ElementId>.CreateValid( null ) : PostBackValueValidationResult<ElementId>.CreateInvalid();   } );   }     internal static void ValidateControls( - bool allowsNoSelection, bool inNoSelectionState, IEnumerable<CommonCheckBox> checkBoxes, bool disableSingleButtonDetection ) { - Control selectedButton = null; + bool allowsNoSelection, bool inNoSelectionState, IEnumerable<ElementId> selectedButtonIds, IEnumerable<ElementId> buttonIds, + bool disableSingleButtonDetection ) { + ElementId selectedButtonId = null;   if( !allowsNoSelection || !inNoSelectionState ) { - var selectedButtons = checkBoxes.Where( i => i.IsChecked ).ToArray(); - if( selectedButtons.Count() != 1 ) + selectedButtonIds = selectedButtonIds.Materialize(); + if( selectedButtonIds.Count() != 1 )   throw new ApplicationException( "If a radio button group is not in the no-selection state, then exactly one radio button must be selected." ); - selectedButton = selectedButtons.Single() as Control; + selectedButtonId = selectedButtonIds.Single();   }   - var checkBoxesOnPage = checkBoxes.Where( i => ( i as Control ).IsOnPage() ).ToArray(); - if( checkBoxesOnPage.Any() ) { - if( selectedButton != null && !selectedButton.IsOnPage() ) + var buttonsIdsOnPage = buttonIds.Where( i => i.Id.Any() ).Materialize(); + if( buttonsIdsOnPage.Any() ) { + if( selectedButtonId != null && !selectedButtonId.Id.Any() )   throw new ApplicationException( "The selected radio button must be on the page." ); - if( !disableSingleButtonDetection && checkBoxesOnPage.Count() < 2 ) { + if( !disableSingleButtonDetection && buttonsIdsOnPage.Count < 2 ) {   const string link = "http://developers.whatwg.org/states-of-the-type-attribute.html#radio-button-state-%28type=radio%29";   throw new ApplicationException( "A radio button group must contain more than one element; see " + link + "." );   }   }   }   - private readonly FormValue<CommonCheckBox> formValue; + private readonly FormValue<ElementId> formValue;   - private readonly List<Tuple<CommonCheckBox, bool, PageModificationValue<bool>>> checkBoxesAndSelectionStatesAndPageModificationValues = - new List<Tuple<CommonCheckBox, bool, PageModificationValue<bool>>>(); + private readonly List<( ElementId id, bool value, PageModificationValue<bool> pmv )> buttonIdAndValueAndPmvTriples = + new List<( ElementId id, bool value, PageModificationValue<bool> pmv )>(); + + private readonly FormAction selectionChangedAction;     /// <summary>   /// Creates a radio button group. @@ -66,75 +63,74 @@
  /// Nielsen Norman Group; see http://www.nngroup.com/articles/checkboxes-vs-radio-buttons/ for more information.</param>   /// <param name="disableSingleButtonDetection">Pass true to allow just a single radio button to be displayed for this group. Use with caution, as this   /// violates the HTML specification.</param> - public RadioButtonGroup( bool allowNoSelection, bool disableSingleButtonDetection = false ) { + /// <param name="selectionChangedAction">The action that will occur when the selection is changed. Pass null for no action.</param> + public RadioButtonGroup( bool allowNoSelection, bool disableSingleButtonDetection = false, FormAction selectionChangedAction = null ) {   formValue = GetFormValue(   allowNoSelection, - () => from i in checkBoxesAndSelectionStatesAndPageModificationValues select i.Item1, - () => from i in checkBoxesAndSelectionStatesAndPageModificationValues where i.Item2 select i.Item1, - v => v != null ? ( (Control)v ).UniqueID : "", - rawValue => from checkBoxAndSelectionState in checkBoxesAndSelectionStatesAndPageModificationValues - let control = (Control)checkBoxAndSelectionState.Item1 - where control.IsOnPage() && control.UniqueID == rawValue - select checkBoxAndSelectionState.Item1 ); + () => from i in buttonIdAndValueAndPmvTriples select i.id, + () => from i in buttonIdAndValueAndPmvTriples where i.value select i.id, + v => v?.Id ?? "", + rawValue => from buttonIdAndValueAndPmv in buttonIdAndValueAndPmvTriples + let id = buttonIdAndValueAndPmv.id + where id.Id.Any() && id.Id == rawValue + select id ); + + this.selectionChangedAction = selectionChangedAction;     EwfPage.Instance.AddControlTreeValidation(   () => ValidateControls(   allowNoSelection, - checkBoxesAndSelectionStatesAndPageModificationValues.All( i => !i.Item2 ), - checkBoxesAndSelectionStatesAndPageModificationValues.Select( i => i.Item1 ), + buttonIdAndValueAndPmvTriples.All( i => !i.value ), + from i in buttonIdAndValueAndPmvTriples where i.value select i.id, + from i in buttonIdAndValueAndPmvTriples select i.id,   disableSingleButtonDetection ) );   }     /// <summary> - /// Creates an in-line radio button that is part of the group. + /// Creates a radio button that is part of the group.   /// </summary> - public EwfCheckBox CreateInlineRadioButton( - bool isSelected, string label = "", FormAction action = null, bool autoPostBack = false, PageModificationValue<bool> pageModificationValue = null ) { - EwfCheckBox checkBox = null; - checkBox = new EwfCheckBox( + /// <param name="value"></param> + /// <param name="label">The radio button label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the radio button.</param> + /// <param name="validationMethod">The validation method. Pass null if you’re only using this control for page modification.</param> + public Checkbox CreateRadioButton( + bool value, IReadOnlyCollection<PhrasingComponent> label, RadioButtonSetup setup = null, + Action<PostBackValue<bool>, Validator> validationMethod = null ) { + setup = setup ?? RadioButtonSetup.Create(); + + var id = new ElementId(); + formValue.AddPageModificationValue( setup.PageModificationValue, v => v == id ); + buttonIdAndValueAndPmvTriples.Add( ( id, value, setup.PageModificationValue ) ); + + return new Checkbox(   formValue, + id, + setup,   label, - action, - () => checkBoxesAndSelectionStatesAndPageModificationValues.Where( i => i.Item3 != null ) - .Select( i => i.Item3.GetJsModificationStatements( i.Item1 == checkBox ? "true" : "false" ) ) ) { AutoPostBack = autoPostBack }; - checkBoxesAndSelectionStatesAndPageModificationValues.Add( - Tuple.Create<CommonCheckBox, bool, PageModificationValue<bool>>( checkBox, isSelected, pageModificationValue ) ); - - if( pageModificationValue != null ) - formValue.AddPageModificationValue( pageModificationValue, value => value == checkBox ); - - return checkBox; + selectionChangedAction, + () => StringTools.ConcatenateWithDelimiter( + " ", + buttonIdAndValueAndPmvTriples.Select( i => i.pmv.GetJsModificationStatements( i.id == id ? "true" : "false" ) ).ToArray() ), + validationMethod != null + ? formValue.CreateValidation( + ( postBackValue, validator ) => validationMethod( + new PostBackValue<bool>( postBackValue.Value == id, postBackValue.ChangedOnPostBack ), + validator ) ) + : null );   }     /// <summary> - /// Creates a block-level radio button that is part of the group. + /// Creates a flow radio button that is part of the group.   /// </summary> - /// <param name="isSelected"></param> - /// <param name="label"></param> - /// <param name="action"></param> - /// <param name="autoPostBack"></param> - /// <param name="pageModificationValue"></param> - /// <param name="validationMethod">The validation method. Pass null if you’re only using this control for page modification.</param> - /// <param name="nestedControlListGetter"></param> - /// <returns></returns> - public BlockCheckBox CreateBlockRadioButton( - bool isSelected, string label = "", FormAction action = null, bool autoPostBack = false, PageModificationValue<bool> pageModificationValue = null, - Action<PostBackValue<bool>, Validator> validationMethod = null, Func<IEnumerable<Control>> nestedControlListGetter = null ) { - BlockCheckBox checkBox = null; - checkBox = new BlockCheckBox( - formValue, - new BlockCheckBoxSetup( action: action, triggersActionWhenCheckedOrUnchecked: autoPostBack, nestedControlListGetter: nestedControlListGetter ), - label.ToComponents(), - () => checkBoxesAndSelectionStatesAndPageModificationValues.Where( i => i.Item3 != null ) - .Select( i => i.Item3.GetJsModificationStatements( i.Item1 == checkBox ? "true" : "false" ) ), - validationMethod ); - checkBoxesAndSelectionStatesAndPageModificationValues.Add( - Tuple.Create<CommonCheckBox, bool, PageModificationValue<bool>>( checkBox, isSelected, pageModificationValue ) ); - - if( pageModificationValue != null ) - formValue.AddPageModificationValue( pageModificationValue, value => value == checkBox ); - - return checkBox; + /// <param name="value"></param> + /// <param name="label">The radio button label. Do not pass null. Pass an empty collection for no label.</param> + /// <param name="setup">The setup object for the flow radio button.</param> + /// <param name="validationMethod">The validation method. Pass null if you’re only using the radio button for page modification.</param> + public FlowCheckbox CreateFlowRadioButton( + bool value, IReadOnlyCollection<PhrasingComponent> label, FlowRadioButtonSetup setup = null, + Action<PostBackValue<bool>, Validator> validationMethod = null ) { + setup = setup ?? FlowRadioButtonSetup.Create(); + return new FlowCheckbox( setup, CreateRadioButton( value, label, setup: setup.RadioButtonSetup, validationMethod: validationMethod ) );   }   }  } \ No newline at end of file