@@ -99,6 +99,14 @@ class BaseHxRequest:
9999 refresh_views_context_on_POST : bool = False
100100 use_current_url : bool = False
101101
102+ #: Maps the friendly phase keys accepted in a dict return from
103+ #: :meth:`get_triggers` to their HTMX response-header names.
104+ trigger_header_map : dict [str , str ] = {
105+ "trigger" : "HX-Trigger" ,
106+ "after_settle" : "HX-Trigger-After-Settle" ,
107+ "after_swap" : "HX-Trigger-After-Swap" ,
108+ }
109+
102110 @cached_property
103111 def is_post_request (self ):
104112 """
@@ -193,33 +201,78 @@ def get_headers(self, **kwargs) -> Dict:
193201 if self .no_swap :
194202 headers ["HX-Reswap" ] = "none"
195203
196- triggers = self .format_triggers (** kwargs )
197- if triggers :
198- headers ["HX-Trigger" ] = triggers
204+ headers .update (self .get_trigger_headers (** kwargs ))
199205 return headers
200206
201- def get_triggers (self , ** kwargs ) -> List [Union [str , dict ]]:
207+ def get_triggers (self , ** kwargs ) -> Union [ List [Union [str , dict ]], Dict [ str , list ]]:
202208 """
203209 Override to set the triggers for the response.
204210
205- Return a list of triggers. Each trigger can be:
206- - A string for a simple trigger (e.g. ``"myEvent"``).
207- - A dict to pass details with the trigger
208- (e.g. ``{"showMessage": {"level": "info", "message": "Saved!"}}``).
211+ Return either:
212+
213+ - A **list** of triggers, emitted on the ``HX-Trigger`` header (fired
214+ as soon as the response is received). Each item can be:
215+
216+ - A string for a simple trigger (e.g. ``"myEvent"``).
217+ - A dict to pass details with the trigger
218+ (e.g. ``{"showMessage": {"level": "info", "message": "Saved!"}}``).
219+
220+ - A **dict** keyed by trigger phase to target the full set of HTMX
221+ trigger headers, where each value is a list of the same shape::
222+
223+ {
224+ "trigger": ["eventA"], # HX-Trigger
225+ "after_settle": ["eventB"], # HX-Trigger-After-Settle
226+ "after_swap": [{"eventC": {...}}], # HX-Trigger-After-Swap
227+ }
209228
210- When any dict is present, the header is formatted as a JSON object
211- per the `HX-Trigger response header spec <https://htmx.org/headers/hx-trigger/>`_.
229+ Within any list, when a dict trigger is present the header value is
230+ formatted as a JSON object per the `HX-Trigger response header spec
231+ <https://htmx.org/headers/hx-trigger/>`_.
212232 """
213233 return []
214234
235+ def get_trigger_headers (self , ** kwargs ) -> Dict [str , str ]:
236+ """
237+ Build the HTMX trigger response headers from :meth:`get_triggers`.
238+
239+ Returns a ``{header_name: header_value}`` dict covering ``HX-Trigger``,
240+ ``HX-Trigger-After-Settle`` and ``HX-Trigger-After-Swap`` as needed.
241+ Phases with no triggers are omitted.
242+ """
243+ triggers = self .get_triggers (** kwargs )
244+ # A plain list keeps the historical behavior: everything on HX-Trigger.
245+ if not isinstance (triggers , dict ):
246+ triggers = {"trigger" : triggers }
247+
248+ headers = {}
249+ for key , header_name in self .trigger_header_map .items ():
250+ value = self ._format_trigger_value (triggers .get (key ) or [])
251+ if value :
252+ headers [header_name ] = value
253+ return headers
254+
215255 def format_triggers (self , ** kwargs ) -> str :
216256 """
217- Format triggers for the ``HX-Trigger`` header.
257+ Format the ``HX-Trigger`` header value from :meth:`get_triggers` .
218258
219- If all triggers are plain strings, they are comma-separated.
220- If any trigger carries details (dict), the header is JSON-encoded.
259+ Retained for backwards compatibility; only covers the ``HX-Trigger``
260+ header. For the full set of trigger headers see
261+ :meth:`get_trigger_headers`.
221262 """
222263 triggers = self .get_triggers (** kwargs )
264+ if isinstance (triggers , dict ):
265+ triggers = triggers .get ("trigger" ) or []
266+ return self ._format_trigger_value (triggers )
267+
268+ @staticmethod
269+ def _format_trigger_value (triggers : List [Union [str , dict ]]) -> str :
270+ """
271+ Format a single list of triggers into one header value.
272+
273+ If all triggers are plain strings, they are comma-separated. If any
274+ trigger carries details (dict), the value is JSON-encoded.
275+ """
223276 has_details = any (isinstance (t , dict ) for t in triggers )
224277 if not has_details :
225278 return ", " .join (triggers )
@@ -621,7 +674,11 @@ def get_triggers(self, **kwargs) -> list:
621674 """
622675 triggers = super ().get_triggers (** kwargs )
623676 if self .is_post_request and self .form .is_valid () and self .close_modal_on_save :
624- triggers .append ("closeHxModal" )
677+ # Support both the list return and the phase-keyed dict return.
678+ if isinstance (triggers , dict ):
679+ triggers .setdefault ("trigger" , []).append ("closeHxModal" )
680+ else :
681+ triggers .append ("closeHxModal" )
625682 return triggers
626683
627684 def get_headers (self , ** kwargs ) -> Dict :
0 commit comments