1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Linq ;
4
+ using System . Text ;
4
5
using System . Text . Json ;
6
+ using System . Text . Json . Nodes ;
5
7
using JetBrains . Annotations ;
6
8
using Microsoft . AspNetCore . Http ;
7
9
@@ -183,7 +185,7 @@ public HtmxResponseHeaders WithTrigger(string eventName, object? detail = null,
183
185
}
184
186
else
185
187
{
186
- _triggers [ timing ] . Add ( eventName , detail ?? string . Empty ) ;
188
+ _triggers [ timing ] . TryAdd ( eventName , detail ?? string . Empty ) ;
187
189
}
188
190
189
191
return this ;
@@ -196,38 +198,66 @@ internal HtmxResponseHeaders Process()
196
198
{
197
199
if ( _triggers . ContainsKey ( HtmxTriggerTiming . Default ) )
198
200
{
199
- if ( _headers . ContainsKey ( Keys . Trigger ) )
200
- {
201
- throw new Exception ( "You must use either WithTrigger(..) or Trigger(..), but not both." ) ;
202
- }
201
+ ParsePossibleExistingTriggers ( Keys . Trigger , HtmxTriggerTiming . Default ) ;
203
202
204
203
_headers [ Keys . Trigger ] = BuildTriggerHeader ( HtmxTriggerTiming . Default ) ;
205
204
}
206
205
207
206
if ( _triggers . ContainsKey ( HtmxTriggerTiming . AfterSettle ) )
208
207
{
209
- if ( _headers . ContainsKey ( Keys . TriggerAfterSettle ) )
210
- {
211
- throw new Exception ( "You must use either WithTrigger(..) or TriggerAfterSettle(..), but not both." ) ;
212
- }
208
+ ParsePossibleExistingTriggers ( Keys . TriggerAfterSettle , HtmxTriggerTiming . AfterSettle ) ;
213
209
214
210
_headers [ Keys . TriggerAfterSettle ] = BuildTriggerHeader ( HtmxTriggerTiming . AfterSettle ) ;
215
211
}
216
212
217
213
// ReSharper disable once InvertIf
218
214
if ( _triggers . ContainsKey ( HtmxTriggerTiming . AfterSwap ) )
219
215
{
220
- if ( _headers . ContainsKey ( Keys . TriggerAfterSwap ) )
221
- {
222
- throw new Exception ( "You must use either WithTrigger(..) or TriggerAfterSwap(..), but not both." ) ;
223
- }
216
+ ParsePossibleExistingTriggers ( Keys . TriggerAfterSwap , HtmxTriggerTiming . AfterSwap ) ;
224
217
225
218
_headers [ Keys . TriggerAfterSwap ] = BuildTriggerHeader ( HtmxTriggerTiming . AfterSwap ) ;
226
219
}
227
220
228
221
return this ;
229
222
}
230
223
224
+ /// <summary>
225
+ /// Checks to see if the response has an existing header defined by headerKey. If it does the
226
+ /// header loads all of the triggers locally so they aren't overwritten by Htmx.
227
+ /// </summary>
228
+ /// <param name="headerKey"></param>
229
+ /// <param name="timing"></param>
230
+ private void ParsePossibleExistingTriggers ( string headerKey , HtmxTriggerTiming timing )
231
+ {
232
+ if ( ! _headers . ContainsKey ( headerKey ) )
233
+ return ;
234
+
235
+ var header = _headers [ headerKey ] ;
236
+ // Attempt to parse existing header as Json, if fails it is a simplified event key
237
+ // assume if the string starts with '{' and ends with '}', that it is JSON
238
+ if ( header . Any ( h => h is [ '{' , .., '}' ] ) )
239
+ {
240
+ var reader = new Utf8JsonReader ( Encoding . UTF8 . GetBytes ( header ) ) ;
241
+ // this might still throw :(
242
+ var jsonObject = JsonNode . Parse ( ref reader ) ? . AsObject ( ) ;
243
+ // Load any existing triggers
244
+ foreach ( var ( key , value ) in jsonObject ! )
245
+ WithTrigger ( key , value , timing ) ;
246
+ }
247
+ else
248
+ {
249
+ foreach ( var headerValue in _headers [ headerKey ] )
250
+ {
251
+ if ( headerValue is null ) continue ;
252
+
253
+ var eventNames = headerValue . Split ( ',' ) ;
254
+
255
+ foreach ( var eventName in eventNames )
256
+ WithTrigger ( eventName , null , timing ) ;
257
+ }
258
+ }
259
+ }
260
+
231
261
private string BuildTriggerHeader ( HtmxTriggerTiming timing )
232
262
{
233
263
// Reduce the payload if the user has only specified 1 trigger with no value
0 commit comments