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

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.