Class: MutableArrayTreeDataProvider

Oracle® JavaScript Extension Toolkit (JET)
17.1.0

G12196-01

Since:
  • 13.0.0
Module:
  • ojmutablearraytreedataprovider

QuickNav

Fields

Description

This class implements TreeDataProvider and is used to represent hierachical data available from an array.

Each array element represents a tree node, which can contain a nested child object array for its subtree. Array elements can be in any shape and form, but is usually an object with a "children" property. The name of the "children" property can optionally be specified with the "childrenAttribute" option.

For nodes that cannot have children, the "children" property should not be set. For nodes that can but don't have children, the "children" property should be set to an empty array.

The events described below will be dispatched to the MutableArrayTreeDataProvider with the appropriate event payload.

Filtering is supported and, by default, applied only on leaf nodes. Empty tree nodes are not collapsed. The filtering on leaf nodes only works by combining the passed in filter definition with an OR expression of the "children" property to determine if a node is a tree or leaf.

Events

Consumers can add event listeners to listen for the following event types and respond to data change. Event listeners should be added to the root-level MutableArrayTreeDataProvider created by the application. The root-level MutableArrayTreeDataProvider receives events for the entire tree. Child-level MutableArrayTreeDataProvider returned by getChildDataProvider does not receive events.

mutate

This event is fired when items are added into a leaf node or updated in an array. The event is the array specific.

Event payload is found under event.detail, which implements the DataProviderMutationEventDetail interface.

refresh

This event with no payload is fired when the data has been refreshed and components need to re-fetch the data. In this case, the re-fetch is from the root.

This event with payload 'keys' is fired when updated nodes include any changes in their children. The 'keys' is the set of keys of the nodes with changes in their children, and components need to re-fetch the sub-tree of 'keys'. In this case, the re-fetch should be from each key.

Event payload is found under event.detail, which implements the DataProviderRefreshEventDetail interface.

Example of a tree structured data:

// initiate an array
const treeData = [
  {title:"News", id:"news"},
  {title:"Blogs", id:"blogs", "children": [
    {title:"Today", id:"today"},
    {title:"Yesterday", id:"yesterday"},
    {title:"Archive", id:"archive", "children": [
      {title: "Links", id:'links'}
     ]}
  ]}
];
Initial an immutable array treeData.

With the data above, the following are actions on an array and events expected. The mutation are created according to Algorithm section, check it for more details.

Action Example Code Expected Events
Add a node as a sibling to non-root node
      
Add a sibling 'Links' node next to 'Today'
// for example you can use util functions from ojimmutabletreedatautils
treeData = addNode(treeData, [1, 1],
 {title: "Links", id: "links"});
rootTreeDataProvider.data = treeData;
      
      
'refresh': {keys: 'blogs'}
Add node 'Links' as a sibling to root node
       
Add a sibling node 'Links' next to 'Blogs'
// for example you can use util functions from ojimmutabletreedatautils
treeData = addNode(treeData, [1],
 {title: "Links", id: "links"});
rootTreeDataProvider.data = treeData;
       
       
'refresh'
Add a child to a non-root non-leaf node
     
Add a child node 'Child3' under 'Archive'
// for example you can use util functions from ojimmutabletreedatautils treeData = addNode(treeData, [1, 2, 0], {title: "Child3", id: "child3"}); rootTreeDataProvider.data = treeData;
'refresh': {keys: 'archive'}
Add a child to a non-root leaf node
     
Add a child node 'Child1' under 'Today'
// for example you can use util functions from ojimmutabletreedatautils
treeData = addNode(treeData,
 [1, 0, 0],
 {title: "Child1", id: "child1"});
rootTreeDataProvider.data = treeData;
     
     
'refresh': {keys: 'blogs'}
Add a child to a root leaf node
       
Add another child node 'Child2' under 'News'
// for example you can use util functions from ojimmutabletreedatautils
treeData = addNode(treeData, [0, 0],
 {title: "Child2", id: "child2"});
rootTreeDataProvider.data = treeData;
       
       
'refresh'
Add a child to a root non-leaf node
      
Add a child node 'Child4' under 'Blogs'
// for example you can use util functions from ojimmutabletreedatautils
treeData = addNode(treeData, [1, 0],
 {title: "Child4", id: "child4"});
rootTreeDataProvider.data = treeData;
      
      
'refresh': {keys: 'blogs'}
Remove a non-root node
      
Remove node 'Yesterday' under parent node Blogs
// for example you can use util functions from ojimmutabletreedatautils
treeData = removeNode(treeData,
 [1, 1]);
rootTreeDataProvider.data = treeData;
      
      
'refresh': {keys: 'blogs'}
Remove a root node
    
Remove node 'News'
// for example you can use util functions from ojimmutabletreedatautils
treeData = removeNode(treeData, [0]);
rootTreeDataProvider.data = treeData;
    
    
'refresh'
Remove (change a root non-leaf node to root leaf node)
    
Remove child node under 'Blogs'
// for example you can use util functions from ojimmutabletreedatautils
treeData = removeNode( treeData, [1, 0]);
rootTreeDataProvider.data = treeData;
    
    
'refresh'
Remove (change a non-root non-leaf node to non-root leaf node)
    
Remove child node under 'Today'
// for example you can use util functions from ojimmutabletreedatautils
treeData = removeNode( treeData,
 [1, 0, 0]);
rootTreeDataProvider.data = treeData;
    
    
'refresh': {keys: 'blogs'}
Update a node
    
Update node 'News' to 'OldNews'
// for example you can use util functions from ojimmutabletreedatautils
const newNode = {...treeData[0]};
treeData = reolaceNode( treeData,
 [0], newNode);
rootTreeDataProvider.data = treeData;
    
    
mutation': {update: {keys: 'news', data:{title: 'OldNews', id: 'news'}}}

Example of consumer listening for the events:

dataProvider.addEventListener("mutate", handleMutate);
dataProvider.addEventListener("refresh", handleRefresh);

const handleMutate = function(event) {
  if (event.detail.update) {
    const updateDetail = event.detail.update;
    // Handle updated items
  }
};

const handleRefresh = function(event) {
  const detail=event.detail;
  if (detail && detail.keys) {
    event.detail.keys.forEach ((key) => {
      // refresh children for key
    });
  }
  else {
    // refresh children for root
  }
}

Algorithm

When setting this property, MutableArrayTreeDataProvider will use the following algorithm to compare: We would use ‘limited’ recursive deep comparison to figure out what mutation events to fire. This is done to avoid having to fire a top-level refresh event for any mutation of the subtree. We can start by optimizing only the case where there the number of children is still the same, and the identity comparison fails for at most one child:

  1. If the two nodes are equal, we are done. There are no changes.
  2. If they are not equal, we fire an ‘update’ mutation. Note that we do not deep-compare the node’s properties.
  3. We compare the node’s old and new child lists. If the sizes are different, we fire a ‘refresh’ event with 'parentKey' and do not recurse further
  4. If we find more than one child with identity comparison failing, we fire a ‘refresh’ event with 'parentKey' and do not recurse further
  5. If we find 0 children with identity comparison failing, we do not recurse further
  6. If we find 1 child with identity comparison failing, we recurse into that child and repeat these steps from the beginning


Usage

Signature:

class MutableArrayTreeDataProvider<K, D> implements TreeDataProvider<K, D>

Generic Parameters
ParameterDescription
KType of Key
DType of Data
Typescript Import Format
//To import this class, use the format below.
import {MutableArrayTreeDataProvider} from "ojs/ojmutablearraytreedataprovider";

For additional information visit:


Constructor

new MutableArrayTreeDataProvider(data, keyAttribute, options)

Parameters:
Name Type Argument Description
data any Applications shouldn't call array mutation methods such as splice or push on "data" directly. Applications need to use ojimmutabletreedatautils or immutable libs like immutablejs to update the array, and set it back into the "data" property.
keyAttribute string | '@value' The field name which stores the key in the data. Can be a string denoting a single key attribute. Please note that the ids in MutableArrayTreeDataProvider must always be unique. Please do not introduce duplicate ids, even during temporary mutation operations.@value will cause MutableArrayTreeDataProvider to use all attributes as key.
options MutableArrayTreeDataProvider.Options.<D> <optional>
Options for the MutableArrayTreeDataProvider
Example
import { MutableArrayTreeDataProvider } from "ojs/ojmutablearraytreedataprovider";
// First initialize the tree data.  This can be defined locally or read from file.
const treeData = [
                 {"attr": {"id": "dir1", "title": "Directory 1"},
                  "children": [
                    {"attr": {"id": "subdir1", "title": "Subdirectory 1"},
                     "children": [
                       {"attr": {"id": "file1", "title": "File 1"}},
                       {"attr": {"id": "file2", "title": "File 2"}},
                       {"attr": {"id": "file3", "title": "File 3"}}
                     ]},
                    {"attr": {"id": "subdir2", "title": "Subdirectory 2"},
                     "children": []}
                  ]},
                 {"attr": {"id": "dir2", "title": "Directory 2"},
                  "children": [
                    {"attr": {"id": "file4", "title": "File 4"}},
                    {"attr": {"id": "file5", "title": "File 5"}},
                  ]}
               ];

// Then create an MutableArrayTreeDataProvider object with the array
create an immutable array treeDataImmutable
const dp = new MutableArrayTreeDataProvider(treeDataImmutable, 'attr.id');

Fields

data

The underlying data array.

Applications can get this property directly only can set it on the root data provider, such as setting it to a different array.

Applications should not call array mutation methods such as splice or push on "data" directly. Applications need to create a new immutable array by ojimmutabletreedatautils or immutable libs and set it back into the "data" property.

Properties:
Name Type
data any
Since:
  • 13.0.0
Examples

Example of mutating the entire tree

const rootTreeDataProvider = new MutableArrayTreeDataProvider(rootData, 'id');
let newRootData;
Initialize newRootData to a new immutable array newRootDataImmutable
rootTreeDataProvider.data = newRootDataImmutable;

Example of mutating the data in a branch

create an immutable array immutableData
const rootTreeDataProvider = new MutableArrayTreeDataProvider(immutableData, 'id');
update immutableData
// set it back to rootTreeDataProvider.data
rootTreeDataProvider.data = immutableData;

Methods

addEventListener(eventType: string, listener: EventListener): void

Add a callback function to listen for a specific event type.
Parameters:
Name Type Description
eventType string The event type to listen for.
listener EventListener The callback function that receives the event notification.

containsKeys(parameters : FetchByKeysParameters<K>) : Promise<ContainsKeysResults<K>>

Check if there are rows containing the specified keys. If this method is called on a DataProvider returned from getChildDataProvider(key), the rows will be only those from the children of the key.
Parameters:
Name Type Description
parameters FetchByKeysParameters contains by key parameters
Since:
  • 4.2.0
Returns:

Returns Promise which resolves to ContainsKeysResults.

Type
Promise.<ContainsKeysResults>
Example

Check if keys 1001 and 556 are contained

let containsKeys = [1001, 556];
let value = await dataprovider.containsKeys({keys: containsKeys});
let results = value['results'];
if (results.has(1001)) {
  console.log('Has key 1001');
} else if (results.has(556){
  console.log('Has key 556');
}

createOptimizedKeyMap(initialMap?: Map<K, D>): Map<K, D>

Return an empty Map which is optimized to store key value pairs

Optionally provided by certain DataProvider implementations for storing key/value pairs from the DataProvider in a performant fashion. Sometimes components will need to temporarily store a Map of keys provided by the DataProvider, for example, in the case of maintaining a Map of selected keys. Only the DataProvider is aware of the internal structure of keys such as whether they are primitives, Strings, or objects and how to do identity comparisons. Therefore, the DataProvider can optionally provide a Map implementation which can performantly store key/value pairs surfaced by the DataProvider.

Parameters:
Name Type Argument Description
initialMap Map.<any> <optional>
Optionally specify an initial map of key/values for the Map. If not specified, then return an empty Map.
Since:
  • 6.2.0
Returns:

Returns a Map optimized for handling keys from the DataProvider.

Type
Map.<any>
Example

create empty key Map

let keyMap = dataprovider.createOptimizedKeyMap();

createOptimizedKeySet(initialSet?: Set<K>): Set<K>

Return an empty Set which is optimized to store keys

Optionally provided by certain DataProvider implementations for storing keys from the DataProvider in a performant fashion. Sometimes components will need to temporarily store a Set of keys provided by the DataProvider, for example, in the case of maintaining a Set of selected keys. Only the DataProvider is aware of the internal structure of keys such as whether they are primitives, Strings, or objects and how to do identity comparisons. Therefore, the DataProvider can optionally provide a Set implementation which can performantly store keys surfaced by the DataProvider.

Parameters:
Name Type Argument Description
initialSet Set.<any> <optional>
Optionally specify an initial set of keys for the Set. If not specified, then return an empty Set.
Since:
  • 6.2.0
Returns:

Returns a Set optimized for handling keys from the DataProvider.

Type
Set.<any>
Example

create empty key Set

let keySet = dataprovider.createOptimizedKeySet();

dispatchEvent(evt: Event): boolean

Dispatch an event and invoke any registered listeners.
Parameters:
Name Type Description
event Event The event object to dispatch.
Returns:

Return false if a registered listener has cancelled the event. Return true otherwise.

Type
boolean

fetchByKeys(parameters : FetchByKeysParameters<K>) : Promise<FetchByKeysResults<K, D>>

Fetch rows by keys. If this method is called on a DataProvider returned from getChildDataProvider(key), the rows will be only those from the children of the key.
Parameters:
Name Type Description
parameters FetchByKeysParameters fetch by key parameters
Since:
  • 4.2.0
Returns:

Returns Promise which resolves to FetchByKeysResults.

Type
Promise.<FetchByKeysResults>
Example

Fetch for keys 1001 and 556

let fetchKeys = [1001, 556];
let value = await dataprovider.fetchByKeys({keys: fetchKeys});
// get the data for key 1001
console.log(value.results.get(1001).data);

fetchByOffset(parameters: FetchByOffsetParameters<D>): Promise<FetchByOffsetResults<K, D>>

Fetch rows by offset

A generic implementation of this method is available from FetchByOffsetMixin. It is for convenience and may not provide the most efficient implementation for your data provider. Classes that implement the DataProvider interface are encouraged to provide a more efficient implementation.

Parameters:
Name Type Description
parameters FetchByOffsetParameters fetch by offset parameters
Since:
  • 4.2.0
Returns:

Returns Promise which resolves to FetchByOffsetResults.

Type
Promise.<FetchByOffsetResults>
Example

Fetch by offset 5 rows starting at index 2

let result = await dataprovider.fetchByOffset({size: 5, offset: 2});
let results = result['results'];
let data = results.map(function(value) {
  return value['data'];
});
let keys = results.map(function(value) {
  return value['metadata']['key'];
});

fetchFirst(parameters?: FetchListParameters<D>): AsyncIterable<FetchListResult<K, D>>

Get an AsyncIterable object for iterating the data.

AsyncIterable contains a Symbol.asyncIterator method that returns an AsyncIterator. AsyncIterator contains a “next” method for fetching the next block of data.

The "next" method returns a promise that resolves to an object, which contains a "value" property for the data and a "done" property that is set to true when there is no more data to be fetched. The "done" property should be set to true only if there is no "value" in the result. Note that "done" only reflects whether the iterator is done at the time "next" is called. Future calls to "next" may or may not return more rows for a mutable data source.

In order for JET components to work correctly, DataProvider implementations should ensure that:

  • The iterator accounts for data mutations when returning the next block of data, and that no row is duplicated or skipped. For example, an offset-based implementation may need to adjust the offset from which the next block of data starts if rows have been added or removed in the returned data.
  • JET components may call "next" on the iterator even after the iterator has returned done:true. If new data is available after the last returned row, the iterator is expected to return the new data and set "done" to false. This differs from the AsyncIterator spec for performance reasons.

Please see the DataProvider documentation for more information on custom implementations.

Parameters:
Name Type Argument Description
params FetchListParameters <optional>
fetch parameters
Since:
  • 4.2.0
See:
Returns:

AsyncIterable with FetchListResult

Type
AsyncIterable.<FetchListResult>
Example

Get an asyncIterator and then fetch first block of data by executing next() on the iterator. Subsequent blocks can be fetched by executing next() again.

let asyncIterator = dataprovider.fetchFirst(options)[Symbol.asyncIterator]();
let result = await asyncIterator.next();
let value = result.value;
let data = value.data;
let keys = value.metadata.map(function(val) {
  return val.key;
});

getCapability(capabilityName: string): any

Determines whether this DataProvider defines a certain feature.
Parameters:
Name Type Description
capabilityName string capability name. Defined capability names are: "fetchByKeys", "fetchByOffset", "sort", "fetchCapability" and "filter".
Since:
  • 4.2.0
Returns:

capability information or null if undefined

Type
Object
Example

Check what kind of fetchByKeys is defined.

let capabilityInfo = dataprovider.getCapability('fetchByKeys');
if (capabilityInfo.implementation == 'iteration') {
  // the DataProvider supports iteration for fetchByKeys
  ...

getChildDataProvider(parentKey: K): MutableArrayTreeDataProvider<K, D> | null

Since:
  • 13.0.0
Returns:

A TreeDataProvider if the row can (but doesn't have to) have children; or null if the row cannot have children. Use the isEmpty method on the returned TreeDataProvider to determine if it currently has children.

Type
MutableArrayTreeDataProvider | null
Example

Example for getChildDataProvider

// Get child data provider for node 10
let childDataProvider = dataprovider.getChildDataProvider(10);

getTotalSize : {Promise.<number>}

Return the total number of rows in this dataprovider
Returns:

Returns a Promise which resolves to the total number of rows. -1 is unknown row count.

Type
Promise.<number>
Example

Get the total rows

let value = await dataprovider.getTotalSize();
if (value == -1) {
  // we don't know the total row count
} else {
  // the total count
  console.log(value);
}

isEmpty(): 'yes' | 'no' | 'unknown'

Returns a string that indicates if this data provider is empty. Valid values are:
  • "yes": this data provider is empty.
  • "no": this data provider is not empty.
  • "unknown": it is not known if this data provider is empty until a fetch is made.
Since:
  • 4.2.0
Returns:

string that indicates if this data provider is empty

Type
"yes" | "no" | "unknown"
Example

Check if empty

let isEmpty = dataprovider.isEmpty();
console.log('DataProvider is empty: ' + isEmpty);

removeEventListener(eventType: string, listener: EventListener): void

Remove a listener previously registered with addEventListener.
Parameters:
Name Type Description
eventType string The event type that the listener was registered for.
listener EventListener The callback function that was registered.

Type Definitions

Options<D>

Properties:
Name Type Argument Description
childrenAttribute string <optional>
Optional field name which stores the children of nodes in the data. Dot notation can be used to specify nested attribute. If this is not specified, the default is "children".
implicitSort Array.<SortCriterion.<D>> <optional>
Optional array of SortCriterion used to specify sort information when the data loaded into the dataprovider is already sorted. This is used for cases where we would like display some indication that the data is already sorted. For example, ojTable will display the column sort indicator for the corresponding column in either ascending or descending order upon initial render. This option is not used for cases where we want the MutableArrayTreeDataProvider to apply a sort on initial fetch. For those cases, please wrap in a ListDataProviderView and set the sortCriteria property on it.
keyAttributeScope 'siblings' | 'global' <optional>
Optional scope of the key values in the fields specified by keyAttribute. Supported values:
  • 'global': the key values are unique within the entire tree.
  • 'siblings': the key values are unique among the siblings of each node.
Default is 'global'.
sortComparators ArrayDataProvider.SortComparators<D> <optional>
Optional sortComparator to use for sort.

Sort follows JavaScript's localeCompare {numeric: true}. Please check String.prototype.localeCompare() for details.

For numbers, we convert them into strings then compare them with localeCompare, which may not sort floating point numbers based on their numeric values. If you want to sort floating point numbers based on their numeric values, sortComparator can be used to do a custom sort.

For undefined and null values, they are considered as the largest values during sorting. For an empty string, it is considered as the smallest value during sorting.

textFilterAttributes string[] <optional>
Optionally specify which attributes the filter should be applied on when a TextFilter filterCriteria is specified. If this option is not specified then the filter will be applied to all attributes.