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

Add HxPush annotation #105

Closed
tschuehly opened this issue Apr 15, 2024 · 11 comments
Closed

Add HxPush annotation #105

tschuehly opened this issue Apr 15, 2024 · 11 comments
Milestone

Comments

@tschuehly
Copy link
Contributor

We have a working HxPush annotation using an Interceptor, should I create a PR?

@Repeatable(HxPushs.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@HxRequest
public @interface HxPush {

  String url() default "";

  String urlPrefix() default "";

  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.METHOD)
  @interface HxPushs {

    HxPush[] value();
  }
}
public class HtmxPushUrlInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(
      @NotNull HttpServletRequest request,
      @NotNull HttpServletResponse response,
      @NotNull Object handler) {
    if (handler instanceof HandlerMethod handlerMethod) {
      var hxPushUrls =
          AnnotatedElementUtils.findMergedRepeatableAnnotations(
              handlerMethod.getMethod(), HxPush.class, HxPush.HxPushs.class);
      if (CollectionUtils.isNotEmpty(hxPushUrls)) {
        var refererPath =
            String.valueOf(request.getHeader("referer"))
                .replace(String.valueOf(request.getHeader("origin")), "");
        var matchingPrefix =
            hxPushUrls.stream()
                .filter(
                    url ->
                        Strings.isNotEmpty(url.urlPrefix())
                            && refererPath.startsWith(url.urlPrefix()))
                .findFirst();
        var withoutPrefix =
            hxPushUrls.stream().filter(url -> Strings.isEmpty(url.urlPrefix()))
                .findFirst();
        if (matchingPrefix.isPresent()) {
          addHeader(request, response, matchingPrefix.get());
          return true;
        }
        withoutPrefix.ifPresent((prefix) -> addHeader(request, response, prefix));
        return true;
      }
    }
    return true;
  }

  private void addHeader(
      @NotNull HttpServletRequest request,
      @NotNull HttpServletResponse response,
      HxPush hxPushUrl) {
    if (Strings.isBlank(hxPushUrl.url())) {
      if (request.getQueryString() != null) {
        response.addHeader("HX-Push-Url",
            request.getServletPath() + "?" + request.getQueryString());
        return;
      }
      response.addHeader("HX-Push-Url", request.getServletPath());
    } else {
      response.addHeader("HX-Push-Url", hxPushUrl.url());
    }
  }
}

@wimdeblauwe
Copy link
Owner

Can you give some examples of where you would use this?

@tschuehly
Copy link
Contributor Author

Any time I make a HX Request with dynamic properties and I want to push the URL into the browser history:
image

@wimdeblauwe
Copy link
Owner

So it is an annotation alternative to using the HtmxResponse return type if I understand it correctly. You can already do this now:

@HxRequest
@GetMapping(path=TasksWebPath.OVERVIEW_SCOPE)
public HtmxResponse taskOverview(@PathVariable("scope") String scope) {
  ViewContext viewContext = taskOverview.renderTaskOverview(TaskScope.validate(scope));
   return HtmxResponse.builder.addTemplate(viewContext).pushUrl("/the/url/here").build();
}

Not as convenient, and we require the URL in the pushUrl method. I think I like this :-)

@checketts
Copy link
Collaborator

I also like it. It will also need to support setting a custom url:

@HxRequest
@HxPush //Implies the value of the current request mapping
@GetMapping(path=TasksWebPath.OVERVIEW_SCOPE)
public HtmxResponse taskOverview(@PathVariable("scope") String scope) {
  ViewContext viewContext = taskOverview.renderTaskOverview(TaskScope.validate(scope));
   return HtmxResponse.builder.addTemplate(viewContext).build();
}

and

@HxRequest
@HxPush("/alternate/path")
@GetMapping(path=TasksWebPath.OVERVIEW_SCOPE)
public HtmxResponse taskOverview(@PathVariable("scope") String scope) {
  ViewContext viewContext = taskOverview.renderTaskOverview(TaskScope.validate(scope));
   return HtmxResponse.builder.addTemplate(viewContext).build();
}

@tschuehly
Copy link
Contributor Author

Yeah most of my improvement ideas do not consider the HtmxResponse builder as I use ViewContexts instead.

@xhaggi
Copy link
Collaborator

xhaggi commented Apr 16, 2024

Just a reminder: I have already done the work for this and all other missing response header annotations in PR #67 (Commit for @HxPush c2020f5).

If you want to push the current request URL to the browser history just set pushUrl to true in HtmxResonse.builder(). BTW the docs for HX-Push-Url are not clear enough about that, but all possible values of hx-push-url also apply to the header.

@HxRequest
@GetMapping(path=TasksWebPath.OVERVIEW_SCOPE)
public HtmxResponse taskOverview(@PathVariable("scope") String scope) {
   return HtmxResponse.builder()
    .view(taskOverview.renderTaskOverview(TaskScope.validate(scope));)
    .pushUrl("true") // or .pushUrl("/alternative/URL")
    .build();
}

BTW What I could imagine to simplify the use of the builder a bit would be a convenience method e.g. pushUrl() without a parameter. We already had that for pushUrl("false") via preventHistoryUpdate()

@wimdeblauwe
Copy link
Owner

Right, thanks for reminding me about that @xhaggi. I don't have much time to work on this, but I would be happy to review a PR if somebody wants to take this up.

@xhaggi
Copy link
Collaborator

xhaggi commented Apr 19, 2024

@tschuehly could you please give us some more insight into your code? Why do you need a prefix and why do you add the request.getServletPath() in case @HxPush is blank?

I would recommend not moving too much htmx-specific code to the server side. If you want to push the current URL that htmx has requested to the browser history, simply add the attribute hx-push-url="true" to the HTML element in your template code.

@tschuehly
Copy link
Contributor Author

We needed a prefix when we had multiple @hxpush annotations but I would probably remove it.
For us @Hxpush most often has no path as the paths are dynamic. The templates are created on the server so I see no difference between doing it on the template or the Endpoint.

I prefer doing it on the Endpoints as I don't know at component creation time if I need to push the URL or not.

@tschuehly
Copy link
Contributor Author

@wimdeblauwe @xhaggi
I've ported xhaggis work to the current main and opened a pull request: #117

@wimdeblauwe
Copy link
Owner

#118 has been merged now and @HxPushUrl is part of it.

@wimdeblauwe wimdeblauwe added this to the 3.4.0 milestone May 5, 2024
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

4 participants