Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question regarding img src replacement #3202

Open
neilyoung opened this issue Feb 20, 2025 · 6 comments
Open

Question regarding img src replacement #3202

neilyoung opened this issue Feb 20, 2025 · 6 comments

Comments

@neilyoung
Copy link

I'm having this image in my NavBar

<img
      id="navbar-avatar"
      src="/profile/photo"
      alt="Avatar"
      class="navbar-avatar"
/>

Then there is a form which deals with Avatar images and allows for upload and deletion. With this code it is possible to enforce a replacement of the src attr on change in the Backend (which is gohtml driven):

<img
     id="avatar"
     src="/profile/photo"
     alt="Avatar"
     hx-get="/profile/photo"
     hx-credentials="include"
     hx-trigger="profilePhotoUpdated from:body"
     hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
     hx-swap="none"
     hx-target="this"
     class="avatar"
/>

Once the backend updates the Imagery it fires a HX-Trigger to the body and the latter element reloads the image. Not so the first. This one is completely unimpressed by the trigger, even though the trigger arrives for body. The event arrives, but I'm only possible to replace the image in the NavBar by re-fetching it like so. The same code as applied for the form doesn't do anything in the NavBar because the profilePhotoUpdate trigger doesn't arrive. The NavBar is embedding the form.

document.body.addEventListener('profilePhotoUpdated', () => {
            const avatar = document.getElementById('navbar-avatar');
            if (avatar) {
                avatar.src = '/profile/photo?v=' + new Date().getTime();
            }
        });

I would like to get rid of the required JS

@Telroshan
Copy link
Collaborator

Not so the first. This one is completely unimpressed by the trigger, even though the trigger arrives for body

Unless I misunderstood, your first image is precisely not htmx powered ; you define a hx-trigger and other hx- attributes on your form's image (avatar), which properly process the event and fetch its src, but the first image (navbar-avatar) doesn't declare any hx attribute, thus never receives any update nor does it any fetch as htmx does not process it.

I think what you would want to do in your situation is to replace your first image's markup by the following:

<img
      id="navbar-avatar"
      src="/profile/photo"
      alt="Avatar"
      class="navbar-avatar"

     hx-get="/profile/photo"
     hx-credentials="include"
     hx-trigger="profilePhotoUpdated from:body"
     hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
     hx-swap="none"
     hx-target="this"
/>

(I simply added all htmx attributes from your second image, to the first.)

Hope this helps!

@neilyoung
Copy link
Author

Not so the first. This one is completely unimpressed by the trigger, even though the trigger arrives for body

Unless I misunderstood, your first image is precisely not htmx powered ; you define a hx-trigger and other hx- attributes on your form's image (avatar), which properly process the event and fetch its src, but the first image (navbar-avatar) doesn't declare any hx attribute, thus never receives any update nor does it any fetch as htmx does not process it.

You got that right, as it is now it has nothing to do with HTMX. What I wanted to say was: If I put the same second construct (residing in a form) into the navbar "as is" (so with HTMX) support, then there in the navbar is no reaction to the trigger.

I think what you would want to do in your situation is to replace your first image's markup by the following:


<img
      id="navbar-avatar"
      src="/profile/photo"
      alt="Avatar"
      class="navbar-avatar"

     hx-get="/profile/photo"
     hx-credentials="include"
     hx-trigger="profilePhotoUpdated from:body"
     hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
     hx-swap="none"
     hx-target="this"
/>

And this exactly is not what I could make work. The profilePhotoUpdated never arrived there. But a document.body event hander for this sees it, so that I'm now using JS to do the src exchange.

Sorry for not being specific enough. I of course would love to have nearly identical constructs in form and navbar and no need to use JS at all.

@Telroshan
Copy link
Collaborator

Oh I see, sorry for misunderstanding that you had already tried that.
This sounds weird indeed, I don't see any reason why this wouldn't work, the structure looks perfectly correct.

  • Could you show what your form looks like? Maybe there's some fancy stuff going on depending on what element it targets & swaps
  • Could you also try manually firing a document.body.dispatchEvent(new CustomEvent("profilePhotoUpdated")) in your dev console, just to see if that doesn't trigger your navbar's image htmx request as well? If it does work with this manual event dispatch, then it'll likely be an issue regarding the form's response handling.
  • Also, could you run in your dev console the following: document.getElementById("navbar-avatar")["htmx-internal-data"]? Just to see if it was indeed initialized by htmx (as if it was not, this would explain why the trigger is never triggered, since htmx wouldn't have processed them)

Hope this helps!

@neilyoung
Copy link
Author

OK, the form:

{{ define "content" }}
    <div id="profile-container">
        {{ template "messages" . }}
        <div class="columns">
            <div class="column is-6">
                <div class="block has-text-weight-light is-size-5">
                    {{ i18n .Context "profile" "edit.title" }}
                </div>
                <div class="block">
                    {{ i18n .Context "profile" "edit.explanation" }}
                </div>
            </div>

            <div class="column is-6">
                {{ $data := .Data }}
                {{ if and $data.ProfileForm $data.ProfilePhotoForm }}
                    {{ $profileForm := $data.ProfileForm }}
                    {{ $profilePhotoForm := $data.ProfilePhotoForm }}


                    <form id="profile-form" method="post" enctype="multipart/form-data" hx-post="{{ url "profile.submit" }}" hx-target="#profile-container" hx-swap="innerHTML">
                        <!-- Profile Photo -->
                        <div class="field">
                            <label class="label">{{ i18n .Context "profile" "edit.photo" }}</label>
                            <div class="control">
                                <img
                                    id="avatar"
                                    src="{{ url "profile.photo.get" }}"
                                    alt="Avatar"
                                    hx-get="{{ url "profile.photo.get" }}"
                                    hx-credentials="include"
                                    hx-trigger="profilePhotoUpdated from:body"
                                    hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
                                    hx-swap="none"
                                    hx-target="this"
                                    class="avatar"
                                />

                                <div class="buttons mt-2">
                                    <!-- HTMX Upload Button -->
                                    <label class="button is-secondary">
                                        <input type="file" name="profile_photo" style="display: none;" hx-put="{{ url "profile.photo.upload" }}" hx-encoding="multipart/form-data" hx-target="#profile-container" hx-swap="innerHTML" />
                                        {{ i18n .Context "profile" "edit.select_photo" }}
                                    </label>

                                    <!-- HTMX Remove Button -->
                                    {{ if not $profileForm.IsGenericAvatar }}
                                        <button class="button is-secondary" hx-delete="{{ url "profile.photo.remove" }}" hx-encoding="multipart/form-data" hx-target="#profile-container" hx-swap="innerHTML">
                                            {{ i18n .Context "profile" "edit.remove_photo" }}
                                        </button>
                                    {{ end }}


                                    <!-- Success/Error Messages profilePhotoForm -->
                                    <div class="form-feedback">
                                        {{ template "form-messages" (dict "Form" $profilePhotoForm) }}
                                    </div>
                                </div>
                            </div>
                        </div>

                        <!-- Name -->
                        <div class="field">
                            <label for="name" class="label">{{ i18n .Context "profile" "edit.name" }}</label>
                            <div class="control">
                                <input id="name" name="name" type="text" class="input" value="{{ $profileForm.Name }}" required />
                                {{ template "field-errors" ($profileForm.Submission.GetFieldErrors "Name") }}
                            </div>
                        </div>

                        <!-- Email -->
                        <div class="field">
                            <label for="email" class="label">{{ i18n .Context "profile" "edit.email" }}</label>
                            <div class="control">
                                <input id="email" name="email" type="email" class="input" value="{{ $profileForm.Email }}" required />
                                {{ template "field-errors" ($profileForm.Submission.GetFieldErrors "EMail") }}
                            </div>
                        </div>

                        <!-- About Me -->
                        <div class="field">
                            <label for="about_me" class="label">{{ i18n .Context "profile" "edit.about_me" }}</label>
                            <div class="control">
                                <textarea id="about_me" name="about_me" class="textarea" maxlength="4096">{{- $profileForm.AboutMe -}}</textarea>
                            </div>
                        </div>

                        <!-- Update Profile -->
                        <div class="field mt-3 is-grouped is-flex is-align-items-center">
                            <div class="control">
                                <button class="button is-secondary">{{ i18n .Context "profile" "edit.save" }}</button>
                            </div>

                            <!-- Success/Error Messages profileForm -->
                            <div class="form-feedback">
                                {{ template "form-messages" (dict "Form" $profileForm) }}
                            </div>
                        </div>

                        {{ template "csrf" . }}
                    </form>
                {{ end }}
            </div>
        </div>
    </div>
{{ end }}

Form in Browser:

Image

Page source code as seen by the browser:

<body style="min-height:100%;">
   <nav class="navbar is-dark" role="navigation" aria-label="main navigation" x-data="{ open: false }">
      <div class="container">
         <div class="navbar-brand">
            <a class="navbar-item" hx-boost="true">
               <svg
                  width="48"
                  height="48"
                  viewBox="0 0 48 48"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                  >
                  <defs>
                     <mask id="donutMask">
                        <rect
                           x="0"
                           y="0"
                           width="48"
                           height="48"
                           fill="white"
                           />
                        <circle
                           cx="15"
                           cy="25"
                           r="12"
                           fill="black"
                           />
                     </mask>
                  </defs>
                  <circle
                     cx="24"
                     cy="24"
                     r="24"
                     fill="goldenrod"
                     mask="url(#donutMask)"
                     />
               </svg>
            </a>
            <a href="/" class="navbar-item">
               <div class="title has-text-weight-light has-text-grey is-family-primary">Eclipse</div>
            </a>
            <a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false" @click="open = !open" :class="{ 'is-active': open }">
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            </a>
         </div>
         <div id="navMenu" class="navbar-menu" :class="{ 'is-active': open }" hx-boost="true">
            <div class="navbar-start mt-1" x-data="{ current: window.location.pathname }">
               <a class="navbar-item" href="/about" :class="{ 'is-active': current === $el.getAttribute('href') }">About</a>
               <a class="navbar-item" href="/contact" :class="{ 'is-active': current === $el.getAttribute('href') }">Contact</a>
               <a class="navbar-item" href="/cache" :class="{ 'is-active': current === $el.getAttribute('href') }">Cache</a>
               <a class="navbar-item" href="/task" :class="{ 'is-active': current === $el.getAttribute('href') }">Task</a>
               <a class="navbar-item" href="/files" :class="{ 'is-active': current === $el.getAttribute('href') }">Files</a>
               <a class="navbar-item" href="/admin/role" :class="{ 'is-active': current === $el.getAttribute('href') }">Roles</a>
               <a class="navbar-item" href="/profile" :class="{ 'is-active': current === $el.getAttribute('href') }">Profile</a>
            </div>
            <div class="navbar-end">
               <div class="navbar-item">
                  <span class="navbar-language-flag mt-1">
                  🇬🇧
                  </span>
               </div>
               <div class="navbar-item has-dropdown is-hoverable">
                  <a class="navbar-link is-arrowless">
                  <img id="navbar-avatar" src="/profile/photo" alt="Avatar" class="navbar-avatar" />
                  </a>
                  <div class="navbar-dropdown">
                     <div class="navbar-item">
                        <strong>Access</strong>
                     </div>
                     <a class="navbar-item" href="/logout">
                     Logout
                     </a>
                     <hr class="navbar-divider" />
                     <div class="navbar-item">
                        <strong>Language</strong>
                     </div>
                     <a class="navbar-item" hx-post="/language" hx-vals='{"language": "de"}' hx-trigger="click" hx-swap="none" hx-credentials="include" hx-on::after-request="window.location.reload()">
                     🇩🇪
                     German
                     </a>
                     <a class="navbar-item" hx-post="/language" hx-vals='{"language": "en"}' hx-trigger="click" hx-swap="none" hx-credentials="include" hx-on::after-request="window.location.reload()">
                     🇬🇧
                     English
                     </a>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </nav>
   <div class="container mt-5">
      <div class="columns">
         <div class="column is-12">
            <div class="box">
               <h1 class="title is-size-3 has-text-weight-medium">Manage Profile</h1>
               <script>
                  document.getElementById('snackbarMessages').innerHTML = '[]';
               </script>
               <div id="profile-container">
                  <div class="columns">
                     <div class="column is-6">
                        <div class="block has-text-weight-light is-size-5">
                           Profil information
                        </div>
                        <div class="block">
                           Update your account's profile information and email address.
                        </div>
                     </div>
                     <div class="column is-6">
                        <form id="profile-form" method="post" enctype="multipart/form-data" hx-post="/profile" hx-target="#profile-container" hx-swap="innerHTML">
                           <div class="field">
                              <label class="label">Photo</label>
                              <div class="control">
                                 <img
                                    id="avatar"
                                    src="/profile/photo"
                                    alt="Avatar"
                                    hx-get="/profile/photo"
                                    hx-credentials="include"
                                    hx-trigger="profilePhotoUpdated from:body"
                                    hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
                                    hx-swap="none"
                                    hx-target="this"
                                    class="avatar"
                                    />
                                 <div class="buttons mt-2">
                                    <label class="button is-secondary">
                                    <input type="file" name="profile_photo" style="display: none;" hx-put="/profile/photo/upload" hx-encoding="multipart/form-data" hx-target="#profile-container" hx-swap="innerHTML" />
                                    Select a new photo
                                    </label>
                                    <button class="button is-secondary" hx-delete="/profile/photo/remove" hx-encoding="multipart/form-data" hx-target="#profile-container" hx-swap="innerHTML">
                                    Remove photo
                                    </button>
                                    <div class="form-feedback">
                                    </div>
                                 </div>
                              </div>
                           </div>
                           <div class="field">
                              <label for="name" class="label">Name</label>
                              <div class="control">
                                 <input id="name" name="name" type="text" class="input" value="Admin" required />
                              </div>
                           </div>
                           <div class="field">
                              <label for="email" class="label">EMail</label>
                              <div class="control">
                                 <input id="email" name="email" type="email" class="input" value="[email protected]" required />
                              </div>
                           </div>
                           <div class="field">
                              <label for="about_me" class="label">About Me</label>
                              <div class="control">
                                 <textarea id="about_me" name="about_me" class="textarea" maxlength="4096"></textarea>
                              </div>
                           </div>
                           <div class="field mt-3 is-grouped is-flex is-align-items-center">
                              <div class="control">
                                 <button class="button is-secondary">Save</button>
                              </div>
                              <div class="form-feedback">
                              </div>
                           </div>
                           <input type="hidden" name="csrf" value="dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx" />
                        </form>
                     </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>
   <script>
      document.body.addEventListener('profilePhotoUpdated', () => {
          const avatar = document.getElementById('navbar-avatar');
          if (avatar) {
              avatar.src = "\/profile\/photo?v=" + new Date().getTime();
          }
      });
   </script>
   <script>
      document.body.addEventListener('htmx:configRequest', function (evt) {
          
          if (evt.detail.verb == "delete") {
              evt.detail.useUrlParams = false;
          }
          if (evt.detail.verb !== "get") {
              evt.detail.parameters['csrf'] = 'dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx';
          }
      })
   </script>
   
   <script>
      document.body.addEventListener('htmx:beforeSwap', function (evt) {
          if (evt.detail.xhr.status >= 400) {
              evt.detail.shouldSwap = true;
              evt.detail.target = htmx.find('body');
          }
      });
   </script>
</body>
</html>

I will do the tests later the day. Thanks

@neilyoung
Copy link
Author

OK, I now completely removed the form image tag to not see any interferences, and set the navbar image to what didn't work for me and what you are suggesting. I also removed the little helper script I was using up to now.

The only active img tag is this now in the navbar:

                                   <img
                                        id="navbar-avatar"
                                        src="/profile/photo"
                                        alt="Avatar"
                                        class="navbar-avatar"
                                        hx-get="/profile/photo"
                                        hx-credentials="include"
                                        hx-trigger="profilePhotoUpdated from:body"
                                        hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText"
                                        hx-swap="none"
                                        hx-target="this"
                                    />
 

After a complete page refresh I see the proper avatar in the navbar. Examining the element itself looks OK:

<img id="navbar-avatar" src="/profile/photo" alt="Avatar" class="navbar-avatar" hx-get="/profile/photo" hx-credentials="include" hx-trigger="profilePhotoUpdated from:body" hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText" hx-swap="none" hx-target="this">

Then I set the trigger in the dev console and indeed - you are right - it made the image to fetch something. So the trigger arrives, even in the navbar

Tshark does see this:

Frame 2599: 1339 bytes on wire (10712 bits), 1339 bytes captured (10712 bits) on interface lo0, id 0
Null/Loopback
Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 62920, Dst Port: 8000, Seq: 224797, Ack: 463164, Len: 1263
Hypertext Transfer Protocol
    GET /profile/photo HTTP/1.1\r\n
        Request Method: GET
        Request URI: /profile/photo
        Request Version: HTTP/1.1
    Host: localhost:8000\r\n
    Connection: keep-alive\r\n
    HX-Trigger: navbar-avatar\r\n
    sec-ch-ua-platform: "macOS"\r\n
    HX-Target: navbar-avatar\r\n
    HX-Current-URL: http://localhost:8000/profile\r\n
    sec-ch-ua: "Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"\r\n
    sec-ch-ua-mobile: ?0\r\n
    HX-Request: true\r\n
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\r\n
    Accept: */*\r\n
    Sec-Fetch-Site: same-origin\r\n
    Sec-Fetch-Mode: cors\r\n
    Sec-Fetch-Dest: empty\r\n
    Referer: http://localhost:8000/profile\r\n
    Accept-Encoding: gzip, deflate, br, zstd\r\n
    Accept-Language: en-US,en;q=0.9,es-AR;q=0.8,es;q=0.7,de-DE;q=0.6,de;q=0.5,it;q=0.4\r\n
     […]Cookie: csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx; csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx; csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx; flash=MTc0MDE1Mjk3MXxEWDhFQVFMX2dBQUJFQUVRQUFBRV80QUFBQT09fASi_dZHRmScM36Fj3_RDhe4BELMcMYUXKi1GPndyckf; lang
        Cookie pair: csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx
        Cookie pair: csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx
        Cookie pair: csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx
        Cookie pair: flash=MTc0MDE1Mjk3MXxEWDhFQVFMX2dBQUJFQUVRQUFBRV80QUFBQT09fASi_dZHRmScM36Fj3_RDhe4BELMcMYUXKi1GPndyckf
        Cookie pair: lang=en
        Cookie pair […]: auth=MTc0MDE1NDA1NXxEWDhFQVFMX2dBQUJFQUVRQUFCYl80QUFBd1p6ZEhKcGJtY01Ed0FOWVhWMGFHVnVkR2xqWVhSbFpBUmliMjlzQWdJQUFRWnpkSEpwYm1jTUNRQUhkWE5sY2w5cFpBTnBiblFFQWdBQ0JuTjBjbWx1Wnd3R0FBUnNZVzVuQm5OMGNtbHVad3dFQUFKbGJnPT18pEwFog5
    dnt: 1\r\n
    If-Modified-Since: Fri, 21 Feb 2025 16:07:23 GMT\r\n
    \r\n
    [Full request URI: http://localhost:8000/profile/photo]

Frame 2609: 855 bytes on wire (6840 bits), 855 bytes captured (6840 bits) on interface lo0, id 0
Null/Loopback
Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 8000, Dst Port: 62920, Seq: 463164, Ack: 226060, Len: 779
Hypertext Transfer Protocol
    HTTP/1.1 304 Not Modified\r\n
        Response Version: HTTP/1.1
        Status Code: 304
        [Status Code Description: Not Modified]
        Response Phrase: Not Modified
    Content-Encoding: gzip\r\n
    Last-Modified: Fri, 21 Feb 2025 16:07:23 GMT\r\n
     […]Set-Cookie: auth=MTc0MDE1NDE1NnxEWDhFQVFMX2dBQUJFQUVRQUFCYl80QUFBd1p6ZEhKcGJtY01Ed0FOWVhWMGFHVnVkR2xqWVhSbFpBUmliMjlzQWdJQUFRWnpkSEpwYm1jTUNRQUhkWE5sY2w5cFpBTnBiblFFQWdBQ0JuTjBjbWx1Wnd3R0FBUnNZVzVuQm5OMGNtbHVad3dFQUFKbGJnPT18K_ZN32_e
    Set-Cookie: csrf=dqBNHZwNlByIjKPIxwOEZWvTHxHbDfpx; Expires=Sat, 22 Feb 2025 16:09:16 GMT; HttpOnly; Secure; SameSite=Lax\r\n
    Vary: Cookie\r\n
    X-Content-Type-Options: nosniff\r\n
    X-Frame-Options: SAMEORIGIN\r\n
    X-Request-Id: SQGzcNhPEaQyxIbZVTCOBWJgzvTOoqOF\r\n
    X-Xss-Protection: 1; mode=block\r\n
    Date: Fri, 21 Feb 2025 16:09:16 GMT\r\n
    \r\n
    [Request in frame: 2599]
    [Time since request: 0.012366000 seconds]
    [Request URI: /profile/photo]
    [Full request URI: http://localhost:8000/profile/photo]

It clearly looks as to be coming from the navbar as hx-get. After this the browser display of the navbar avatar is broken, most likely because this is now the examination result of the image:

<img id="navbar-avatar" src="�PNG
�
���
IHDR���������������sI����sRGB��������	pHYs���%���%�IR$���@�IDATx����$9���ZGd�Ϊ,�ղ��޼}��v������3�ե�Rgh��!���H��f��!RU�e�9�� ��08�f>���6&amp;&amp;�8&amp;&amp;'��|���������I''��s�c���9��p$����0g|N���,U�A��

---- shortened ---

!��Ͷ}�Fj�,��R��Z��x�	��?[_񯚿���?��ڞ����Q�ߴ���H��CP�b����8hC�X�֬���/+u�q�-�{�o�?d���I6�F�~nd\'�Nމ��4��\�x��w��-�����|8�B�!�>��݂�1�iu��-Y/�I��>�}�k{5��t��~[C�Mz0����&amp;�z���[��X��i�L�7Y%�*��=�|K#��VM�VT�JǙ������
���c�����r��A� �0��Ū��]u�h��P�W�,D�	Q�qK���~�{����1юR�GP�����IEND�B`�" alt="Avatar" class="navbar-avatar" hx-get="/profile/photo" hx-credentials="include" hx-trigger="profilePhotoUpdated from:body" hx-on::after-request="event.detail.target.src=event.detail.xhr.responseText" hx-swap="none" hx-target="this">

The entire image content has been put into the src attr as like a string, so that the browser immediately fetches this nonsense as URL, which fails.

Image

So something happens, but it is the wrong.

@neilyoung
Copy link
Author

Oh, this one seems to work:

                                    <img
                                        id="navbar-avatar"
                                        alt="Avatar"
                                        class="navbar-avatar"
                                        hx-get="/profile/photo"
                                        hx-credentials="include"
                                        hx-trigger="load, profilePhotoUpdated from:body"
                                        hx-on::after-request="event.detail.target.src='/profile/photo/?v=' + Date.now()"
                                        hx-swap="none"
                                        hx-target="this"
                                    />

I'm using the triggers load and reception of the update notification to trigger an XHR fetch of the image just to use the after request to replace the src tag with a cache busting URL to enforce a new fetch. This ends up in at least two fetches, but works fine.

That also works in the form. No extra js needed. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants