Frontend Cheatsheet
Quick reference for the Fullfinity frontend framework.
Module Structure
Section titled “Module Structure”my_module/└── static/ └── src/ └── js/ └── components.js # Your components hereComponents are auto-loaded based on module dependencies.
Components
Section titled “Components”Register a Component
Section titled “Register a Component”const { registry, html, useState } = window.fullfinity;
registry.component('MyComponent', function({ prop1, prop2 }) { const [state, setState] = useState(initialValue);
return html` <div class="my-component"> ${prop1} </div> `;});Use in Template (Jinja2)
Section titled “Use in Template (Jinja2)”<div data-component="MyComponent" data-props='{"prop1": "value", "prop2": {{ variable | tojson }}}'></div>HTML Template Syntax
Section titled “HTML Template Syntax”const { html } = window.fullfinity;
// Elementshtml`<div class="container">Content</div>`
// Expressionshtml`<span>${value}</span>`
// Eventshtml`<button onClick=${handleClick}>Click</button>`
// Conditionalhtml`${condition && html`<div>Shown</div>`}`html`${condition ? html`<div>A</div>` : html`<div>B</div>`}`
// Listshtml`<ul>${items.map(i => html`<li key=${i.id}>${i.name}</li>`)}</ul>`
// Nested componentshtml`<${ChildComponent} prop="value" />`
// Refshtml`<input ref=${inputRef} />`const { useState, // Local state useEffect, // Side effects useRef, // Mutable ref useCallback, // Memoized callback useMemo, // Memoized value useStore, // Access store useSlot, // Access slot (re-renders on change) useLifecycle, // Lifecycle callbacks} = window.fullfinity;useState
Section titled “useState”const [value, setValue] = useState(initial);setValue(newValue); // DirectsetValue(prev => prev + 1); // FunctionaluseEffect
Section titled “useEffect”// Run on every renderuseEffect(() => { /* ... */ });
// Run once on mountuseEffect(() => { // setup return () => { /* cleanup */ };}, []);
// Run when deps changeuseEffect(() => { /* ... */ }, [dep1, dep2]);useRef
Section titled “useRef”const ref = useRef(initialValue);ref.current = newValue; // Mutable, doesn't trigger re-render
// DOM refconst inputRef = useRef(null);html`<input ref=${inputRef} />`;inputRef.current.focus();useLifecycle
Section titled “useLifecycle”const { useLifecycle } = window.fullfinity;
useLifecycle({ onMounted: () => console.log('Mounted'), onUpdated: () => console.log('Updated'), onWillUnmount: () => console.log('Unmounting')});Stores
Section titled “Stores”Create Store
Section titled “Create Store”const { registry, bus } = window.fullfinity;
registry.store('cart', { items: [], add(item) { this.items.push(item); fullfinity.notifyStore('cart'); // Trigger re-renders bus.emit('cart:updated'); // Notify listeners }});Use Store
Section titled “Use Store”const { useStore } = window.fullfinity;const cart = useStore('cart');cart.add({ id: 1, name: 'Product' });Event Bus
Section titled “Event Bus”const { bus } = window.fullfinity;
// Subscribeconst unsubscribe = bus.on('event:name', (data) => { });
// Emitbus.emit('event:name', { key: 'value' });
// Unsubscribeunsubscribe();
// Oncebus.once('event:name', (data) => { });Patching
Section titled “Patching”Wrap Component
Section titled “Wrap Component”const { patch, html } = window.fullfinity;
patch('ExistingComponent', (Original) => function(props) { return html` <div class="wrapper"> <${Original} ...${props} /> <div>Extra content</div> </div> `;}, { id: 'my-wrapper' });With Priority & Conditions
Section titled “With Priority & Conditions”patch('Component', patcher, { id: 'my-patch', // Required for unpatch/after priority: 20, // Lower runs first (default: 10) after: 'other-patch', // Run after this patch when: () => condition // Only apply if true});Modify Props
Section titled “Modify Props”patch('Component', (Original) => function(props) { const modifiedProps = { ...props, newProp: 'value' }; return html`<${Original} ...${modifiedProps} />`;}, { id: 'props-modifier' });Remove Patch
Section titled “Remove Patch”const { unpatch } = window.fullfinity;unpatch('Component', 'my-patch');Method Patching
Section titled “Method Patching”const { patchMethod, unpatchMethod } = window.fullfinity;
// Multiple patches stack in priority orderpatchMethod('CartWidget', 'computeTotal', function(_super, items) { return _super(items) * 0.9; // 10% discount}, { id: 'discount', priority: 10 });
patchMethod('CartWidget', 'computeTotal', function(_super, items) { return _super(items) + 5; // Add shipping (runs after discount)}, { id: 'shipping', priority: 20 });
// Remove a method patchunpatchMethod('CartWidget', 'computeTotal', 'discount');Extension Slots
Section titled “Extension Slots”Define Slot (in component)
Section titled “Define Slot (in component)”const { useSlot } = window.fullfinity;
registry.component('ProductCard', function(props) { // useSlot re-renders when addons register const extras = useSlot('ProductCard:afterPrice'); return html` <div class="product"> <span class="price">${props.price}</span> ${extras.map(C => html`<${C} ...${props} />`)} </div> `;});Register into Slot
Section titled “Register into Slot”registry.slot('ProductCard:afterPrice', LoyaltyBadge, { priority: 10, id: 'loyalty', when: () => userHasLoyalty});Remove from Slot
Section titled “Remove from Slot”registry.removeSlot('ProductCard:afterPrice', 'loyalty');Common Patterns
Section titled “Common Patterns”Fetch Data
Section titled “Fetch Data”registry.component('DataList', function({ endpoint }) { const { useState, useEffect, html } = window.fullfinity; const [data, setData] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { fetch(endpoint) .then(r => r.json()) .then(setData) .finally(() => setLoading(false)); }, [endpoint]);
if (loading) return html`<div>Loading...</div>`; return html`<ul>${data.map(i => html`<li>${i.name}</li>`)}</ul>`;});Form Handling
Section titled “Form Handling”registry.component('Form', function({ onSubmit }) { const { useState, html } = window.fullfinity; const [value, setValue] = useState('');
function handleSubmit(e) { e.preventDefault(); onSubmit(value); }
return html` <form onSubmit=${handleSubmit}> <input value=${value} onInput=${e => setValue(e.target.value)} /> <button type="submit">Submit</button> </form> `;});Conditional Classes
Section titled “Conditional Classes”html`<div class=${'base ' + (active ? 'active' : '')}>...</div>`html`<div class=${['base', active && 'active'].filter(Boolean).join(' ')}>...</div>`API Reference
Section titled “API Reference”Registry
Section titled “Registry”registry.component('Name', fn) // Registerregistry.component('Name') // Getregistry.getOriginal('Name') // Get unpatched originalregistry.hasComponent('Name') // Checkregistry.listComponents() // List all
registry.store('name', obj) // Registerregistry.store('name') // Getregistry.hasStore('name') // Checkregistry.listStores() // List all
registry.slot('Name:slot', Comp, opts) // Register into slotregistry.getSlot('Name:slot') // Get slot componentsregistry.removeSlot('Name:slot', id) // Remove from slotregistry.listSlots('Name') // List slotsPatching
Section titled “Patching”fullfinity.patch('Name', fn, opts) // Patch componentfullfinity.unpatch('Name', 'patch-id') // Remove patchfullfinity.patchMethod('Name', 'method', fn, opts) // Patch methodfullfinity.unpatchMethod('Name', 'method', 'id') // Remove method patchfullfinity.listPatches('Name') // List patch IDsfullfinity.listMethodPatches('Name', 'method') // List method patch IDsMounting
Section titled “Mounting”fullfinity.mountAll() // Mount all registered componentsfullfinity.mountComponent('Name') // Mount specific componentfullfinity.unmount(element) // Unmount from elementUtilities
Section titled “Utilities”fullfinity.notifyStore('name') // Trigger store re-rendersfullfinity.bus.on/emit/off/once // Event busDebugging
Section titled “Debugging”// In browser consolewindow.fullfinity.registry.listComponents() // See all componentswindow.fullfinity.registry.listStores() // See all storeswindow.fullfinity.registry.store('cart') // Inspect store
// Check if component existswindow.fullfinity.registry.hasComponent('ProductCard')
// Inspect patcheswindow.fullfinity.listPatches('ProductCard') // See patch IDswindow.fullfinity.registry.getOriginal('ProductCard') // Get unpatched
// Inspect slotswindow.fullfinity.registry.listSlots('ProductCard') // See slotswindow.fullfinity.registry.getSlot('ProductCard:afterPrice') // Get slot contents