Skip to content

Commit

Permalink
Allow existing triggers to be merged in with new trigger definitions
Browse files Browse the repository at this point in the history
Removed unused using statement

Code cleanup

Cleaning up tests

Deal with possible existing simple compound event triggers

It's possible for triggers to take the form "showMessage,doStuff" so be sure to split these triggers into multiple triggers.
  • Loading branch information
tanczosm committed Sep 25, 2023
1 parent 31fc07b commit 1cd5bcf
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 38 deletions.
80 changes: 53 additions & 27 deletions src/Htmx/HtmxResponseHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public HtmxResponseHeaders WithTrigger(string eventName, object? detail = null,
}
else
{
_triggers[timing].Add(eventName, detail ?? string.Empty);
_triggers[timing].TryAdd(eventName, detail ?? string.Empty);
}

return this;
Expand All @@ -194,38 +194,64 @@ public HtmxResponseHeaders WithTrigger(string eventName, object? detail = null,
/// </summary>
internal HtmxResponseHeaders Process()
{
if (_triggers.ContainsKey(HtmxTriggerTiming.Default))
{
if (_headers.ContainsKey(Keys.Trigger))
{
throw new Exception("You must use either WithTrigger(..) or Trigger(..), but not both.");
}
if (_triggers.ContainsKey(HtmxTriggerTiming.Default))
{
ParsePossibleExistingTriggers(Keys.Trigger, HtmxTriggerTiming.Default);

_headers[Keys.Trigger] = BuildTriggerHeader(HtmxTriggerTiming.Default);
}
_headers[Keys.Trigger] = BuildTriggerHeader(HtmxTriggerTiming.Default);
}

if (_triggers.ContainsKey(HtmxTriggerTiming.AfterSettle))
{
if (_headers.ContainsKey(Keys.TriggerAfterSettle))
{
throw new Exception("You must use either WithTrigger(..) or TriggerAfterSettle(..), but not both.");
}
if (_triggers.ContainsKey(HtmxTriggerTiming.AfterSettle))
{
ParsePossibleExistingTriggers(Keys.TriggerAfterSettle, HtmxTriggerTiming.AfterSettle);

_headers[Keys.TriggerAfterSettle] = BuildTriggerHeader(HtmxTriggerTiming.AfterSettle);
}
_headers[Keys.TriggerAfterSettle] = BuildTriggerHeader(HtmxTriggerTiming.AfterSettle);
}

// ReSharper disable once InvertIf
if (_triggers.ContainsKey(HtmxTriggerTiming.AfterSwap))
{
if (_headers.ContainsKey(Keys.TriggerAfterSwap))
{
throw new Exception("You must use either WithTrigger(..) or TriggerAfterSwap(..), but not both.");
}
// ReSharper disable once InvertIf
if (_triggers.ContainsKey(HtmxTriggerTiming.AfterSwap))
{
ParsePossibleExistingTriggers(Keys.TriggerAfterSwap, HtmxTriggerTiming.AfterSwap);

_headers[Keys.TriggerAfterSwap] = BuildTriggerHeader(HtmxTriggerTiming.AfterSwap);
}
_headers[Keys.TriggerAfterSwap] = BuildTriggerHeader(HtmxTriggerTiming.AfterSwap);
}

return this;
return this;
}

/// <summary>
/// Checks to see if the response has an existing header defined by headerKey. If it does the
/// header loads all of the triggers locally so they aren't overwritten by Htmx.
/// </summary>
/// <param name="headerKey"></param>
/// <param name="timing"></param>
private void ParsePossibleExistingTriggers(string headerKey, HtmxTriggerTiming timing)
{
if (!_headers.ContainsKey(headerKey))
return;

// Attempt to parse existing header as Json, if fails it is a simplified event key
try
{
var existingTriggers = JsonSerializer.Deserialize<Dictionary<string, object?>>(_headers[headerKey]) ??
new Dictionary<string, object?>();

// Load any existing triggers
foreach (var eventName in existingTriggers.Keys)
WithTrigger(eventName, existingTriggers[eventName], timing);
}
catch (System.Text.Json.JsonException)
{
foreach (var headerValue in _headers[headerKey])
{
if (headerValue is null) continue;

var eventNames = headerValue.Split(',');

foreach (var eventName in eventNames)
WithTrigger(eventName, null, timing);
}
}
}

private string BuildTriggerHeader(HtmxTriggerTiming timing)
Expand Down
45 changes: 34 additions & 11 deletions test/Htmx.Tests/HtmxHttpResponseExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,24 +122,47 @@ public void Can_add_multiple_triggers_with_detail()
}

[Fact]
public void Cant_use_legacy_trigger_and_with_trigger()
public void Can_use_existing_trigger_with_trigger()
{
Response.Htmx(h => h.Trigger("cool"));
Assert.Throws<Exception>(() => Response.Htmx(h => h.WithTrigger("neat")));
const string expected = @"{""neat"":"""",""cool"":""""}";

Response.Htmx(h => h.WithTrigger("cool"));
Response.Htmx(h => h.WithTrigger("neat"));

Assert.True(Headers.ContainsKey(Keys.Trigger));
Assert.Equal(expected, Headers[Keys.Trigger]);
}

[Fact]
public void Cant_use_legacy_triggeraftersettle_and_with_trigger()
public void Can_use_existing_trigger_with_multiple_triggers_with_detail()
{
Response.Htmx(h => h.TriggerAfterSettle("cool"));
Assert.Throws<Exception>(() => Response.Htmx(h => h.WithTrigger("neat", timing: HtmxTriggerTiming.AfterSettle)));
const string expected = @"{""cool"":{""magic"":""something""},""neat"":{""moremagic"":false},""wow"":""""}";

Response.Htmx(h => h.WithTrigger("wow"));
Response.Htmx(h =>
{
h.WithTrigger("cool", new { magic = "something" });
h.WithTrigger("neat", new { moremagic = false });
});

Assert.True(Headers.ContainsKey(Keys.Trigger));
Assert.Equal(expected, Headers[Keys.Trigger]);
}

[Fact]
public void Cant_use_legacy_triggerafterswap_and_with_trigger()
public void Can_use_existing_trigger_with_detail_with_multiple_triggers_with_detail()
{
Response.Htmx(h => h.TriggerAfterSwap("cool"));
Assert.Throws<Exception>(() => Response.Htmx(h => h.WithTrigger("neat", timing: HtmxTriggerTiming.AfterSwap)));
const string expected = @"{""cool"":{""magic"":""something""},""neat"":{""moremagic"":false},""wow"":{""display"":true}}";

Response.Htmx(h => h.WithTrigger("wow", new { display = true }));
Response.Htmx(h =>
{
h.WithTrigger("cool", new { magic = "something" });
h.WithTrigger("neat", new { moremagic = false });
});

Assert.True(Headers.ContainsKey(Keys.Trigger));
Assert.Equal(expected, Headers[Keys.Trigger]);
}

[Fact]
Expand Down

0 comments on commit 1cd5bcf

Please sign in to comment.