Skip to content

Custom Template @Tpl

@Tpl is the single annotation Erupt uses to describe every kind of "custom template page". It is never placed on a class or field directly — it always appears as the value of another annotation's attribute, declaring where the template lives, which engine renders it, and how the rendered result is presented.

TIP

Import the erupt-tpl module first, otherwise no template engine is registered.

Four Usage Sites

Host annotationAttributePresentationVariables available in the template
@EdittplTypeAn iframe embedded directly in the formnone (only request / response / base)
@ViewtplThe table column becomes a link that opens a popuprow (current row data)
@RowOperationtplA row action button that opens a popuprows (array of selected rows)
@VistplViewA full-page embed inside a multi-view tabnone (only request / response / base)
java
// 1. Field level: template embedded in the form
@EruptField(edit = @Edit(title = "Custom Content", type = EditType.TPL,
        tplType = @Tpl(path = "/tpl/custom.ftl")))
private String tplField;

// 2. Column level: clicking the cell value opens the template
@EruptField(views = @View(title = "Detail", tpl = @Tpl(path = "/tpl/detail.ftl")))
private String detail;
java
// 3. Button level: clicking the button opens the template
// 4. View level: a full-page template inside a multi-view tab
@Entity
@Erupt(
    name = "Example",
    rowOperation = @RowOperation(
        code = "tpl", title = "Template Button", type = RowOperation.Type.TPL,
        tpl = @Tpl(path = "/tpl/operator.ftl", width = "800px")),
    vis = @Vis(
        code = "report", title = "Report", type = Vis.Type.TPL,
        tplView = @Tpl(path = "/tpl/report.ftl", height = "600px"))
)
public class TplDemo extends BaseModel {

}

WARNING

The attribute on @Edit is named tplType, not tpl; on @Vis it is tplView. Using the wrong name simply will not compile.

All Attributes

Source of truth: xyz.erupt.annotation.sub_erupt.Tpl.

AttributeTypeDefaultDescription
pathStringrequiredTemplate file path or route address, resolved from the classpath root; a trailing ?k=v query string can be used to append binding variables
enablebooleantrueWhether the template is enabled
paramsString[]{}Custom parameters passed to tplHandler
tplHandlerClass<? extends Tpl.TplHandler>Tpl.TplHandler.classTemplate data binding class implementing bindTplData(Map, String[])
engineTpl.EngineFreeMarkerTemplate engine, see the enum below
embedTypePageEmbedTypeIFRAMEHow the popup content is embedded: IFRAME / MICRO_FRONTEND
widthString""Popup width, unit required, e.g. 500px, 80%
heightString""Popup height, unit required
openWayOpenWayMODALHow the template opens: MODAL / DRAWER / ROUTER
drawerPlacementPlacementRIGHTDrawer direction: TOP / BOTTOM / LEFT / RIGHT

params, tplHandler and engine are marked @Transient — they take part in server-side rendering only and are never sent to the frontend.

What embedType = MICRO_FRONTEND requires

In micro-frontend mode the template must render a complete HTML document with a <head> — a fragment leaves the popup blank. The micro-frontend sandbox is same-origin with the admin and is not a security boundary, so use it only for content you control; sub-apps with SSR streaming hydration are not supported. See erupt-tpl micro-frontend integration.

Engine

ValueDescription
NativeRaw HTML, no template parsing; tplHandler is not supported in this mode
FreeMarkerDefault engine
ThymeleafThymeleaf
VelocityVelocity
BeetlBeetl
EnjoyJFinal Enjoy

Engine implementations are registered on demand at startup. If the corresponding jar is not on the classpath, the engine is unavailable and rendering fails at runtime with XXX jar not found.

OpenWay and Placement

OpenWayDescription
MODALDialog (default); width comes from width, falling back to the built-in modal-lg
DRAWERDrawer; direction comes from drawerPlacement, and both width and height default to 40%
ROUTERNo popup — navigates to a frontend route instead. path supports {xxx} placeholders, replaced at runtime with the same-named field value of the current row

Placement values: TOP / BOTTOM / LEFT / RIGHT.

Which Attributes Apply Where

Not every @Tpl attribute is consumed at all four sites:

Attribute@Edit.tplType@View.tpl@RowOperation.tpl@Vis.tplView
path / engine / tplHandler / paramsyesyesyesyes
enablenoyesnono
openWay / embedType / drawerPlacementnoyesyesno
widthnoyesyesno
heightnoyesyesyes

Why width/height do not apply at field level

@Edit.tplType is marked @Transient, so the whole @Tpl object is never serialized to the frontend. An EditType.TPL field renders as a height-adaptive inline iframe inside the form, pointing straight at the backend rendering endpoint /erupt-api/tpl/html-field/{erupt}/{field}. width, height, openWay, drawerPlacement and embedType therefore have nothing to act on — only the server-side attributes path, engine, tplHandler and params are meaningful.

@View.tpl is the opposite: it must reach the frontend to decide how the popup opens when the cell is clicked, which is why enable is only really consumed here — setting it to false stops the column from becoming a clickable link.

Binding Data with TplHandler

Beyond the variables the framework injects automatically, tplHandler can push arbitrary data into the template. The implementation must be a Spring bean:

java
@Component
public class ReportTplHandler implements Tpl.TplHandler {

    @Resource
    private EruptDao eruptDao;

    @Override
    public void bindTplData(Map<String, Object> binding, String[] params) {
        // params holds whatever was configured via @Tpl(params = {...})
        binding.put("title", params.length > 0 ? params[0] : "Report");
        binding.put("total", eruptDao.lambdaQuery(EruptUser.class).count());
    }

}
java
@RowOperation(
    code = "report", title = "View Report", type = RowOperation.Type.TPL,
    tpl = @Tpl(
        path = "/tpl/report.ftl",
        engine = Tpl.Engine.FreeMarker,
        tplHandler = ReportTplHandler.class,
        params = {"Monthly Report"}
    )
)

Pre-injected Template Variables

Regardless of the engine, the framework injects the following variables into the template context:

VariableDescription
requestThe HttpServletRequest object
responseThe HttpServletResponse object
baseThe application context path, useful for building static resource URLs
rowCurrent row data — @View.tpl only
rowsArray of selected rows — @RowOperation.tpl only; not injected when engine = Native or the button's mode = BUTTON

In addition, key/value pairs after ? in path are parsed and placed directly into the template context. For example @Tpl(path = "/tpl/report.ftl?type=month") makes ${type} available in the template.

Contributors

The avatar of contributor named as YuePeng YuePeng
The avatar of contributor named as Claude Opus 5 (1M context) Claude Opus 5 (1M context)

Changelog

Released under the Apache-2.0 License.