---
name: create-list-object
description: >
    Use when creating or modifying .listobject.xml + .listitem.xml file pairs for CG Mobile
    modeler contracts. Trigger phrases: "create an LO", "list object for X", "list of Xs",
    ".listobject.xml", "list screen for X", "add a list to Y".
    Invokes verify-sqlite-schema first for every table/column the companion DS references.
    NOT for cockpit cards (use add-cockpit-card workflow) or reference lookups (use
    create-lookup-object).
---

# Create ListObject — CG Mobile Modeler

A ListObject (LO) represents a **collection of rows** for display, selection, or filtering. Each row's shape is defined in a companion ListItem (LI) file. Together they form a typed, DS-backed collection that a Process can load, a BO can own as a child, or a card method can query via `Facade.getListAsync`.

LOs are for **lists of records**. If the use case is a single record's detail screen, use `create-business-object`. If the use case is a reference registry (product lookup, category validator), use `create-lookup-object`. If the use case is a cockpit card, wire an LO into the cockpit card using the `add-cockpit-card` workflow skill.

## When to Use This Skill

| Invoked by      | Phrase                                                                                    |
| --------------- | ----------------------------------------------------------------------------------------- |
| User directly   | "create an LO", "list object for X", "list of Xs", ".listobject.xml", "list screen for X" |
| Workflow skills | `add-list-screen` (for the backing LO+LI), `add-cockpit-card` (for a card's item list)    |

NOT for:

-   Cockpit card layout or Process wiring — use `add-cockpit-card`
-   Reference lookups (cached registries) — use `create-lookup-object`
-   Single-record detail screens — use `create-business-object`
-   Build errors in an existing LO — use `build-and-simulate`

---

## Prerequisites

**Invoke `verify-sqlite-schema` for every table/column the companion DS references BEFORE writing the LO or DS file. Stop if any column is missing and cannot be remediated.**

```bash
# Quick manual check:
sqlite3 appl/data/app.db3 "PRAGMA table_info(Visit);"
sqlite3 appl/data/app.db3 "PRAGMA table_info(Account);"
```

Context-menu LOs (DS returns `undefined`) are exempt — they touch no DB columns.

---

## Mandatory Checklist

-   [ ] DS exists and has been verified via `verify-sqlite-schema` (or DS `external="true"` for context menus)
-   [ ] LO name starts with `Lo<Name>`; both files live in `BO/Lo<Name>/`
-   [ ] LI name starts with `Li<Name>` and lives in the same folder as the LO
-   [ ] `<Item objectClass="Li<Name>"/>` in the LO matches the LI's `name` attribute exactly
-   [ ] LI has exactly one `<SimpleProperty id="true"/>` (the pKey)
-   [ ] All LI `dataSourceProperty` values match real `<Attribute name="...">` entries in the DS
-   [ ] `generateLoadMethod="true"` on LO (exception: context menus and fully custom loaders use `"false"`)

---

## Failure Modes

### Failure mode 1: UI list shows empty rows

**Symptom:** The list screen renders rows but every cell shows `undefined`. No build error.
**Diagnosis:** LI `<SimpleProperty>` has a `dataSourceProperty` that doesn't match any `<Attribute name="...">` in the DS. The mapping is case-sensitive.
**Fix:** Open the DS file, list all `<Attribute name="...">` values, and verify each LI `dataSourceProperty` matches exactly.

```xml
<!-- WRONG — "visitname" vs "visitName" -->
<SimpleProperty name="visitName" type="DomText" dataSourceProperty="visitname"/>

<!-- CORRECT -->
<SimpleProperty name="visitName" type="DomText" dataSourceProperty="visitName"/>
```

---

### Failure mode 2: `getListAsync` returns an empty array

**Symptom:** `Facade.getListAsync("LoVisit", jsonQuery)` resolves with `[]` even though DB rows exist.
**Diagnosis:** A required DS parameter is missing from `jsonQuery.params`, so the `<QueryCondition>` contains an unbound `#paramName#` that silently evaluates to empty and returns no rows.
**Fix:** Check every `#placeholder#` in the DS `<QueryCondition>`. Each must have a matching `{field: "placeholder", value: ...}` entry in `jsonQuery.params`. See `references/facade-getlist.md` for the full parameter flow.

---

### Failure mode 3: LO/LI name mismatch — build error

**Symptom:** `sf mdl build` fails with a class-not-found or objectClass resolution error.
**Diagnosis:** `<Item objectClass="LiWrongName"/>` in the LO doesn't match the `name` attribute on the LI root element, or the LI file is in a different folder.
**Fix:** Ensure `<Item objectClass="Li<Name>"/>` exactly matches `<ListItem name="Li<Name>">`, and both files are in `BO/Lo<Name>/`.

---

### Failure mode 4: Context-menu LO shows 0 items

**Symptom:** Opening the context menu shows nothing. The LO exists and BL code exists.
**Diagnosis:** The `<Method name="beforeLoadAsync"/>` declaration is missing from the LO's `<Methods>` block. The framework won't call the BL method if it isn't declared.
**Fix:** Add the declaration:

```xml
<Methods>
  <Method name="beforeLoadAsync"/>
</Methods>
```

Also verify the BL method calls `me.removeAllItems()` before `me.addItem(...)`.

---

### Failure mode 5: Duplicate rows in the list

**Symptom:** The same record appears two or more times.
**Diagnosis:** (a) DS `<QueryCondition>` produces duplicate rows due to a cross-product in `<Entities>` (missing join condition), or (b) the LI `pKey` isn't bound to a truly unique DS attribute (e.g., bound to a non-unique field).
**Fix (a):** Audit the `<Entities>` block — every entity beyond the first must have a `<Join>` element with a `<SimpleJoin><Condition>` that constrains the relationship.
**Fix (b):** Bind the LI `id="true"` property to the Salesforce object's `Id` field via `dataSourceProperty="pKey"`, and confirm the DS attribute maps to `column="Id"`.

---

## Minimal Template (inline preview)

Full templates at `templates/basic-list.listobject.xml.template` and `templates/basic-list.listitem.xml.template`. Preview:

```xml
<!-- Lo<NAME>.listobject.xml -->
<ListObject name="Lo<NAME>" generateLoadMethod="true" schemaVersion="1.1" paging="false">
  <DataSource name="DsLo<NAME>" />
  <Item objectClass="Li<NAME>" />
  <Methods>
    <Method name="beforeLoadAsync" />
    <Method name="afterLoadAsync" />
    <Method name="loadAsync" />
  </Methods>
</ListObject>

<!-- Li<NAME>.listitem.xml -->
<ListItem name="Li<NAME>">
  <SimpleProperties>
    <SimpleProperty id="true" name="pKey" type="DomPKey" dataSourceProperty="pKey" />
    <SimpleProperty name="name" type="DomText" dataSourceProperty="name" />
    <SimpleProperty name="status" type="DomText" dataSourceProperty="status" />
  </SimpleProperties>
</ListItem>
```

---

## Common Variants

### Variant 1 — Basic list for a screen (records from DB)

Use when: a list screen shows records (visits for today, orders by account).

Templates: `templates/basic-list.listobject.xml.template` + `templates/basic-list.listitem.xml.template`

Key settings:

-   `generateLoadMethod="true"`, `paging="false"` for bounded lists; `paging="true"` for open-ended catalogs
-   DS must be declarative (`external="false"`) or scripted (`external="true"`) — both work
-   `loadAsync` + `beforeLoadAsync` + `afterLoadAsync` declared in LO `<Methods>`

Real-file anchor:

-   `src/Visit/BO/LoVisit/LoVisit.listobject.xml`
-   `src/Visit/BO/LoVisit/LiVisit.listitem.xml`

---

### Variant 2 — Context menu (BL-populated, no DB query)

Use when: a right-click / action menu appears on a selected item, with rows built by BL code.

Template: `templates/context-menu.listobject.xml.template`

Key settings:

-   Companion DS: `external="true"`, `Load` block returns `undefined`
-   LO: `generateLoadMethod="false"`
-   LO `<Methods>` includes `<Method name="beforeLoadAsync"/>` — this BL method populates items
-   LI properties: `pKey`, `id`, `actionImg`, `actionId`, `processEvent`, `actionEnabled`, `actionVisible`

Real-file anchors:

-   `src/Visit/BO/LoKPIContextMenu/LoKPIContextMenu.listobject.xml`
-   `src/Visit/BO/LoKPIContextMenu/LiKPIContextMenu.listitem.xml`
-   `src/Visit/BO/LoAttachmentsContextMenu/LoAttachmentsContextMenu.listobject.xml`

---

### Variant 3 — Quick filter (small LO driving a filter dropdown)

Use when: a filter tab strip or dropdown above a list screen needs a small set of options (categories, statuses).

Template: start from `templates/basic-list.listobject.xml.template` but add `generateLoadMethod="false"` and a `createAsync` method.

Key settings:

-   `paging="false"` (always small)
-   `generateLoadMethod="false"` — the `createAsync` BL injects an "All" option then queries the DB
-   LI typically has: `pKey`, `text`, `isSelected` (`DomFilterBool`), optionally `specialOption`

Real-file anchors:

-   `src/Visit/BO/LoProductCategoryQuickFilter/LoProductCategoryQuickFilter.listobject.xml`
-   `src/Visit/BO/LoProductCategoryQuickFilter/LiProductCategoryQuickFilter.listitem.xml`

---

## Decision Table

| User wants...                                          | Use template                                        | `generateLoadMethod` | `paging` |
| ------------------------------------------------------ | --------------------------------------------------- | -------------------- | -------- |
| List of records loaded from DB (visit list, task list) | `basic-list` pair                                   | `true`               | `false`  |
| Large open-ended catalog with scroll-for-more          | `basic-list` pair                                   | `true`               | `true`   |
| Right-click / action context menu                      | `context-menu` template                             | `false`              | omit     |
| Filter dropdown or tab strip above a list              | `basic-list` pair + custom `createAsync` BL         | `false`              | `false`  |
| Child list inside a parent BO (visit → tasks)          | `basic-list` pair wired into BO via `<ListObjects>` | `true`               | `false`  |

---

## File and Folder Layout

```
src/<Module>/
├── DS/
│   └── DsLo<Name>_sf.datasource.xml       ← companion DS (create first via create-datasource)
└── BO/
    └── Lo<Name>/
        ├── Lo<Name>.listobject.xml         ← this file
        ├── Li<Name>.listitem.xml           ← row model (same folder, always)
        └── Mv2/
            ├── LoadAsync/
            │   ├── Lo<Name>.BeforeLoadAsync.bl.js
            │   └── Lo<Name>.AfterLoadAsync.bl.js
            └── Lo<Name>.GetItemsForCard.bl.js   ← custom methods
```

All `.bl.js` files under `Mv2/` must use `@namespace CUSTOM` in their JSDoc block.

---

## Real LO Structure (LoVisit summary)

From `src/Visit/BO/LoVisit/LoVisit.listobject.xml`:

```xml
<ListObject name="LoVisit" generateLoadMethod="true" schemaVersion="1.1" paging="false">
  <DataSource name="DsLoVisit" />
  <Item objectClass="LiVisit" />
  <Methods>
    <Method name="beforeSaveAsync" />
    <Method name="afterSaveAsync" />
    <Method name="afterLoadAsync" />
    <Method name="beforeLoadAsync" />
    <Method name="afterDoValidateAsync" />
    <Method name="beforeDoValidateAsync" />
    <Method name="loadAsync" />
    <Method name="saveAsync" />
    <Method name="getCalendarTitle" />
    <Method name="getVisitsByDate" />
    <Method name="getDailyViewTitle" />
    <Method name="getRetailStoreId" />
    <Method name="navigateToCustomer" />
    <Method name="prepareMapDetails" />
  </Methods>
</ListObject>
```

Key observations:

-   Root element: `<ListObject name="..." generateLoadMethod="true" schemaVersion="1.1" paging="false">` — exact attribute order matters for readability; `schemaVersion="1.1"` (NOT `2.0`).
-   No `<SimpleProperties>` in the LO — those live in `LiVisit.listitem.xml`.
-   All custom methods (non-lifecycle) are declared alongside lifecycle methods.
-   The `<DataSource name="DsLoVisit"/>` reference must match the DS file's `name` attribute exactly.

---

## Real LI Structure (LiVisit summary)

From `src/Visit/BO/LoVisit/LiVisit.listitem.xml`:

```xml
<ListItem name="LiVisit">
  <SimpleProperties>
    <SimpleProperty id="true" name="pKey" type="DomPKey" dataSourceProperty="pKey" />
    <SimpleProperty name="visitStatus" type="DomVisitStatus" dataSourceProperty="visitStatus"/>
    <SimpleProperty name="plannedStartDateTime" type="DomDateTime" dataSourceProperty="plannedVisitStartTime"/>
    <SimpleProperty name="retailStoreName" type="DomText" dataSourceProperty="retailStoreName"/>
    <!-- datetime-split examples: -->
    <SimpleProperty name="plannedStartDate" type="DomDate" id="false" />
    <SimpleProperty name="plannedStartTime" type="DomTime" id="false" />
    <!-- computed (no dataSourceProperty): -->
    <SimpleProperty name="visibleInMap" type="DomBool" />
    <!-- map properties: -->
    <SimpleProperty name="latitude" type="DomDegree" dataSourceProperty="latitude"/>
    <SimpleProperty name="longitude" type="DomDegree" dataSourceProperty="longitude"/>
    <SimpleProperty name="color" type="DomRgbColor" storable="true" dataSourceProperty="color"/>
  </SimpleProperties>
</ListItem>
```

Key observations:

-   Domain-specific types (`DomVisitStatus`, `DomDegree`, `DomRgbColor`) are used where available.
-   `storable="true"` appears on `color` — this LO supports saving individual item colors.
-   Properties without `dataSourceProperty` (e.g., `visibleInMap`, split date/time fields) are computed in `afterLoadAsync`.

---

## Wiring an LO as a Child of a BO

When a parent BO owns a child list (e.g., a Visit owns a list of tasks), wire it in the BO's `.businessobject.xml`:

```xml
<ListObjects>
  <ListObject name="loAssessmentTasks"
              objectClass="LoVisitAssessmentTask"
              dataSourceProperty="pKey"
              listProperty="visitId"
              loadMode="LoadImmediate"/>
</ListObjects>
```

-   `dataSourceProperty="pKey"` — the parent BO's key to pass to the child DS.
-   `listProperty="visitId"` — the `<Parameter name="visitId">` in the child LO's DS.
-   `loadMode="LoadImmediate"` — loads when the parent BO loads. Use `onDemand` to lazy-load.

---

## Escape Hatch

When the above isn't enough, consult in order:

1. `references/lo-vs-li.md` — LO vs LI responsibilities, `generateLoadMethod`, `paging`, file colocation, attribute reference
2. `references/facade-getlist.md` — `Facade.getListAsync` usage, `jsonQuery` structure, parameter flow, real BL example
3. `references/context-menus-and-quick-filters.md` — empty DS pattern, BL item building, quick filter shape, failure modes
4. `ai-wiki/wiki/list-objects.md` (if present) — full 409-line wiki page with Process wiring, collection ops, best practices

Real-file anchors:

-   Basic LO+LI: `src/Visit/BO/LoVisit/LoVisit.listobject.xml` + `LiVisit.listitem.xml`
-   Context menu: `src/Visit/BO/LoKPIContextMenu/LoKPIContextMenu.listobject.xml`
-   Quick filter: `src/Visit/BO/LoProductCategoryQuickFilter/LoProductCategoryQuickFilter.listobject.xml`
-   Map-backed LO: `src/Visit/BO/LoVisitMap/LoVisitMap.listobject.xml`

---

## Verify

After writing the LO, LI, DS, and BL files, run a build:

```bash
# cd to your workspace root
sf mdl build 2>&1 | tail -20
```

Then load the owning screen or trigger the owning Process in the simulator:

```bash
sf mdl simulate
# Open devtools → Console tab
# Look for: undefined property values, "is not a function" errors, SQLITE_ERROR in queries
# Navigate to the list screen / trigger the card load that invokes this LO
```

A clean build and rows appearing in the list with populated properties confirms the LO+LI pair is correctly wired.
