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
Markdown
# 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
<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.