UNPKG

turbopug

Version:

No-junk JS component library with insignificant weight. All in one mush: Web-Components, Localization, Routing, Reactive binding, Debounce, Unique IDs, PSW hashing

227 lines (173 loc) 7.6 kB
# TURBOPUG No-junk JS component library with insignificant weight. If you're 17.5% too weak or 11.4% too unwilling to use heavy-weight component frameworks, but still want all the stuff in one mush: - Web-Components - Localization - Templates - Routing - Reactive binding - Debounce - Unique IDs - PSW hashing > Plus 133.7% of all the quickility. ## KEEPING IT SIMPLE SINCE 1903 Handmade master craftsmanship gives you the full-bodied low-fat experience you expect from a component framework: - No inheritance - No bundler - No build-step - No TypeScript - No junk > All the folding chair comfort you ever hoped for. ## REQUIREMENTS Because `TURBOPUG` is so bare bones, your customer's browser almost needs to be from the JavaScript-future: - modules & imports - template literals - Web Components - All the words: `let`, `const`, `...` Yes: That's all the browsers, since a couple of years. ## GET OVER YOUR SPEED-ANGST Running in an up-to-date browsers your SPA doesn't need all the vanity junk. Just use a server that doesn't crack on HTML/2 push and gzipped response compression. Real quickility comes with not doing stuff. And thats what `TURBOPUG` does: not doing stuff. If you want wing chair comfort and a nurse giving you a hand, don't use `TURBOPUG`. If you know your JavaScript, get cracking. ## FRIENDLY TO CARBONS OUT OF THE BOX `TURBOPUG` is hertz-saving for computers. So somewhere someone doesn't need to burn fatty non-renewables. But keep in mind: what you do is up to you: Run your stuff on a Windows-11-VM through 5 VPNs and 6 SSH-tunnels if you like to roll like a coaler. # Docs `TURBOPUG` happened while writing an SPA in vanilla JavaScript in a reactive, event-driven design by factoring out the accidental complexity. ## Component Example ```js import Comp from '../turbo/comp.js'; import Store from '../turbo/store.js'; export class MyCounter extends Comp { #store; static get observedAttributes() { return ['label']; } constructor() { super(); this.#store = new Store({ label: (value = '', event) => { switch (event.type) { case 'set': return event.value; default: return value; } }, counter: (value = 0, event) => { switch (event) { case 'incremented': return value + 1; case 'decremented': return value - 1; default: return value; } }, }); this.#store.merge({ label: null, counter: 1, }); } attributeChangedCallback(name, _, value) { switch (name) { case 'label': this.#store.send.label({type: 'set', value}); break; default: break; } } get label() { return this.#store.state.label; } set label(value) { this.#store.send.label({type: 'set', value}); } render() { return /*html*/` <div> <span class="label">${this.#store.state.label}</span> <span class="counter">${this.#store.state.counter}</span> </div> <button class="decrement">Decrement</button> <button class="increment">Increment</button> `; } bind(valElem, decBtn, incBtn) { const labelElem = valElem.querySelector('.label'); this.#store.on.label((_, value) => { labelElem.innerText = value; }); const counterElem = valElem.querySelector('.counter'); this.#store.on.counter((_, value) => { counterElem.innerText = value; }); decBtn.addEventListener('click', () => { this.#store.send.counter('decremented'); }); incBtn.addEventListener('click', () => { this.#store.send.counter('incremented'); }); } } customElements.define('my-counter', MyCounter); ``` - An `TURBOPUG` component is defined by extending the class Comp. - For state management a Store is initialized and assigned onto a private variable `#store`. Stores are inspired by redux und made of variables that can be changed by sending events and reacting to them via reducers. - The 2 methods `static get observedAttributes()` and `attributeChangedCallback(name, _, value)` are part of the web-components specification. - A component can have a render-method which must return a rendered HTML-string. JS template strings are used for that. - After such a HTML-string is turned into DOM elements the bind-method is called. In which event-listeners from the DOM are attached and changes coming from the store are inserted into the DOM. ## Components are state machines ### How to program with `TURBOPUG` An `TURBOPUG` comp is a state machine. Programming is done via variable changes over time. I.e. setting one variable through user-input, reacting to a change, setting another variable and reacting to the change, until one change is reflected in HTML via a binding or triggers an outside variable-change-handler. ## Localization/i18n bindings `lcz-prop` and `lcz-attr` are used to bind to localization keys from the translations file. Syntax is: `[<attrib-or-prop-name>=]<LOCALIZATION_KEY>` Example: ```html <p lcz-prop="textContent=LOCALIZABLE_KEY:Fallback Translation"></p> <input lcz-attr="value=LOCALIZABLE_KEY:Fallback Translation" type="text"> ``` **<attrib-or-prop-name>** The name of the prop or attrib to set the localization on. Can be omitted, defaults to `textContent`. **<LOCALIZATION_KEY>** The localization key to use to retrieve the value from the translations file. Fallback is value of `<attrib-or-prop-name>` if not found, default value `textContent` included. ## Localization/i18n setup `insig` must rely on script loading order to make translations available ASAP during page-loading. Translations file and LCZ-module must be setup as follows: ```html <!doctype html> <html lang="en"> <head> <script src="/translations.js"></script> <!-- TRANSLATIONS FILE IS FIRST JS-FILE LOADED --> <!-- OTHER SCRIPT AFTERWARDS ... --> </head> <body> <!-- CONTENT COMES HERE --> <script src="/insig/lcz.js" type="module"></script> <!-- LCZ MODULE LOADED DIRECTLY AFTER CONTENT --> </body> </html> ``` The localizations file must set a the property `localizations` on the window object. If you only need one central localizations file, use the simple object notation in which each language locale is a key, i.e. `de` for german or `"de-CH"` for swiss german: ```js window.localizations = { de: { DOGS: 'Hunde', CATS: 'Katzen', DOG: 'Hund', CAT: 'Katze', DONE: 'Fertig' } }; ``` If you want to use several localization files, use the array notation in which each entry is one of the above. Use the following terse notation per file to automatically extend a previously loaded definition: ```js window.localizations = (l => [...l, { de: { DOGS: 'Hunde', CATS: 'Katzen', DOG: 'Hund', CAT: 'Katze', DONE: 'Fertig' } }])(window.localizations || []); ``` The actual translations objects are merged in this case. The order in which the files are loaded in the HTML is significant: Files loaded later overwrite translation-keys from a file loaded earlier, so you can extend on base localizations.