Logic & Events
The Logic extensions make it possible to intervene in the configurator's data flow and event pipeline. They can transform configuration data, adapt screenshots and react to lifecycle events.
Basic structure
Logic callbacks are registered in the logic object:
// In Plugin.tsx
export default {
// ...
logic: {
config: {
onUpdate: { fn: ..., description: "..." },
onSave: { fn: ..., description: "..." },
// weitere Config Callbacks
},
camera: {
onSetScreenshotCameras: { fn: ..., description: "..." },
// weitere Camera Callbacks
},
core: {
preprocessFullApp: { fn: ..., description: "..." },
// weitere Core Callbacks
}
}
}
Each callback requires:
- fn: The callback function
- description: Description (String or LocalizedString)
Config callbacks
Config callbacks intervene in the configuration lifecycle.
onUpdate
Is called on every configuration change (e.g. every time a characteristic value is changed).
config: {
onUpdate: {
fn: (config: K3Configuration) => {
// Custom Logik
console.log("Current config:", config);
// Konfiguration modifizieren
const modified = {
...config,
customField: "my-value"
};
return modified;
},
description: "Fügt Custom-Feld zu Konfiguration hinzu"
}
}
K3Configuration Interface:
interface K3Configuration {
id: number | string;
appId: number;
code: string; // Eindeutiger Konfig-Code
price: number | null; // Berechneter Preis
lang: string; // Sprachcode
json: {
variables: unknown[]; // Merkmalswerte
bom: unknown[]; // Stückliste
summary: unknown; // Zusammenfassung
};
files: Record<string, Record<string, string>>; // Screenshots
// Weitere Felder...
}
Application examples:
- Custom price calculations
- Adding additional metadata
- Validation and constraints
- Analytics tracking
onSave
Is called before saving a configuration (for orders, shopping cart, PDF, etc.).
config: {
onSave: {
fn: (config: K3Configuration | K3NewConfiguration) => {
// Vor dem Speichern modifizieren
const enhanced = {
...config,
savedAt: new Date().toISOString(),
userAgent: navigator.userAgent
};
return enhanced;
},
description: "Fügt Timestamp und UserAgent hinzu"
}
}
Application examples:
- Add additional metadata before saving
- Notify external systems
- Verify final prices
onSaveFiles
Allows you to customize the screenshots that are saved with the configuration.
interface K3UploadFile {
key: string; // Kamera-Name oder Custom Key
file: Blob; // Die Bild-Datei
fileName?: string; // Optional: Dateiname
}
config: {
onSaveFiles: {
fn: (files: K3UploadFile[]) => {
// Zusätzliche Screenshots hinzufügen
const extraFile: K3UploadFile = {
key: "custom-view",
file: new Blob([/* ... */]),
fileName: "custom-screenshot.png"
};
return [...files, extraFile];
},
description: "Fügt zusätzlichen Screenshot hinzu"
}
}
Application examples:
- Additional screenshots from custom views
- Watermark on screenshots
- Adjust screenshot quality or size
- Filter/remove screenshots
onSaveEvent
Is called after successfully saving with the event payload.
interface K3ConfigurationSavedEvent {
type: "K3ConfigurationSaved";
actionKey: string; // "cart", "pdf", "email", etc.
code: string; // Konfigurations-Code
customer?: {
email?: string;
firstName?: string;
lastName?: string;
};
}
config: {
onSaveEvent: {
fn: (payload: K3ConfigurationSavedEvent) => {
// Analytics Event
if (window.gtag) {
window.gtag("event", "configuration_saved", {
action: payload.actionKey,
code: payload.code
});
}
// Custom Fields
return {
...payload,
timestamp: Date.now()
};
},
description: "Sendet Analytics und fügt Timestamp hinzu"
}
}
Application examples:
- Analytics/Tracking
- Trigger external webhooks
- Customer journey tracking
Camera callbacks
Camera callbacks allow you to customize cameras and screenshots.
onSetScreenshotCameras
Adjusts the list of cameras used for automatic screenshots.
interface K3Camera {
id: string;
name: string;
type: "PerspectiveCamera" | "OrthographicCamera";
position: [number, number, number];
lookAt: [number, number, number];
rotation: [number, number, number];
resolution?: { width: number; height: number };
zoom?: number;
scope?: K3CameraScope;
// Weitere Felder...
}
camera: {
onSetScreenshotCameras: {
fn: (cameras: K3Camera[]) => {
// Nur Front-View für Screenshots
return cameras.filter(cam => cam.name === "Front");
},
description: "Verwendet nur Front-Kamera für Screenshots"
}
}
Application examples:
- Filter screenshot cameras
- Additional cameras for screenshots
- Change camera order
- Overwrite camera parameters
onSetCameraList
Adjusts the complete list of all cameras available in the viewer.
camera: {
onSetCameraList: {
fn: (cameras: K3Camera[]) => {
// Custom Kamera hinzufügen
const customCamera: K3Camera = {
id: "custom-1",
name: "Detail-Ansicht",
type: "PerspectiveCamera",
position: [2, 2, 2],
lookAt: [0, 0, 0],
rotation: [0, 0, 0]
};
return [...cameras, customCamera];
},
description: "Fügt zusätzliche Detail-Kamera hinzu"
}
}
Application examples:
- Add programmed camera positions
- Generate cameras from external data
- Filter cameras based on configuration
getScreenshotDimensions
Determines the pixel dimensions for screenshot rendering.
interface K3ScreenshotDimensions {
width: number;
height: number;
}
camera: {
getScreenshotDimensions: {
fn: (dim: K3ScreenshotDimensions) => {
// Höhere Auflösung für Screenshots
return {
width: dim.width * 2,
height: dim.height * 2
};
},
description: "Verdoppelt Screenshot-Auflösung"
}
}
Application examples:
- High-resolution screenshots for printing
- Adjust image size based on license
- Responsive screenshot sizes
Core callbacks
Core callbacks intervene in central app lifecycle events.
preprocessFullApp
Is called once before store initialization with all app data.
interface K3FullApp {
app: { id: number; /* ... */ };
client: { id: number; /* ... */ };
groups: Array<{ id: number; /* ... */ }>;
variables: Array<{ id: number; /* ... */ }>;
values: Array<{ id: number | string; /* ... */ }>;
articles: Array<{ id: number; /* ... */ }>;
prices: Array<{ id: number; /* ... */ }>;
materials: Array<{ id: number; /* ... */ }>;
models: Array<{ id: number; /* ... */ }>;
images: Array<{ id: number; /* ... */ }>;
rules: Array<{ id: number; /* ... */ }>;
ruleItems: Array<{ id: number; /* ... */ }>;
}
core: {
preprocessFullApp: {
fn: (app: K3FullApp) => {
// App-Daten vor dem Laden modifizieren
const modifiedApp = {
...app,
variables: app.variables.map(v => ({
...v,
customFlag: true
}))
};
return modifiedApp;
},
description: "Fügt Custom-Flag zu allen Variablen hinzu"
}
}
Application examples:
- Data migration
- A/B testing (switch features on/off)
- White labeling (replace branding)
- Complex data pre-processing
preprocessFullApp is very powerful and should be used with caution. Errors here can make the entire configurator unusable.
onOpenPdf
Is called up after PDF generation. You can take over the PDF opening.
interface K3SaveResult {
code?: string;
price?: {
okay: boolean;
priceSent?: number;
priceCalculated?: number;
};
error?: boolean;
message?: string;
shopLink?: string;
pdf?: string; // URL zum PDF
pdfName?: string; // Vorgeschlagener Dateiname
}
core: {
onOpenPdf: {
fn: (result: K3SaveResult) => {
if (result.pdf) {
// Custom PDF-Handling
console.log("PDF generated:", result.pdf);
// Eigenes Modal statt direktem Download
showCustomPdfModal(result.pdf);
// Analytics
if (window.gtag) {
window.gtag("event", "pdf_generated", {
code: result.code
});
}
}
},
description: "Custom PDF-Handling mit Analytics"
}
}
Application examples:
- Custom download modal
- Open PDF in own viewer
- Analytics tracking
- Automatic upload to external service
onExportAR
Enables user-defined AR export.
interface K3ARExportContext {
platform: "iOS" | "AndroidOS";
scene: K3Scene; // Three.js Scene Graph
}
interface K3Scene {
type: string;
children: readonly K3Scene[];
userData: Record<string, unknown>;
traverse(callback: (object: K3Scene) => void): void;
getObjectByName(name: string): K3Scene | undefined;
}
core: {
onExportAR: {
fn: async (ctx: K3ARExportContext) => {
console.log("Exporting for platform:", ctx.platform);
// Scene traversieren
ctx.scene.traverse((obj) => {
console.log("Object:", obj.type, obj.userData);
});
// Custom GLTF/USDZ Export
const blob = await customARExporter(ctx.scene, ctx.platform);
return blob;
},
description: "Custom AR-Export-Logik"
}
}
Application examples:
- Custom GLTF/USDZ export
- Scene optimizations for AR
- Platform-specific customizations
- External AR services
Complete example
__PROTECTED_0__
K3Configuration,
K3Camera,
K3FullApp,
K3UploadFile,
K3ConfigurationSavedEvent,
K3ScreenshotDimensions
} from "k3-plugin-api";
// Config Callbacks
const onUpdateCallback = (config: K3Configuration) => {
// Bei jeder Änderung tracken
if (window.analytics) {
window.analytics.track("config_updated", {
price: config.price,
variables: config.json.variables.length
});
}
return config;
};
const onSaveCallback = (config: K3Configuration) => {
// Zusätzliche Metadaten vor Speicherung
return {
...config,
metadata: {
savedAt: new Date().toISOString(),
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
}
};
};
const onSaveFilesCallback = (files: K3UploadFile[]) => {
// Nur hochauflösende Screenshots speichern
return files.filter(f =>
f.fileName?.includes("high-res")
);
};
const onSaveEventCallback = (payload: K3ConfigurationSavedEvent) => {
// Multi-Channel Analytics
if (window.gtag) {
window.gtag("event", "configuration_saved", {
action: payload.actionKey,
code: payload.code
});
}
if (window.fbq) {
window.fbq("track", "AddToCart");
}
return {
...payload,
tracked: true
};
};
// Camera Callbacks
const onSetScreenshotCamerasCallback = (cameras: K3Camera[]) => {
// Nur Kameras mit "Screenshot" im Namen
const filtered = cameras.filter(cam =>
cam.name.toLowerCase().includes("screenshot")
);
// Mindestens eine Kamera behalten
return filtered.length > 0 ? filtered : [cameras[0]];
};
const getScreenshotDimensionsCallback = (dim: K3ScreenshotDimensions) => {
// 4K Screenshots für Premium-User
const isPremium = checkPremiumStatus();
if (isPremium) {
return { width: 3840, height: 2160 };
}
return dim;
};
// Core Callbacks
const preprocessFullAppCallback = (app: K3FullApp) => {
// Feature-Flags basierend auf Kunde
const customerFeatures = getCustomerFeatures(app.client.id);
return {
...app,
app: {
...app.app,
features: customerFeatures
}
};
};
const onOpenPdfCallback = (result) => {
if (result.pdf && !result.error) {
// Custom PDF-Modal statt direktem Download
showPdfPreviewModal({
url: result.pdf,
filename: result.pdfName || "konfiguration.pdf",
onDownload: () => {
window.open(result.pdf, "_blank");
},
onEmail: () => {
openEmailDialog(result.pdf);
}
});
// Tracking
trackPdfGeneration(result.code);
}
};
// Im Plugin registrieren:
export default {
// ...
logic: {
config: {
onUpdate: {
fn: onUpdateCallback,
description: "Analytics-Tracking bei Updates"
},
onSave: {
fn: onSaveCallback,
description: "Fügt erweiterte Metadaten hinzu"
},
onSaveFiles: {
fn: onSaveFilesCallback,
description: "Filtert Low-Res Screenshots"
},
onSaveEvent: {
fn: onSaveEventCallback,
description: "Multi-Channel Analytics bei Save"
}
},
camera: {
onSetScreenshotCameras: {
fn: onSetScreenshotCamerasCallback,
description: "Filtert Screenshot-Kameras nach Namen"
},
getScreenshotDimensions: {
fn: getScreenshotDimensionsCallback,
description: "Höhere Auflösung für Premium-User"
}
},
core: {
preprocessFullApp: {
fn: preprocessFullAppCallback,
description: "Kundespezifische Feature-Flags"
},
onOpenPdf: {
fn: onOpenPdfCallback,
description: "Custom PDF-Vorschau-Modal"
}
}
}
};
Best Practices
1. consider immutability
Do not change objects directly, but create copies:
// ❌ Schlecht
fn: (config) => {
config.customField = "value";
return config;
}
// ✅ Gut
fn: (config) => {
return {
...config,
customField: "value"
};
}
2. error handling
Callbacks can break the configurator. Use try-catch:
fn: (config) => {
try {
// Ihre Logik
return enhanceConfig(config);
} catch (error) {
console.error("Plugin error:", error);
return config; // Original zurückgeben bei Fehler
}
}
3. performance
onUpdate is called very frequently. Avoid expensive operations:
// ❌ Schlecht: Bei jedem Update API-Call
onUpdate: {
fn: async (config) => {
await fetch("/api/track", { /* ... */ });
return config;
}
}
// ✅ Gut: Debounced oder in onSave
let timeout: NodeJS.Timeout;
onUpdate: {
fn: (config) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fetch("/api/track", { /* ... */ });
}, 1000);
return config;
}
}
4. type safety
Use TypeScript for type safety:
__PROTECTED_1__
K3Configuration,
K3Camera,
K3FullApp,
CallbackWithDescription
} from "k3-plugin-api";
const onUpdateCallback: CallbackWithDescription<
(config: K3Configuration) => K3Configuration
> = {
fn: (config) => config,
description: "My callback"
};
5. document side effects
If your callback has side effects, document them clearly:
onSave: {
fn: (config) => {
// Side Effect: Externes System benachrichtigen
notifyExternalSystem(config);
return config;
},
description: "Speichert Konfiguration und benachrichtigt CRM-System"
}
Debugging
Log extensively during development:
logic: {
config: {
onUpdate: {
fn: (config) => {
console.group("Config Update");
console.log("Config ID:", config.id);
console.log("Price:", config.price);
console.log("Variables:", config.json.variables);
console.groupEnd();
return config;
},
description: "Debug logging"
}
}
}
Async callbacks
Most callbacks should be synchronous. Only onExportAR is explicitly async:
// ✅ onExportAR: async erlaubt
onExportAR: {
fn: async (ctx) => {
const blob = await generateARFile(ctx);
return blob;
}
}
// ⚠️ Andere Callbacks: synchron halten
onUpdate: {
fn: (config) => {
// Async-Operationen in Promise starten, nicht awaiten
performAsyncOperation(config).catch(console.error);
return config;
}
}