Skip to content

Commit b5a45fa

Browse files
committed
Merge branch 'list-view'
2 parents bfdc8c2 + 48f48a6 commit b5a45fa

15 files changed

Lines changed: 923 additions & 158 deletions

File tree

docs/docs/code-views/data-array.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: Data Arrays
3+
sidebar_label: Data Arrays
4+
sidebar_position: 500
5+
---
6+
7+
To make common data manipuation operations simple, datacore provides the `DataArray` abstraction, which is a [proxied](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) wrapper around a regular list with a large set of additional functions.
8+
9+
## Creation
10+
11+
Data arrays are mainly present in two places.
12+
13+
### Via `dc.useArray`
14+
15+
The most common route for using data arrays is the `dc.useArray` hook, which takes in a regular array
16+
of data, converts it to a data array, performs operations on it, and then converts it back to a regular array:
17+
18+
```js
19+
return function View() {
20+
// Start with some data you want to process...
21+
const books = dc.useQuery("#books and @page");
22+
// Use `dc.useArray` to get a data array for processing.
23+
const groupedBooks = dc.useArray(books, array =>
24+
array.sort(book => book.$name)
25+
.groupBy(book => book.value("genre")));
26+
27+
// Then render it.
28+
return <dc.List rows={groupedBooks} />;
29+
}
30+
```
31+
32+
### Via `dc.array`
33+
34+
You can also directly make `DataArray`s via the utility function `dc.array(data)`, which accepts a
35+
regular array as input and produces a data array.
36+
37+
```js
38+
return function View() {
39+
// da is a `DataArray`.
40+
const da = dc.array([1, 2, 3]);
41+
// da2 is still a `DataArray`.
42+
const da2 = da.map(x => x + 4).limit(2);
43+
44+
// To get a regular array back, use `.array()`.
45+
const data = da2.array();
46+
}
47+
```
48+
49+
## Indexing and Swizzling
50+
51+
Data arrays support regular indexing just like normal arrays (like `array[0]`), but importantly, they also support
52+
query-language-style "swizzling": if you index into a data array with a field name (like `array.field`), it
53+
automatically maps every element in the array to `field`, flattening `field` if it itself is also an array.
54+
55+
```js
56+
const data = dc.array(dc.query("#books and @page"));
57+
58+
data.$name // => List of all book names.
59+
data.$ctime // => List of all book created times.
60+
```
61+
62+
## Raw Interface
63+
64+
The full interface for the data array implementation is provided below for reference:
65+
66+
```ts
67+
/** A function which maps an array element to some value. */
68+
export type ArrayFunc<T, O> = (elem: T, index: number, arr: T[]) => O;
69+
70+
/** A function which compares two types. */
71+
export type ArrayComparator<T> = (a: T, b: T) => number;
72+
73+
/**
74+
* Proxied interface which allows manipulating array-based data. All functions on a data array produce a NEW array
75+
* (i.e., the arrays are immutable).
76+
*/
77+
export interface DataArray<T> {
78+
/** The total number of elements in the array. */
79+
length: number;
80+
81+
/** Filter the data array down to just elements which match the given predicate. */
82+
where(predicate: ArrayFunc<T, boolean>): DataArray<T>;
83+
/** Alias for 'where' for people who want array semantics. */
84+
filter(predicate: ArrayFunc<T, boolean>): DataArray<T>;
85+
86+
/** Map elements in the data array by applying a function to each. */
87+
map<U>(f: ArrayFunc<T, U>): DataArray<U>;
88+
/** Map elements in the data array by applying a function to each, then flatten the results to produce a new array. */
89+
flatMap<U>(f: ArrayFunc<T, U[]>): DataArray<U>;
90+
/** Mutably change each value in the array, returning the same array which you can further chain off of. */
91+
mutate(f: ArrayFunc<T, any>): DataArray<any>;
92+
93+
/** Limit the total number of entries in the array to the given value. */
94+
limit(count: number): DataArray<T>;
95+
/**
96+
* Take a slice of the array. If `start` is undefined, it is assumed to be 0; if `end` is undefined, it is assumed
97+
* to be the end of the array.
98+
*/
99+
slice(start?: number, end?: number): DataArray<T>;
100+
/** Concatenate the values in this data array with those of another iterable / data array / array. */
101+
concat(other: Iterable<T>): DataArray<T>;
102+
103+
/** Return the first index of the given (optionally starting the search) */
104+
indexOf(element: T, fromIndex?: number): number;
105+
/** Return the first element that satisfies the given predicate. */
106+
find(pred: ArrayFunc<T, boolean>): T | undefined;
107+
/** Find the index of the first element that satisfies the given predicate. Returns -1 if nothing was found. */
108+
findIndex(pred: ArrayFunc<T, boolean>, fromIndex?: number): number;
109+
/** Returns true if the array contains the given element, and false otherwise. */
110+
includes(element: T): boolean;
111+
112+
/**
113+
* Return a string obtained by converting each element in the array to a string, and joining it with the
114+
* given separator (which defaults to ', ').
115+
*/
116+
join(sep?: string): string;
117+
118+
/**
119+
* Return a sorted array sorted by the given key; an optional comparator can be provided, which will
120+
* be used to compare the keys in leiu of the default dataview comparator.
121+
*/
122+
sort<U>(key: ArrayFunc<T, U>, direction?: "asc" | "desc", comparator?: ArrayComparator<U>): DataArray<T>;
123+
124+
/**
125+
* Return an array where elements are grouped by the given key; the resulting array will have objects of the form
126+
* { key: <key value>, rows: DataArray }.
127+
*/
128+
groupBy<U>(key: ArrayFunc<T, U>, comparator?: ArrayComparator<U>): DataArray<{ key: U; rows: DataArray<T> }>;
129+
130+
/**
131+
* Return distinct entries. If a key is provided, then rows with distinct keys are returned.
132+
*/
133+
distinct<U>(key?: ArrayFunc<T, U>, comparator?: ArrayComparator<U>): DataArray<T>;
134+
135+
/** Return true if the predicate is true for all values. */
136+
every(f: ArrayFunc<T, boolean>): boolean;
137+
/** Return true if the predicate is true for at least one value. */
138+
some(f: ArrayFunc<T, boolean>): boolean;
139+
/** Return true if the predicate is FALSE for all values. */
140+
none(f: ArrayFunc<T, boolean>): boolean;
141+
142+
/** Return the first element in the data array. Returns undefined if the array is empty. */
143+
first(): T;
144+
/** Return the last element in the data array. Returns undefined if the array is empty. */
145+
last(): T;
146+
147+
/** Map every element in this data array to the given key, and then flatten it.*/
148+
to(key: string): DataArray<any>;
149+
/**
150+
* Recursively expand the given key, flattening a tree structure based on the key into a flat array. Useful for handling
151+
* hierarchical data like tasks with 'subtasks'.
152+
*/
153+
expand(key: string): DataArray<any>;
154+
155+
/** Run a lambda on each element in the array. */
156+
forEach(f: ArrayFunc<T, void>): void;
157+
158+
/** Convert this to a plain javascript array. */
159+
array(): T[];
160+
161+
/** Allow iterating directly over the array. */
162+
[Symbol.iterator](): Iterator<T>;
163+
164+
/** Map indexes to values. */
165+
[index: number]: any;
166+
/** Automatic flattening of fields. Equivalent to implicitly calling `array.to("field")` */
167+
[field: string]: any;
168+
}
169+
```

docs/docs/code-views/list-view.md

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
---
2+
title: List Views (dc.List)
3+
sidebar_label: List Views
4+
sidebar_position: 100
5+
---
6+
7+
List views, available as `dc.List`, generate pageable lists of results. They support grouping, heirarchies, paging, and several view modes. They come in three varieties:
8+
9+
- **Unordered** (`unordered`): A bullet-point style list.
10+
- **Ordered** (`ordered`): A numbered list of items.
11+
- **Block**: (`block`): A list with no additional formatting - just shows elements in a vertical list of 'blocks'.
12+
13+
## Quickstart
14+
15+
Most common usages of lists will use `rows` and `renderer`:
16+
17+
```jsx
18+
return function View() {
19+
// Start by fetching your data via a query.
20+
const data = dc.useQuery("#book and @page");
21+
22+
// Pass the full data to `rows`, and specify what to show in the list via `renderer`:
23+
return <dc.List rows={data} renderer={book => book.$link} />;
24+
}
25+
```
26+
27+
Lists are also commonly used for rendering embeds:
28+
29+
```jsx
30+
return function View() {
31+
const data = dc.useQuery("#important-note and @block");
32+
33+
// Uses `dc.embed` to render an embed of all of the given blocks, with paging for performnace.
34+
return <dc.List rows={data} paging={true} renderer={dc.embed} />;
35+
}
36+
```
37+
38+
For the full set of available options, read on.
39+
40+
## Basic Usage (`rows`)
41+
42+
The list view is available in the local API as `dc.List`; it at a minimum requires a list of elements to show (`rows`):
43+
44+
```js
45+
const ITEMS = ["First", "Second", "Third"];
46+
47+
return function View() {
48+
return <dc.List rows={ITEMS} />;
49+
}
50+
```
51+
52+
Which will produce a simple list like so:
53+
54+
- First
55+
- Second
56+
- Third
57+
58+
### List Types (`type`)
59+
60+
You can control which of the list types you want via the `type` property.
61+
62+
```js
63+
const ITEMS = ["First", "Second", "Third"];
64+
65+
return function View() {
66+
return <dc.List type="ordered" rows={ITEMS} />;
67+
}
68+
```
69+
70+
The three options available are:
71+
72+
- **Unordered** (`unordered`): A bullet-point style list. This is the default.
73+
- **Ordered** (`ordered`): A numbered list of items. Numbering starts at 1 and increments.
74+
- **Block**: (`block`): A list with no additional formatting - just shows elements in a vertical list of 'blocks'.
75+
- Block formatting is best for embeds or other use cases where you do not want visible formatting from a regular list.
76+
77+
### Specifying How To Render Data (`renderer`)
78+
79+
When working with queries and any other non-trivial object, you will likely want to specify exactly what to render and how. This can be done via the `renderer` prop, which accepts a function that maps
80+
each row to the value or JSX to render.
81+
82+
```jsx
83+
return function View() {
84+
// This will give back a set of MarkdownPage objects, which are not useful to render on their own.
85+
const books = dc.useQuery("#book and @page");
86+
87+
// Render books by rendering their links.
88+
return <dc.List rows={books} renderer={book => book.$link} />;
89+
}
90+
```
91+
92+
Some built-in rendering functions already exist, such as `dc.embed`, which renders embeds of files
93+
automatically:
94+
95+
```jsx
96+
return function View() {
97+
// Fetch all blocks referencing a specific tag.
98+
const notes = dc.useQuery("#life-notes and @block");
99+
100+
// Render the notes as embeds in block format for minimal formatting.
101+
return <dc.List type="block" rows={notes} renderer={dc.embed} />;
102+
}
103+
```
104+
105+
### Paging (`paging` / `scrollOnPaging`)
106+
107+
You can add paging to any list using the `paging` prop, which accepts several options.
108+
109+
```js
110+
// Explicitly disable paging.
111+
<dc.List paging={false} ... />
112+
113+
// Enable paging, with the page size equal to your default page size in the Datacore settings.
114+
<dc.List paging={true} ... />
115+
116+
// Enable paging with the specific page size.
117+
<dc.List paging={10} ... />
118+
```
119+
120+
If `paging` is not specified, it defaults to whatever your default paging configuration is in
121+
the Datacore settings.
122+
123+
### Grouping (`groupings`)
124+
125+
List views automatically support rendering grouped data; grouped data can be created most easily
126+
using [Data Array](data-array) syntax.
127+
128+
```jsx
129+
return function View() {
130+
// Fetch all books and then group them by genre.
131+
const books = dc.useQuery("#book and @page");
132+
const booksByGenre = dc.useArray(books, array => array.groupBy(book => book.value("genre")));
133+
134+
// No extra configuration is required by default to show groups.
135+
return <dc.List rows={booksByGenre} renderer={book => book.$link} />;
136+
}
137+
```
138+
139+
By default, grouped data will render the grouping headers using the default text renderer. If you'd
140+
like to add embellishments, such as converting each of the 'genres' in the above examples into links,
141+
you can use the `groupings` prop:
142+
143+
```jsx
144+
return function View() {
145+
// Fetch all books and then group them by genre.
146+
const books = dc.useQuery("#book and @page");
147+
const booksByGenre = dc.useArray(books, array => array.groupBy(book => book.value("genre")));
148+
149+
// Render each grouping key as a file link instead of just text.
150+
return <dc.List rows={booksByGenre} renderer={book => book.$link} groupings={(key) => dc.fileLink(key)} />;
151+
}
152+
```
153+
154+
You can also choose to construct an explicit `GroupingConfig` instead of passing a function:
155+
156+
```jsx
157+
const LINK_GROUPING = {
158+
render: (key, rows) => dc.fileLink(key)
159+
};
160+
161+
return function View() {
162+
// Fetch all books and then group them by genre.
163+
const books = dc.useQuery("#book and @page");
164+
const booksByGenre = dc.useArray(books, array => array.groupBy(book => book.value("genre")));
165+
166+
// Render each grouping key as a file link instead of just text.
167+
return <dc.List rows={booksByGenre} renderer={book => book.$link} groupings={LINK_GROUPING} />;
168+
}
169+
```
170+
171+
If you group multiple times, you can specify a separate rendering for each grouping level by passing an array of grouping configurations to `groupings`.
172+
173+
### Heirarchies / Children (`childProp` / `maxChildDepth`)
174+
175+
## Full Reference
176+
177+
The full set of available properties is provided below:
178+
179+
```js
180+
export interface ListState<T> {
181+
/**
182+
* Whether the list should be ordered, unordered, or block.
183+
*
184+
* Block lists do not use an actual list element and instead just render a series of contiguous
185+
* div elements with no other annotations.
186+
*/
187+
type?: "ordered" | "unordered" | "block";
188+
189+
/** The full collection of elements in the list. */
190+
rows: Grouping<T>;
191+
192+
/** Allows for grouping header lines to be overridden with custom rendering/logic. */
193+
groupings?: GroupingConfig<T> | GroupingConfig<T>[] | ((key: Literal, rows: Grouping<T>) => Literal | VNode);
194+
195+
/**
196+
* Custom render function to use for rendering each leaf element. Can produce either JSX or a plain value which will be
197+
* rendered as a literal.
198+
*/
199+
renderer?: (row: T) => React.ReactNode | Literal;
200+
201+
/** Controls whether paging is enabled for this element. If true, uses default page size. If a number, paging is enabled with the given page size. */
202+
paging?: boolean | number;
203+
204+
/**
205+
* Whether the view will scroll to the top automatically on page changes. If true, will always scroll on page changes.
206+
* If a number, will scroll only if the number is greater than the current page size.
207+
**/
208+
scrollOnPaging?: boolean | number;
209+
210+
/** Maximum level of children that will be rendered; a level of 0 means no children expansion will occur. */
211+
maxChildDepth?: number;
212+
213+
/**
214+
* Property name, list of property names, or function to be applied to obtain children for a given entry.
215+
* Defaults to the `$children` and `children` props.
216+
*
217+
* If null, child extraction is disabled and no children will be fetched. If undefined, uses the default.
218+
*/
219+
childProp?: null | string | string[] | ((row: T) => T[]);
220+
}
221+
222+
export interface GroupingConfig<T> {
223+
/** How a grouping with the given key and set of rows should be rendered. */
224+
render?: (key: Literal, rows: Grouping<T>) => Literal | VNode;
225+
}
226+
```

0 commit comments

Comments
 (0)