Skip to content

Commit 3be51a2

Browse files
committed
working on ojs documentation
1 parent 1b29ea1 commit 3be51a2

6 files changed

Lines changed: 421 additions & 1 deletion

File tree

docs/events.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Events & Broker
2+
3+
OpenScript uses a **Broker** to manage communication between components and mediators. This decoupled architecture relies on a structured event system.
4+
5+
## Event Structure & Registration
6+
7+
Events are typically defined as a structured "fact" object in `events.js`. This structure allows you to use auto-completion and compile-time checking for event names.
8+
9+
When you register this object with the broker using `broker.registerEvents(appEvents)`, OpenScript parses it and replaces the leaf values (set to `true`) with the actual namespaced event string.
10+
11+
```javascript
12+
// events.js (Before Registration)
13+
export const appEvents = {
14+
auth: {
15+
login: true, // Becomes "auth:login"
16+
logout: true, // Becomes "auth:logout"
17+
},
18+
user: {
19+
is: {
20+
authenticated: true, // Becomes "user:is:authenticated"
21+
},
22+
},
23+
};
24+
```
25+
26+
### Registration (ojs.config.js)
27+
28+
```javascript
29+
import { appEvents } from "./events.js";
30+
31+
// ... in configureApp()
32+
broker.registerEvents(appEvents);
33+
container.value("appEvents", appEvents);
34+
```
35+
36+
## Using Object Paths
37+
38+
After registration, you can use the `appEvents` object keys to reference the event strings directly. This prevents typo-related bugs.
39+
40+
```javascript
41+
// Instead of this:
42+
this.send("auth:login", data);
43+
44+
// Use this:
45+
this.send(appEvents.auth.login, data);
46+
```
47+
48+
## Payloads (`payload` / `EventData`)
49+
50+
When sending events, it is best practice to wrap your data in a standard **Payload**. OpenScript provides a `payload` or `eData` helper for this.
51+
52+
A payload consists of:
53+
54+
- **Message**: The actual data (body).
55+
- **Meta**: Metadata about the event (timestamps, source, etc).
56+
57+
```javascript
58+
import { payload } from "modular-openscriptjs";
59+
60+
// Sending an event with a payload
61+
this.send(
62+
appEvents.auth.login,
63+
payload(
64+
{ username: "Levi", id: 1 }, // Message
65+
{ timestamp: Date.now() }, // Meta (optional)
66+
),
67+
);
68+
```
69+
70+
### Receiving Payloads
71+
72+
Listeners created with `$$` (or standard broker listeners) receive these arguments.
73+
74+
```javascript
75+
async $$auth_login(payloadObj) {
76+
// payloadObj is the message/data directly if spread,
77+
// OR the EventData object depending on how it was sent.
78+
// Generally, OpenScript broker spreads arguments.
79+
80+
// If sent as above:
81+
// args[0] = { username: "Levi", id: 1 }
82+
// args[1] = { timestamp: ... }
83+
}
84+
```
85+
86+
> **Note**: `EventData` is the underlying class, but `payload()` is the convenient helper function to create instances of it.
87+
88+
## Parsing Received Events
89+
90+
When an event is received (e.g., in a Mediator), the payload is often a **JSON string**. You must parse it back into an `EventData` object to access the helpers.
91+
92+
```javascript
93+
import { EventData } from "modular-openscriptjs";
94+
95+
// ... in a listener
96+
const eventData = EventData.parse(payloadString);
97+
```
98+
99+
### EventData Helper Methods
100+
101+
The parsed object provides `message` and `meta` properties, each with the following methods:
102+
103+
- `has(key)`: Checks if a key exists.
104+
- `get(key, defaultValue)`: Gets a value (returns `defaultValue` if missing).
105+
- `put(key, value)`: Sets a value.
106+
- `remove(key)`: Deletes a value.
107+
- `getAll()`: Returns the raw object.
108+
109+
```javascript
110+
const userId = eventData.message.get("id");
111+
if (eventData.meta.has("timestamp")) {
112+
// ...
113+
}
114+
```

docs/mediators.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Mediators
2+
3+
Mediators act as the bridge between your application's logic (backend/services) and the frontend (components). They facilitate a clean separation of concerns by handling business logic and communicating via the unrelated **Broker**.
4+
5+
## Concept & Role
6+
7+
Mediators are **stateless** classes that listen for events, execute logic (like API calls or data processing), and then potentially emit new events. They do not manipulate the DOM directly.
8+
9+
```javascript
10+
// Example Mediator structure
11+
export default class AuthMediator extends Mediator {
12+
shouldRegister() {
13+
return true; // Control registration logic
14+
}
15+
}
16+
```
17+
18+
## Broker Registration
19+
20+
When you define a Mediator, it is automatically registered with the **Broker** if `shouldRegister()` returns `true`. The broker scans the mediator for special properties to set up event listeners.
21+
22+
## Event Listening (`$$`)
23+
24+
The `$$` prefix is used to define event listeners. OpenScript interprets these property names and values to decide which events to listen to.
25+
26+
### Underscore means "OR"
27+
28+
If you use an underscore in the method name after `$$`, it acts as an **OR** operator. The method will key off **multiple independent events**.
29+
30+
```javascript
31+
import { EventData } from "modular-openscriptjs";
32+
33+
/*
34+
* Listens for:
35+
* - 'user' event
36+
* - 'login' event
37+
* (NOT 'user:login')
38+
*/
39+
async $$user_login(eventData) {
40+
// Parse the JSON string payload
41+
const data = EventData.parse(eventData);
42+
43+
console.log("Triggered by 'user' OR 'login' event");
44+
console.log("User ID:", data.message.get("id"));
45+
}
46+
```
47+
48+
### Namespacing (Nested Objects)
49+
50+
To listen to namespaced events (e.g., `user:login`, `user:logout`), you should use **nested objects**. This structure treats events as facts and allows for organized event definitions.
51+
52+
```javascript
53+
// Property starts with $$ -> 'auth' namespace
54+
$$auth = {
55+
// Listens for 'auth:login'
56+
login: async (eventData) => {
57+
const data = EventData.parse(eventData);
58+
this.handleLogin(data);
59+
},
60+
61+
// Listens for 'auth:logout'
62+
logout: async () => {
63+
this.handleLogout();
64+
},
65+
66+
// Nested further: 'auth:password:reset'
67+
password: {
68+
reset: (email) => { ... }
69+
}
70+
}
71+
```
72+
73+
## Creating "Events as Facts"
74+
75+
It is a best practice to structure your events like facts (`noun:verb` or `subject:predicate:object`). This is typically done in `events.js` and enforced by `broker.requireEventsRegistration(true)`.
76+
77+
```javascript
78+
// appEvents definition (events.js)
79+
export const appEvents = {
80+
user: {
81+
is: {
82+
authenticated: true, // event: 'user:is:authenticated'
83+
unauthenticated: true,
84+
},
85+
has: {
86+
ordered: true, // event: 'user:has:ordered'
87+
},
88+
},
89+
};
90+
```
91+
92+
## Sending Events
93+
94+
Mediators can send events using `this.send(event, payload(...))` or `this.broadcast(event, payload(...))`.
95+
96+
```javascript
97+
import { payload } from "modular-openscriptjs";
98+
99+
async $$auth_login(eventData) {
100+
// Validate...
101+
this.send(
102+
"user:is:authenticated",
103+
payload({ userProfile })
104+
);
105+
}
106+
```

docs/osm.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,38 @@ h.div(
190190
),
191191
);
192192
```
193+
194+
## Fragments (`h.$` or `h._`)
195+
196+
Fragments allow you to group multiple elements without adding an extra node to the DOM. This is particularly useful when returning multiple root elements from a component or `h.call`.
197+
198+
To create a fragment, use `h.$()` or `h._()`.
199+
200+
> [!IMPORTANT]
201+
> **Single Root Requirement**: Even within a fragment, you must ensure there is a **single top-level element** that acts as the parent for the other elements in that fragment structure.
202+
203+
```javascript
204+
// Correct Usage
205+
h.$(
206+
h.div(
207+
// Top-level parent in the fragment
208+
h.span("Part 1"),
209+
h.span("Part 2"),
210+
),
211+
);
212+
213+
// Incorrect Usage (Multiple top-level siblings)
214+
// h.$ (
215+
// h.div("Part 1"),
216+
// h.div("Part 2")
217+
// )
218+
```
219+
220+
### Component Wrapper Behavior
221+
222+
Normally, a Component's markup is automatically wrapped in a custom element (e.g., `<ojs-my-component>`). However, **if a component returns a fragment**, this wrapper is **NOT** created.
223+
224+
> [!CAUTION]
225+
> **State Reactivity Limitation**: Components that return fragments **cannot react to state changes** efficiently because there is no wrapper element to anchor the reconciler. Use fragments in components primarily for splitting up large render functions or for static content.
226+
>
227+
> **Top-Level Requirement**: While fragments avoid wrappers, your final application structure **Must** typically have a stable top-level element in the final markup for the framework to attach to.

docs/special-attributes.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Special Attributes
2+
3+
OpenScript provides several special attributes that control how elements are rendered and how component wrappers are styled.
4+
5+
## Render Placement Attributes
6+
7+
These attributes control _where_ and _how_ an element triggers the rendering process relative to a parent.
8+
9+
### `parent`
10+
11+
Specifies the DOM element to append the new element to.
12+
13+
```javascript
14+
const myContainer = document.getElementById("container");
15+
h.div({ parent: myContainer }, "I am appended to #container");
16+
```
17+
18+
### `resetParent`
19+
20+
If `true`, clears the `parent` element's content before appending the new element.
21+
22+
```javascript
23+
h.div({ parent: myContainer, resetParent: true }, "I am the only child now");
24+
```
25+
26+
### `replaceParent`
27+
28+
If `true`, the new element **replaces** the `parent` element in the DOM.
29+
30+
```javascript
31+
h.div(
32+
{ parent: oldElement, replaceParent: true },
33+
"I replaced the old element",
34+
);
35+
```
36+
37+
### `firstOfParent`
38+
39+
If `true`, prepends the element to the `parent` instead of appending.
40+
41+
```javascript
42+
h.div({ parent: myContainer, firstOfParent: true }, "I am first!");
43+
```
44+
45+
## Component Wrapper Attributes
46+
47+
When a class component is rendered, it is wrapped in a custom element (e.g., `<ojs-my-component>`). You can pass attributes to this wrapper from within the component's render method or when using the component.
48+
49+
### `c_attr` (Component Attributes)
50+
51+
Used to pass a group of attributes to the component's wrapper.
52+
53+
```javascript
54+
// Inside a component's render method
55+
render() {
56+
return h.div(
57+
{
58+
c_attr: {
59+
class: "my-component-wrapper",
60+
"data-theme": "dark",
61+
onclick: "console.log('Wrapper clicked')"
62+
}
63+
},
64+
"Component Content"
65+
);
66+
}
67+
// Renders: <ojs-comp class="my-component-wrapper" data-theme="dark" ...><div>...</div></ojs-comp>
68+
```
69+
70+
### `$` Prefix Attributes
71+
72+
Attributes starting with `$` are treated as wrapper attributes. This is a shorthand for `c_attr`.
73+
74+
```javascript
75+
h.MyComponent({
76+
$class: "theme-dark",
77+
$id: "main-component",
78+
$style: "border: 1px solid red;",
79+
});
80+
// Renders: <ojs-my-component class="theme-dark" id="main-component" style="...">...</ojs-my-component>
81+
```
82+
83+
### `withCAttr`
84+
85+
If set to `true`, it serializes the `c_attr` object and adds it as a `c-attr` attribute on the element. This is mostly used internally or for debugging.
86+
87+
```javascript
88+
h.div({ c_attr: { id: 1 }, withCAttr: true }, "...");
89+
// <div c-attr='{"id":1}'>...</div>
90+
```

0 commit comments

Comments
 (0)