Master-detail
Display a list of items and show details for the selected item using dcupl queries.
Key dcupl functions
| Function | Purpose |
|---|---|
query.items() |
Get list of items for the master view |
query.one({ itemKey }) |
Get the selected item by key |
query.one({ itemKey, projection }) |
Get the selected item with fields of its referenced items |
query.setCondition() |
Filter a related list (replaces that attribute's condition) |
Example data
The examples use products with a category, and reviews that reference a product:
import { Dcupl } from '@dcupl/core';
const dcupl = new Dcupl();
dcupl.models.set({
key: 'Category',
properties: [{ key: 'name', type: 'string' }],
data: [{ key: 'cat-1', name: 'Electronics' }],
});
dcupl.models.set({
key: 'Product',
properties: [
{ key: 'name', type: 'string' },
{ key: 'price', type: 'float' },
],
references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
data: [
{ key: 'prod-123', name: 'Laptop', price: 999, category: 'cat-1' },
{ key: 'prod-456', name: 'Phone', price: 599, category: 'cat-1' },
],
});
dcupl.models.set({
key: 'Review',
properties: [
{ key: 'rating', type: 'int' },
{ key: 'text', type: 'string' },
],
references: [{ key: 'product', type: 'singleValued', model: 'Product' }],
data: [
{ key: 'r1', rating: 5, text: 'Fast', product: 'prod-123' },
{ key: 'r2', rating: 4, text: 'Good camera', product: 'prod-456' },
{ key: 'r3', rating: 3, text: 'Loud fan', product: 'prod-123' },
],
});
await dcupl.init();Getting the list
Use items() to retrieve all items for the master list.
const list = dcupl.lists.create({ modelKey: 'Product' });
// Get all products for the list view
const products = list.catalog.query.items();Selecting an item
When user selects an item, look it up by its key with one(). This does not change the list's query, so the master view keeps its filters. The selection is your own state: to clear it or select another item, change that state and look up the new key.
function selectItem(itemKey: string) {
// null if the list's query filters the item out (start/count paging is ignored)
return list.catalog.query.one({ itemKey });
}
const selected = selectItem('prod-999');
if (!selected) {
// Not found: show an empty detail view
}Check the result with if (!selected) (or === null): one() returns null, typed T | null, for a missing key or an item the list's query filters out.
To read an item regardless of the list's filters, use the global lookup: dcupl.query.one({ modelKey: 'Product', itemKey }) (returns null if the key does not exist).
A reference comes back as { key }. To show fields of referenced items in the detail view, pass a projection:
const product = list.catalog.query.one({
itemKey: 'prod-123',
projection: { $: true, category: { $: true } },
});
// { key: 'prod-123', name: 'Laptop', price: 999, category: { key: 'cat-1', name: 'Electronics' } }Loading related data
Load associated data for the selected item using a separate list. A condition on a reference matches the referenced item's key:
// Create the related list once and reuse it
const reviewList = dcupl.lists.create({ modelKey: 'Review' });
function getReviews(productKey: string) {
// setCondition replaces the previous condition on 'product'
reviewList.catalog.query.setCondition({
attribute: 'product',
operator: 'eq',
value: productKey,
});
return reviewList.catalog.query.items();
}
getReviews('prod-123'); // r1, r3The same works with a plain string field that holds the key (for example productId), but a reference is indexed, can be projected, and lets dcupl check that the key exists.
Complete flow
// 1. Create list and get all items
const list = dcupl.lists.create({ modelKey: 'Product' });
const allProducts = list.catalog.query.items();
// 2. User clicks on product "prod-123": the detail with its category
const selectedProduct = list.catalog.query.one({
itemKey: 'prod-123',
projection: { $: true, category: { $: true } },
});
// 3. Load related data
const reviewList = dcupl.lists.create({ modelKey: 'Review' });
reviewList.catalog.query.setCondition({
attribute: 'product',
operator: 'eq',
value: 'prod-123',
});
const reviews = reviewList.catalog.query.items(); // r1, r3
// 4. User selects different product - look up the new key and refresh the reviews
const newSelection = list.catalog.query.one({ itemKey: 'prod-456' });
reviewList.catalog.query.setCondition({
attribute: 'product',
operator: 'eq',
value: 'prod-456',
});
const newReviews = reviewList.catalog.query.items(); // r2