Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/webjetcms/webjetcms/llms.txt

Use this file to discover all available pages before exploring further.

WebJET CMS uses the standard Spring application-event mechanism to decouple components. Any bean can publish an event; any @Component can listen for it. Both synchronous and asynchronous delivery are supported. A detailed introduction to Spring events is available at Baeldung: Spring Events.

The WebjetEvent carrier

Most WebJET events are transported by the generic class WebjetEvent<T>. It holds three fields:
FieldTypeDescription
sourceTThe domain object (e.g. DocDetails, GroupDetails)
eventTypeWebjetEventTypeThe lifecycle stage of the event
clazzStringFully-qualified class name of the source — used for filtering in @EventListener conditions

Event types

The WebjetEventType enum defines the standard lifecycle stages:
ValueWhen it fires
ON_STARTAt the beginning of the operation — source object can still be modified
AFTER_SAVEAfter the object has been persisted
ON_DELETEImmediately before deletion
AFTER_DELETEAfter deletion completes
ON_XHR_FILE_UPLOADAfter a file is uploaded via /XhrFileUpload
ON_ENDAt the end of an operation that does not save (i.e. when AFTER_SAVE is not fired)

Standard WebJET events

WebJET publishes WebjetEvent for all core CMS operations:
Published by EditorFacade.save before and after persisting a page.
  • Payload type: DocDetails
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.doc.DocDetails'
To distinguish a full publish from saving a draft, check doc.getEditorFields().isRequestPublish() — it returns false for working-version saves.
Published by DeleteServlet.deleteDoc before and after deletion.
  • Payload type: DocDetails
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.doc.DocDetails'
Published by GroupsDB.setGroup and GroupsDB.deleteGroup.
  • Payload type: GroupDetails
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.doc.GroupDetails'
Two events are published when a page is rendered.
  • ON_START — after DocDetails is retrieved; doc is not yet set, only docId. Set forceShowDoc on the DocDetails object here to override the displayed page.
  • ON_END — before routing to the JSP template; doc contains the fully populated DocDetails.
  • Payload type: ShowDocBean
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.doc.ShowDocBean'
Published when a page is published on a timer.
  • Payload type: DocumentPublishEvent (contains DocDetails and oldVirtualPath)
  • Event type: ON_PUBLISH
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.system.spring.events.DocumentPublishEvent'
Published by ConfDB.setName after a configuration variable is saved through the UI.
  • Payload type: ConfDetails
  • Condition filter: #event.clazz eq 'sk.iway.iwcm.system.ConfDetails'
Published after a file upload to /XhrFileUpload.
  • Payload type: java.io.File
  • Event type: ON_XHR_FILE_UPLOAD
  • Condition filter: #event.clazz eq 'java.io.File'

DataTable events

WebJET automatically publishes DatatableEvent<T> for every CRUD operation performed through DatatableRestControllerV2. These events are available for all datatable-backed entities without any extra configuration.

DatatableEvent fields

FieldDescription
sourceThe entity being operated on
eventTypeDatatableEventType lifecycle stage
clazzFully-qualified entity class name
originalEntityThe entity as it was before saving (available on AFTER_SAVE)
entityIdID of the deleted record (available on AFTER_DELETE)
originalIdID of the record that was duplicated (available on AFTER_DUPLICATE)

DataTable event types

ValueWhen it fires
BEFORE_SAVEAfter beforeSave() is called, before the entity is written
AFTER_SAVEAfter afterSave() is called, entity is already persisted
BEFORE_DELETEAfter beforeDelete() returns true, before deletion
AFTER_DELETEAfter afterDelete() is called
BEFORE_DUPLICATEAfter beforeDuplicate(), before the copy is written
AFTER_DUPLICATEAfter afterDuplicate(), copy is already persisted
Events are published after the corresponding hook method (beforeSave, afterSave, etc.) is called — even if you override those methods in a subclass of DatatableRestControllerV2. You cannot suppress event publishing by overriding the hook.
Events are also published during bulk operations: imports and bulk column updates (editItemByColumn). When importing large datasets this can result in many events firing in quick succession — consider using @Async listeners for expensive operations in those scenarios.

AFTER_SAVE: saved vs. original entity

In an AFTER_SAVE listener:
  • event.getSource() — the saved entity with its database-assigned ID
  • event.getOriginalEntity() — the entity as received by the REST call (ID is 0 or null for new records)
Use original.getUserId() == null || original.getUserId() < 1 to detect whether a record was created or updated.

Listening for events

Implement a @Component with a method annotated @EventListener. Use the condition attribute to filter by clazz and optionally by eventType.
Generic type inference alone is not sufficient for filtering — always include an explicit condition matching on #event.clazz to avoid handling events for the wrong entity type.

Synchronous listener

Synchronous listeners run in the same thread as the publisher. You can modify the source object before it is saved.
package sk.iway.custom;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.doc.DocDetails;
import sk.iway.iwcm.system.spring.events.WebjetEvent;
import sk.iway.iwcm.system.spring.events.WebjetEventType;

@Component
public class SaveListener {

    // Listen for all events on GroupDetails
    @EventListener(condition = "#event.clazz eq 'sk.iway.iwcm.doc.GroupDetails'")
    public void handleGroupSave(final WebjetEvent<GroupDetails> event) {
        // event.getSource() is the GroupDetails object
    }

    // Filter by both entity class and event type
    @EventListener(condition = "#event.eventType.name() == 'AFTER_SAVE' && #event.clazz eq 'sk.iway.iwcm.doc.DocDetails'")
    public void handleAfterSaveDoc(final WebjetEvent<DocDetails> event) {
        // only fires after a DocDetails is persisted
    }
}

Asynchronous listener

Add @Async to run the listener in a separate thread. The publisher does not wait for it to complete. Async support is enabled globally by @EnableAsync in BaseSpringConfig.
package sk.iway.custom;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import sk.iway.iwcm.Logger;
import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.system.spring.events.WebjetEvent;

@Component
public class SaveListenerAsync {

    @EventListener(condition = "#event.clazz eq 'sk.iway.iwcm.doc.GroupDetails'")
    @Async
    public void handleGroupSaveAsync(final WebjetEvent<GroupDetails> event) {
        Logger.debug(SaveListenerAsync.class,
            "Group save async, type=" + event.getEventType()
            + " thread=" + Thread.currentThread().getName());
    }
}

DataTable listener examples

package sk.iway.custom;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import sk.iway.iwcm.Logger;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.components.users.userdetail.UserDetailsEntity;
import sk.iway.iwcm.system.datatable.events.DatatableEvent;
import sk.iway.iwcm.system.datatable.events.DatatableEventType;

@Component
public class UserDatatableListener {

    @EventListener(condition = "#event.clazz eq 'sk.iway.iwcm.components.users.userdetail.UserDetailsEntity'")
    public void handleUserSave(final DatatableEvent<UserDetailsEntity> event) {
        if (event.getEventType() == DatatableEventType.BEFORE_SAVE) {
            UserDetailsEntity user = event.getSource();

            // Modify entity data before it is written to the database
            if (Tools.isEmpty(user.getFieldA())) {
                user.setFieldA(user.getEditorFields().getLogin() + "@example.com");
            }
        }

        if (event.getEventType() == DatatableEventType.AFTER_SAVE) {
            UserDetailsEntity saved = event.getSource();
            UserDetailsEntity original = event.getOriginalEntity();
            Logger.debug(UserDatatableListener.class,
                "User saved ID=" + saved.getId() + " originalId=" + original.getId());
        }
    }
}

Publishing an event

Use WebjetEvent.publishEvent() — a convenience method that delegates to WebjetEventPublisher. By convention, publish ON_START at the beginning of the operation and AFTER_SAVE after the data is persisted.
public boolean setGroup(GroupDetails group) {
    (new WebjetEvent<GroupDetails>(group, WebjetEventType.ON_START)).publishEvent();

    // ... persist the group ...

    (new WebjetEvent<GroupDetails>(newGroup, WebjetEventType.AFTER_SAVE)).publishEvent();
    return true;
}

Updating codes in text

If you need to add custom placeholder codes to page text (for example !CUSTOM_CODE!), use the UpdateCodesEvent. WebJET publishes this event before (ON_START) and after (ON_END) its own code-substitution pass in DocTools.updateCodes. In your listener, retrieve the StringBuilder from the event, replace your placeholders, and set the modified text back.
package sk.iway.custom;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import sk.iway.iwcm.Tools;
import sk.iway.iwcm.common.UpdateCodesEvent;
import sk.iway.iwcm.system.spring.events.WebjetEvent;
import sk.iway.iwcm.system.spring.events.WebjetEventType;

@Component
public class CustomCodesListener {

    @EventListener(condition = "#event.clazz eq 'sk.iway.iwcm.common.UpdateCodesEvent'")
    public void handleUpdateCodes(final WebjetEvent<UpdateCodesEvent> event) {
        if (event.getEventType() != WebjetEventType.ON_START) {
            return; // only process before WebJET's own substitution pass
        }

        UpdateCodesEvent updateCodesEvent = event.getSource();
        StringBuilder text = updateCodesEvent.getText();

        text = Tools.replace(text, "!CUSTOM_CODE!", "My Company VAT ID");
        text = Tools.replace(text, "!COMPANY_NAME!", "My Company Ltd.");

        updateCodesEvent.setText(text);
    }
}
The UpdateCodesEvent exposes the following fields:
FieldDescription
textThe page HTML text (modifiable via setText)
userCurrently logged-in user
currentDocIdID of the page being rendered
requestThe current HTTP request
Use ON_START to replace codes before WebJET processes its own codes, or ON_END to replace codes after — for example, if your replacement text might itself contain standard WebJET codes that should be expanded.

WebjetEvent vs. DatatableEvent

WebjetEventDatatableEvent
Use forSpecific business operations (page save/delete, config change, file upload)All CRUD operations on any datatable entity
Published byCore WebJET services (EditorFacade, GroupsDB, etc.)DatatableRestControllerV2 automatically
Modify data?Yes, in ON_STARTYes, in BEFORE_SAVE
Bulk operationsNot applicableYes — fires once per record