1.0.3 • Published 10 months ago

@linttrap/oem-core v1.0.3

Weekly downloads
-
License
ISC
Repository
github
Last release
10 months ago

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 attributes
  • useClassName - Add css class names
  • useEventListener - attach an event listener
  • useInnerHtml - Foolishly insert inner html
  • useInnerText - Insert inner text
  • useState - Listen to a state object and trigger a trait each time it changes
  • useStyle - 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.

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago