Skill for programming WebJET CMS datatable backend - JPA entity, Spring DATA repository, REST controller, frontend HTML page, modinfo.properties and automated tests. Use when creating new admin CRUD applications, adding datatable entities, REST endpoints, or modifying existing datatable-based admin pages.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Skill for programming WebJET CMS datatable backend - JPA entity, Spring DATA repository, REST controller, frontend HTML page, modinfo.properties and automated tests. Use when creating new admin CRUD applications, adding datatable entities, REST endpoints, or modifying existing datatable-based admin pages.
WebJET CMS DataTable Backend Programming Skill
This skill provides complete instructions for creating a datatable-based admin application in WebJET CMS. The architecture uses JPA entities with @DataTableColumn annotations, Spring DATA repositories, DatatableRestControllerV2 REST controllers, and Thymeleaf/HTML frontends.
Architecture Overview
A complete datatable application consists of:
JPA Entity - annotated with @DataTableColumn for automatic UI generation
Spring DATA Repository - extends JpaRepository + JpaSpecificationExecutor
NEVER use primitive types (int, long, boolean) - always use wrapper objects (Integer, Long, Boolean). Primitive types break search/filtering because ExampleMatcher cannot use NULL for primitives.
Primary key must be Long id - if the DB column has a different name, use @Column(name = "actual_column_name").
Always use @EntityListeners for automatic audit logging with appropriate Adminlog.TYPE_* constant.
Use @Getter and @Setter from Lombok - do not write getters/setters manually.
Use Jakarta imports (jakarta.persistence.*, jakarta.validation.*), NOT javax versions.
DataTableColumnType Reference
Field types for @DataTableColumn(inputType = ...):
Type
Description
ID
Primary key column
OPEN_EDITOR
Text field that opens editor on click - use for main/name field
TEXT
Standard single-line text input
TEXTAREA
Multi-line text input
NUMBER
Numeric input
SELECT
Dropdown selection
MULTISELECT
Multi-value dropdown
BOOLEAN
Checkbox for true/false
CHECKBOX
Checkbox with custom values
DATE
Date picker
DATETIME
Date and time picker
TIME_HM
Time picker (hours:minutes)
HIDDEN
Hidden field (not shown in editor)
DISABLED
Read-only field
QUILL
Simple HTML editor
WYSIWYG
Full HTML editor
JSON
Directory/page tree selector
DATATABLE
Nested datatable
ELFINDER
File/link browser
UPLOAD
File upload
PASSWORD
Password field
RADIO
Radio button selection
COLOR
Color picker
ICON
Icon selector (Tabler Icons)
ROW_REORDER
Drag & drop row ordering
STATIC_TEXT
Static text display
@DataTableColumn Key Attributes
title - Translation key for column header. If omitted, auto-generated as components.entity_name.field_name
tab - Editor tab ID where this field appears
hidden - Hide column from table (user cannot show it)
visible - Hide column but user can show it via column selector
hiddenEditor - Don't show field in editor
filter - Set false to hide filter in table header
sortAfter - Field name after which to position this field
@DataTableColumn(inputType = DataTableColumnType.TEXT, title="my.field", editor = {
@DataTableColumnEditor(
type = "text",
tab = "basic",
attr = {
@DataTableColumnEditorAttr(key = "disabled", value = "disabled"),
@DataTableColumnEditorAttr(key = "data-dt-field-hr", value = "after"),
@DataTableColumnEditorAttr(key = "data-dt-field-headline", value = "translation.key.for.headline")
},
options = {
@DataTableColumnEditorAttr(key = "Label text", value = "option_value")
}
)
})
Validation Annotations
@NotBlank// Non-empty, no whitespace only@NotEmpty// Non-empty, allows whitespace@Size(min=5, max=255)// Length constraint@Email// Email validation@Pattern(regexp = "^.+@.+\\.")// Regex validation@DecimalMax(value="200")// Max numeric value@DecimalMin(value="100")// Min numeric value@Future// Date must be in the future@Past// Date must be in the past@NotNull// Must not be null
Nested/Editor Fields
For additional non-database fields that appear in the editor, use @Transient with @DataTableColumnNested:
And use DomainIdRepository instead of JpaRepository (see Repository section).
Upgrading Database Schema
When you create a new table or modify an existing table (add/alter columns), you must add a new UpdateDBBean XML element at the bottom of the file src/main/webapp/WEB-INF/sql/autoupdate-webjet9.xml (before the closing </object></java> tags). Each entry must contain SQL for all four supported databases: MySQL, MSSQL (Microsoft SQL Server), Oracle and PostgreSQL.
XML Structure
<voidmethod="add"><objectclass="sk.iway.iwcm.system.UpdateDBBean"><voidproperty="author"><string>your_name</string></void><voidproperty="date"><string>DD.MM.YYYY</string></void><voidproperty="desc"><string>Short description of the change in English</string></void><voidproperty="mysql"><string>SQL FOR MYSQL</string></void><voidproperty="mssql"><string>SQL FOR MSSQL</string></void><voidproperty="oracle"><string>SQL FOR ORACLE</string></void><voidproperty="pgsql"><string>SQL FOR POSTGRESQL</string></void></object></void>
Oracle (requires separate SEQUENCE + TRIGGER for auto-increment):
CREATE TABLE my_table (
id INTEGERNOT NULLPRIMARY KEY,
name NVARCHAR2(255) NOT NULL,
description CLOB,
domain_id INTEGERDEFAULT1NOT NULL
);
CREATE SEQUENCE S_my_table STARTWITH1;
CREATETRIGGER T_my_table BEFORE INSERTON my_table
FOREACHROWDECLARE
val INTEGER|BEGIN
IF :new.id ISNULLTHENSELECT S_my_table.nextval into val FROM dual|
:new.id:= val|END IF|END|
;
PostgreSQL (requires separate SEQUENCE):
CREATE SEQUENCE S_my_table STARTWITH1;
CREATE TABLE my_table (
id INTDEFAULT NEXTVAL('S_my_table') PRIMARY KEYNOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
domain_id INTDEFAULT1NOT NULL
);
Important Rules
Oracle trigger syntax: Use | (pipe) instead of ; (semicolon) as statement delimiter inside trigger bodies. The semicolon at the very end terminates the whole block.
MSSQL uses NVARCHAR/NTEXT for Unicode string support (not plain VARCHAR/TEXT).
Oracle uses NVARCHAR2/CLOB for Unicode strings.
Always include all four <void property="..."> elements (mysql, mssql, oracle, pgsql), even if the SQL is identical — use an empty <string></string> only if the change genuinely does not apply to that database.
Multiple SQL statements can be placed in a single <string> block, separated by semicolons.
Date format in <void property="date"> is DD.MM.YYYY.
When creating a new table, also consider inserting a row into pkey_generator if the WebJET primary key generator is used (not needed when JPA @TableGenerator handles ID generation).
Always use @Datatable annotation - ensures correct error response handling.
Always use @PreAuthorize - controls access permissions.
URL pattern: /admin/rest/apps/{appname}/ for custom apps, /admin/rest/components/{name} for components, /admin/rest/settings/{name} for settings.
Constructor must call super(repository) or super(repository, EntityClass.class). Passing the entity class enables fetchOnCreate (default values for new records from server).
Never override REST endpoint methods with @Override - always override the xxxItem methods instead.
Override Methods (Life Cycle Hooks)
All of these are optional. Override only what you need:
// Called BEFORE saving (insert or edit)publicvoidbeforeSave(T entity) { }
// Called AFTER saving entitypublicvoidafterSave(T entity, T saved) { }
// Called BEFORE deletingpublicbooleanbeforeDelete(T entity) { returntrue; }
// Called AFTER deletingpublicvoidafterDelete(T entity, long id) { }
// Called BEFORE duplicatingpublicvoidbeforeDuplicate(T entity) { }
// Called AFTER duplicatingpublicvoidafterDuplicate(T entity, Long originalId) { }
// Validation before save/deletepublicvoidvalidateEditor(HttpServletRequest request, DatatableRequest<Long, T> target,
Identity user, Errors errors, Long id, T entity) { }
// Permission check per itempublicbooleancheckItemPerms(T entity, Long id) { returntrue; }
// Process action button clickspublicbooleanprocessAction(T entity, String action) { returnfalse; }
// Set select box options (recommended way)publicvoidgetOptions(DatatablePageImpl<T> page) { }
// Convert entity before returning via RESTpublic T processFromEntity(T entity, ProcessItemAction action) { return entity; }
// Convert entity before saving via RESTpublic T processToEntity(T entity, ProcessItemAction action) { return entity; }
// Custom search conditions (JPA Specification)publicvoidaddSpecSearch(Map<String, String> params, List<Predicate> predicates,
Root<T> root, CriteriaBuilder builder) { }
// Custom sortingpublic Pageable addSpecSort(Map<String, String> params, Pageable pageable) { }
// Data manipulation without repositorypublic T insertItem(T entity) { }
public T editItem(T entity, long id) { }
publicbooleandeleteItem(T entity, long id) { }
public T getOneItem(long id) { }
public Page<T> getAllItems(Pageable pageable) { }
public Page<T> searchItem(Map<String, String> params, Pageable pageable, T search) { }
Place in src/main/webapp/apps/{appname}/modinfo.properties:
# Translation key for menu item name
leftMenuNameKey=components.contact.title
# Permission key
itemKey=cmp_contact
# Allow permission assignment to users
userItem=true
# Menu link URL
leftMenuLink=/apps/contact/admin/
# FontAwesome icon (https://tabler.io/icons/)
icon=address-book
# If true, disabled by default for all users
defaultDisabled=true
# If true, shown as customer app at the beginning
custom=true
# Optional submenu
#leftSubmenu1NameKey=components.contact.subpage.title
#leftSubmenu1Link=/apps/contact/admin/subpage/
Add translations to src/main/webapp/WEB-INF/classes/text-webjet9.properties in Slovak language, text_cz-webjet9.properties for Czech and text_en-webjet9.properties for English:
After adding, reload translations by visiting /admin/?userlngr=true or restart the server.
8. Common Patterns
Row Styling
In BaseEditorFields subclass:
addRowClass("is-disabled"); // Red text - inactive
addRowClass("is-not-public"); // Red text - not public
addRowClass("is-default-page"); // Bold - default page
Status Icons
addStatusIcon("ti ti-map-pin"); // Show pinned icon
addStatusIcon("ti ti-lock-filled"); // Show lock icon