Skip to content

Commit d539d1f

Browse files
committed
Introduce Param.JS
1 parent 2421653 commit d539d1f

File tree

4 files changed

+104
-14
lines changed

4 files changed

+104
-14
lines changed

docs/content/features/actions.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ Examples:
5454
<input type="text" hydro-on:keyup.shift.enter="@(() => Model.Save())">
5555
```
5656

57-
## Arguments
57+
## Parameters
5858

59-
If your action contains arguments, you can pass them in a regular way.
59+
If your action contains parameters, you can pass them in a regular way.
6060

6161
Example:
6262

@@ -86,6 +86,50 @@ public class Counter : HydroComponent
8686
</div>
8787
```
8888

89+
## JavaScript expression as a parameter
90+
91+
In some cases, like integrating with JavaScript libraries like maps, rich-text editors, etc. it might be useful to
92+
call a Hydro action with parameters evaluated on client side via JavaScript expression. You can use then `Param.JS<T>(string value)` method, where:
93+
- `T`: type of the parameter
94+
- `value`: JavaScript expression to evaluate
95+
96+
If your parameter type is a `string`, you can use a shorter version:
97+
98+
`Param.JS(string value)`
99+
100+
Example:
101+
102+
```c#
103+
// RichContent.cshtml.cs
104+
105+
public class Content : HydroComponent
106+
{
107+
public void Update(string value)
108+
{
109+
// ...
110+
}
111+
}
112+
```
113+
114+
```razor
115+
<!-- Content.cshtml -->
116+
117+
@model Content
118+
<div>
119+
<input type="text" id="myInput">
120+
<button hydro-on:click="@(() => Model.Update(Param.JS("window.myInput.value")))">
121+
Update content
122+
</button>
123+
</div>
124+
```
125+
126+
After clicking the button from the code above, Hydro will execute the expression
127+
`window.myInput.value` on the client side, and pass it as a `value` parameter to the `Update` action.
128+
129+
> NOTE: In case of using widely this feature in your component, you can add:
130+
>
131+
> ```@using static Hydro.Param``` and call `JS` without `Param.` prefix.
132+
89133
## Asynchronous actions
90134

91135
If you want to use asynchronous operations in your actions, just change the signature of the method as in this example:

src/ExpressionExtensions.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ namespace Hydro;
44

55
internal static class ExpressionExtensions
66
{
7+
private const string JsIndicationStart = "HYDRO_JS(";
8+
private const string JsIndicationEnd = ")HYDRO_JS";
9+
710
public static (string Name, IDictionary<string, object> Parameters)? GetNameAndParameters(this Expression<Action> expression)
811
{
912
if (expression is not { Body: MethodCallExpression methodCall })
1013
{
1114
return null;
1215
}
13-
16+
1417
var name = methodCall.Method.Name;
1518
var paramInfos = methodCall.Method.GetParameters();
1619
var arguments = methodCall.Arguments;
@@ -37,15 +40,30 @@ private static object EvaluateExpressionValue(Expression expression)
3740
{
3841
case ConstantExpression constantExpression:
3942
return constantExpression.Value;
40-
43+
4144
case MemberExpression memberExpression:
4245
var objectMember = Expression.Convert(memberExpression, typeof(object));
4346
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
4447
var getter = getterLambda.Compile();
4548
return getter();
46-
49+
50+
case MethodCallExpression callExpression
51+
when callExpression.Method.DeclaringType == typeof(Param)
52+
&& callExpression.Method.Name == nameof(Param.JS)
53+
&& callExpression.Arguments.Any()
54+
&& callExpression.Arguments[0] is ConstantExpression constantExpression:
55+
56+
return EncodeJsExpression(constantExpression.Value);
57+
4758
default:
4859
throw new NotSupportedException("Unsupported expression type: " + expression.GetType().Name);
4960
}
5061
}
62+
63+
private static string EncodeJsExpression(object expression) =>
64+
$"{JsIndicationStart}{expression}{JsIndicationEnd}";
65+
66+
internal static string DecodeJsExpressionsInJson(string json) =>
67+
json.Replace("\"" + JsIndicationStart, "")
68+
.Replace(JsIndicationEnd + "\"", "");
5169
}

src/Param.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Hydro;
2+
3+
/// <summary>
4+
/// Used in Hydro action calls for passing JavaScript expressions as parameters
5+
/// </summary>
6+
public static class Param
7+
{
8+
/// <summary>
9+
/// Pass JavaScript expression as a parameter to Hydro action
10+
/// </summary>
11+
/// <param name="value">JavaScript expression</param>
12+
public static T JS<T>(string value) => default;
13+
14+
/// <summary>
15+
/// Pass JavaScript expression as a parameter to Hydro action
16+
/// </summary>
17+
/// <param name="value">JavaScript expression</param>
18+
public static string JS(string value) => default;
19+
}

src/TagHelpers/HydroOnTagHelper.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public IDictionary<string, Expression<Action>> Handlers
2424
get => _handlers ??= new Dictionary<string, Expression<Action>>(StringComparer.OrdinalIgnoreCase);
2525
set => _handlers = value;
2626
}
27-
27+
2828
/// <summary>
2929
/// Disable during execution
3030
/// </summary>
@@ -48,7 +48,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
4848
{
4949
return;
5050
}
51-
51+
5252
foreach (var eventItem in _handlers)
5353
{
5454
var eventData = eventItem.Value.GetNameAndParameters();
@@ -57,22 +57,31 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
5757
{
5858
continue;
5959
}
60-
60+
6161
var eventDefinition = eventItem.Key;
6262

63-
var invokeData = JsonConvert.SerializeObject(new
64-
{
65-
eventData.Value.Name,
66-
eventData.Value.Parameters
67-
}, JsonSettings.SerializerSettings);
63+
var jsInvokeExpression = GetJsInvokeExpression(eventData.Value.Name, eventData.Value.Parameters);
6864

6965
output.Attributes.RemoveAll(HandlersPrefix + eventDefinition);
70-
output.Attributes.Add(new TagHelperAttribute($"x-on:{eventDefinition}", new HtmlString($"invoke($event, {invokeData})"), HtmlAttributeValueStyle.SingleQuotes));
66+
output.Attributes.Add(new TagHelperAttribute($"x-on:{eventDefinition}", new HtmlString(jsInvokeExpression), HtmlAttributeValueStyle.SingleQuotes));
7167

7268
if (Disable || new[] { "click", "submit" }.Any(e => e.StartsWith(e)))
7369
{
7470
output.Attributes.Add(new("data-loading-disable"));
7571
}
7672
}
7773
}
74+
75+
private static string GetJsInvokeExpression(string name, IDictionary<string, object> parameters)
76+
{
77+
var invokeJson = JsonConvert.SerializeObject(new
78+
{
79+
Name = name,
80+
Parameters = parameters
81+
}, JsonSettings.SerializerSettings);
82+
83+
var invokeJsObject = ExpressionExtensions.DecodeJsExpressionsInJson(invokeJson);
84+
85+
return $"invoke($event, {invokeJsObject})";
86+
}
7887
}

0 commit comments

Comments
 (0)