Custom Buttons @RowOperation
The @RowOperation annotation adds custom action buttons to the operation column of an Erupt table, enabling complex business logic to be triggered from the UI.

Usage
@Erupt(
name = "Erupt",
rowOperation = {
@RowOperation(
title = "Single Row Action",
mode = RowOperation.Mode.SINGLE,
operationHandler = OperationHandlerImpl.class),
@RowOperation(
title = "Multi-Row Action",
operationHandler = OperationHandlerImpl.class),
@RowOperation(
title = "Standalone Button",
operationHandler = OperationHandlerImpl.class,
mode = RowOperation.Mode.BUTTON,
tip = "Executes without depending on any row data"),
},
)
public class EruptTest extends BaseModel {
}Button handler implementation:
/**
* Generic type parameters:
* <EruptTest> — the type of the target data
* <Void> — can be replaced with another Erupt class to render a form as input;
* Void is used here as a placeholder since no form input is needed
**/
public class OperationHandlerImpl implements OperationHandler<EruptTest, Void> {
// The return value is executed by the browser as JavaScript
@Override
public String exec(List<EruptTest> data, Void vo, String[] param) {
// TODO your logic
// return "alert(23333)"
return null;
// Display the result in a code editor modal (arg 1: language, arg 2: code)
// return "codeModal('sql',`select * from xxx`)"
// Trigger a download
// return "window.open('https://xxxxx')"
// Show an info message
// return "window.msg.info('This is a normal message')"
}
}Annotation Definition
public @interface RowOperation {
String code() default ""; // unique identifier for this button
String title(); // display name
String tip() default ""; // additional tooltip text
// whether to collapse single-row buttons (useful when there are many buttons, supported in 1.12.17+)
boolean fold() default false;
// confirmation prompt shown before execution; empty means no prompt (supported in 1.12.11+)
String callHint() default "erupt.operation.call_hint";
// dynamically control button visibility (supported in 1.5.4+)
ExprBool show() default @ExprBool;
// button icon; refer to Font Awesome
String icon() default "fa fa-dot-circle-o";
Mode mode() default Mode.MULTI; // button trigger mode
Type type() default Type.ERUPT; // button type
// associate an Erupt class to render a form when the button is clicked
Class<?> eruptClass() default void.class;
// parameters passed to operationHandler
String[] operationParam() default {};
// backend handler executed when the button is clicked
Class<? extends OperationHandler> operationHandler() default OperationHandler.class;
// requires the erupt-tpl module
Tpl tpl() default @Tpl(path = "");
// controls whether the button is enabled or disabled (JS expression)
// available variable: item — e.g. item.status == 1
String ifExpr() default "";
// controls whether ifExpr affects button visibility or clickability
IfExprBehavior ifExprBehavior() default IfExprBehavior.DISABLE;
enum Mode {
SINGLE, // requires exactly one selected row
MULTI, // can operate on multiple rows at once
MULTI_ONLY, // requires multiple rows selected (supported in 1.12.16+)
BUTTON // standalone button, no row selection required
}
enum Type {
ERUPT, // used with operationHandler and eruptClass
TPL // used with tpl
}
enum IfExprBehavior {
HIDE, // ifExpr controls button visibility (show/hide)
DISABLE // ifExpr controls button clickability (enabled/disabled)
}
}Form Dialog (Form)
@Erupt(
name = "Erupt",
rowOperation = @RowOperation(
title = "Form Button",
mode = RowOperation.Mode.BUTTON,
eruptClass = DialogForm.class, // form definition shown when the button is clicked
operationHandler = DialogFormHandler.class // button handler class
),
)
public class EruptTest extends BaseModel {
}@Erupt(name = "Form Dialog", authVerify = false)
@Getter
@Setter
public class SimpleDialog extends BaseModel {
@EruptField(
edit = @Edit(title = "Text", notNull = true)
)
private String text;
@EruptField(
edit = @Edit(title = "Time", notNull = true)
)
private Date date;
@EruptField(
edit = @Edit(title = "Number", notNull = true)
)
private Long number;
}Note: It is recommended to add
authVerify = falseto dialog form classes to avoid permission issues with file uploads and similar operations.
Pre-populating Form Values (1.12.13+)
public class DialogFormHandler implements OperationHandler<EruptTest, SimpleDialog> {
@Override
public String exec(List<Complex> data, SimpleDialog simpleDialog, String[] param) {
return "";
}
// Pre-populate form values when the dialog opens (override as needed, supported in 1.12.13+)
@Override
public SimpleDialog eruptFormValue(List<EruptTest> data, SimpleDialog simpleDialog, String[] param) {
simpleDialog.setText(data.get(0).getColor());
return simpleDialog;
}
}TPL Template Dialog
Set type to RowOperation.Type.TPL to open a custom template page in a dialog when the button is clicked. This is ideal for complex display or interaction scenarios.
TIP
Make sure the erupt-tpl module is imported before using this feature.
@Erupt(
name = "Button Opens Template",
rowOperation = @RowOperation(
code = "tpl", title = "Template Button", type = RowOperation.Type.TPL,
tpl = @Tpl(
path = "/tpl/operator.ftl", // template file path
tplHandler = TestErupt.class, // data binding handler (optional)
engine = Tpl.Engine.FreeMarker // default value
)
)
)
@Entity
@Getter
public class TestErupt extends BaseModel implements Tpl.TplHandler {
@EruptField(
views = @View(title = "Name"),
edit = @Edit(title = "Name")
)
private String name;
@EruptField(
views = @View(title = "Number"),
edit = @Edit(title = "Number")
)
private Integer number;
@Override
public void bindTplData(Map<String, Object> binding, String[] params) {
binding.put("title", "Selected Data");
}
}Template file example (resources/tpl/operator.ftl):
<div>
<#-- title is bound via bindTplData -->
<h1 align="center">${title}</h1>
<table border="1" cellpadding="0" style="width: 100%">
<#list rows as row>
<tr>
<td>${row.id}</td>
<td style="background: #09f;color: #fff">${row.name}</td>
<td>${row.number}</td>
</tr>
</#list>
</table>
</div>Pre-injected Template Variables
| Variable | Description |
|---|---|
request | HttpServletRequest object |
response | HttpServletResponse object (1.6.12+) |
rows | Selected row data (array); not available when engine is native |
Closing the Dialog
The template page is embedded in an iframe and the admin UI does not listen for message events, so close it by operating on the parent page's DOM directly:
// Modal (MODAL): click the close button in the top-right corner
window.parent.document.querySelector('.ant-modal-close').click();
// Drawer (DRAWER): no close button is rendered, click the mask instead
window.parent.document.querySelector('.ant-drawer-mask').click();Closing the Dialog and Refreshing Data
Closing the dialog does not refresh the list automatically — trigger the list's query button (its id is erupt-btn-query) once more to reload the data:
window.parent.document.querySelector('.ant-modal-close').click();
window.parent.document.querySelector('#erupt-btn-query').click();WARNING
Both snippets rely on the same-origin policy — the template page must share the admin UI's origin (which is the case by default when the template is served by the erupt backend). The query button is only rendered when query permission is enabled, so check that the element exists before calling click().
Dialog Width and Height
Width and height are configured on the nested @Tpl (@RowOperation itself has no width/height attribute):
- Width:
@Tpl(width = "..."), unit required, e.g.500px,80%; falls back to the built-inmodal-lgwidth when omitted - Height:
@Tpl(height = "..."), unit required; auto-adapts to the custom page height when omitted
@RowOperation(
code = "tpl", title = "Template Button", type = RowOperation.Type.TPL,
tpl = @Tpl(path = "/tpl/operator.ftl", width = "800px", height = "600px")
)To open it as a drawer instead, configure @Tpl(openWay = OpenWay.DRAWER, drawerPlacement = Placement.RIGHT).
Button Permissions
@Erupt(
name = "Menu-Controlled Button Permissions",
rowOperation = {
@RowOperation(
code = "btn",
title = "Menu-Controlled Button Permissions",
operationHandler = OperationHandlerImpl.class,
show = @ExprBool(
exprHandler = ViaMenuValueCtrl.class, // controls visibility based on menu type value
params = "testBtn" // permission identifier; add a menu with type "Button" and type value "testBtn" to control this button
)
)
}
)
public class TestErupt extends BaseModel {
}Add a menu entry, fill the params value into the menu type value field, and set the menu type to "Button" to control the show/hide of that button.