Skip to content

Commit 2f606cc

Browse files
committed
Added feature: Merging props
1 parent 597f4cf commit 2f606cc

11 files changed

+99
-35
lines changed

InertiaNetCore/Extensions/InertiaExtensions.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,26 @@ namespace InertiaNetCore.Extensions;
66

77
internal static class InertiaExtensions
88
{
9-
internal static List<string> GetPartialData(this ActionContext context)
9+
private static List<string> GetInertiaHeaderData(this ActionContext context, string header)
1010
{
11-
return context.HttpContext.Request.Headers.TryGetValue("X-Inertia-Partial-Data", out var data)
11+
return context.HttpContext.Request.Headers.TryGetValue(header, out var data)
1212
? data.FirstOrDefault()?.Split(",")
1313
.Where(s => !string.IsNullOrEmpty(s))
1414
.ToList() ?? []
1515
: [];
1616
}
17+
internal static List<string> GetPartialData(this ActionContext context)
18+
{
19+
return context.GetInertiaHeaderData("X-Inertia-Partial-Data");
20+
}
21+
internal static List<string> GetInertiaExcepts(this ActionContext context)
22+
{
23+
return context.GetInertiaHeaderData("X-Inertia-Partial-Except");
24+
}
25+
internal static List<string> GetInertiaResetData(this ActionContext context)
26+
{
27+
return context.GetInertiaHeaderData("X-Inertia-Reset");
28+
}
1729

1830
internal static bool IsInertiaPartialComponent(this ActionContext context, string component)
1931
{

InertiaNetCore/Inertia.cs

+3
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,7 @@ public static class Inertia
4444

4545
public static AlwaysProp<T> Always<T>(Func<T?> callback) => _factory.Always(callback);
4646
public static AlwaysProp<T> Always<T>(Func<Task<T?>> callback) => _factory.Always(callback);
47+
48+
public static MergeProp<T> Merge<T>(Func<T?> callback) => _factory.Merge(callback);
49+
public static MergeProp<T> Merge<T>(Func<Task<T?>> callback) => _factory.Merge(callback);
4750
}

InertiaNetCore/Models/InertiaPage.cs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public readonly record struct InertiaPage
44
{
55
public required InertiaProps Props { get; init; }
66
public required Dictionary<string, List<string>> DeferredProps { get; init; }
7+
public required List<string> MergeProps { get; init; }
78
public required string Component { get; init; }
89
public required string? Version { get; init; }
910
public required string Url { get; init; }

InertiaNetCore/Models/InertiaProps.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ namespace InertiaNetCore.Models;
44

55
public class InertiaProps : Dictionary<string, object?>
66
{
7-
internal async Task<InertiaProps> ToProcessedProps(List<string>? partials)
7+
internal async Task<InertiaProps> ToProcessedProps(bool isPartial, List<string> partials, List<string> excepts)
88
{
99
var props = new InertiaProps();
1010

11-
if(partials is not null && partials.Count == 0)
12-
partials = null;
13-
1411
foreach (var (key, value) in this)
1512
{
16-
if(partials is null && value is IIgnoreFirstProp)
13+
if(isPartial && excepts.Contains(key, StringComparer.InvariantCultureIgnoreCase))
14+
continue;
15+
16+
if(!isPartial && value is IIgnoreFirstProp)
1717
continue;
1818

19-
if(partials is not null && value is not IAlwaysProp && !partials.Contains(key, StringComparer.InvariantCultureIgnoreCase))
19+
if(isPartial && value is not IAlwaysProp && !partials.Contains(key, StringComparer.InvariantCultureIgnoreCase))
2020
continue;
2121

2222
props.Add(key, value switch

InertiaNetCore/Response.cs

+44-21
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class Response(string component, InertiaProps props, string? version, Ine
1313
: IActionResult
1414
{
1515
private IDictionary<string, object>? _viewData;
16-
16+
1717
public async Task ExecuteResultAsync(ActionContext context)
1818
{
1919
var page = new InertiaPage
@@ -23,8 +23,9 @@ public async Task ExecuteResultAsync(ActionContext context)
2323
Url = context.HttpContext.RequestedUri(),
2424
Props = await GetFinalProps(context),
2525
DeferredProps = GetDeferredProps(context),
26+
MergeProps = GetMergeProps(context)
2627
};
27-
28+
2829
if (!context.HttpContext.IsInertiaRequest())
2930
{
3031
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), context.ModelState)
@@ -45,33 +46,36 @@ public async Task ExecuteResultAsync(ActionContext context)
4546
context.HttpContext.Response.Headers.Append("X-Inertia", "true");
4647
context.HttpContext.Response.Headers.Append("Vary", "Accept");
4748
context.HttpContext.Response.StatusCode = 200;
48-
49-
var jsonResult = new JsonResult(page, jsonSerializerOptions);
49+
50+
var jsonResult = new JsonResult(page, options.JsonSerializerOptions);
5051
await jsonResult.ExecuteResultAsync(context);
5152
}
5253
}
53-
54+
5455
private async Task<InertiaProps> GetFinalProps(ActionContext context)
5556
{
56-
var partials = context.IsInertiaPartialComponent(component) ? context.GetPartialData() : null;
57+
var isPartial = context.IsInertiaPartialComponent(component);
58+
var partials = isPartial ? context.GetPartialData() : [];
59+
var excepts = isPartial ? context.GetInertiaExcepts() : [];
60+
5761
var shared = context.HttpContext.Features.Get<InertiaSharedProps>();
58-
var flash = context.HttpContext.Features.Get<InertiaFlashMessages>()
62+
var flash = context.HttpContext.Features.Get<InertiaFlashMessages>()
5963
?? InertiaFlashMessages.FromSession(context.HttpContext);
6064
var errors = GetErrors(context);
61-
62-
var finalProps = await props.ToProcessedProps(partials);
63-
65+
66+
var finalProps = await props.ToProcessedProps(isPartial, partials, excepts);
67+
6468
finalProps = finalProps
6569
.Merge(shared?.GetData())
6670
.AddTimeStamp()
6771
.AddFlash(flash.GetData())
6872
.AddErrors(errors);
69-
73+
7074
flash.Clear(false);
71-
75+
7276
return finalProps;
7377
}
74-
78+
7579
private Dictionary<string, List<string>> GetDeferredProps(ActionContext context)
7680
{
7781
if (context.IsInertiaPartialComponent(component))
@@ -95,31 +99,50 @@ private Dictionary<string, List<string>> GetDeferredProps(ActionContext context)
9599
g => g.Select(x => x.Key).ToList()
96100
);
97101
}
98-
102+
103+
private List<string> GetMergeProps(ActionContext context)
104+
{
105+
var resetData = context.GetInertiaResetData();
106+
107+
var tmp = new Dictionary<string, string>();
108+
109+
foreach (var (key, value) in props)
110+
{
111+
if (value is IMergeableProp { Merge: true } && !resetData.Contains(key))
112+
tmp[key] = key;
113+
}
114+
115+
// apply json serialization options to dictionary keys before grouping them
116+
var jsonOptions = options.JsonSerializerOptions as JsonSerializerOptions;
117+
tmp = JsonSerializer.Deserialize<Dictionary<string, string>>(JsonSerializer.Serialize(tmp, jsonOptions), jsonOptions);
118+
119+
return tmp!.Select(prop => prop.Key).ToList();
120+
}
121+
99122
private static Dictionary<string, string> GetErrors(ActionContext context)
100123
{
101124
var sessionErrors = context.HttpContext.Session.GetString("errors");
102125
if (sessionErrors is not null)
103126
{
104127
var errors = JsonSerializer.Deserialize<Dictionary<string, string>>(sessionErrors);
105128
context.HttpContext.Session.Remove("errors");
106-
107-
if(errors is not null)
129+
130+
if (errors is not null)
108131
return errors;
109132
}
110-
111-
if (context.ModelState.IsValid)
133+
134+
if (context.ModelState.IsValid)
112135
return [];
113-
136+
114137
return context.ModelState.ToDictionary(
115138
kv => kv.Key,
116139
kv => kv.Value?.Errors.FirstOrDefault()?.ErrorMessage ?? ""
117-
);
140+
);
118141
}
119142

120143
public Response WithViewData(IDictionary<string, object> viewData)
121144
{
122145
_viewData = viewData;
123146
return this;
124147
}
125-
}
148+
}

InertiaNetCore/ResponseFactory.cs

+2
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,6 @@ public void Flash(string key, string? value)
117117
public DeferredProp<T> Defer<T>(Func<Task<T?>> callback, string? group) => new(callback, group);
118118
public AlwaysProp<T> Always<T>(Func<T?> callback) => new(callback);
119119
public AlwaysProp<T> Always<T>(Func<Task<T?>> callback) => new(callback);
120+
public MergeProp<T> Merge<T>(Func<T?> callback) => new(callback);
121+
public MergeProp<T> Merge<T>(Func<Task<T?>> callback) => new(callback);
120122
}

InertiaNetCore/Utils/DeferProp.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace InertiaNetCore.Utils;
66
/// </summary>
77
public class DeferredProp<T> : InvokableProp<T>, IDeferredProp
88
{
9+
public bool Merge { get; set; }
910
public string? Group { get; }
1011

1112
public DeferredProp(Func<T?> callback, string? group) : base(callback)

InertiaNetCore/Utils/Interfaces.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ internal interface IAlwaysProp;
1717

1818
internal interface IIgnoreFirstProp;
1919

20-
internal interface ILazyProp : IIgnoreFirstProp;
21-
22-
internal interface IDeferredProp : IIgnoreFirstProp
20+
internal interface IDeferredProp : IIgnoreFirstProp, IMergeableProp
2321
{
2422
string? Group { get; }
25-
}
23+
}
24+
25+
public interface IMergeableProp
26+
{
27+
bool Merge { get; set; }
28+
}

InertiaNetCore/Utils/LazyProp.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace InertiaNetCore.Utils;
55
/// OPTIONALLY included on partial reloads (you should call <c>router.reload({ only: ["propName"] })</c>) <br/>
66
/// ONLY evaluated when needed
77
/// </summary>
8-
public class LazyProp<T> : InvokableProp<T>, ILazyProp
8+
public class LazyProp<T> : InvokableProp<T>, IIgnoreFirstProp
99
{
1010
public LazyProp(Func<T?> callback) : base(callback)
1111
{

InertiaNetCore/Utils/MergeProp.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace InertiaNetCore.Utils;
2+
3+
/// <summary>
4+
/// By default, Inertia overwrites props with the same name when reloading a page.
5+
/// However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior.
6+
/// In these cases, you can merge props instead of overwriting them.
7+
/// </summary>
8+
public class MergeProp<T> : InvokableProp<T>, IMergeableProp
9+
{
10+
public bool Merge { get; set; } = true;
11+
12+
public MergeProp(Func<T?> callback) : base(callback)
13+
{
14+
}
15+
16+
public MergeProp(Func<Task<T?>> callbackAsync) : base(callbackAsync)
17+
{
18+
}
19+
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,5 +334,5 @@ export default defineConfig({
334334
## Work in progress
335335

336336
- [x] Deferred props
337-
- [ ] Merging props
337+
- [x] Merging props
338338
- [ ] History encryption

0 commit comments

Comments
 (0)