Constructos: Building Reusable Components
Overview
Constructos are the building blocks of WinnetouJs applications. They are HTML components that get compiled into reusable JavaScript classes, allowing you to create modular, maintainable, and type-safe user interfaces.
When WinnetouJs is installed, it provides a WBR.js file which transpiles .wcto.html files into .wcto.js files. You write HTML components (called constructos) inside .wcto.html files, and WBR compiles them into reusable JS classes that can be imported into your JS/TS applications.
Creating Your First Constructo
File Structure
Constructos are created in files with the .wcto.html extension. The compiled .wcto.js files will be generated alongside the .wcto.html files automatically by WBR.
Best Practices for File Organization:
- Put all similar constructos in one single HTML file
- Group them in folders by functionality
- Use a consistent naming convention
Recommended Project Structure:
application/
├── src/
│ ├── app.ts
│ ├── common-constructos/
│ │ ├── common.wcto.html
│ │ ├── _common.scss
│ │ └── common.ts
│ ├── cards/
│ │ ├── cards.wcto.html
│ │ ├── _cards.scss
│ │ └── cards.ts
├── libs/
├── sass/
│ ├── main.scss
├── package.json
├── win.config.json
├── wbr.js
└── README.md
Basic Constructo Syntax
Every constructo must be created inside <winnetou> tags:
<winnetou>
<div id="[[myDiv]]">{{content}}</div>
</winnetou>
Important: Do not duplicate the <winnetou> tag. Each constructo should have exactly one opening and closing <winnetou> tag.
❌ Bad Pattern:
<winnetou></winnetou>
<!-- rest of code -->
</winnetou>
✅ Good Pattern:
<winnetou>
<!-- all content here -->
</winnetou>
Adding Descriptions
You can add a description attribute to the <winnetou> tag to document what the constructo does:
<winnetou description="A price card component for displaying product pricing">
<div id="[[priceCard]]" class="card">
<h3>{{productName}}</h3>
<p class="price">{{price}}</p>
</div>
</winnetou>
Working with IDs
ID Syntax
IDs in constructos must be enclosed in double square brackets: [[id]]
<winnetou>
<div id="[[myFirstDiv]]">Hello World!</div>
</winnetou>
Constructo Class Names
When a constructo HTML file is compiled, the ID of the constructo becomes the class name prefixed with $:
<!-- myComponents.wcto.html -->
<winnetou>
<div id="[[myFirstDiv]]">{{content}}</div>
</winnetou>
// app.js
import { $myFirstDiv } from "./myComponents.wcto";
new $myFirstDiv({ content: "Hello!" }).create("#app");
Accessing IDs at Runtime
When constructos are placed in the HTML, WinnetouJs creates unique string IDs using the formula: originalId-win-randomNumber
The create() method returns an object containing all the IDs:
const myConstructo = new $myFirstDiv({ content: "Test" }).create("#app");
console.log(myConstructo.ids.myFirstDiv); // "myFirstDiv-win-1"
Custom Identifiers
You can override the random number with a custom identifier using the identifier option:
let left = new $myDiv({}, { identifier: "leftDiv" }).create("#app");
console.log(left.ids.myDiv); // "myDiv-win-leftDiv"
let right = new $myDiv({}, { identifier: "rightDiv" }).create("#app");
console.log(right.ids.myDiv); // "myDiv-win-rightDiv"
This is particularly useful for DOM manipulations when you need predictable IDs.
Multiple IDs in One Constructo
A constructo can have many IDs. The first ID becomes the class name, and all IDs are returned by the create() method:
<winnetou description="A card with multiple interactive elements">
<div id="[[mainCard]]" class="card">
<h2 id="[[cardTitle]]">{{title}}</h2>
<p id="[[cardText]]">{{text}}</p>
<button id="[[cardButton]]">{{buttonText}}</button>
</div>
</winnetou>
import { $mainCard } from "./cards.wcto";
const card = new $mainCard({
title: "Welcome",
text: "This is a card component",
buttonText: "Click Me",
}).create("#app");
// Access all IDs
const { mainCard, cardTitle, cardText, cardButton } = card.ids;
// Manipulate specific elements
document.getElementById(cardTitle).style.color = "blue";
document.getElementById(cardButton).addEventListener("click", () => {
console.log("Button clicked!");
});
Working with Props
Props allow you to pass dynamic data into your constructos, making them flexible and reusable.
Basic Props
Create props using double curly brackets: {{prop}}
<winnetou>
<h1 id="[[title]]">{{text}}</h1>
</winnetou>
import { $title } from "./components.wcto";
new $title({ text: "My Page Title" }).create("#app");
Optional Props
Make a prop optional by adding a ? token:
<winnetou>
<div id="[[userCard]]" class="{{className?}}">
<h3>{{userName}}</h3>
<p>{{bio?}}</p>
</div>
</winnetou>
// Valid - bio is optional
new $userCard({
userName: "John Doe",
}).create("#app");
// Also valid - providing all props
new $userCard({
userName: "John Doe",
className: "featured",
bio: "Software Developer",
}).create("#app");
Typed Props
Add type annotations using the : token for TypeScript/JSDoc type checking:
<winnetou>
<div id="[[counter]]">
<span>{{label:string}}</span>
<strong>{{count:number}}</strong>
</div>
</winnetou>
new $counter({
label: "Total Items:",
count: 42,
}).create("#app");
Props with Descriptions
Document your props using parentheses:
<winnetou>
<div
id="[[colorBox]]"
style="background-color: {{color(A valid CSS color value)}}">
{{content(The text content to display inside the box)}}
</div>
</winnetou>
Combining All Prop Features
You can mix optional markers, types, and descriptions:
<winnetou description="A customizable alert component">
<div
id="[[alert]]"
class="alert {{type(The alert type)?:'success'|'warning'|'danger'}}">
<h4>{{title(The alert title):string}}</h4>
<p>{{message(The alert message):string}}</p>
<span>{{timestamp(ISO timestamp)?:string}}</span>
</div>
</winnetou>
import { $alert } from "./components.wcto";
// Using with all props
new $alert({
type: "danger",
title: "Error",
message: "Something went wrong!",
timestamp: "2025-10-13T10:30:00Z",
}).create("#app");
// Using without optional props
new $alert({
type: "success",
title: "Success",
message: "Operation completed!",
}).create("#app");
Constructo Methods
Every constructo class has two main methods for rendering:
1. create() Method
The create() method attaches a constructo to the DOM. It requires a CSS selector string as a parameter.
Basic Usage:
import { $title } from "./components.wcto";
new $title({ text: "Hello World" }).create("#app");
With Options:
The create() method accepts an options object as a second parameter:
new $title({ text: "Simple title" }).create("#titles", {
clear: true, // Clear the target element before inserting
reverse: true, // Insert at the top instead of bottom
});
Options Explained:
clear: true- Removes all existing content from the target element before inserting the constructoreverse: true- Inserts the constructo at the beginning of the target element (instead of appending)
Example with Multiple Constructos:
import { $card } from "./cards.wcto";
// Clear the container and add first card
new $card({ title: "First Card" }).create("#container", { clear: true });
// Add more cards without clearing
new $card({ title: "Second Card" }).create("#container");
new $card({ title: "Third Card" }).create("#container");
// Add a card at the top
new $card({ title: "Priority Card" }).create("#container", { reverse: true });
2. constructoString() Method
The constructoString() method returns the HTML string of a constructo without attaching it to the DOM. This is essential for nesting constructos inside other constructos.
Nesting Constructos:
<!-- components.wcto.html -->
<winnetou>
<div id="[[outerDiv]]" class="outer">{{content}}</div>
</winnetou>
<winnetou>
<div id="[[innerDiv]]" class="inner">{{text}}</div>
</winnetou>
import { $outerDiv, $innerDiv } from "./components.wcto";
new $outerDiv({
content: new $innerDiv({
text: "I'm nested inside!",
}).constructoString(),
}).create("#app");
Building Lists:
import { $list, $listItem } from "./lists.wcto";
const items = ["Apple", "Banana", "Orange", "Grape"];
const listItems = items
.map(fruit => new $listItem({ text: fruit }).constructoString())
.join("");
new $list({ items: listItems }).create("#app");
⚠️ Important Note: While
constructoString()is useful for simple nesting and building lists, in complex systems with multiple nested components, it's better to create separated constructos and use constructos chaining (see below) to avoid confusion and code smell.
Constructos Chaining
Constructos chaining is a powerful pattern for building complex UIs in a clean and maintainable way. Instead of nesting constructos with constructoString(), you create separate constructos and use their IDs to attach them to each other.
This approach ensures:
- ✅ Clean, readable code
- ✅ Reusable constructos
- ✅ Better separation of concerns
- ✅ Easier maintenance and testing
Basic Chaining Pattern
<!-- layout.wcto.html -->
<winnetou>
<header id="[[header]]">I'm a header</header>
</winnetou>
<winnetou>
<footer id="[[footer]]">I'm a footer</footer>
</winnetou>
<winnetou>
<div id="[[page]]">I'm a page</div>
</winnetou>
import { $header, $footer, $page } from "./layout.wcto";
// Create the main page container first
const pageId = new $page().create("#app").ids.page;
// Chain other constructos to the page
new $header().create(pageId);
new $footer().create(pageId);
Advanced Chaining Example: Dashboard Layout
Let's build a complete dashboard using constructos chaining:
<!-- dashboard.wcto.html -->
<winnetou description="Main dashboard container">
<div id="[[dashboardContainer]]" class="dashboard">
<div id="[[dashboardHeader]]" class="dashboard-header"></div>
<div id="[[dashboardBody]]" class="dashboard-body">
<aside id="[[dashboardSidebar]]" class="sidebar"></aside>
<main id="[[dashboardContent]]" class="content"></main>
</div>
</div>
</winnetou>
<winnetou description="Dashboard header with navigation">
<div id="[[topNav]]" class="top-nav">
<h1>{{title:string}}</h1>
<nav id="[[navLinks]]"></nav>
</div>
</winnetou>
<winnetou description="Sidebar menu">
<div id="[[sidebarMenu]]" class="menu">
<h3>{{menuTitle:string}}</h3>
<ul id="[[menuItems]]"></ul>
</div>
</winnetou>
<winnetou description="Menu item">
<li id="[[menuItem]]" class="menu-item" onclick="{{onClick}}">
{{label:string}}
</li>
</winnetou>
<winnetou description="Main content area">
<div id="[[contentArea]]" class="content-area">
<h2>{{pageTitle:string}}</h2>
<div id="[[contentBody]]">{{content}}</div>
</div>
</winnetou>
import { W } from "winnetoujs";
import {
$dashboardContainer,
$topNav,
$sidebarMenu,
$menuItem,
$contentArea,
} from "./dashboard.wcto";
// Build the dashboard using chaining
function buildDashboard() {
// 1. Create main container
const dashboard = new $dashboardContainer().create("#app");
// 2. Chain header to container
const header = new $topNav({ title: "My Dashboard" }).create(
dashboard.ids.dashboardHeader
);
// 3. Chain sidebar to container
const sidebar = new $sidebarMenu({ menuTitle: "Navigation" }).create(
dashboard.ids.dashboardSidebar
);
// 4. Chain menu items to sidebar
const menuData = [
{ label: "Home", route: "/home" },
{ label: "Profile", route: "/profile" },
{ label: "Settings", route: "/settings" },
{ label: "Logout", route: "/logout" },
];
menuData.forEach(item => {
new $menuItem({
label: item.label,
onClick: W.fx(() => {
console.log(`Navigating to ${item.route}`);
navigateTo(item.route);
}),
}).create(sidebar.ids.menuItems);
});
// 5. Chain main content to container
const content = new $contentArea({
pageTitle: "Welcome",
content: "Dashboard content goes here",
}).create(dashboard.ids.dashboardContent);
return { dashboard, header, sidebar, content };
}
// Initialize the dashboard
const app = buildDashboard();
function navigateTo(route) {
// Navigation logic
console.log(`Route: ${route}`);
}
Chaining with Dynamic Content
Constructos chaining works great for dynamic content updates:
<!-- blog.wcto.html -->
<winnetou description="Blog post container">
<div id="[[blogContainer]]" class="blog">
<div id="[[postsArea]]" class="posts-area"></div>
</div>
</winnetou>
<winnetou description="Individual blog post">
<article id="[[blogPost]]" class="post">
<h2 id="[[postTitle]]">{{title:string}}</h2>
<p class="meta">By {{author:string}} on {{date:string}}</p>
<div id="[[postContent]]">{{content:string}}</div>
<div id="[[commentsSection]]"></div>
</article>
</winnetou>
<winnetou description="Comment component">
<div id="[[comment]]" class="comment">
<strong>{{username:string}}</strong>
<p>{{text:string}}</p>
<small>{{timestamp:string}}</small>
</div>
</winnetou>
import { $blogContainer, $blogPost, $comment } from "./blog.wcto";
// Create blog with posts and comments using chaining
async function loadBlog() {
// 1. Create container
const blog = new $blogContainer().create("#app");
// 2. Fetch and display posts
const posts = await fetchPosts();
posts.forEach(post => {
// Create each post
const postElement = new $blogPost({
title: post.title,
author: post.author,
date: post.date,
content: post.content,
}).create(blog.ids.postsArea);
// Chain comments to each post
post.comments.forEach(commentData => {
new $comment({
username: commentData.username,
text: commentData.text,
timestamp: commentData.timestamp,
}).create(postElement.ids.commentsSection);
});
});
}
async function fetchPosts() {
// Simulated API call
return [
{
title: "First Post",
author: "John Doe",
date: "2025-10-10",
content: "This is the first post content...",
comments: [
{
username: "Jane",
text: "Great post!",
timestamp: "2025-10-11 10:30",
},
{
username: "Bob",
text: "Very helpful!",
timestamp: "2025-10-11 14:20",
},
],
},
{
title: "Second Post",
author: "Jane Smith",
date: "2025-10-12",
content: "This is the second post content...",
comments: [
{ username: "John", text: "Thanks!", timestamp: "2025-10-12 09:15" },
],
},
];
}
loadBlog();
When to Use Chaining vs constructoString()
Use Constructos Chaining when:
- ✅ Building complex layouts with multiple levels
- ✅ Creating reusable, independent components
- ✅ Need to manipulate individual components after creation
- ✅ Working with dynamic content that updates frequently
- ✅ Want clean, maintainable, and testable code
Use constructoString() when:
- ✅ Building simple lists or repeated elements
- ✅ Creating static content that won't change
- ✅ Nesting is shallow (one or two levels max)
- ✅ The nested content is simple and doesn't need separate manipulation
Practical Examples
Example 1: Simple Button Component
<!-- buttons.wcto.html -->
<winnetou description="A reusable button component">
<button
id="[[customButton]]"
class="btn {{variant:'primary'|'secondary'|'danger'}}"
onclick="{{onclick}}">
{{text(Button text):string}}
</button>
</winnetou>
import { W } from "winnetoujs";
import { $customButton } from "./buttons.wcto";
new $customButton({
variant: "primary",
text: "Save Changes",
onclick: W.fx(() => {
console.log("Save button clicked!");
}),
}).create("#toolbar");
Example 2: User Profile Card
<!-- profiles.wcto.html -->
<winnetou description="User profile card with avatar and details">
<div id="[[profileCard]]" class="profile-card">
<img id="[[avatar]]" src="{{avatarUrl:string}}" alt="{{userName}} avatar" />
<div id="[[profileInfo]]">
<h3 id="[[userName]]">{{userName:string}}</h3>
<p id="[[userBio]]">{{bio?:string}}</p>
<span id="[[userRole]]" class="role-badge">
{{role(User role)?:string}}
</span>
</div>
<button id="[[contactButton]]" onclick="{{onContact}}">Contact</button>
</div>
</winnetou>
import { W } from "winnetoujs";
import { $profileCard } from "./profiles.wcto";
const profile = new $profileCard({
avatarUrl: "/images/john-doe.jpg",
userName: "John Doe",
bio: "Full-stack developer passionate about web technologies",
role: "Senior Developer",
onContact: W.fx(() => {
alert("Opening contact form...");
}),
}).create("#team-members");
// Access specific elements for further manipulation
const { userName, userRole } = profile.ids;
document.getElementById(userName).style.fontWeight = "bold";
Example 3: Dynamic Product Grid
<!-- products.wcto.html -->
<winnetou description="Container for product items">
<div id="[[productGrid]]" class="grid">{{products}}</div>
</winnetou>
<winnetou description="Individual product card">
<div id="[[productCard]]" class="product-card">
<img id="[[productImage]]" src="{{image:string}}" alt="{{name}}" />
<h4 id="[[productName]]">{{name:string}}</h4>
<p id="[[productPrice]]" class="price">{{price:string}}</p>
<button id="[[addToCartBtn]]" onclick="{{onAddToCart}}">Add to Cart</button>
</div>
</winnetou>
import { W } from "winnetoujs";
import { $productGrid, $productCard } from "./products.wcto";
const productsData = [
{ id: 1, name: "Laptop", price: "$999", image: "/images/laptop.jpg" },
{ id: 2, name: "Mouse", price: "$29", image: "/images/mouse.jpg" },
{ id: 3, name: "Keyboard", price: "$79", image: "/images/keyboard.jpg" },
];
const productCards = productsData
.map(product =>
new $productCard({
name: product.name,
price: product.price,
image: product.image,
onAddToCart: W.fx(() => {
console.log(`Adding ${product.name} to cart`);
addToCart(product.id);
}),
}).constructoString()
)
.join("");
new $productGrid({ products: productCards }).create("#shop");
function addToCart(productId) {
// Cart logic here
console.log(`Product ${productId} added to cart`);
}
Best Practices
1. Naming Conventions
- Use descriptive, camelCase names for constructo IDs
- Prefix component-specific IDs to avoid conflicts
- Group related constructos in the same file
<!-- Good -->
<winnetou>
<div id="[[userProfileCard]]">...</div>
</winnetou>
<!-- Avoid -->
<winnetou>
<div id="[[card1]]">...</div>
</winnetou>
2. Prop Documentation
Always document your props with types and descriptions for better developer experience:
<winnetou description="A well-documented component">
<div id="[[documentedComponent]]">
<h2>{{title(The main heading):string}}</h2>
<p>{{description(Component description)?:string}}</p>
<span>{{priority(Priority level from 1-5):number}}</span>
</div>
</winnetou>
3. Use Constructos Chaining for Complex UIs
Break complex UIs into smaller, reusable constructos and chain them together:
// ❌ Avoid - One massive nested constructo
new $complexPage({
header: new $pageHeader({...}).constructoString(),
sidebar: new $pageSidebar({...}).constructoString(),
content: new $pageContent({...}).constructoString(),
footer: new $pageFooter({...}).constructoString()
}).create("#app");
// ✅ Better - Use constructos chaining
const page = new $pageLayout().create("#app");
new $pageHeader({...}).create(page.ids.headerArea);
new $pageSidebar({...}).create(page.ids.sidebarArea);
new $pageContent({...}).create(page.ids.contentArea);
new $pageFooter({...}).create(page.ids.footerArea);
This approach ensures clean code, better reusability, and easier maintenance.
4. Consistent File Organization
Keep related files together:
components/
├── buttons/
│ ├── buttons.wcto.html
│ ├── _buttons.scss
│ └── buttons.ts
├── forms/
│ ├── forms.wcto.html
│ ├── _forms.scss
│ └── forms.ts
5. Use Identifiers for Testing
Use custom identifiers for elements you need to test or manipulate:
const loginForm = new $loginForm(
{
username: "",
password: "",
},
{ identifier: "loginForm" }
).create("#auth");
// Easy to find in tests
const formId = loginForm.ids.loginForm; // "loginForm-win-loginForm"
Summary
Constructos are the foundation of WinnetouJs applications. Key takeaways:
- ✅ Create constructos in
.wcto.htmlfiles inside<winnetou>tags - ✅ Use
[[id]]syntax for component IDs - ✅ Define props with
{{prop}}and add types, descriptions, and optional markers - ✅ Use
create()to attach constructos to the DOM - ✅ Use
constructoString()for simple nesting and building lists - ✅ Use constructos chaining for complex UIs and better maintainability
- ✅ Access generated IDs through the
idsproperty - ✅ Organize constructos by functionality in separate folders
- ✅ Document your constructos with descriptions and typed props
With these fundamentals, you're ready to build powerful, reusable components in WinnetouJs!