Files
markitect-main/testdrive-jsui/static/js/widgets/base/Widget.js
tegwick 8ef356af57 feat: implement plugin infrastructure for rendering engines
Added comprehensive plugin system for independent JavaScript UI development:

**Plugin Infrastructure:**
- Extended existing MarkiTect plugin system with RenderingEnginePlugin base class
- Added RENDERING plugin type to PluginType enum
- Created RenderingConfig for asset management and deployment
- Implemented RenderingEngineManager for plugin discovery and lifecycle

**TestDrive JSUI Plugin:**
- Extracted JavaScript UI components to independent testdrive-jsui plugin
- Created standalone development environment (no Python required)
- Implemented compass-positioned control panels (NW, NE, E, SE)
- Added clean JSON configuration interface for Python↔JavaScript data transfer

**Asset Management:**
- Development mode: serve assets directly from plugin source directory
- Production mode: deploy to _markitect/plugins/[plugin-name]/ structure
- Configurable asset URLs and deployment strategies
- Support for external dependencies (CDN resources)

**Standalone Development:**
- testdrive-jsui/test.html for browser-based development
- Package.json with npm scripts for development server
- Complete separation of JavaScript development from Python environment
- Hot reload and standard web development workflow

**Integration Demo:**
- demo_plugin_integration.py showcasing all plugin capabilities
- Standalone, plugin discovery, production deployment examples
- Asset URL generation for different deployment modes

This enables JavaScript-first development while maintaining clean integration
with the MarkiTect Python ecosystem. Developers can now work on UI components
independently using standard web development tools and workflows.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 06:49:41 +01:00

141 lines
3.5 KiB
JavaScript

/**
* Base Widget Class
*
* Foundation class for all Markitect UI widgets following the plugin architecture.
* Provides core functionality for event handling, state management, and lifecycle.
*/
export class Widget extends EventTarget {
constructor(options = {}) {
super();
// Core properties
this.id = options.id || `widget-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
this.container = options.container || document.body;
this.config = { ...this.getDefaultConfig(), ...options };
// State management
this.state = new Map();
this.isInitialized = false;
this.isDestroyed = false;
// Mixin support
this.mixins = [];
// Lifecycle hooks
this.onInitialize = options.onInitialize || (() => {});
this.onDestroy = options.onDestroy || (() => {});
}
/**
* Initialize the widget
*/
async initialize() {
if (this.isInitialized || this.isDestroyed) {
return this;
}
try {
await this.onInitialize(this);
this.isInitialized = true;
this.emit('initialized');
return this;
} catch (error) {
this.emit('error', { phase: 'initialize', error });
throw error;
}
}
/**
* Destroy the widget and clean up resources
*/
async destroy() {
if (this.isDestroyed) {
return;
}
try {
await this.onDestroy(this);
this.isDestroyed = true;
this.emit('destroyed');
} catch (error) {
this.emit('error', { phase: 'destroy', error });
throw error;
}
}
/**
* State management
*/
setState(key, value) {
const oldValue = this.state.get(key);
this.state.set(key, value);
this.emit('state-changed', { key, value, oldValue });
}
getState(key, defaultValue = null) {
return this.state.get(key) ?? defaultValue;
}
/**
* Event emission wrapper
*/
emit(eventType, data = {}) {
const event = new CustomEvent(eventType, {
detail: { widget: this, ...data }
});
this.dispatchEvent(event);
}
/**
* Apply mixin functionality
*/
applyMixin(mixin) {
if (typeof mixin === 'object') {
Object.assign(this, mixin);
this.mixins.push(mixin);
}
return this;
}
/**
* Default configuration (override in subclasses)
*/
getDefaultConfig() {
return {};
}
/**
* Utility method for creating DOM elements with styling
*/
createElement(tag, options = {}) {
const element = document.createElement(tag);
if (options.className) {
element.className = options.className;
}
if (options.textContent) {
element.textContent = options.textContent;
}
if (options.innerHTML) {
element.innerHTML = options.innerHTML;
}
if (options.style) {
if (typeof options.style === 'string') {
element.style.cssText = options.style;
} else {
Object.assign(element.style, options.style);
}
}
if (options.attributes) {
Object.entries(options.attributes).forEach(([key, value]) => {
element.setAttribute(key, value);
});
}
return element;
}
}