State
Available Via: elegance-js/server/stateState 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/loadHookBasic 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/createStateBasic 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/layoutA 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.