Advanced
ContentBody and ContentBlock
A body that several blocks fill together; the ContentBody body, ContentBlock parts, piling up, disposal and how it differs from Transport.
Transport shows a single sender's content in a slot; ContentBody, on the other hand, is a body that several blocks fill together. A body is opened in the layout, blocks in different places on the page add their parts to it, and the parts pile up in the body.
Usage
In the example the layout's bottom bar opens a body; the folder list adds the total number of notes to it, and the note page adds the edit time of the open note. The body:
import { Component, ContentBody } from "@motifx/core";
export class StatusBar extends Component<HTMLElement> {
static override elementTag = "footer";
override onConfigured() {
this.class.add("statusbar");
}
override view() {
return <ContentBody name="status" />;
}
}
import { Component, ContentBody } from "@motifx/core";
export class StatusBar extends Component<HTMLElement> {
static override elementTag = "footer";
override onConfigured() {
this.class.add("statusbar");
}
override view() {
return new ContentBody({ name: "status" });
}
}
import { ContentBody } from "@motifx/core";
export function StatusBar() {
return (
<footer class="statusbar">
<ContentBody name="status" />
</footer>
);
}
import { ComponentBase, ContentBody, motifComponent } from "@motifx/core";
export function StatusBar() {
return motifComponent("footer", (footer: ComponentBase) => {
footer.class.add("statusbar");
footer.controls.add(new ContentBody({ name: "status" }));
});
}
import { ContentBody, motifOptions } from "@motifx/core";
export const StatusBar = () => motifOptions({
el: "footer",
onConfigured() {
this.class.add("statusbar");
},
view() {
return <ContentBody name="status" />;
},
});
import { ContentBody, motifOptions } from "@motifx/core";
export const StatusBar = () => motifOptions({
el: "footer",
onConfigured() {
this.class.add("statusbar");
},
view() {
return new ContentBody({ name: "status" });
},
});
import { Component, ContentBody } from "@motifx/core";
export class StatusBar extends Component {
static elementTag = "footer";
onConfigured() {
this.class.add("statusbar");
}
view() {
return <ContentBody name="status" />;
}
}
import { Component, ContentBody } from "@motifx/core";
export class StatusBar extends Component {
static elementTag = "footer";
onConfigured() {
this.class.add("statusbar");
}
view() {
return new ContentBody({ name: "status" });
}
}
import { ContentBody } from "@motifx/core";
export function StatusBar() {
return (
<footer class="statusbar">
<ContentBody name="status" />
</footer>
);
}
import { ContentBody, motifComponent } from "@motifx/core";
export function StatusBar() {
return motifComponent("footer", (footer) => {
footer.class.add("statusbar");
footer.controls.add(new ContentBody({ name: "status" }));
});
}
import { ContentBody, motifOptions } from "@motifx/core";
export const StatusBar = () => motifOptions({
el: "footer",
onConfigured() {
this.class.add("statusbar");
},
view() {
return <ContentBody name="status" />;
},
});
import { ContentBody, motifOptions } from "@motifx/core";
export const StatusBar = () => motifOptions({
el: "footer",
onConfigured() {
this.class.add("statusbar");
},
view() {
return new ContentBody({ name: "status" });
},
});
The blocks:
import { Component, ContentBlock } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class CountStatus extends Component {
notes = this.getService(NotesService)!;
override view() {
return (
<ContentBlock target="status">
<span>{() => `${this.notes.count("notes")} notes`}</span>
</ContentBlock>
);
}
}
export class EditedStatus extends Component<any, { note: Note }> {
override view() {
const { note } = this.props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
}
}
import { Component, ComponentBase, ContentBlock, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class CountStatus extends Component {
notes = this.getService(NotesService)!;
override view() {
const count = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `${this.notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
}
}
export class EditedStatus extends Component<any, { note: Note }> {
override view() {
const { note } = this.props;
const edited = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
}
}
import { ContentBlock, FromService } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function CountStatus() {
const notes = FromService(NotesService)!;
return (
<ContentBlock target="status">
<span>{() => `${notes.count("notes")} notes`}</span>
</ContentBlock>
);
}
export function EditedStatus(props: { note: Note }) {
const { note } = props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
}
import { ComponentBase, ContentBlock, FromService, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function CountStatus() {
const notes = FromService(NotesService)!;
const count = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `${notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
}
export function EditedStatus(props: { note: Note }) {
const { note } = props;
const edited = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
}
import { ContentBlock, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const CountStatus = () => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService)!;
return (
<ContentBlock target="status">
<span>{() => `${notes.count("notes")} notes`}</span>
</ContentBlock>
);
},
});
export const EditedStatus = (props: { note: Note }) => motifOptions({
el: null,
view() {
const { note } = props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
},
});
import { ComponentBase, ContentBlock, motifComponent, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const CountStatus = () => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService)!;
const count = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `${notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
},
});
export const EditedStatus = (props: { note: Note }) => motifOptions({
el: null,
view() {
const { note } = props;
const edited = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
},
});
import { Component, ContentBlock } from "@motifx/core";
import { NotesService } from "../services/notes";
export class CountStatus extends Component {
notes = this.getService(NotesService);
view() {
return (
<ContentBlock target="status">
<span>{() => `${this.notes.count("notes")} notes`}</span>
</ContentBlock>
);
}
}
export class EditedStatus extends Component {
view() {
const { note } = this.props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
}
}
import { Component, ContentBlock, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";
export class CountStatus extends Component {
notes = this.getService(NotesService);
view() {
const count = motifComponent("span", (span) => span.bindings.text(() => `${this.notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
}
}
export class EditedStatus extends Component {
view() {
const { note } = this.props;
const edited = motifComponent("span", (span) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
}
}
import { ContentBlock, FromService } from "@motifx/core";
import { NotesService } from "../services/notes";
export function CountStatus() {
const notes = FromService(NotesService);
return (
<ContentBlock target="status">
<span>{() => `${notes.count("notes")} notes`}</span>
</ContentBlock>
);
}
export function EditedStatus(props) {
const { note } = props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
}
import { ContentBlock, FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";
export function CountStatus() {
const notes = FromService(NotesService);
const count = motifComponent("span", (span) => span.bindings.text(() => `${notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
}
export function EditedStatus(props) {
const { note } = props;
const edited = motifComponent("span", (span) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
}
import { ContentBlock, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
export const CountStatus = () => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService);
return (
<ContentBlock target="status">
<span>{() => `${notes.count("notes")} notes`}</span>
</ContentBlock>
);
},
});
export const EditedStatus = (props) => motifOptions({
el: null,
view() {
const { note } = props;
return (
<ContentBlock target="status">
<span>{() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`}</span>
</ContentBlock>
);
},
});
import { ContentBlock, motifComponent, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
export const CountStatus = () => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService);
const count = motifComponent("span", (span) => span.bindings.text(() => `${notes.count("notes")} notes`));
return motifComponent(ContentBlock, { target: "status", childs: [count] });
},
});
export const EditedStatus = (props) => motifOptions({
el: null,
view() {
const { note } = props;
const edited = motifComponent("span", (span) => span.bindings.text(() => `Edited ${new Date(note.updatedAt).toLocaleString("en-US")}`));
return motifComponent(ContentBlock, { target: "status", childs: [edited] });
},
});
The layout places the bottom bar with <StatusBar />, the folder list with <CountStatus />, and the note page with <EditedStatus note={note} />.
The layout places the bottom bar with new StatusBar(), the folder list with new CountStatus(), and the note page with new EditedStatus({ note }).
The layout places the bottom bar with motifComponent(StatusBar), the folder list with motifComponent(CountStatus), and the note page with motifComponent(EditedStatus, { note }).
ContentBody and ContentBlock are classes of the core. Without JSX, the body is built with new ContentBody({ name }), and a block, as the compiler would produce it, with motifComponent(ContentBlock, { target, childs }).
While the note page is open, two parts sit side by side in the bar: the number of notes and the edit time. Both are updated by the binding of the component they were written in. When the page closes, only its part goes away; the folder list's part stays.
Props
| Component | Prop | Description |
|---|---|---|
ContentBody |
name |
The body's name. If a second body with the same name is set up, new blocks go to it; disposing the old body later does not spoil the new body's registration. |
ContentBlock |
target |
The name of the body the parts are added to. |
Behaviour
- Both are fragment-rooted; they add no element where they are.
- While the target body exists, a block does not build its children itself: every child added (the
childsgiven with the block, and those that arrive later withcontrols.add) is moved to the body'scontrolsat that moment and built there; its parent becomes the body.childsare moved in the block's constructor, before the block is built. The block'scontroladdedevent fires for each child. - If the body does not exist yet, the children the block has while it is being built are built together with the block and appear in the block's place; those added with
controls.addafter the block is built wait without being built. The moment a body with the same name is created, all of them are moved to the body; the final placement does not depend on the order. - Parts pile up in the body; each block knows only the parts it added itself. When a block is disposed, it removes and disposes its own parts in the body (in development mode an
MJX109warning is written for each part); the other blocks' parts stay. - When the body is disposed, all the parts inside it are disposed with it; the blocks are not informed, and even if a new body with the same name is set up, the parts do not come back.
- The bindings and event handlers of the moved parts keep working with the state of the component they were written in.
- The body and the blocks communicate through the application's events, so they are used inside an
Applicationthat has been set up. A body created before the application is set up gives an error.
Comparison with Transport
Transport / TransportTo |
ContentBody / ContentBlock |
|
|---|---|---|
| Purpose | Showing a sender's content in a slot | Piling up the parts of many blocks in a body |
| Several sources | mode: 'replace' keeps the last one; 'merge' combines them |
Always piles up |
| When the sender is disposed | Removes its own content | Removes its own parts (MJX109) |
| Placement | The slot is a fragment, the sender leaves an empty <div> |
Both are fragments |
Next step
To show large lists with virtual scrolling, go on to Virtual Lists (Virtualization). For a single-sender slot and moving in code, see Transport and TransportTo; for the basics of the component tree, Components.