Skip to content

Commit

Permalink
docs(tree): updates and fixes (#23455)
Browse files Browse the repository at this point in the history
## Description

Updates and fixes to the existing docs.

## Breaking Changes

None
  • Loading branch information
yann-achard-MS authored Jan 8, 2025
1 parent 1824858 commit ba65d67
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 45 deletions.
33 changes: 23 additions & 10 deletions docs/docs/data-structures/tree/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,31 @@ title: Events
sidebar_position: 5
---

`SharedTree` supports two node level events: `nodeChanged` and `treeChanged`.
## Whole-Tree Events

Additionally, the `TreeView` object includes 2 events that operate over the whole tree.
These are `rootChanged` and `commitApplied`.
The `TreeView` object exposes 2 events that communicate changes that affect the whole tree.

`rootChanged` fires when the root field (the field that contains the root node) changes.
`rootChanged` fires when the contents of the root field (the field that contains the root node) change.
That is, if a new root node is assigned or the schema changes.
This will not fire when the node itself changes.

`changed` fires whenever a change is applied outside of a transaction or when a transaction is committed.
`commitApplied` fires whenever a change is applied outside of a transaction or when a transaction is committed.
This is used to get `Revertible` objects to put on the undo or redo stacks.
See [undo redo support](./undo-redo.mdx) and [Transactions](./transactions.mdx).

## Event Handling
Here is an example of how to subscribe to one of these events:

```typescript
const unsubscribe = treeView.events.on("rootChanged", () => {...});

// Later at some point when the event subscription is not needed anymore
unsubscribe();
```

## Node-Level Events

`SharedTree` supports two node-level events: `nodeChanged` and `treeChanged`.
These can be subscribed to using the `Tree.on` method, which has the following signature:

```typescript
on<K extends keyof TreeChangeEvents>(
Expand All @@ -28,9 +39,12 @@ on<K extends keyof TreeChangeEvents>(

`Tree.on` assigns the specified `listener` function to the specified `eventName` for the specified `node`.
The `node` can be any node of the tree.
The `eventName` can be either "treeChanged" or "nodeChanged".
`nodeChanged` fires whenever one or more properties of the specified node change.
`treeChanged` fires whenever one or more properties of the specified node or any node in its subtree, change.

The `eventName` can be either `"treeChanged"` or `"nodeChanged"`:

- `nodeChanged` fires whenever one or more properties of the specified node change.
- `treeChanged` fires whenever one or more properties of the specified node or any node in its subtree, change.

We recommend looking at the documentation of each of the events for more details.

The `Tree.on()` method returns a function that unsubscribes the handler from the event. This method is typically called in clean up code when the node is being removed. For example:
Expand All @@ -40,5 +54,4 @@ const unsubscribe = Tree.on(myTreeNode, "nodeChanged", () => {...});

// Later at some point when the event subscription is not needed anymore
unsubscribe();

```
20 changes: 14 additions & 6 deletions docs/docs/data-structures/tree/nodes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ See [node types](./node-types.mdx) for details on the types of nodes that can be

Below are some utilities provided to make working with nodes easier.

### Node Information
### `Tree.key`

```typescript
Tree.key(node: SharedTreeNode): number | string
```

Returns the key of the `node`. This is a string in all cases, except an array node, in which case it returns the index of the node.
Returns the field key that the `node` is stored under.
This is a string in all cases, except an array node, in which case it returns the index of the node.

### `Tree.parent`

```typescript
Tree.parent(node: SharedTreeNode): SharedTreeNode
Expand All @@ -33,23 +36,28 @@ if (Tree.is(parent, Notes) || Tree.is(parent, Items)) {
}
```

### `Tree.status`

```typescript
Tree.status(node: SharedTreeNode): TreeStatus
```

Returns the current status of `node`. Possible values are:

- **InDocument**: The node is in the tree.
- **Removed**: The node has been removed from the tree but is still restorable by undo.
- **Deleted**: The node is deleted and unrestorable.
- **New**: The node is created but has not yet been inserted into the tree.
- **InDocument**: The node is parented (either directly or indirectly) under the root field.
- **Removed**: The node is not parented under the root field but may still be restorable by this client or other clients.
- **Deleted**: The node is deleted and cannot be restored by this client, though it may still be restorable by other clients.

### `Tree.schema`

```typescript
Tree.schema(node: SharedTreeNode): TreeNodeSchema
```

Returns the object that defines the schema of the `node` object.

### Type Guard
### `Tree.is`

When your code needs to process nodes only of a certain type and it has a reference to an object of an unknown type, you can use the `Tree.is()` method to test for the desired type as in the following examples.

Expand Down
61 changes: 40 additions & 21 deletions docs/docs/data-structures/tree/reading-and-editing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ title: Reading and Editing
sidebar_position: 4
---

The `TreeView` object and its children provide methods that enable your code to add nodes to the tree, remove nodes, and move nodes within the tree. You can also set and read the values of leaf nodes. The APIs have been designed to match as much as possible the syntax of TypeScript primitives, objects, maps, and arrays; although some editing APIs are different for the sake of making the merge semantics clearer.
The `TreeView` object and its children expose properties and methods that allow applications read and edit the tree.
The APIs have been designed to match as much as possible the syntax of TypeScript primitives, objects, maps, and arrays (although some editing APIs are different for the sake of making the merge semantics clearer).

## Leaf Node APIs

Expand All @@ -23,14 +24,14 @@ const pointsForDetroitTigers: number = seasonTree.tigersTeam.game1.points;

### Reading Object Properties

Your code reads object nodes and their properties exactly as it would read a JavaScript object. The following are some examples.
Your code reads object nodes and their properties exactly as it would from a JavaScript object. The following are some examples.

```typescript
const pointsForDetroitTigers: number = seasonTree.tigersTeam.game1.points;

const counterHandle: FluidHandle = myTree.myObjectNode.myHandle;

const myItems: Array = stickyNotesTree.items;
const myItems: Items = stickyNotesTree.items;
```

### Creating Objects
Expand All @@ -52,6 +53,7 @@ We show how to add this note to an array of notes in the tree in [Array node API
### Editing Object Properties

To update the property on an object node, you assign a new node or value to it with the assignment operator (`=`).
See [here](https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/user-facing/object-merge-semantics.md) for details on the merge semantics of these edits.

```typescript
rectangle.topLeft = new Point({ x: 0, y: 0 });
Expand All @@ -67,6 +69,9 @@ Optional properties can be cleared by assigning `undefined` to them.
proposal.text = undefined;
```

Note that if the new value is a node (as opposed to a primitive), then its [status](./nodes.mdx#treestatus) must be `TreeStatus.New`,
In other words, it is not possible to set a node that has already been inserted in the tree in the past, even if that node has since been removed.

## Map Node APIs

### Map Node Read APIs
Expand All @@ -77,31 +82,31 @@ The read APIs for map nodes have the same names and syntax as the corresponding
has(key): boolean
```

Returns `true`` if the key is present in the map.
Returns `true` if the key is present in the map.

```typescript
get(key): T | undefined
```

Returns the value of the property with the specified key.
Returns the value associated with the specified key if any, and returns `undefined` if no value is associated with that key.

```typescript
keys(): IterableIterator<string>
```

Returns an Iterator that contains the keys in the map node. The keys are iterated in the order that they were added.
Returns an Iterator that contains the keys in the map node.

```typescript
values(): IterableIterator<T>
```

Returns an Iterator that contains the values in the map node. The values are iterated in the order that they were added.
Returns an Iterator that contains the values in the map node.

```typescript
entries(): IterableIterator<[string, T]>
```

Returns an Iterator that contains the key/value pairs in the map node. The pairs are iterated in the order that they were added.
Returns an Iterator that contains the key/value pairs in the map node.

```typescript
map(callback: ()=>[]): IterableIterator<[string, T]>
Expand All @@ -111,7 +116,8 @@ Returns an array, _not a map node or array node_, that is the result of applying

### Map Node Write APIs

The write methods for map nodes are also the same as the corresponding methods for JavaScript `Map` objects.
The write methods for map nodes are similar to the corresponding methods for JavaScript `Map` objects.
See [here](https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/user-facing/map-merge-semantics.md) for details on the merge semantics of these edits.

```typescript
set(key: string, value: T)
Expand All @@ -120,6 +126,8 @@ set(key: string, value: T)
The `set()` method sets/changes the value of the item with the specified key. If the key is not present, the item is added to the map. Note the following:

- The `T` can be any type that conforms to the map node's schema. For example, if the schema was defined with `class MyMap extends sf.map([sf.number, sf.string]);`, then `T` could be `number` or `string`.
- If the `value` argument is a node (as opposed to a primitive), then its [status](./nodes.mdx#treestatus) must be `TreeStatus.New`,
In other words, it is not possible to set a node that has already been inserted in the tree in the past, even if that node has since been removed.
- If multiple clients set the same key simultaneously, the key gets the value set by the last edit to apply. For the meaning of "simultaneously", see [Types of distributed data structures](../overview.mdx).

```typescript
Expand All @@ -145,10 +153,16 @@ Array nodes have all the same non-mutating read methods as the JavaScript [Array
### Array Node Write APIs
The write APIs for array nodes are quite different from JavaScript arrays. They are more suitable for data items that are being worked on collaboratively by multiple people. There are three categories of write APIs: Insert, Remove, and Move.
See [here](https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/user-facing/array-merge-semantics.md) for details on the merge semantics of these edits.
#### Insert Methods
Array nodes have three methods that insert new items into the node. Note that in all of the following, the `T` can be any type that conforms to the array node's schema. For example, if the schema was defined with `class MyArray extends sf.array([sf.number, sf.string]);`, then `T` could be `number` or `string`.
Array nodes have three methods that insert items into the array.
Note the following:
- In all of the following, the `T` can be any type that conforms to the array node's schema. For example, if the schema was defined with `class MyArray extends sf.array([sf.number, sf.string]);`, then `T` could be `number` or `string`.
- Inserted values that are nodes (as opposed to a primitive), must have a [status](./nodes.mdx#treestatus) equal `TreeStatus.New`.
In other words, it is not possible to set a node that has already been inserted in the tree in the past, even if that node has since been removed.
```typescript
insertAt(index: number, value: Iterable<T>)
Expand Down Expand Up @@ -194,27 +208,32 @@ Removes the items indicated by the `start` index (inclusive) and `end` index (ex
Array nodes have three methods that move items within an array or from one array node to another. When moving from one array node to another, these methods must be called from the destination array node. Note that in all of the following, the `T` can be any type that is derived from an object that is returned by a call of `SchemaFactory.array()`, such as the `Notes` and `Items` classes in the sticky notes example.
Note the following about these methods:
- If multiple clients simultaneously move an item, then that item will be moved to the destination indicated by the move of the client whose edit is ordered last.
- A moved item may be removed as a result of a simultaneous remove operation from another client. For example, if one client moves items 3-5, and another client simultaneously removes items 4 and 5, then, if the remove operation is ordered last, items 4 and 5 are removed from their destination by the remove operation. If the move operation is ordered last, then all three items will be moved to the destination.
- Moved items that are nodes (as opposed to a primitives), must have a [status](./nodes.mdx#treestatus) equal `TreeStatus.InDocument`.
- We also expose variations of these methods for moving a single item.
For the meaning of "simultaneously", see [Types of distributed data structures](../overview.mdx).
```typescript
moveToStart(sourceStartIndex: number, sourceEndIndex: number, source?: T)
moveRangeToStart(sourceStartIndex: number, sourceEndIndex: number, source?: T)
```
Moves the specified items to the start of the array. Specify a `source` array if it is different from the destination array.
```typescript
moveToEnd(sourceStartIndex: number, sourceEndIndex: number, source?: T)
moveRangeToEnd(sourceStartIndex: number, sourceEndIndex: number, source?: T)
```
Moves the specified items to the end of the array. Specify a `source` array if it is different from the destination array.
```typescript
moveToIndex(index: number, sourceStartIndex: number, sourceEndIndex: number, source?: T)
moveRangeToIndex(destinationGap: number, sourceStartIndex: number, sourceEndIndex: number, source?: T)
```
Moves the items to the specified `index` in the destination array. The item that is at `index` before the method is called will be at the first index position that follows the moved items after the move. Specify a `source` array if it is different from the destination array. If the items are being moved within the same array, the `index` position is calculated including the items being moved (as if a new copy of the moved items were being inserted, without removing the originals).
Note the following about these methods:
- If multiple clients simultaneously move an item, then that item will be moved to the destination indicated by the move of the client whose edit is ordered last.
- A moved item may be removed as a result of a simultaneous remove operation from another client. For example, if one client moves items 3-5, and another client simultaneously removes items 4 and 5, then, if the remove operation is ordered last, items 4 and 5 are removed from their destination by the remove operation. If the move operation is ordered last, then all three items will be moved to the destination.
For the meaning of "simultaneously", see [Types of distributed data structures](../overview.mdx).
Moves the items to the specified `destinationGap` in the destination array.
Specify a `source` array if it is different from the destination array.
If the items are being moved within the same array, the `destinationGap` position is interpreted including the items being moved (as if a new copy of the moved items were being inserted, without removing the originals).
See [the doc comments](https://fluidframework.com/docs/api/tree/treearraynode-interface#moverangetoindex-methodsignature) for details.
3 changes: 1 addition & 2 deletions docs/docs/data-structures/tree/schema-definition.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,11 @@ type _check = ValidateRecursiveSchema<typeof myRecursiveType>;
## Setting Properties as Optional
To specify that a property is not required, pass it to the `SchemaFactory.optional()` method inline. The following example shows a schema with two optional properties.
To specify that a property is not required, pass it to the `SchemaFactory.optional()` method inline. The following example shows a schema with an optional property.
```typescript
class Proposal = sf.object('Proposal', {
id: sf.string,
text: sf.optional(sf.string),
comments: sf.optional(sf.array(Comment)),
});
```
12 changes: 7 additions & 5 deletions docs/docs/data-structures/tree/transactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ sidebar_position: 6

TODO:#27374: update this page to describe the new transaction API

If you want the `SharedTree` to treat a set of changes atomically, wrap these changes in a transaction.
Using a transaction guarantees that (if applied) all of the changes will be applied together synchronously and no other changes (either from this client or from a remote client) can be interleaved with those changes. Note that the Fluid Framework guarantees this already for any sequence of changes that are submitted synchronously. However, the changes may not be applied at all if the transaction is given one or more constraints.
If any constraint on a transaction is not met, then the transaction and all its changes will ignored by all clients.
Additionally, all changes in a transaction will be reverted together as a single unit by [undo/redo code](./undo-redo.mdx), because changes within a transaction are exposed through a single `Revertible` object.
It is also more efficient for SharedTree to process a large number of changes in a row as a transaction rather than as changes submitted separately.
If you want the `SharedTree` to treat a set of changes atomically, then you can wrap these changes in a transaction.
Using a transaction guarantees that (if applied) all of the changes will be applied together synchronously and no other changes (either from this client or from a remote client) can be interleaved with those changes.
Note that the Fluid Framework guarantees this already for any sequence of changes that are submitted synchronously.
However, using a transaction has the following additional implications:
- If [reverted](./undo-redo.mdx) (e.g. via an "undo" operation), all the changes in the transaction are reverted together.
- It is also more efficient for SharedTree to process and transmit a large number of changes as a transaction rather than as changes submitted separately.
- It is possible to specify constraints on a transaction so that the transaction will be ignored if one or more of these constraints are not met.

To create a transaction use the `Tree.runTransaction()` method. You can cancel a transaction from within the callback function by returning the special "rollback object", available via `Tree.runTransaction.rollback`. Also, if an error occurs within the callback, the transaction will be canceled automatically before propagating the error.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/data-structures/tree/undo-redo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ sidebar_position: 7

To undo a change, call the `revert` method on the `Revertible` object. This will return the properties of the `TreeView` object last changed by the local client to the their previous state. If changes were made to those properties by other clients in the meantime these changes will be overwritten. For example, if the local client moves 3 items into an array, and then a remote client moves one of those items somewhere else, when the local client reverts their change, the item moved by the remote client will be returned to its original position.

There is an example of a working undo/redo stack here: [Shared Tree Demo](https://github.com/microsoft/FluidExamples/tree/main/brainstorm).
There is an example of a working undo/redo stack here: [Shared Tree Demo](https://github.com/microsoft/FluidExamples/tree/main/brainstorm/src/utils/undo.ts).

0 comments on commit ba65d67

Please sign in to comment.