# presentation/renderers/record.renderer.ts

> 129 lines of code and 23 definitions.

Tree: Bane's Lab Site
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-renderers-record-renderer-ts
Source text: https://banes-lab.com/source/tree/presentation/renderers/record.renderer.ts.txt

Listed in [presentation/renderers](https://banes-lab.com/api/source/tree/presentation/renderers.md), after [presentation/renderers/panel.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/panel.renderer.ts.md) and before [presentation/renderers/schema.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/schema.renderer.ts.md).

## Definitions

- `linkOf` (lexical_declaration, line 26)
- `fieldOf` (lexical_declaration, line 30)
- `renderRecordRow` (lexical_declaration, line 117, exported)
- `renderDetails` (lexical_declaration, line 93, exported)
- `placementOf` (lexical_declaration, line 48)
- `renderRecordBody` (lexical_declaration, line 60, exported)
- `groupOf` (lexical_declaration, line 34)
- `renderChips` (lexical_declaration, line 84)
- `markupOf` (lexical_declaration, line 41)
- `pageLink` (lexical_declaration, line 112)
- `sentence` (lexical_declaration, line 52)
- `layer` (lexical_declaration, line 61, exported)
- `aliases` (lexical_declaration, line 62, exported)
- `haystack` (lexical_declaration, line 80)
- `list` (lexical_declaration, line 85)
- `details` (lexical_declaration, line 94, exported)
- `text` (lexical_declaration, line 113)
- `{ id, title }` (lexical_declaration, line 123, exported)
- `page` (lexical_declaration, line 124, exported)
- `name` (lexical_declaration, line 125, exported)
- `header` (lexical_declaration, line 126, exported)
- `children` (lexical_declaration, line 133, exported)
- `search` (lexical_declaration, line 134, exported)

## Contained in

- [presentation/renderers](https://banes-lab.com/anatomy/tree/folder-presentation-renderers.md)

## Uses

- [configuration/strings/reference.strings.ts](https://banes-lab.com/source/tree/configuration/strings/reference.strings.ts.md)
- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [core/formatters/link.formatter.ts](https://banes-lab.com/source/tree/core/formatters/link.formatter.ts.md)
- [core/normalizers/markdown.normalizer.ts](https://banes-lab.com/source/tree/core/normalizers/markdown.normalizer.ts.md)
- [core/predicates/document.predicate.ts](https://banes-lab.com/source/tree/core/predicates/document.predicate.ts.md)
- [presentation/renderers/text.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/text.renderer.ts.md)

## Used by

- [presentation/renderers/ontology.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/ontology.renderer.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/tree/folder-configuration-strings.md)
- [core/factories](https://banes-lab.com/anatomy/tree/folder-core-factories.md)
- [core/formatters](https://banes-lab.com/anatomy/tree/folder-core-formatters.md)
- [core/normalizers](https://banes-lab.com/anatomy/tree/folder-core-normalizers.md)
- [core/predicates](https://banes-lab.com/anatomy/tree/folder-core-predicates.md)
- [presentation/renderers](https://banes-lab.com/anatomy/tree/folder-presentation-renderers.md)

## Source

```typescript
import {
    ALIASES_LABEL,
    DETAILS_LABEL,
    KIND_LABEL,
    LABEL_SEPARATOR,
    LAYER_LABEL,
    LIST_SEPARATOR,
    recordLinkText,
} from "#configuration/strings/ontology.strings";
import { RECORD_CLASS, SEARCH_ATTRIBUTE } from "#configuration/constants/ontology.constants";
import type { RecordGroup, RecordLink, RecordPlacement, RecordSubject } from "#types/page.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { fillMarkup, plainText } from "#presentation/renderers/text.renderer";
import type { ElementChild } from "#types/element.types";
import type { RowIdentity } from "#types/ontology.types";
import { SPACE } from "#configuration/constants/document.constants";
import { TOGGLE_EVENT } from "#configuration/constants/element.constants";
import { anchorMarkup } from "#core/formatters/link.formatter";
import { escaped } from "#core/normalizers/markdown.normalizer";
import { isGenericLabel } from "#core/predicates/link.predicate";
import { isStaticRender } from "#core/predicates/document.predicate";
import { listedInSentence } from "#configuration/strings/catalog.strings";
import { recordPageOf } from "#domain/converters/ontology.converter";
import { relationLabelOf } from "#configuration/strings/reference.strings";

const linkOf = function linkOf(link: RecordLink): ElementChild {
    return link.path === null ? link.label : createElement("a", { attributes: { href: link.path }, text: link.label });
};

const fieldOf = function fieldOf(label: string, value: readonly ElementChild[]): Element {
    return createElement("p", { children: [label + LABEL_SEPARATOR, ...value], className: "record-field" });
};

const groupOf = function groupOf(group: RecordGroup): readonly Element[] {
    return [
        createElement("h2", { text: relationLabelOf(group.relation) }),
        createElement("ul", { children: group.links.map((link) => createElement("li", { children: [linkOf(link)] })) }),
    ];
};

const markupOf = function markupOf(link: RecordLink | null): string | null {
    if (link === null) {
        return null;
    }
    return anchorMarkup(link.path, escaped(link.label));
};

const placementOf = function placementOf(placement: RecordPlacement | null): readonly Element[] {
    if (placement === null) {
        return [];
    }
    const sentence = listedInSentence(
        markupOf(placement.up) ?? "",
        markupOf(placement.previous),
        markupOf(placement.next),
    );
    return [fillMarkup(createElement("p", { className: "record-placement" }), sentence)];
};

export const renderRecordBody = function renderRecordBody(subject: RecordSubject): Element {
    const layer = subject.layer === null ? [] : [fieldOf(LAYER_LABEL, [linkOf(subject.layer)])];
    const aliases = subject.aliases.length === 0 ? [] : [fieldOf(ALIASES_LABEL, [subject.aliases.join(LIST_SEPARATOR)])];
    return createElement("article", {
        children: [
            createElement("nav", { children: [linkOf(subject.collection)] }),
            createElement("h1", { text: subject.name }),
            ...(subject.code === null ? [] : [createElement("p", { children: [createElement("code", { text: subject.code })] })]),
            fieldOf(KIND_LABEL, [subject.kind]),
            ...layer,
            ...aliases,
            ...subject.facts.map((fact) => fieldOf(fact.label, [linkOf(fact.value)])),
            ...(subject.summary === null ? [] : [createElement("p", { text: subject.summary })]),
            ...placementOf(subject.placement),
            ...subject.relations.filter((group) => group.links.length > 0).flatMap(groupOf),
        ],
        className: "record-page",
    });
};

const haystack = function haystack(parts: readonly string[]): string {
    return parts.map(plainText).join(SPACE).toLowerCase();
};

const renderChips = function renderChips(items: readonly string[]): Element {
    const list = createElement("ul", { className: "record-chips" });
    appendChildren(
        list,
        items.map((item) => fillMarkup(createElement("li", { className: "record-chip" }), item)),
    );
    return list;
};

export const renderDetails = function renderDetails(label: string, build: () => readonly Element[]): Element {
    const details = createElement("details", {
        children: [createElement("summary", { className: "record-summary", text: label })],
        className: "record-details",
    });
    if (isStaticRender()) {
        appendChildren(details, build());
        return details;
    }
    details.addEventListener(
        TOGGLE_EVENT,
        () => {
            appendChildren(details, build());
        },
        { once: true },
    );
    return details;
};

const pageLink = function pageLink(page: string, row: RowIdentity): Element {
    const text = isGenericLabel(row.title) ? recordLinkText(row.title, row.context) : row.title;
    return createElement("a", { attributes: { href: page }, text });
};

export const renderRecordRow = function renderRecordRow(
    row: RowIdentity,
    chips: readonly string[],
    body: (() => readonly Element[]) | null,
    searchable: readonly string[],
): Element {
    const { id, title } = row;
    const page = isStaticRender() ? recordPageOf(id) : null;
    const name = page === null ? title : pageLink(page, row);
    const header = createElement("header", {
        children: [
            createElement("h3", { children: [name], className: "record-title" }),
            ...(chips.length === 0 ? [] : [renderChips(chips)]),
        ],
        className: "record-header",
    });
    const children = body === null || page !== null ? [header] : [header, renderDetails(DETAILS_LABEL, body)];
    const search = isStaticRender() ? {} : { [SEARCH_ATTRIBUTE]: haystack([title, ...chips, ...searchable]) };
    return createElement("article", {
        attributes: { id, ...search },
        children,
        className: RECORD_CLASS,
    });
};
```
