@xcons/widget
v3.0.8
Published
XCon Studio widget utilities with advanced template rendering, reactive binding system and registry pattern support
Downloads
161
Maintainers
Readme
@xcons/widget
XCon Studio widget tools offering advanced template rendering engine, reactive binding system, lifecycle interfaces and registry pattern support for modern web applications. Designed with the "Write once, run everywhere" philosophy.
Features
- 🎯 Decorator-Based Widget System - Simple
@Widgetdecorator for component registration - 🔄 Advanced Reactive System -
@xpropertyand@xcomputeddecorators with automatic dependency tracking - 🌐 Template Rendering Engine - Dynamic template processing with reactive data binding
- 📋 Registry Pattern - Centralized widget management and automatic DOM scanning
- 🔧 Lifecycle Hooks - Flexible lifecycle interfaces (
OnWidgetInit,OnWidgetDestroy, etc.) - 🎨 Style Encapsulation - Multiple encapsulation modes (none, emulated, shadow, component)
- 💉 Dependency Injection - Service injection with
@xinjectdecorator - 🔌 Two-Way Data Binding - Model binding with
x:modeldirective - 🔍 SEO Friendly - Static HTML rendering support and search engine friendly structure
- 📱 TypeScript First - Full TypeScript support with comprehensive type definitions
- 🚀 Zero Dependencies - Lightweight and performant
Installation
npm install @xcons/widgetQuick Start
1. Creating a Basic Widget with Reactive Properties
import { Widget, xproperty, xcomputed, OnWidgetInit } from '@xcons/widget';
@Widget({
selector: '.hello-widget',
widgetName: 'HelloWidget',
template: `
<div>
<h3>Hello <span x:text="name"></span>!</h3>
<p>Full Name: <span x:text="fullName"></span></p>
<p>Counter: <span x:text="count"></span></p>
<button x:on:click="incrementCount">Increment</button>
</div>
`,
styles: [`
.hello-widget {
padding: 20px;
border: 1px solid #ccc;
border-radius: 4px;
}
`]
})
export class HelloWidget implements OnWidgetInit {
@xproperty() name: string = 'World';
@xproperty() firstName: string = 'John';
@xproperty() lastName: string = 'Doe';
@xproperty() count: number = 0;
@xcomputed({ dependencies: ['firstName', 'lastName'] })
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
onWidgetInit(): void {
console.log('HelloWidget initialized!');
}
incrementCount(): void {
this.count++;
}
}2. Bootstrap Your Application
import { XConBootstrap } from '@xcons/widget';
import { HelloWidget } from './hello-widget';
// Bootstrap the application with widgets
XConBootstrap.run({
widgets: [
{ widget: HelloWidget, initMode: 'auto' }
]
});3. Using Widget in HTML
<div class="hello-widget"></div>Advanced Usage
Services with Dependency Injection
import { xinjectable, xsingleton, xinject } from '@xcons/widget';
// Service definition
@xsingleton()
export class UserService {
@xproperty() users: User[] = [];
async loadUsers(): Promise<void> {
// Load users from API
this.users = await fetch('/api/users').then(r => r.json());
}
getUsers(): User[] {
return this.users;
}
}
// Using service in widget
@Widget({
selector: '.user-list',
template: `
<div>
<h3>User List (<span x:text="userCount"></span>)</h3>
<ul>
<template x:for="user in filteredUsers">
<li x:on:click="selectUser(user.id)">
<span x:text="user.name"></span>
</li>
</template>
</ul>
<input x:model="searchQuery" placeholder="Search..." />
</div>
`
})
export class UserListWidget implements OnWidgetInit, OnWidgetReady {
@xproperty() searchQuery: string = '';
@xproperty() selectedUserId: number | null = null;
@xinject(UserService)
private userService: UserService;
@xcomputed({ dependencies: ['userService.users'] })
get userCount(): number {
return this.userService.getUsers().length;
}
@xcomputed({ dependencies: ['userService.users', 'searchQuery'] })
get filteredUsers(): User[] {
if (!this.searchQuery) {
return this.userService.getUsers();
}
const query = this.searchQuery.toLowerCase();
return this.userService.getUsers().filter(user =>
user.name.toLowerCase().includes(query)
);
}
onWidgetInit(): void {
console.log('UserListWidget initialized');
}
async onWidgetReady(templateReady: boolean): Promise<void> {
if (templateReady) {
await this.userService.loadUsers();
}
}
selectUser(userId: number): void {
this.selectedUserId = userId;
}
}
// Start the application
XConBootstrap.run({
widgets: [
{ widget: UserListWidget, initMode: 'auto' }
],
services: [
{ service: UserService }
]
});SEO and Search Engine Compatibility
XCon Widget allows you to create SEO-friendly web applications with its search engine optimized structure:
@Widget({
selector: '.product-widget',
template: `
<article itemscope itemtype="https://schema.org/Product">
<h1 itemprop="name" x:text="product.name"></h1>
<img itemprop="image" x:attr:src="product.imageUrl" x:attr:alt="product.name" />
<div itemprop="description" x:text="product.description"></div>
<div itemprop="offers" itemscope itemtype="https://schema.org/Offer">
<span itemprop="price" x:text="product.price"></span>
<meta itemprop="priceCurrency" content="TRY" />
</div>
</article>
`
})
export class ProductWidget implements OnWidgetInit {
@xproperty() product: Product = {
name: 'Product Name',
description: 'Product description',
imageUrl: '/images/product.jpg',
price: '199.99'
};
onWidgetInit(): void {
// Load initial data for server-side rendering
this.loadProductData();
}
async loadProductData(): Promise<void> {
// Render critical content first for SEO
const productId = this.getProductIdFromUrl();
this.product = await this.fetchProduct(productId);
}
}SEO Advantages:
- ✅ Static HTML content support - Search engines can read content directly
- ✅ Semantic HTML structure - Use of meaningful HTML elements
- ✅ Structured Data support - Rich snippets with Schema.org markups
- ✅ Progressive Enhancement - Basic content can be displayed without JavaScript
- ✅ Fast initial render - Widgets bind on top of existing HTML
Template Syntax
Text Binding (x:text)
<span x:text="message"></span>
<div x:text="username"></div>
<p x:text="computedValue"></p>HTML Binding (x:html)
⚠️ Security Warning: Be careful when using x:html, it can lead to XSS (Cross-Site Scripting) vulnerabilities with untrusted content. Only use with trusted or sanitized HTML content.
<div x:html="trustedHtmlContent"></div>Example Usage:
@Widget({
selector: '.content-widget',
template: `
<div class="content">
<div x:html="articleContent"></div>
</div>
`
})
export class ContentWidget {
@xproperty() articleContent: string = '<h2>Title</h2><p>Content text</p>';
// Example of creating safe HTML content
setSafeContent(userInput: string): void {
// Sanitize content first
this.articleContent = this.sanitizeHtml(userInput);
}
private sanitizeHtml(html: string): string {
// Use HTML sanitization library (e.g., DOMPurify)
// return DOMPurify.sanitize(html);
return html;
}
}Directives
Conditional Rendering (x:if)
<div x:if="isVisible">This content is visible</div>
<div x:if="count > 0">Counter is greater than zero</div>List Rendering (x:for)
<template x:for="item in items">
<li x:text="item.name"></li>
</template>
<!-- With index -->
<template x:for="item, index in items">
<div>
<span x:text="index"></span>: <span x:text="item.name"></span>
</div>
</template>Switch Cases (x:switch)
<div x:switch="status">
<div x:switch:case="loading">Loading...</div>
<div x:switch:case="success">Successful!</div>
<div x:switch:case="error">An error occurred</div>
<div x:switch:default>Unknown state</div>
</div>Event Binding (x:on:*)
<button x:on:click="handleClick">Click</button>
<button x:on:click="handleClickWithArgs('hello', 123)">With Arguments</button>
<input x:on:input="handleInput" />
<form x:on:submit="handleSubmit">...</form>Attribute Binding (x:attr:*)
<input x:attr:disabled="isDisabled" x:attr:placeholder="placeholderText" />
<img x:attr:src="imageUrl" x:attr:alt="imageAlt" />
<a x:attr:href="linkUrl" x:attr:target="linkTarget"></a>Class Binding (x:class:*)
<div x:class:active="isActive" x:class:highlighted="isHighlighted">Content</div>
<button x:class:disabled="!isEnabled" x:class:primary="isPrimary">Button</button>Two-Way Binding (x:model)
<input x:model="username" />
<textarea x:model="description"></textarea>
<select x:model="selectedOption">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<!-- With configuration -->
<input x:model="username" x:model:config='{"debounce": 500, "trim": true}' />Isolation (x:isolate)
Ensures child widgets maintain their own independent binding contexts:
<div class="parent-widget">
<p x:text="parentProperty">Parent content</p>
<!-- This area and its child elements are isolated from parent binding -->
<div x:isolate class="child-widget">
<p x:text="childProperty">Child content</p>
</div>
</div>API Reference
Decorators
@Widget(config)
Widget class decorator:
@Widget({
selector: string, // CSS selector (required)
widgetName?: string, // Widget name
template?: string, // Inline HTML template
templateUrl?: string, // External HTML file path
styles?: string[], // Inline CSS styles
styleUrls?: string[], // External CSS file paths
encapsulation?: EncapsulationMode, // Style encapsulation mode
initMode?: InitializationMode, // Initialization mode
logger?: IWidgetLoggerConfig // Logger configuration
})@xproperty(config?)
Reactive property decorator:
@xproperty({
autoUpdate?: boolean, // Auto update (default: true)
deepWatch?: boolean // Deep watching (default: false)
})@xcomputed(config?)
Computed property decorator:
@xcomputed({
dependencies?: string[], // Explicit dependencies
autoDetectDependencies?: boolean // Auto dependency detection (default: true)
})@xinject(token)
Service injection decorator:
@xinject(ServiceClass) // Inject by class
@xinject('ServiceToken') // Inject by token@xinjectable(config?)
Service registration decorator:
@xinjectable({
scope?: 'singleton' | 'transient', // Service scope (default: 'singleton')
providedIn?: 'root' // Auto registration in root
})Lifecycle Interfaces
interface OnWidgetInit {
onWidgetInit(): void;
}
interface OnWidgetReady {
onWidgetReady(templateReady: boolean): void;
}
interface OnWidgetDestroy {
onWidgetDestroy(): void;
}
interface OnWidgetRendered {
onWidgetRendered(container: HTMLElement): void;
}
interface OnWidgetDataUpdated {
onWidgetDataUpdated(): void;
}
interface OnWidgetResize {
onWidgetResize(): void;
}
interface OnWidgetEditModeChanged {
onWidgetEditModeChanged(): void;
}
interface OnWidgetMobileModeChanged {
onWidgetMobileModeChanged(): void;
}
interface OnWidgetPropertyChanged {
onWidgetPropertyChanged(propertyKey?: string, oldValue?: any, newValue?: any): void;
}
interface OnWidgetInitializationError {
onWidgetInitializationError(error: any): void;
}Bootstrap API
// Bootstrap configuration
interface BootstrapConfig {
rootWidget?: any; // Single root widget
widgets?: BootstrapWidget[]; // Widget list
services?: BootstrapService[]; // Services to register
providers?: BootstrapProvider[]; // Custom providers
}
// Start the application
XConBootstrap.run(config: BootstrapConfig): Promise<void>
// Rescan DOM for new widgets
XConBootstrap.rescanDOM(): void
// Setup auto-scan for dynamic content
XConBootstrap.setupAutoScan(): void
// Get widget instance from DOM element
XConBootstrap.getWidgetInstance(element: HTMLElement): any
// Destroy widget on element
XConBootstrap.destroyWidget(element: HTMLElement): booleanGlobal XConWidgets API
The framework provides a global XConWidgets object for widget management:
// Registry access
XConWidgets.registry // Widget registry instance
XConWidgets.domManager // DOM management instance
// Widget registration
XConWidgets.registerWidget(selector, widgetClass, config, initMode)
// Widget activation
XConWidgets.activateWidget(widgetClass, context)
// Registry queries
XConWidgets.isRegistered(selector) // Check if registered
XConWidgets.getWidgets() // Get all selectors
XConWidgets.getRegisteredWidgets() // Get all with details
XConWidgets.getWidgetBySelector(selector) // Get widget by selector
// DOM management
XConWidgets.scanDOM() // Scan DOM for widgets
XConWidgets.rescanDOM() // Rescan DOM
XConWidgets.setupAutoScan() // Setup auto-scanBrowser Support
- Chrome 60+
- Firefox 60+
- Safari 12+
- Edge 79+
Dependencies
Peer Dependencies
@xcons/core ^2.0.1@xcons/common ^2.0.16typescript >=4.5.0
Zero Runtime Dependencies
The package has no runtime dependencies to be lightweight and performant.
📄 License
MIT License - see LICENSE file for details.
XCon Studio Team | 🌐 Documentation
Made with ❤️ by XCon Studio Team
