casid / jte

Secure and speedy templates for Java and Kotlin.
https://jte.gg
Apache License 2.0
789 stars 58 forks source link

Allow expressions in HTML attribute names for certain types #326

Closed tschuehly closed 1 week ago

tschuehly commented 8 months ago

Currently If I put an expression in the html next to an attribute:

 @if( item.action instanceof HTMXAction action)
        <summary
                ${action}
                hx-get="${action.type() == Type.GET ? action.path(): null }"
                hx-post="${action.type() == Type.POST ? action.path(): null }"
                hx-target="${action.target()}"
        >
        </summary>
@endif

I get this error Illegal HTML attribute name ${action}! Expressions in HTML attribute names are not allowed.

In our use case we have a record that represents HTMX attributes: public record HTMXAction(String path, String target, Type type, Swap swa)

According to this it is for html sanitation -> https://github.com/casid/jte/issues/300#issuecomment-1820709769

I think there are several ways to implement this while still allowing sanitation .

One way would be to have a certain type similar to Content that would allow for expressions in attributes.

public class HtmxAction implements HtmlAttributeList{
   private final String htmxType = "hx-get";
  private final String path = "/helloWorld";
   @Override
  public void writeTo(TemplateOutput output){
     output.writeAttribute(htmxType, path)
  }
}

What do you think about this idea?

It would allow to have much cleaner html templates

casid commented 8 months ago

Sounds complicated to me.

If I recall correctly there has been a similiar issue: https://github.com/casid/jte/pull/256 Maybe one of the solutions there would help?

tschuehly commented 8 months ago

Ok, thanks for the pointer. Will take a look next week!

tschuehly commented 6 months ago

Hey @casid.

Finally got some time to try it out. I tried it out both with $unsafe and the custom Policy, but it does not behave like I would expect it

public static final String htmx = "hx-get=/helloWorld hx-swap=outerHTML";

This HTML: <div ${htmx} > is rendered to this: <div ${htmx}="">

this: <div ${htmx} = "" > is rendered to <div hx-get="/helloWorld" hx-swap="outerHTML" =="" ""=""></div>

What do you think?

casid commented 6 months ago

Ugh, that's indeed unexpected!

Problem is, that $unsafe{htmx} / ${htmx} is parsed as attribute name and thus not evaluated by jte.

I've pushed a fix for this, could you have a try if this improves for you with the latest snapshot version?

tschuehly commented 6 months ago

What is the maven Repository URL for snapshots? I tried https://s01.oss.sonatype.org/content/repositories/snapshots/ but it didn't find it.

casid commented 6 months ago

@tschuehly you can give it a try via jitpack: https://jitpack.io/#casid/jte/-SNAPSHOT

tschuehly commented 6 months ago

@casid Works with the SNAPSHOT!

checketts commented 6 months ago

@tschuehly I'm also interested in creating a helper for htmx attributes. Do you have an example of what is working for you?

My goal is to make something like this work:

@param hx: HtmxHelper

<div ${hx.get(url="/my/url/"+item.id, swap="outerHTML", target="someId")}>Click me</div>

That would generate:

<div hx-get="/my/url/123" hx-swap="outerHTML" hx-target="someId">Click me</div>

My helper would handle the escaping if $unsafe were needed.

tschuehly commented 6 months ago

@checketts Here I have an example of a config that works: https://github.com/tschuehly/easy-spring-auth/commit/731c0f3d769e8e27410e49cab570ae0a135df7c2

I'm not really sure how to approach this the best. I really come to like using static public final variables to make it easy to see what controller endpoint htmx is calling:

<button hx-get="${URI(GET_EDIT_USER_MODAL,uuid)}"
               hx-target="#${MODAL_CONTAINER_ID}">
          <img src="/edit.svg">
</button>
  public static final String GET_EDIT_USER_MODAL = "/save-user/modal/{uuid}";

  @GetMapping(GET_EDIT_USER_MODAL)
  public ViewContext editUserModal(@PathVariable UUID uuid) {
    return editUserComponent.render(uuid);
 }

UserRowComponent

For the templates you could create a HTMX object that is then stringified to htmx attributes. In a Spring specific context I'm thinking of maybe creating a RequestMapping annotation that takes that HTMX object and creates a mapping. This way you still have the easy navigation where it is used.

maybe interesting to you aswell: HtmxUtil.java