Как завернуть шаблон в шаблон Spring boot FreeMaker?

196
24 января 2018, 15:29

У меня есть некий контроллер вида:

package controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/login")
public class Login {
    @RequestMapping(value = "auth")
    public String auth() {
        return "login/auth";
    }
}

Где login/auth это путь до файла auth.ftl. В качестве шаблонизатора использую FreeMaker.

Что я хочу: у меня есть некий шаблон content.ftl с содержанием:

<!DOCTYPE html>
<head>...</head>
<body>${content}</body>

В который должны передаваться данные из метода auth() и уже после отображаться.

В целом я понимаю логику: наследуемся от какого-то класса шаблонизатора, сначала рендерим auth.ftl, потом кидаем данные в content.ftl, опять рендерним и отдаем данные уже в браузер. Но где взять пример? WebMvcConfigurerAdapter?

Answer 1

Вариант 1: вклиниваемся в рендеринг

Если мы заставим Spring резолвить имя view в подконтрольный нам объект, то сможем рендерить его как захотим. Чтобы это сделать, подсунем Spring'у view resolver, который будет выдавать наши view:

// Наш view resolver, проксирующий вызовы к настоящему
@Bean
public ViewResolver viewResolver(FreeMarkerViewResolver freeMarkerViewResolver) {
    FreeMarkerWrappedViewResolver viewResolver =
        new FreeMarkerWrappedViewResolver(freeMarkerViewResolver);
    // Так как в контексте будет и наш, и настоящий view resolver,
    // нужно заставить Spring применять наш раньше настоящего.
    // Этого можно добиться, установив order в значение поменьше.
    viewResolver.setOrder(0);
    return viewResolver;
}
// Настоящий view resolver. Его нужно создавать в контексте Spring'а, иначе он не работает
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
    FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver();
    freeMarkerViewResolver.setPrefix("");
    freeMarkerViewResolver.setSuffix(".ftl");
    return freeMarkerViewResolver;
}

Кастомный view resolver будет возвращать кастомные view. Используя настоящий FreeMarkerViewResolver, мы резолвим базовый view и view, который хотим в него вставить:

class FreeMarkerWrappedViewResolver extends FreeMarkerViewResolver {
    private FreeMarkerViewResolver freeMarkerViewResolver;
    public FreeMarkerWrappedViewResolver(FreeMarkerViewResolver freeMarkerViewResolver) {
        this.freeMarkerViewResolver = freeMarkerViewResolver;
    }
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        View content = freeMarkerViewResolver.resolveViewName(viewName, locale);
        View base = freeMarkerViewResolver.resolveViewName("base", locale);
        return new WrappedView(content, base);
    }
}

В кастомном view мы сначала рендерим контент в MockHttpServletResponse, потом этот контент заворачиваем в модель, которую скармливаем базовому view и рендерим в настоящий HttpServletResponse:

class WrappedView implements View {
    private View nestedView;
    private View wrappingView;
    public WrappedView(View nestedView, View wrappingView) {
        this.nestedView = nestedView;
        this.wrappingView = wrappingView;
    }
    @Override
    public String getContentType() {
        return nestedView.getContentType();
    }
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        MockHttpServletResponse mockResponse = new MockHttpServletResponse();
        nestedView.render(model, request, mockResponse);
        HashMap<String, Object> bodyModel = new HashMap<>();
        bodyModel.put("content", mockResponse.getContentAsString());
        wrappingView.render(bodyModel, request, response);
    }
}

Готово! Теперь все view будут заворачиваться в наш темплейт.

Вариант 2: используем встроенные механизмы FreeMarker

В FreeMarker есть механизм наследования темплейтов. В base.ftl задаём структуру страницы с плейсхолдерами:

<#macro content>
    <!-- Этот контент нам предоставит наследник -->
</#macro>
<#macro page>
    <html>
        <body>
            <p>Content is</p>
            <p><@content/></p>
        </body>
    </html>
</#macro>

А в наследнике импортируем базовый темплейт и предоставляем значения плейсхолдеров:

<#include "base.ftl"/>
<#macro content>
    <b>${message}</b>
</#macro>
<@page/> <!-- Не забываем включить непосредственно контент родителя -->

Второй вариант предоставляет больше гибкости: плейсхолдеров может быть больше одного (например, каждый наследник может независимо переопределить хедер, контент и футер). В первом варианте добиться такого будет проблематично. С другой стороны, первый вариант позволяет избежать постоянного копипаста импорта родительского темплейта. Что лучше подходит под ваши задачи - решать вам.

READ ALSO
Многопоточный бинарный поиск MPI - C++ / Java Threads [требует правки]

Многопоточный бинарный поиск MPI - C++ / Java Threads [требует правки]

здравствуйте, может кто поделиться кодом многопоточного бинарного поиска числа в массиве, при помощи C++ MPI и/или Java ThreadsСпасибо

166
Что означает класс реализует интерфейс?

Что означает класс реализует интерфейс?

Встретил это в книге по Java Яков Файн "Программирование на Java

178
Внутренние отступы в окне Java FX

Внутренние отступы в окне Java FX

Создал окноДобавил элементов

164
Java Jsoup, как оставить &amp;quot как &amp;quot

Java Jsoup, как оставить &quot как &quot

Мне нужно скачать html-документ, используя Jsoup, но при этом так, чтобы символы, которые написаны кодом остались как код (например, &quot;)

145