Skip to content

Commit

Permalink
Merge branch 'main' into release/4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasherceg committed Dec 11, 2022
2 parents e8f2996 + 8e61050 commit 4fa5216
Show file tree
Hide file tree
Showing 37 changed files with 848 additions and 80 deletions.
10 changes: 3 additions & 7 deletions src/AutoUI/Core/AutoUIContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,6 @@ public IValueBinding CreateValueBinding(PropertyDisplayMetadata property)
});
}

public IValueBinding CreateValueBinding(string expression, params Type[] nestedDataContextTypes)
{
var dataContextStack = CreateChildDataContextStack(DataContextStack, nestedDataContextTypes);

return BindingService.Cache.CreateValueBinding(expression, dataContextStack);
}

public DataContextStack CreateChildDataContextStack(DataContextStack dataContextStack, params Type[] nestedDataContextTypes)
{
foreach (var type in nestedDataContextTypes)
Expand All @@ -79,6 +72,9 @@ public DataContextStack CreateChildDataContextStack(DataContextStack dataContext
return dataContextStack;
}

public DataContextStack CreateChildDataContextStack(params Type[] nestedDataContextTypes) =>
CreateChildDataContextStack(DataContextStack, nestedDataContextTypes);

public IViewContext CreateViewContext()
{
return new ViewContext()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ public override DotvvmControl CreateControl(PropertyDisplayMetadata property, Au
{
var selectorConfiguration = property.SelectionConfiguration!;
var selectorDataSourceBinding = SelectorHelper.DiscoverSelectorDataSourceBinding(context, selectorConfiguration.SelectionType);
var nestedDataContext = context.CreateChildDataContextStack(selectorConfiguration.SelectionType);

return new Repeater()
.SetCapability(props.Html)
.SetProperty(c => c.WrapperTagName, "ul")
.SetProperty(c => c.DataSource, selectorDataSourceBinding)
.SetProperty(c => c.ItemTemplate, new CloneTemplate(
new HtmlGenericControl("li")
.SetProperty(Internal.DataContextTypeProperty, context.CreateChildDataContextStack(context.DataContextStack, selectorConfiguration.SelectionType))
.SetProperty(Internal.DataContextTypeProperty, nestedDataContext)
.AppendChildren(
new CheckBox()
.SetProperty(c => c.Text, context.CreateValueBinding("DisplayName", selectorConfiguration.SelectionType))
.SetProperty(c => c.CheckedValue, context.CreateValueBinding("Value", selectorConfiguration.SelectionType))
.SetProperty(c => c.Text, context.BindingService.Cache.CreateValueBinding<string>("DisplayName", nestedDataContext))
.SetProperty(c => c.CheckedValue, context.BindingService.Cache.CreateValueBinding("Value", nestedDataContext))
.SetProperty(c => c.CheckedItems, props.Property)
.SetProperty(c => c.Enabled, props.Enabled)
.SetProperty(c => c.Changed, props.Changed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ public override DotvvmControl CreateControl(PropertyDisplayMetadata property, Au
{
var selectorConfiguration = property.SelectionConfiguration!;
var selectorDataSourceBinding = SelectorHelper.DiscoverSelectorDataSourceBinding(context, selectorConfiguration.SelectionType);
var nestedDataContext = context.CreateChildDataContextStack(selectorConfiguration.SelectionType);

return new ComboBox()
.SetCapability(props.Html)
.SetProperty(c => c.DataSource, selectorDataSourceBinding)
.SetProperty(c => c.ItemTextBinding, context.CreateValueBinding("DisplayName", selectorConfiguration.SelectionType))
.SetProperty(c => c.ItemValueBinding, context.CreateValueBinding("Value", selectorConfiguration.SelectionType))
.SetProperty(c => c.ItemTextBinding, context.BindingService.Cache.CreateValueBinding<string>("DisplayName", nestedDataContext))
.SetProperty(c => c.ItemValueBinding, context.BindingService.Cache.CreateValueBinding("Value", nestedDataContext))
.SetProperty(c => c.SelectedValue, props.Property)
.SetProperty(c => c.Enabled, props.Enabled)
.SetProperty(c => c.SelectionChanged, props.Changed);
Expand Down
58 changes: 48 additions & 10 deletions src/Framework/Framework/Controls/ListBox.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using DotVVM.Framework.Binding;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Compilation.Validation;
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Runtime;
using DotVVM.Framework.Utils;
Expand All @@ -11,7 +14,8 @@
namespace DotVVM.Framework.Controls
{
/// <summary>
/// Renders the HTML list box.
/// Renders the HTML list box - <c>&lt;select size="10"</c> element.
/// This component allows selecting only one value, for multiple selection use the <see cref="MultiSelect"/> component.
/// </summary>
public class ListBox : SelectHtmlControlBase
{
Expand All @@ -20,16 +24,7 @@ public class ListBox : SelectHtmlControlBase
/// </summary>
public ListBox()
{

}

/// <summary>
/// Adds all attributes that should be added to the control begin tag.
/// </summary>
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
base.AddAttributesToRender(writer, context);
writer.AddKnockoutDataBind("size", this, SizeProperty, () => writer.AddAttribute("size", Size.ToString()));
}

/// <summary>
Expand All @@ -43,5 +38,48 @@ public int Size

public static readonly DotvvmProperty SizeProperty =
DotvvmProperty.Register<int, ListBox>(t => t.Size, defaultValue: 10);

/// <summary>
/// Adds all attributes that should be added to the control begin tag.
/// </summary>
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
base.AddAttributesToRender(writer, context);
writer.AddKnockoutDataBind("size", this, SizeProperty, () => writer.AddAttribute("size", Size.ToString()));
}

[ControlUsageValidator(Override = true)]
public static new IEnumerable<ControlUsageError> ValidateUsage(ResolvedControl control)
{
if (!(control.GetValue(SelectedValueProperty) is ResolvedPropertySetter selectedValueBinding)) yield break;

if (control.GetValue(ItemValueBindingProperty) is ResolvedPropertySetter itemValueBinding)
{
var to = selectedValueBinding.GetResultType();
var from = itemValueBinding.GetResultType();

if (!IsValueAssignable(from, to))
{
yield return CreateSelectedValueTypeError(selectedValueBinding, to, from);
}
}
else if (control.GetValue(DataSourceProperty) is ResolvedPropertySetter dataSourceBinding)
{
var to = selectedValueBinding.GetResultType();
var from = dataSourceBinding.GetResultType()?.UnwrapNullableType()?.GetEnumerableType();

if (!IsCollectionPropertySetter(dataSourceBinding))
{
yield return new ($"{nameof(DataSource)} must be a collection.", selectedValueBinding.DothtmlNode);
}
if (!IsDataSourceItemAssignable(from, to))
{
yield return CreateSelectedValueTypeError(selectedValueBinding, to, from);
}
}
}

private static bool IsCollectionPropertySetter(ResolvedPropertySetter setter)
=> new ResolvedTypeDescriptor(setter.GetResultType()).TryGetArrayElementOrIEnumerableType() is not null;
}
}
1 change: 1 addition & 0 deletions src/Framework/Framework/Controls/MultiSelect.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace DotVVM.Framework.Controls
{
/// <summary> Renders HTML list box - the <c>&lt;select multiple size=10</c> element. </summary>
public class MultiSelect : MultiSelectHtmlControlBase
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequest

protected virtual void RenderMultipleAttribute(IHtmlWriter writer)
{
writer.AddAttribute("multiple", "multiple");
writer.AddAttribute("multiple", "");
}

protected virtual void RenderEnabledProperty(IHtmlWriter writer)
Expand Down
44 changes: 43 additions & 1 deletion src/Framework/Framework/Controls/MultiSelector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DotVVM.Framework.Binding;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Compilation.Validation;
using DotVVM.Framework.Utils;
using FastExpressionCompiler;

namespace DotVVM.Framework.Controls
{
Expand All @@ -13,7 +20,6 @@ protected MultiSelector(string tagName)
{
}


/// <summary>
/// Gets or sets the values of selected items.
/// </summary>
Expand All @@ -26,5 +32,41 @@ public IEnumerable? SelectedValues
public static readonly DotvvmProperty SelectedValuesProperty =
DotvvmProperty.Register<object?, MultiSelector>(t => t.SelectedValues);


[ControlUsageValidator]
public static new IEnumerable<ControlUsageError> ValidateUsage(ResolvedControl control)
{
if (!(control.GetValue(SelectedValuesProperty) is ResolvedPropertySetter selectedValueBinding)) yield break;

if (GetCollectionType(selectedValueBinding) is not {} selectedValueType)
{
yield return new(
$"{nameof(SelectedValues)} must be a collection.",
selectedValueBinding.DothtmlNode);
}
else if (control.GetValue(ItemValueBindingProperty) is ResolvedPropertySetter itemValueBinding)
{
var from = itemValueBinding.GetResultType();

if (!IsValueAssignable(from, selectedValueType))
{
yield return CreateSelectedValueTypeError(selectedValueBinding, selectedValueType, from);
}
}
else if (control.GetValue(DataSourceProperty) is ResolvedPropertySetter dataSourceBinding)
{
if (GetCollectionType(dataSourceBinding) is not {} dataSourceType)
{
yield return new ($"{nameof(DataSource)} must be a collection, but is {dataSourceBinding.GetResultType().ToCode()}.", selectedValueBinding.DothtmlNode);
}
else if (!IsDataSourceItemAssignable(dataSourceType, selectedValueType))
{
yield return CreateSelectedValueTypeError(selectedValueBinding, selectedValueType, dataSourceType);
}
}
}

private static Type? GetCollectionType(ResolvedPropertySetter setter)
=> ResolvedTypeDescriptor.ToSystemType(new ResolvedTypeDescriptor(setter.GetResultType()).TryGetArrayElementOrIEnumerableType());
}
}
2 changes: 1 addition & 1 deletion src/Framework/Framework/Controls/RouteLinkHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public static string GenerateKnockoutHrefExpression(string routeName, RouteLink
{
var urlSuffixBase =
control.GetValueBinding(RouteLink.UrlSuffixProperty)
?.Apply(binding => binding.GetKnockoutBindingExpression(control))
?.Apply(binding => binding.GetKnockoutBindingExpression(control, unwrapped: true))
?? KnockoutHelper.MakeStringLiteral(control.UrlSuffix ?? "");
var queryParamsArray = control.QueryParameters.RawValues.ToArray();
Array.Sort(queryParamsArray, (a, b) => a.Key.CompareTo(b.Key)); // deterministic order of query params
Expand Down
13 changes: 4 additions & 9 deletions src/Framework/Framework/Controls/Selector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,21 @@ public object? SelectedValue
if (control.GetValue(ItemValueBindingProperty) is ResolvedPropertySetter itemValueBinding)
{
var to = selectedValueBinding.GetResultType();
var nonNullableTo = to?.UnwrapNullableType();
var from = itemValueBinding.GetResultType();

if (to != null && from != null
&& !to.IsAssignableFrom(from) && !nonNullableTo!.IsAssignableFrom(from))
if (!IsValueAssignable(from, to))
{
yield return new ControlUsageError($"Type '{from.FullName}' is not assignable to '{to.FullName}'.", selectedValueBinding.DothtmlNode);
yield return CreateSelectedValueTypeError(selectedValueBinding, to, from);
}
}
else if (control.GetValue(DataSourceProperty) is ResolvedPropertySetter dataSourceBinding)
{
var to = selectedValueBinding.GetResultType();
var nonNullableTo = to?.UnwrapNullableType();
var from = dataSourceBinding.GetResultType()?.UnwrapNullableType()?.GetEnumerableType();

if (to != null && from != null
&& !to.IsAssignableFrom(from) && !nonNullableTo!.IsAssignableFrom(from)
&& !(to.IsEnum && from == typeof(string)) && !(to.UnwrapNullableType().IsEnum && from == typeof(string)))
if (!IsDataSourceItemAssignable(from, to))
{
yield return new ControlUsageError($"Type '{from.FullName}' is not assignable to '{to.FullName}'.", selectedValueBinding.DothtmlNode);
yield return CreateSelectedValueTypeError(selectedValueBinding, to, from);
}
}
}
Expand Down
37 changes: 32 additions & 5 deletions src/Framework/Framework/Controls/SelectorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Compilation.Validation;
using DotVVM.Framework.Utils;
using FastExpressionCompiler;

namespace DotVVM.Framework.Controls
{
Expand Down Expand Up @@ -37,13 +38,13 @@ public bool Enabled
[ControlPropertyBindingDataContextChange(nameof(DataSource))]
[CollectionElementDataContextChange(1)]
[BindingCompilationRequirements(required: new[]{ typeof(SelectorItemBindingProperty) })]
public IValueBinding? ItemTextBinding
public IValueBinding<string>? ItemTextBinding
{
get { return (IValueBinding?)GetValue(ItemTextBindingProperty); }
get { return (IValueBinding<string>?)GetValue(ItemTextBindingProperty); }
set { SetValue(ItemTextBindingProperty, value); }
}
public static readonly DotvvmProperty ItemTextBindingProperty =
DotvvmProperty.Register<IValueBinding?, SelectorBase>(nameof(ItemTextBinding));
DotvvmProperty.Register<IValueBinding<string>?, SelectorBase>(nameof(ItemTextBinding));

/// <summary>
/// The expression of DataSource item that will be passed to the SelectedValue property when the item is selected.
Expand Down Expand Up @@ -87,8 +88,7 @@ public IValueBinding? ItemTitleBinding
[ControlUsageValidator]
public static IEnumerable<ControlUsageError> ValidateUsage(ResolvedControl control)
{
if (control.Properties.ContainsKey(SelectorBase.ItemValueBindingProperty) &&
control.Properties.GetValue(SelectorBase.ItemValueBindingProperty) is ResolvedPropertyBinding valueBinding)
if (control.GetValue(SelectorBase.ItemValueBindingProperty) is ResolvedPropertyBinding valueBinding)
{
var t = valueBinding.Binding.ResultType;
if (!t.IsPrimitiveTypeDescriptor())
Expand All @@ -97,5 +97,32 @@ public static IEnumerable<ControlUsageError> ValidateUsage(ResolvedControl contr
}
}
}

protected static ControlUsageError CreateSelectedValueTypeError(ResolvedPropertySetter selectedValueBinding, Type? to, Type? from)
=> new ($"Type '{from?.ToCode() ?? "?"}' is not assignable to '{to?.ToCode() ?? "?"}'.", selectedValueBinding.DothtmlNode);

protected static bool IsValueAssignable(Type? valueType, Type? to)
{
var nonNullableTo = to?.UnwrapNullableType();

return
to == null ||
valueType == null ||
to.IsAssignableFrom(valueType) ||
nonNullableTo!.IsAssignableFrom(valueType);
}

protected static bool IsDataSourceItemAssignable(Type? itemType, Type? to)
{
var nonNullableTo = to?.UnwrapNullableType();

return
to == null ||
itemType == null ||
to.IsAssignableFrom(itemType) ||
nonNullableTo!.IsAssignableFrom(itemType) ||
(to.UnwrapNullableType().IsEnum && itemType == typeof(string));
}
}

}
4 changes: 4 additions & 0 deletions src/Framework/Framework/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,5 +569,9 @@ internal static void ClearCaches(Type[] types)
cache_GetTypeHash.TryRemove(t, out _);
}
}

internal static bool IsInitOnly(this PropertyInfo prop) =>
prop.SetMethod is { ReturnParameter: {} returnParameter } &&
returnParameter.GetRequiredCustomModifiers().Any(t => t == typeof(System.Runtime.CompilerServices.IsExternalInit));
}
}
Loading

0 comments on commit 4fa5216

Please sign in to comment.