diff --git a/src/Framework/Framework/Controls/CheckableControlBase.cs b/src/Framework/Framework/Controls/CheckableControlBase.cs
index e675b7422b..3a19b66b76 100644
--- a/src/Framework/Framework/Controls/CheckableControlBase.cs
+++ b/src/Framework/Framework/Controls/CheckableControlBase.cs
@@ -194,13 +194,15 @@ protected virtual void AddAttributesToInput(IHtmlWriter writer)
}
// handle enabled attribute
- writer.AddKnockoutDataBind("enable", this, EnabledProperty, () =>
+ var enabled = this.GetValueRaw(EnabledProperty);
+ if (enabled is IValueBinding enabledBinding)
{
- if (!Enabled)
- {
- writer.AddAttribute("disabled", "disabled");
- }
- });
+ writer.AddKnockoutDataBind("enable", this, enabledBinding);
+ }
+ if (false.Equals(KnockoutHelper.TryEvaluateValueBinding(this, enabled)))
+ {
+ writer.AddAttribute("disabled", "disabled");
+ }
if (!string.IsNullOrEmpty(InputCssClass))
{
diff --git a/src/Framework/Framework/Controls/HtmlGenericControl.cs b/src/Framework/Framework/Controls/HtmlGenericControl.cs
index db33cd9501..9073a7d142 100644
--- a/src/Framework/Framework/Controls/HtmlGenericControl.cs
+++ b/src/Framework/Framework/Controls/HtmlGenericControl.cs
@@ -256,16 +256,9 @@ protected virtual void AddVisibleAttributeOrBinding(in RenderState r, IHtmlWrite
writer.AddKnockoutDataBind("visible", valueBinding.GetKnockoutBindingExpression(this));
}
- try
+ if (false.Equals(KnockoutHelper.TryEvaluateValueBinding(this, v)))
{
- if (false.Equals(EvalPropertyValue(VisibleProperty, v)))
- {
- writer.AddAttribute("style", "display:none");
- }
- }
- catch (Exception) when (valueBinding is {})
- {
- // suppress value binding errors
+ writer.AddAttribute("style", "display:none");
}
}
@@ -311,19 +304,15 @@ protected override void RenderEndTag(IHtmlWriter writer, IDotvvmRequestContext c
private void AddCssClassesToRender(IHtmlWriter writer)
{
KnockoutBindingGroup cssClassBindingGroup = new KnockoutBindingGroup();
- foreach (var cssClass in CssClasses.Properties)
+ foreach (var (cssClass, rawValue) in CssClasses.RawValues)
{
- if (HasValueBinding(cssClass))
+ if (rawValue is IValueBinding binding)
{
- cssClassBindingGroup.Add(cssClass.GroupMemberName, this, cssClass);
+ cssClassBindingGroup.Add(cssClass, this, binding);
}
- try
- {
- if (true.Equals(this.GetValue(cssClass)))
- writer.AddAttribute("class", cssClass.GroupMemberName, append: true, appendSeparator: " ");
- }
- catch when (HasValueBinding(cssClass)) { }
+ if (true.Equals(KnockoutHelper.TryEvaluateValueBinding(this, rawValue)))
+ writer.AddAttribute("class", cssClass, append: true, appendSeparator: " ");
}
if (!cssClassBindingGroup.IsEmpty) writer.AddKnockoutDataBind("css", cssClassBindingGroup);
@@ -332,24 +321,19 @@ private void AddCssClassesToRender(IHtmlWriter writer)
private void AddCssStylesToRender(IHtmlWriter writer)
{
KnockoutBindingGroup? cssStylesBindingGroup = null;
- foreach (var styleProperty in CssStyles.Properties)
+ foreach (var (style, rawValue) in CssStyles.RawValues)
{
- if (HasValueBinding(styleProperty))
+ if (rawValue is IValueBinding binding)
{
if (cssStylesBindingGroup == null) cssStylesBindingGroup = new KnockoutBindingGroup();
- cssStylesBindingGroup.Add(styleProperty.GroupMemberName, this, styleProperty);
+ cssStylesBindingGroup.Add(style, this, binding);
}
- try
+ var value = KnockoutHelper.TryEvaluateValueBinding(this, rawValue)?.ToString();
+ if (!string.IsNullOrEmpty(value))
{
- var value = GetValue(styleProperty)?.ToString();
- if (!string.IsNullOrEmpty(value))
- {
- writer.AddStyleAttribute(styleProperty.GroupMemberName, value!);
- }
+ writer.AddStyleAttribute(style, value!);
}
- // suppress all errors when we have rendered the value binding anyway
- catch when (HasValueBinding(styleProperty)) { }
}
if (cssStylesBindingGroup != null)
diff --git a/src/Framework/Framework/Controls/KnockoutBindingGroup.cs b/src/Framework/Framework/Controls/KnockoutBindingGroup.cs
index 8c2e6bd58a..bd6a6eadbf 100644
--- a/src/Framework/Framework/Controls/KnockoutBindingGroup.cs
+++ b/src/Framework/Framework/Controls/KnockoutBindingGroup.cs
@@ -41,6 +41,12 @@ public void Add(string name, KnockoutBindingGroup nestedGroup)
Add(name, nestedGroup.ToString());
}
+ public void Add(string name, DotvvmBindableObject contextControl, IValueBinding binding)
+ {
+ var expression = binding.GetKnockoutBindingExpression(contextControl);
+ Add(name, expression);
+ }
+
[Obsolete("Use Add or AddValue instead")]
public virtual void Add(string name, string expression, bool surroundWithDoubleQuotes)
{
diff --git a/src/Framework/Framework/Controls/KnockoutHelper.cs b/src/Framework/Framework/Controls/KnockoutHelper.cs
index d205d427b5..e0438393ea 100644
--- a/src/Framework/Framework/Controls/KnockoutHelper.cs
+++ b/src/Framework/Framework/Controls/KnockoutHelper.cs
@@ -15,6 +15,24 @@ namespace DotVVM.Framework.Controls
{
public static class KnockoutHelper
{
+ /// If value is a binding, evaluates it. If the binding is a value binding, any thrown exceptions are suppressed
+ internal static object? TryEvaluateValueBinding(DotvvmBindableObject control, object? valueOrBinding)
+ {
+ if (valueOrBinding is IStaticValueBinding b)
+ {
+ try
+ {
+ return b.Evaluate(control);
+ }
+ catch when (b is IValueBinding)
+ {
+ return null;
+ }
+ }
+ return valueOrBinding;
+ }
+
+
///
/// Adds the data-bind attribute to the next HTML element that is being rendered. The binding expression is taken from the specified property. If in server rendering mode, the binding is also not rendered.
///
diff --git a/src/Framework/Framework/Controls/RadioButton.cs b/src/Framework/Framework/Controls/RadioButton.cs
index db5c7a16f9..08cb94a92e 100644
--- a/src/Framework/Framework/Controls/RadioButton.cs
+++ b/src/Framework/Framework/Controls/RadioButton.cs
@@ -6,6 +6,7 @@
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Utils;
using System.Collections.Generic;
+using DotVVM.Framework.Binding.Expressions;
namespace DotVVM.Framework.Controls
{
@@ -18,12 +19,14 @@ public class RadioButton : CheckableControlBase
/// Gets or sets whether the control is checked.
///
[MarkupOptions(AllowHardCodedValue = false)]
+ [Obsolete("Checked property probably does not work as a reasonable person would expect. Use CheckedItem and CheckedValue instead.")]
public bool Checked
{
get { return (bool)GetValue(CheckedProperty)!; }
set { SetValue(CheckedProperty, value); }
}
+ [Obsolete("Checked property probably does not work as a reasonable person would expect. Use CheckedItem and CheckedValue instead.")]
public static readonly DotvvmProperty CheckedProperty =
DotvvmProperty.Register(t => t.Checked, false);
@@ -42,7 +45,6 @@ public object? CheckedItem
///
/// Gets or sets an unique name of the radio button group.
///
- [MarkupOptions(AllowBinding = false)]
public string GroupName
{
get { return (string)GetValue(GroupNameProperty)!; }
@@ -63,11 +65,14 @@ protected override void RenderInputTag(IHtmlWriter writer)
protected virtual void RenderGroupNameAttribute(IHtmlWriter writer)
{
- var group = new KnockoutBindingGroup();
- group.Add("name", this, GroupNameProperty, () => {
- writer.AddAttribute("name", GroupName);
- });
- writer.AddKnockoutDataBind("attr", group);
+ var valueRaw = GetValueRaw(GroupNameProperty);
+ if (valueRaw is IValueBinding valueBinding)
+ {
+ writer.AddKnockoutDataBind("attr", new KnockoutBindingGroup { { "name", this, valueBinding } });
+ }
+ var value = KnockoutHelper.TryEvaluateValueBinding(this, valueRaw);
+ if (value is not null)
+ writer.AddAttribute("name", (string)value);
}
protected virtual void RenderTypeAttribute(IHtmlWriter writer)
@@ -78,26 +83,35 @@ protected virtual void RenderTypeAttribute(IHtmlWriter writer)
protected virtual void RenderCheckedValueAttribute(IHtmlWriter writer)
{
- writer.AddKnockoutDataBind("checkedValue", this, CheckedValueProperty, () => {
- var checkedValue = (CheckedValue ?? string.Empty).ToString();
- if (!string.IsNullOrEmpty(checkedValue))
- {
- writer.AddKnockoutDataBind("checkedValue", KnockoutHelper.MakeStringLiteral(checkedValue));
- }
- });
+ var checkedValue = GetValueOrBinding