copied to clipboard

State

Available Via: elegance-js/server/state

State is, simply put, a collection of variables.
You initialize it on the server using the createState() function.

const superEpicState = createState("MMMMMMM STATE");

Usage

The createState() function takes in two values.
The initial value of the state, and an options object.
The options object may currently only define a bind to the state (more on this later)

The function stores the created state in the servers current state store,
so that upon completion of compilation, it may be serialized into page_data.

Return Value

The return value of createState() is a State Object Attribute,
which you can use to refer back to the created state.

{
    type: ObjectAttributeType.STATE,
    id: 0,
    value: "MMMMMMM STATE", 
    bind: undefined,
}
const isUsingDarkMode = createState(false);

div ({
    class: observe(
        [isUsingDarkMode],
        (value) => value ? "bg-black" : "bg-white",
    ),
})

Many functions like load hooks, event listeners, and observe, take in optional SOAs.

Bind

State, in the browser, is kept in the global pd object, and indexed via pathnames.
All state for the pathname /recipes/apple-pie will be in pd["/recipes/apple-pie"]

However, in some scenarios you may want to reference the same state on multiple pages.
For this, you may bind the state to a Layout.

Then, the state will go into pd[layout_id], instead of the pathname of the page.

const docsLayout = createLayout("docs-layout");

const timeSpentOnPage = createState(0, {
    bind: docsLayout
});

Important Considerations

State persists it's value during page navigation.
Meaning if you want it to reset it's value, you must do so yourself.

Load Hooks

Available Via: elegance-js/server/loadHook

Basic Usage

Load hooks are functions that are called on the initial page load, and subsequent navigations.
A load hook is registered using the createLoadHook() function.

createLoadHook({
    fn: () => {
        console.log("The page has loaded!");
    },
});

Cleanup Function

The return value of a load hook is referred to as a cleanup function.
It is called whenever the load hook goes out of scope.

You'll want to do things like clearInterval() & element.removeEventListener()
here, so you don't get any unintended/undefined behavior.

const counter = createState(0);

createLoadHook({
    deps: [counter],
    fn: (state, counter) => {
        const timer = setInterval(() => {
            counter.value++;
            counter.signal();
        }, 100);

        return () => {
            // Begone, timer!
            clearInterval(timer);
        }
    },
});

Load Hook Scope

The scope of a load hook is either the page it is on, or the layout it is bound to.
If a load hook is bound to layout, it is called when that layout first appears.
Subsequently, its cleanup function will get called once it's bound layout no longer exists on the page.

To bind a load hook to a layout, use the bind attribute, and pass in a Layout ID

const layout = createLayout("epic-layout");

createLoadHook({
    bind: layout,
    fn: () => {
        alert("epic layout was just rendered")

        return () => {
            alert ("epic layout is no longer with us :(")
        };
    },
})

Important Considerations

It's important to note that the load hook function body exists in
browser land not server land. Therefore the code is untrusted.

Event Listener

Available Via: elegance-js/server/createState

Basic Usage

Event listeners are a type of state, that you can create with the
createEventListener() function.

const handleClick = createEventListener({
    eventListener: (params: SetEvent<MouseEvent, HTMLDivElement>) => {
        console.log(params.event);
        console.log(params.event.currentTarget);
    },
});

div ({
    onClick: handleClick,
});

This function returns an SOA, which can then be put on any event listener option of an element.

The eventListener parameter of createEventListener() takes in two types values.
First, a params object, which by default contains the native event which was triggered.

Dependencies

The second parameter, is a spread parameter, containing the dependencies of the event listener.

const counter = createState(0);

const handleClick = createEventListener({
    dependencies: [counter],
    eventListener: (params, counter) => {
        counter++;
        counter.signal();
    },
});

Extra Params

You may also extend the params object parameter of the event listener,
With the params attribute.

This is handy for when you need to pass some value to the client,
that is not necessarily a state variable, but it can change per compilation.

const reference = createReference();

createEventListener({
    params: {
        someElementReference: reference,
        pageCompiledAt: new Date(),
    },

    eventListener: (params) => {
        console.log("i am now aware of: ", params.someElementReference);
        console.log("This page was originally compiled at: ", pageCompiledAt);
    },
});

Important Considerations

It's important to note that the event listener function body exists in
browser land not server land. Therefore the code is untrusted.

Layouts

Available Via: elegance-js/server/layout

A layout is a section of a page that is not re-rendered between
page navigations, to pages that share the same layout order.

Instead, the layouts children are replaced.

This has a few advantages. The main one being, that since the elements themselves,
are not re-rendered, they maintain things like their hover state.

Basic Usage

Layouts work a bit differently in Elegance than you may perhaps be used to.
For example, in Next.JS, layouts are inherited to every subsequent page.

So a layout defined at / would apply to every single page.
Which you may think is nice and saves time, but almost always I find myself in a situation
where I want a layout for every page of a given depth, except one.

And then, I have to either move the special page one depth upward
or the others one-depth downward.

Conversly, layouts in Elegance are not inherited, and are are opt-in.

To create a layout, use the createLayout() function, and pass in a name.
The name is used so any subsequent calls to this function by other pages will get the same ID.

const superAwesomeLayoutID  = createLayout("super-awesome-layout");

This layout ID can then be passed to state, load hooks, etc.

Breakpoints

Creating the actual layout element is simple.
Just make a function that takes in child elements, and have it return some kind of simple layout.

const superAwesomeLayoutID = createLayout("super-awesome-layout");

const SuperAwesomeLayout = (...children: Child[]) => div ({
    style: "background-color: #000; color: #fff",
},
    ...children
);

Then, wrap the children with the built-in Breakpoint() element.

const superAwesomeLayoutID = createLayout("super-awesome-layout");

const SuperAwesomeLayout = (...children: Child[]) => div ({
    style: "background-color: #000; color: #fff",
},
    Breakpoint ({
        id: superAwesomeLayoutID
    },
        ...children
    ),
);

Important Considerations

The Breakpoint() element is the one that gets replaced
when navigating within any given layout.

All sibling and parent elements stay untouched.

Also note, that in complex pages where there are multiple nested layouts,
the one that has its children replaced, is the layout that is last shared.

For example:
Page 1 Layouts: A,B,C,D,E
Page 2 Layouts: A,B,D,C,E
In this instance, the B layout is the last shared layout.