Git Product home page Git Product logo

Comments (4)

don41382 avatar don41382 commented on August 16, 2024 1

Thanks for pointing out the HtmlInceptor. Meanwhile, I will use my own validation setup / path the BindingResult manually.

You are using stripes at work and with jte?

from jte.

casid avatar casid commented on August 16, 2024

No, this isn't implemented in the spring-boot-starter as far as I'm aware of.

I think you would need to find a way to pass the BindingResult to the template and could do it by hand as in thymeleaf template. I'm no Spring user, so I'm not sure what the best practice for this is.

That being said, jte has an HtmlInterceptor feature that could be used to make this even more usable. We wrote such an interceptor for the Stripes framework we're using at work. Unfortunately that one is still closed source so I can't share it here.

But the unit tests here show what's possible:
https://github.com/casid/jte/blob/main/jte/src/test/java/gg/jte/TemplateEngine_HtmlInterceptorTest.java

You can basically automatically bind controller values to HTML elements, show field errors, etc. At least we could do all of that in Stipes :-)

from jte.

casid avatar casid commented on August 16, 2024

Yes, but it is rather for historic reasons and we maintain our own fork of it by now. But it is a very lightweight, easy to understand and highly customizable framework.

Good luck with the validation!

from jte.

maxwellt avatar maxwellt commented on August 16, 2024

I saw this issue and wanted to give some insight into how I've solved this.

I didn't want to add the BindingResult to the Model each time because it is repetitive and the view you're returning of the form would likely used templates for the form inputs which meant you'd have to pass the BindingResult along with each of those templates.

Spring has the concept of an HandlerInterceptor. Which exposes a preHandle and postHandle hook. The documentation for the postHandle reads:

Interception point after successful execution of a handler. Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view. Can expose additional model objects to the view via the given ModelAndView.

This seems to me exactly what I want, to automatically add the BindingResult BEFORE the view is rendered. So I setup a JteContextInterceptor which implements the HandlerInterceptor.

In the JteContextInterceptor I implement the postHandle method and there I "initialize" a JteContext class which holds a number of ThreadLocals, one of them being the one for the BindingResult. So my JteContext class looks like this (now only contains the "form" inner static class, but normally contains others as well):

public class JteContext {
    public static class ctx {
        public static class form {

            private static final ThreadLocal<BindingResult> bindingResultContext = new ThreadLocal<>();

            public static void init(BindingResult bindingResult) {
                bindingResultContext.set(bindingResult);
            }

            public static boolean hasFieldError(String fieldName) {
                return bindingResultContext.get() != null && bindingResultContext.get().hasFieldErrors(fieldName);
            }

            public static boolean hasGlobalError() {
                return bindingResultContext.get() != null && bindingResultContext.get().hasGlobalErrors();
            }

            public static String getFieldError(String fieldName) {
                if (bindingResultContext.get() == null) {
                    return "";
                }

                FieldError fieldError = bindingResultContext.get().getFieldError(fieldName);

                return Optional.ofNullable(fieldError)
                        .map(fe -> i18n.localizerContext.get()
                                .getMessageSource()
                                .getMessage(fe, LocaleContextHolder.getLocale())
                        )
                        .orElse("");
            }

            public static String getGlobalError() {
                BindingResult bindingResult = bindingResultContext.get();
                if (bindingResult == null) {
                    return "";
                }

                if (!bindingResult.hasGlobalErrors()) {
                    return "";
                }

                return bindingResult.getGlobalErrors().iterator().next().getDefaultMessage();
            }
        }
    }
}

The ThreadLocal is necessary to couple the right BindingResult to the in-flight request and can then be accessed when rendering the template. The code to init the JteContext in the JteContextInterceptor:

@RequiredArgsConstructor
public class JteContextInterceptor implements HandlerInterceptor {

    private final MessageSource messageSource;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        JteContext.ctx.i18n.init(new JteLocalizer(this.messageSource, LocaleContextHolder.getLocale()));
        JteContext.ctx.request.init(request);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        if (modelAndView == null) {
            return;
        }

        // in order to be able to access the form's BindingResult we need to intercept it here and initialize it in
        // the JteContext as a ThreadLocal.
        // the alternative would be to declare it as a param on the relevant .jte file, but seeing as that would be a form, it would then
        // have to pass the BindingResult as a param to each input on the form, this leads to a cascade of passing the BindingResult along
        // the approach used here to set it in a ThreadLocal which can then be accessed directly at the correct level seems easier and cleaner
        Map<String, Object> model = modelAndView.getModel();
        if (model.containsKey("org.springframework.validation.BindingResult.form")) {
            BindingResult bindingResult = (BindingResult) model.get("org.springframework.validation.BindingResult.form");
            JteContext.ctx.form.init(bindingResult);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        JteContext.dispose();
    }
}

I also init some other things as you can see in the preHandle method, but that's not important.

Having done this, I now simply do a static import of my JteContext in my templates and can then easily interact with the BindingResult without having to pass it around from controller to view and to its children templates, an example would look like this:

@import static be.bluemagma.web.infrastructure.jte.JteContext.*

<input  class="input ${ctx.form.hasFieldError("firstName") ? "is-danger" : ""}">

@if(ctx.form.hasFieldError("firstName"))
    <p class="help is-danger">
        ${ctx.form.getFieldError("firstName")}
    </p>
@endif

I've left out some bits of code here and there as I've taken this from a larger project so apologies if there are any inconsistencies.

Happy to hear your thouhts too @casid about the implementation approach.

from jte.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.