Skip to content

Commit dc5085a

Browse files
committed
Merge branch 'improvement/inferred-values' into develop
Introduced support for displaying placeholders for inferred values, such as `DefaultValue`, `InheritedValue`, and `ImplicitValue`. For text input fields, this is straightforward, and simply required extending the `placeholder` definition. For dropdown lists—such as `FileList` and `TopicList`—this required changing the instructional label if an inferred value was present. For the `Boolean` attribute, I introduced a third, empty-valued radio button which displayed the inferred value. For both dropdown lists and radio buttons, I provide not only the inferred value, but also the source of the inferred value—i.e., "default value", "inherited value", or "implicit value". This satisfies the requirements of #26. Note: This does _not_ account for the `TopicReference`, which introduces unique challenges. That will be deferred to a later date based on further evaluation. A new issue, #40, has been created for tracking that, but it hasn't been assigned to a milestone yet.
2 parents 6a7e3ff + 2cc88ea commit dc5085a

File tree

14 files changed

+210
-54
lines changed

14 files changed

+210
-54
lines changed

OnTopic.Editor.AspNetCore.Attributes/BooleanAttribute/BooleanAttributeViewModel.cs

+63-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
| Project Topics Library
55
\=============================================================================================================================*/
66
using System;
7+
using System.Diagnostics.CodeAnalysis;
78
using OnTopic.Editor.AspNetCore.Models;
89
using OnTopic.Editor.AspNetCore.Models.Metadata;
910

@@ -44,10 +45,7 @@ public BooleanAttributeViewModel(
4445
/// Determines whether the value is explicitly set to true.
4546
/// </summary>
4647
public bool? IsTrue() {
47-
if (
48-
(Value?.Equals("1", StringComparison.OrdinalIgnoreCase)?? false) ||
49-
(Value?.Equals("true", StringComparison.OrdinalIgnoreCase)?? false)
50-
) {
48+
if (IsBoolean(Value, out var value) && value.Value) {
5149
return true;
5250
}
5351
return null;
@@ -60,15 +58,72 @@ public BooleanAttributeViewModel(
6058
/// Determines whether value is explicitly set to false.
6159
/// </summary>
6260
public bool? IsFalse() {
61+
if (IsBoolean(Value, out var value) && !value.Value) {
62+
return true;
63+
}
64+
return null;
65+
}
66+
67+
/*==========================================================================================================================
68+
| IS BOOLEAN?
69+
\-------------------------------------------------------------------------------------------------------------------------*/
70+
/// <summary>
71+
/// Determines if the <paramref name="input"/> is a <see cref="Boolean"/>. If it is, converts it to a <see cref="Boolean"
72+
/// /> via the <paramref name="value"/>.
73+
/// </summary>
74+
public static bool IsBoolean(string? input, [NotNullWhen(true)] out bool? value) {
75+
value = null;
76+
if (String.IsNullOrEmpty(input)) {
77+
return false;
78+
}
6379
if (
64-
(Value?.Equals("0", StringComparison.OrdinalIgnoreCase) ?? false) ||
65-
(Value?.Equals("false", StringComparison.OrdinalIgnoreCase) ?? false)
80+
input.Equals("1", StringComparison.OrdinalIgnoreCase) ||
81+
input.Equals("true", StringComparison.OrdinalIgnoreCase)
6682
) {
67-
return false;
83+
value = true;
84+
return true;
6885
}
69-
return null;
86+
if (
87+
input.Equals("0", StringComparison.OrdinalIgnoreCase) ||
88+
input.Equals("false", StringComparison.OrdinalIgnoreCase)
89+
) {
90+
value = false;
91+
return true;
92+
}
93+
return false;
7094
}
7195

96+
/*==========================================================================================================================
97+
| IS VALUE INFERRED?
98+
\-------------------------------------------------------------------------------------------------------------------------*/
99+
/// <summary>
100+
/// Determines whether the value is explicitly set to .
101+
/// </summary>
102+
public bool IsValueInferred([NotNullWhen(true)] out bool? value, out string? source) {
103+
104+
/*------------------------------------------------------------------------------------------------------------------------
105+
| Determine source
106+
\-----------------------------------------------------------------------------------------------------------------------*/
107+
source = null;
108+
109+
if (IsBoolean(AttributeDescriptor.DefaultValue, out value)) {
110+
if (!IsBoolean(Value, out _)) {
111+
source = "default value";
112+
}
113+
}
114+
else if (IsBoolean(AttributeDescriptor.ImplicitValue, out value)) {
115+
source = "implicit value";
116+
}
117+
else if (IsBoolean(InheritedValue, out value)) {
118+
source = "inherited value";
119+
}
120+
121+
/*------------------------------------------------------------------------------------------------------------------------
122+
| Handle default
123+
\-----------------------------------------------------------------------------------------------------------------------*/
124+
return source is not null;
125+
126+
}
72127

73128
} // Class
74129
} // Namespace

OnTopic.Editor.AspNetCore.Attributes/FileListAttribute/FileListViewComponent.cs

+39-11
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,16 @@ string htmlFieldPrefix
6363
/*------------------------------------------------------------------------------------------------------------------------
6464
| Establish view model
6565
\-----------------------------------------------------------------------------------------------------------------------*/
66-
var model = new FileListAttributeViewModel(currentTopic, attribute);
66+
var model = new FileListAttributeViewModel(currentTopic, attribute) {
67+
AbsolutePath = _webHostEnvironment.ContentRootPath + attribute.Path
68+
};
6769

6870
/*------------------------------------------------------------------------------------------------------------------------
6971
| Set model values
7072
\-----------------------------------------------------------------------------------------------------------------------*/
71-
foreach (var file in GetFiles(model.InheritedValue, attribute, model.AbsolutePath)) {
73+
foreach (var file in GetFiles(model)) {
7274
model.Files.Add(file);
7375
};
74-
model.AbsolutePath = _webHostEnvironment.ContentRootPath + attribute.Path;
7576

7677
/*------------------------------------------------------------------------------------------------------------------------
7778
| Return view with view model
@@ -86,20 +87,18 @@ string htmlFieldPrefix
8687
/// <summary>
8788
/// Retrieves a collection of files in a directory, given the provided <see cref="Path"/>.
8889
/// </summary>
89-
public static Collection<SelectListItem> GetFiles(
90-
string? inheritedValue,
91-
FileListAttributeDescriptorViewModel attribute,
92-
string absolutePath
93-
) {
90+
public static Collection<SelectListItem> GetFiles(FileListAttributeViewModel viewModel) {
9491

9592
/*------------------------------------------------------------------------------------------------------------------------
9693
| Validate parameters
9794
\-----------------------------------------------------------------------------------------------------------------------*/
98-
Contract.Requires(attribute, nameof(attribute));
95+
Contract.Requires(viewModel, nameof(viewModel));
9996

10097
/*------------------------------------------------------------------------------------------------------------------------
10198
| INSTANTIATE OBJECTS
10299
\-----------------------------------------------------------------------------------------------------------------------*/
100+
var attribute = viewModel.AttributeDescriptor;
101+
var absolutePath = viewModel.AbsolutePath;
103102
var files = new Collection<SelectListItem>();
104103
var searchPattern = "*";
105104
var searchOption = attribute.IncludeSubdirectories is true? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
@@ -129,9 +128,22 @@ string absolutePath
129128
\-----------------------------------------------------------------------------------------------------------------------*/
130129
var foundFiles = Directory.GetFiles(absolutePath, searchPattern, searchOption);
131130

132-
if (!String.IsNullOrEmpty(inheritedValue)) {
133-
files.Add(new("", inheritedValue));
131+
/*------------------------------------------------------------------------------------------------------------------------
132+
| Set label
133+
\-----------------------------------------------------------------------------------------------------------------------*/
134+
if (!String.IsNullOrEmpty(viewModel.InheritedValue)) {
135+
setLabel(viewModel.InheritedValue, "inherited value");
136+
}
137+
else if (!String.IsNullOrEmpty(viewModel.AttributeDescriptor.DefaultValue)) {
138+
setLabel(viewModel.AttributeDescriptor.DefaultValue, "default value");
134139
}
140+
else if (!String.IsNullOrEmpty(viewModel.AttributeDescriptor.ImplicitValue)) {
141+
setLabel(viewModel.AttributeDescriptor.ImplicitValue, "implicit default");
142+
}
143+
else {
144+
setLabel("Select a file…");
145+
}
146+
135147
foreach (var foundFile in foundFiles) {
136148
var fileName = foundFile.Replace(absolutePath, "", StringComparison.OrdinalIgnoreCase);
137149
var fileNameKey = fileName.Replace("." + attribute.Extension, "", StringComparison.OrdinalIgnoreCase);
@@ -143,6 +155,22 @@ string absolutePath
143155
\-----------------------------------------------------------------------------------------------------------------------*/
144156
return files;
145157

158+
/*------------------------------------------------------------------------------------------------------------------------
159+
| Function: Set Label
160+
\-----------------------------------------------------------------------------------------------------------------------*/
161+
void setLabel(string value, string? contextualLabel = null) {
162+
var label = value;
163+
if (contextualLabel is not null) {
164+
label += " (" + contextualLabel + ")";
165+
}
166+
viewModel.Files.Add(
167+
new() {
168+
Value = "",
169+
Text = label
170+
}
171+
);
172+
}
173+
146174
}
147175

148176
} // Class

OnTopic.Editor.AspNetCore.Attributes/LastModifiedByAttribute/LastModifiedByViewComponent.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ string htmlFieldPrefix
5555
currentTopic.Attributes.TryGetValue(attribute.Key, out var value);
5656

5757
var model = new LastModifiedByAttributeViewModel(currentTopic, attribute) {
58-
CurrentValue = currentTopic.Attributes["LastModifiedBy"]?? value,
59-
Value = HttpContext.User.Identity.Name?? "System"
58+
CurrentValue = value,
59+
Value = HttpContext.User.Identity.Name
6060
};
6161

6262
/*------------------------------------------------------------------------------------------------------------------------

OnTopic.Editor.AspNetCore.Attributes/TopicListAttribute/TopicListViewComponent.cs

+34-11
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,6 @@ public IViewComponentResult Invoke(
7070
\-----------------------------------------------------------------------------------------------------------------------*/
7171
var viewModel = new TopicListAttributeViewModel(currentTopic, attribute);
7272

73-
/*------------------------------------------------------------------------------------------------------------------------
74-
| Set label
75-
\-----------------------------------------------------------------------------------------------------------------------*/
76-
viewModel.TopicList.Add(
77-
new() {
78-
Value = "",
79-
Text = attribute.DefaultLabel
80-
}
81-
);
82-
8373
/*------------------------------------------------------------------------------------------------------------------------
8474
| Set default value
8575
\-----------------------------------------------------------------------------------------------------------------------*/
@@ -122,6 +112,22 @@ public IViewComponentResult Invoke(
122112
attribute.AttributeValue
123113
);
124114

115+
/*------------------------------------------------------------------------------------------------------------------------
116+
| Set label
117+
\-----------------------------------------------------------------------------------------------------------------------*/
118+
if (!String.IsNullOrEmpty(viewModel.InheritedValue)) {
119+
setLabel(viewModel.InheritedValue, "inherited value");
120+
}
121+
else if (!String.IsNullOrEmpty(viewModel.AttributeDescriptor.DefaultValue)) {
122+
setLabel(viewModel.AttributeDescriptor.DefaultValue, "default value");
123+
}
124+
else if (!String.IsNullOrEmpty(viewModel.AttributeDescriptor.ImplicitValue)) {
125+
setLabel(viewModel.AttributeDescriptor.ImplicitValue, "implicit default");
126+
}
127+
else {
128+
setLabel(attribute.DefaultLabel?? "Select an option…");
129+
}
130+
125131
/*------------------------------------------------------------------------------------------------------------------------
126132
| Get values from repository
127133
\-----------------------------------------------------------------------------------------------------------------------*/
@@ -146,10 +152,27 @@ public IViewComponentResult Invoke(
146152
return View(viewModel);
147153

148154
/*------------------------------------------------------------------------------------------------------------------------
149-
| Helper functions
155+
| Function: Get Value
150156
\-----------------------------------------------------------------------------------------------------------------------*/
151157
string getValue(QueryResultTopicViewModel topic) => ReplaceTokens(topic, "{" + attribute.ValueProperty + "}");
152158

159+
/*------------------------------------------------------------------------------------------------------------------------
160+
| Function: Set Label
161+
\-----------------------------------------------------------------------------------------------------------------------*/
162+
void setLabel(string value, string? contextualLabel = null) {
163+
var inheritedTopic = topics.Where(t => t.Key == value).FirstOrDefault();
164+
var label = inheritedTopic?.Title ?? value;
165+
if (contextualLabel is not null) {
166+
label += " (" + contextualLabel + ")";
167+
}
168+
viewModel?.TopicList.Add(
169+
new() {
170+
Value = "",
171+
Text = label
172+
}
173+
);
174+
}
175+
153176
}
154177

155178
/*==========================================================================================================================

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/Boolean/Default.cshtml

+16-5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,23 @@
66
}
77

88
<div class="FormField Field form-check form-check-inline">
9-
<label class="form-check-label">
10-
<input type="radio" asp-for="Value" value="1" id="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("Yes"), "_")" class="form-check-input" checked=@Model.IsTrue() disabled=@(!Model.AttributeDescriptor.IsEnabled) required=@Model.AttributeDescriptor.IsRequired /> Yes
9+
<input type="radio" asp-for="Value" value="1" id="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("Yes"), "_")" class="form-check-input default" checked=@Model.IsTrue() disabled=@(!Model.AttributeDescriptor.IsEnabled) required=@Model.AttributeDescriptor.IsRequired />
10+
<label class="form-check-label" for="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("Yes"), "_")">
11+
Yes
1112
</label>
1213
</div>
1314
<div class="form-check form-check-inline">
14-
<label class="form-check-label">
15-
<input type="radio" asp-for="Value" value="0" id="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("No"), "_")" class="form-check-input" checked=@Model.IsFalse() disabled=@(!Model.AttributeDescriptor.IsEnabled) required=@Model.AttributeDescriptor.IsRequired /> No
15+
<input type="radio" asp-for="Value" value="0" id="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("No"), "_")" class="form-check-input" checked=@Model.IsFalse() disabled=@(!Model.AttributeDescriptor.IsEnabled) required=@Model.AttributeDescriptor.IsRequired />
16+
<label class="form-check-label" for="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("No"), "_")">
17+
No
1618
</label>
17-
</div>
19+
</div>
20+
21+
@if (Model.IsValueInferred(out var value, out var source)) {
22+
<div class="form-check form-check-inline">
23+
<input type="radio" asp-for="Value" value="" id="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("Inferred"), "_")" class="form-check-input" checked=@(String.IsNullOrEmpty(Model.Value)? true : null) disabled=@(!Model.AttributeDescriptor.IsEnabled) required=@Model.AttributeDescriptor.IsRequired />
24+
<label class="form-check-label" for="@TagBuilder.CreateSanitizedId(ViewData.TemplateInfo.GetFullHtmlFieldName("No"), "_")">
25+
<span class="context">@(value.Value? "Yes" : "No") (@source)</span>
26+
</label>
27+
</div>
28+
}

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/FilePath/Default.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
type ="text"
1010
asp-for =@Model.Value
1111
class ="FormField Field form-control"
12-
placeholder =@Model.InheritedValue
12+
placeholder =@(Model.AttributeDescriptor.DefaultValue?? Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue)
1313
disabled =@(!Model.AttributeDescriptor.IsEnabled)
1414
required =@Model.AttributeDescriptor.IsRequired
1515
/>

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/HTML/Default.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
asp-for ="Value"
2121
rows =@Model.AttributeDescriptor.Rows
2222
class ="FormField Field form-control"
23-
placeholder =@Model.InheritedValue
23+
placeholder =@(Model.AttributeDescriptor.DefaultValue?? Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue)
2424
disabled =@(!Model.AttributeDescriptor.IsEnabled)
2525
required =@Model.AttributeDescriptor.IsRequired
2626
></textarea>

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/LastModified/Default.cshtml

+16-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
Layout = "~/Areas/Editor/Views/Editor/Components/_Layout.cshtml";
66
}
77

8-
<div class="label">
9-
@Model.CurrentValue
10-
</div>
11-
12-
<input type="hidden" asp-for="Value" />
8+
<div class="form-row">
9+
@if (Model.CurrentTopic.Id > 0 && !String.IsNullOrEmpty(Model.CurrentValue + Model.InheritedValue + Model.AttributeDescriptor.ImplicitValue)) {
10+
<div class="col input-group">
11+
<div class="input-group-prepend">
12+
<div class="input-group-text">Current Value</div>
13+
</div>
14+
<input type="text" asp-for="CurrentValue" class="form-control" placeholder=@(Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue) readonly>
15+
</div>
16+
}
17+
<div class="col input-group">
18+
<div class="input-group-prepend">
19+
<div class="input-group-text">New Value</div>
20+
</div>
21+
<input type="text" asp-for="Value" class="form-control" readonly>
22+
</div>
23+
</div>

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/LastModifiedBy/Default.cshtml

+16-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
Layout = "~/Areas/Editor/Views/Editor/Components/_Layout.cshtml";
66
}
77

8-
<div class="label">
9-
@Model.CurrentValue
10-
</div>
11-
12-
<input type="hidden" asp-for="Value" />
8+
<div class="form-row">
9+
@if (Model.CurrentTopic.Id > 0 && !String.IsNullOrEmpty(Model.CurrentValue + Model.InheritedValue + Model.AttributeDescriptor.ImplicitValue)) {
10+
<div class="col input-group">
11+
<div class="input-group-prepend">
12+
<div class="input-group-text">Current Value</div>
13+
</div>
14+
<input type="text" asp-for="CurrentValue" class="form-control" placeholder=@(Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue) readonly>
15+
</div>
16+
}
17+
<div class="col input-group">
18+
<div class="input-group-prepend">
19+
<div class="input-group-text">New Value</div>
20+
</div>
21+
<input type="text" asp-for="Value" class="form-control" placeholder="@(Model.AttributeDescriptor.DefaultValue?? Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue)" readonly>
22+
</div>
23+
</div>

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/Number/Default.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
min =@Model.AttributeDescriptor.MinimumValue
1212
max =@Model.AttributeDescriptor.MaximumValue
1313
class ="FormField Field form-control"
14-
placeholder =@Model.InheritedValue
14+
placeholder =@(Model.AttributeDescriptor.DefaultValue?? Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue)
1515
disabled =@(!Model.AttributeDescriptor.IsEnabled)
1616
required =@Model.AttributeDescriptor.IsRequired
1717
/>

OnTopic.Editor.AspNetCore.Attributes/Views/Editor/Components/Text/Default.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class ="FormField Field form-control"
1414
title =@Model.AttributeDescriptor.ValidationMessage
1515
pattern =@Model.AttributeDescriptor.Pattern
16-
placeholder =@Model.InheritedValue
16+
placeholder =@(Model.AttributeDescriptor.DefaultValue?? Model.InheritedValue?? Model.AttributeDescriptor.ImplicitValue)
1717
disabled =@(!Model.AttributeDescriptor.IsEnabled)
1818
required =@Model.AttributeDescriptor.IsRequired
1919
/>

0 commit comments

Comments
 (0)