Package org.jgrapes.webconlet.examples.helloalpine


package org.jgrapes.webconlet.examples.helloalpine
Pattern for AlpineJS-based conlets.

This conlet serves as a template for writing conlets that use AlpineJS for reactive UI updates driven by a centralised data store. The pattern separates server-side state management (Java model) from client-side reactive rendering (AlpineJS directives) while keeping both sides in sync.

Architecture

The pattern consists of three layers that communicate through well-defined channels:

  1. Server model -- Java POJO holding the conlet's state, persisted via the key-value store.
  2. Data store -- TypeScript class extending Map that holds Alpine.reactive() wrapped state objects, keyed by conlet ID. Lives as a global object on window.
  3. Preview/View templates -- FreeMarker templates that use Alpine directives (x-data, x-init, x-show, @click) to bind the UI to the data store.

Browser-Side Structure

The TypeScript file (*-functions.ts) defines four elements:

1. State Interface

An interface describing the shape of the client-side state per conlet instance. The corresponding server model properties should mirror this structure.

 interface ConletState {
     worldVisible: boolean;
 }
 

2. Data Store Class

A class extending Map<string, ConletState> that:

  • Holds reactive state objects (Alpine.reactive(...)) per conlet instance, keyed by conlet ID.
  • Declares onLoad and onUnload as function-typed properties (using definite assignment assertion !:) for use by the framework's data-jgwc-on-load and data-jgwc-on-unload hook attributes. These must be plain functions -- not class methods -- because the framework invokes them without this binding.
  • Provides implementation methods (addConletState, removeConletState) that the hook functions delegate to.

3. Global Registration

An instance of the data store is assigned to a window property (declared via declare global). The onLoad and onUnload properties are assigned as standalone functions that close over the store instance and delegate to its methods:

 const conletStates = new ConletDataStore();
 window.orgJGrapesConletsExampleMyConlet = conletStates;

conletStates.onLoad = function(conletEl, _isUpdate) { conletStates.addConletState(conletEl); };

conletStates.onUnload = function(conletEl, isUpdate) { conletStates.removeConletState(conletEl, isUpdate); };

4. Server Notification Handler

A function registered via JGConsole.registerConletFunction() that receives state updates from the server (pushed via NotifyConletView). It modifies the reactive state in the data store, triggering Alpine's automatic DOM updates:

 JGConsole.registerConletFunction(
     "org.jgrapes.webconlet.examples.myconlet.MyConlet",
     "updateState",
     function(conletId, newState) {
         const state = conletStates.get(conletId);
         if (state) {
             state.someProperty = newState.someProperty;
         }
     });
 

View Template

The view template (*-view.ftl.html) is the reactive UI. It uses three Alpine-related attributes on the root element:

data-jgwc-on-load="globalObj.onLoad"
Initialises the conlet's state in the data store when the view is first rendered. The framework calls this before Alpine processes x-init.
x-data="{ conletId: null, state: null }"
Declares local Alpine variables. The state reference will point to the reactive object from the global data store.
x-init="conletId = ...; state = globalObj.get(conletId);"
Runs after onLoad, obtains the conlet ID from the nearest [data-conlet-id] ancestor, and stores a reference to the global reactive state. Alpine directives in child elements then react to changes in state.

UI elements use Alpine directives against state:

  • x-show="state.visible" -- reactive visibility toggle
  • x-bind:class="..." -- reactive class binding
  • @click="state.value = !state.value; JGConsole.notifyConletModel(conletId, 'toggleValue')" -- local reactive change + server notification

Preview Template

The preview (*-preview.ftl.html) is static content shown in the conlet picker dialog. It carries data-jgwc-on-unload="globalObj.onUnload" so that when the conlet is removed from the page (not just layout-refreshed), the data store cleans up its state entry and optionally shows a notification. The isUpdate parameter distinguishes a layout refresh (true) from actual removal (false).

Server-Side Communication Flow

State changes flow in two directions:

Client to Server (user action)

  1. User clicks a button with an @click handler.
  2. The handler mutates the local reactive state (immediate UI update) and calls JGConsole.notifyConletModel(conletId, 'methodName').
  3. The server's doUpdateConletState method handles the event, updates the Java model, persists it, and responds with NotifyConletView to push the new state back.
  4. The registered conlet function receives the update and mutates the reactive state (Alpine re-renders affected bindings).

Server to Client (initial render + push)

  1. On initial view render, the server's doRenderConlet sends a NotifyConletView event with the current model state after sending the rendered HTML.
  2. The registered conlet function populates the data store's reactive state with the server's values.
  3. Alpine's bindings reflect the server-provided initial state.

Build Configuration

The jdbld project config (_jdbld/src/jdbld/conlet/HelloAlpine.java) declares a dependency on AlpineJs (provider) and sets up a TypeScript build via NpmExecutor. The TypeScript file is compiled to ESM and output to build/generated/resources/, packaged into the JAR as a conlet resource.