@linttrap/oem-core v1.0.3
OEM / Core
Why OEM?
All a ui library needs to do is provide an easy way to generate html and a sane way to regenerate it when something happens. It also helps if there's an easy way to extend it. Do we really need complicated libraries, languages and frameworks to do that? I don't think so. I think we can do it with regular ol' javascript and a set of easy-to-follow conventions. That's what OEM is.
Clean Code
Here's an example of what oem looks like:
// A counter component in ~40 LOC. Add in the "framework" and the total is ~80 LOC!
function CounterExample() {
// 1. Declare state
const count = State(0, {
inc: (a: number, b: number) => a + b,
dec: (a: number, b: number) => a + b,
});
// 2. Declare template engine
const { div, button } = Html({
append: useAppend,
event: useEventListener,
inner_text: useInnerText,
inner_text_on_count: useState(count, useInnerText)
});
// 3. Render DOM
return div(
['append',
button(['inner_text','-'], ['event','click', count.inc]),
div(['inner_text_on_count', count.get]),
button(['inner_text','+'], ['event','click', count.dec])
]);
}
Simple Patterns
At the heart of OEM sits a single ~20 LOC "html" function. The function implements a convention for chaining together behavior using a single isomorphic syntax. This includes: styling, reactivity, state and whatever else you can think of. Each behavior in OEM is implemented as a pure function called a "trait". OEM includes a small standard library of traits to do basic things but don't worry, that's intentional. Traits are the core units of your application and therefore you will want to write your own or use some from the community. Community traits and trait packs are made available on the oem website. Feel free to contribute your own!
Getting Started
npm i @linttrap/oem-core
Generating HTML
Before you can output any html, you need to create a template engine with the Html
function. Like this:
const html = Html();
At this point html
is an object with a function per html tag which can be destructured like so:
const { div, span } = Html();
You can now render a tag like this:
div() // renders: <div></div>
To give your html properties and behaviors you must declare "traits". Here's an example of a div
tag defining inner_text
and on_click
attributes with the useInnerText
and useOnClick
trait.
const { button } = Html({
inner_text: useInnerText,
event: useEventListener
});
Now when you render the tag you can optionally render inner_text
like this:
button(
['inner_text', 'Hello World'],
['event','click', () => alert('Hi!')]
)
// renders: <buton>Hello World</buton>
// behavior: clicking the button shows an alert of "Hi!"
Controlling State
OEM controls state by offering a way to create a simple event bus
const count = State(0);
// get count: count.get()
// set count: count.set(1)
// listen for changes: count.sub((x) => alert(x))
The count
object now has a get
, set
and sub
method to interact with the event bus. The State
object also supports an optional set of reducer functions which helps clean up helper functions. Here's an example:
const count = State(0, {
inc: (a: number, b: number) => a + b,
})
// increment count by one: count.inc(1)
Reacting To State
The most important trait to call out is the useState
because it's the mechanism that gives your html reactivity. The useState
trait is unique in that it will re/run another trait each time the state changes. Here's an example:
const count = State(0);
const html = Html({ inner_text_on_count: useState(count, useInnerText)});
html.button(['inner_text_count', count.get])
// renders: <button>0</button>
count.set(1);
// rerenders: <button>1</button>
What happens here is that the useState
trait listens to count
and re/runs the useInnerText
trait on any html that is using the inner_text_on_count
attribute each time count
changes - which results in count.get
getting run and updating the button. The operation is declarative, surgical and efficient. No virtual dom, no crazy complicated framework, etc.
Available Traits
Here's a list of the traits that ships with oem-core
.
useAttr
- Add html attributesuseClassName
- Add css class namesuseEventListener
- attach an event listeneruseInnerHtml
- Foolishly insert inner htmluseInnerText
- Insert inner textuseState
- Listen to a state object and trigger a trait each time it changesuseStyle
- Apply inline styles
Want more? Community traits and trait packs are made available on the oem website.
Writing Your Own Custom Traits
You can write your own traits. Traits are simple functions that take in, manipulate and return the element. The only requirement is that the element is the first argument.
function useSuffix(){
return function(el: HTMLElement, suffix:string){
el.innerText = el.innerText + suffix;
return el;
}
}
Usage then looks like this:
const {div} = Html({
suffix: useSuffix
});
div(['suffix', 'jack']) // => <div>... jack!</div>
Contributing
Go to the oem-website repo and follow it's instructions on how to add community traits.