azure-devops-ui
Version:
React components for building web UI in Azure DevOps
143 lines (142 loc) • 6.02 kB
JavaScript
import { Observable, ObservableLike } from '../Core/Observable';
/**
* An AggregateItemProvider takes a set of arrays, ObservableArrays, and ItemProviders and returns listItems from them as if they were a single
* ItemProvider.
*/
export class AggregateItemProvider extends Observable {
constructor() {
super();
this.internalCollections = [];
this.items = [];
}
get length() {
return this.items.length;
}
get value() {
return this.items;
}
get collections() {
return this.internalCollections;
}
/**
* Adds an additional collection of items to the end of the array
*
* @param collection Array of items or an observable array of items
*/
push(collection) {
let collectionEntry;
if (ObservableLike.isObservable(collection)) {
const provider = collection;
const subscriber = this.getSubscriber(this.internalCollections.length);
collectionEntry = { items: provider.value, provider, subscriber };
if (this.subscriberCount) {
ObservableLike.subscribe(collectionEntry.provider, subscriber);
}
}
else if (collection.value) {
collectionEntry = { items: collection.value, provider: collection };
}
else if (collection.length) {
collectionEntry = { items: collection };
}
if (collectionEntry) {
this.internalCollections.push(collectionEntry);
if (collectionEntry.items.length) {
this.items.push(...collectionEntry.items);
if (this.subscriberCount) {
this.notify({ addedItems: collectionEntry.items, index: this.items.length - collectionEntry.items.length }, "push");
}
}
}
}
getItem(index) {
try {
let accumulatedIndex = 0;
for (let collectionIndex = 0; collectionIndex < this.internalCollections.length; collectionIndex++) {
const collection = this.internalCollections[collectionIndex];
const provider = collection.provider;
const collectionLength = provider ? provider.length : collection.items.length;
// If the overall index falls within the current collection...
if (index < accumulatedIndex + collectionLength) {
const localIndex = index - accumulatedIndex;
if (provider && provider.getItem) {
return provider.getItem(localIndex);
}
else {
return collection.items[localIndex];
}
}
accumulatedIndex += collectionLength;
}
// Return undefined if index is out of bounds.
return undefined;
}
catch (error) {
document.dispatchEvent(new CustomEvent('vss-telemetry-proxy', {
detail: {
area: "VssDropdown",
component: "AggregateItemProvider",
feature: "VssDropdown.AggregateItemProvider",
level: 3,
method: "getItem",
message: "An error occurred while getting item",
properties: {
error: error.message,
stack: error.stack,
index: index,
values: this.value
}
}, bubbles: true
}));
}
}
subscribe(observer, action) {
const subscription = super.subscribe(observer, action);
if (this.subscriberCount === 1) {
for (const collection of this.internalCollections) {
if (collection.subscriber && collection.provider && collection.provider.subscribe) {
collection.provider.subscribe(collection.subscriber);
}
}
// Collections may have changed between the time they were pushed and subscribed to.
// Update the aggregate provider's items to match the current state of its collections.
const currentItems = [];
for (const collection of this.internalCollections) {
currentItems.push(...collection.items);
}
this.items.splice(0, this.items.length, ...currentItems);
}
return subscription;
}
unsubscribe(observer, action) {
super.unsubscribe(observer, action);
if (this.subscriberCount === 0) {
for (const collection of this.internalCollections) {
if (collection.subscriber && collection.provider && collection.provider.unsubscribe) {
collection.provider.unsubscribe(collection.subscriber);
}
}
}
}
getSubscriber(collectionIndex) {
return (args) => {
// Find the index in our aggregate array
let index = args.index;
for (let i = 0; i < collectionIndex; i++) {
index += this.internalCollections[i].items.length;
}
if (args.changedItems) {
// Handle change event
this.items.splice(index, args.changedItems.length, ...args.changedItems);
this.notify({ changedItems: args.changedItems, index }, "change");
}
else {
// Handle splice, push, pop events
const removedItems = args.removedItems || [];
const addedItems = args.addedItems || [];
this.items.splice(index, removedItems.length, ...addedItems);
this.notify({ removedItems, addedItems, index }, "splice");
}
};
}
}