predix-ui
Version:
px-* web components as React styled components
437 lines (402 loc) • 13.5 kB
JavaScript
/* eslint-disable */
/**
* observers: [
'_selectedChanged(selected.*)',
'_selectedRouteChanged(selectedRoute.*)'
],
listeners: {
'px-app-asset-graph-created': '__selectInitialAssets',
'px-app-asset-should-be-selected' : '_assetSelectedByEvent',
'px-app-asset-should-be-deselected' : '_assetDeselectedByEvent'
},
*/
export default class AssetSelectable {
constructor(props) {
this.multiSelect = props.multiSelect || false;
this._lastSelection = {
source: null,
reason: null,
item: null
};
this.selected = this.multiSelect ? [] : null;
this.selectedRoute = this.multiSelect ? [] : null;
this.selectedMeta = this.multiSelect ? [] : null;
}
set(name, val) {
this[name] = val;
}
fire(name, data) {
console.log('fire', name, data);
}
get multiSelect() {
return this._multiSelect;
}
set multiSelect(val) {
if (val !== this._multiSelect) {
this._multiSelect = val;
this._toggleMultiSelect(val);
}
}
_toggleMultiSelect(allowMultiSelect) {
if (allowMultiSelect && !Array.isArray(this.selected)) {
this.selected = [];
this.selectedRoute = [];
}
if (!allowMultiSelect && Array.isArray(this.selected)) {
this.selected = this.selected.length ? this.selected[0] : null;
}
}
/**
* Selects an item. Call with an object that is a direct reference to one
* of the `items` objects.
*
* If multi-select mode is enabled, call with an object to select a single
* item or an array of objects to select multiple items.
*
* An optional source of the change can be provided as a string.
*
* @param {Object|Array} item
* @param {String} source
* @return {Object|Array} - The next `selected` item or items
*/
select(item, source = 'METHOD') {
if ((!this.multiSelect && item === null && this._lastSelection.item !== null) ||
(this.multiSelect && item === null && this.selected.length) ||
(this.multiSelect && Array.isArray(item) && !item.length && this.selected.length)) {
this.deselect(Array.isArray(this.selected) ? [...this.selected] : this.selected);
return this.selected;
}
if (!item || (!this.multiSelect && item === this._lastSelection.item) || (this.multiSelect && this.selected.indexOf(item) > -1)) return this.selected;
if (this.multiSelect && Array.isArray(item) && item.length) {
for (let i = 0; i < item.length; i++) {
this.select(item[i], source);
}
return this.selected;
}
if (this._assetGraph.hasNode(item)) {
this._selectAsset(item, source);
return this.selected;
}
throw new Error(`The following item could not be found in the items graph:
${JSON.stringify(item)}`);
}
/**
* Deselects an item. Call with a direct reference to the selected item
* or with `null` to deselect whatever is selected.
*
* If multi-select mode is enabled, call with a direct reference to one of
* the selected items to deselect it, or an array of selected items to
* deselect multiple, or `null` to deselect all items.
*
* @param {Object|Array|null} item
* @param {String} source
* @return {Object|Array|null} - The remaining `selected` items or null
*/
deselect(item, source = 'METHOD') {
if (!this.multiSelect && (!item || this.selected === item)) {
this._deselectAsset(this.selected);
return this.selected;
}
if (this.multiSelect && !item) {
this.deselect([...this.selected]);
return this.selected;
}
if (this.multiSelect && Array.isArray(item) && this.selected.length) {
for (let i = 0; i < item.length; i++) {
this.deselect(item[i], source);
}
return this.selected;
}
if (this.multiSelect && item && this.selected.indexOf(item) > -1) {
this._deselectAsset(item);
return this.selected;
}
}
_getSelectedMeta(selected) {
if (selected.base && !Array.isArray(selected.base) && this._assetGraph && this._assetGraph.hasNode(selected.base)) {
const {
path,
route,
parent,
children,
siblings
} = this._assetGraph.getInfo(selected.base, this.keys.id);
return {
item: selected.base,
path,
route,
parent,
children,
siblings
};
}
if (selected.base && Array.isArray(selected.base) && selected.base.length) {
return selected.base.map((item) => {
const {
path,
route,
parent,
children,
siblings
} = this._assetGraph.getInfo(item, this.keys.id);
return {
item,
path,
route,
parent,
children,
siblings
};
});
}
return {
item: null,
path: null,
route: null,
parent: null,
children: null,
siblings: null
};
}
/**
* The event `detail.item` should be a reference to an item in the asset graph.
*/
_assetSelectedByEvent(evt) {
evt.stopPropagation();
if (evt.detail.item) {
this.select(evt.detail.item, 'DOM_EVENT');
}
}
/**
* The event `detail.item` should be a reference to an item in the asset graph.
*/
_assetDeselectedByEvent(evt) {
evt.stopPropagation();
if (evt.detail.item) {
this.deselect(evt.detail.item, 'DOM_EVENT');
}
}
/**
* Should only sync changes to `selectedRoute` when necessary to avoid
* infinite loop of `selected` observer triggering `selectedRoute` observer.
*/
_selectedRouteChanged(ref) {
if (!ref || !ref.path || !this._assetGraph || this._squashSelectedRouteChange) return;
if (!this.multiSelect && ref.path === 'selectedRoute') {
this._updateSelectedFromRoute(ref.base);
}
if (this.multiSelect && (ref.path === 'selectedRoute' || ref.path == 'selectedRoute.splices')) {
this._updateSelectedFromRouteMulti(ref.base);
}
}
_updateSelectedFromRoute(route) {
if (!this.selected && (route === null || (Array.isArray(route) && !route.length))) {
/* We hit this case when the asset graph is created early (before our
default `selectedRoute` property has been evaluated to null) and
we call into this method. We should do nothing in this case. */
return;
}
if (this.selected && (route === null || (Array.isArray(route) && !route.length))) {
this._squashSelectedChange = true;
this.deselect(this.selected, 'ROUTE_CHANGED');
this._squashSelectedChange = false;
return;
}
const item = this._assetGraph.getNodeAtRoute(route, this.keys.id);
if (item === this.selected) {
} else if (item) {
this.select(item, 'ROUTE_CHANGED');
} else {
throw new Error(`The route ${JSON.stringify(route)} could not be found in the items graph.`);
}
}
_updateSelectedFromRouteMulti(route) {
if (this.selected.length && (route === null || (Array.isArray(route) && !route.length))) {
this._squashSelectedChange = true;
this.deselect(null, 'ROUTE_CHANGED');
this._squashSelectedChange = false;
return;
}
if (Array.isArray(route)) {
for (let i = 0; i < route.length; i++) {
this._squashSelectedChange = true;
this.selected = route.map((route) => {
const item = this._assetGraph.getNodeAtRoute(route, this.keys.id);
if (!item) {
throw new Error(`The following item could not be found in the items graph:
${JSON.stringify(item)}`);
}
return item;
});
this._squashSelectedChange = false;
}
}
}
/**
* Should only sync changes to `selectedRoute` when necessary to avoid
* infinite loop of `selected` observer triggering `selectedRoute` observer.
*/
_selectedChanged(ref) {
if (!ref || !ref.path || this._squashSelectedChange || !this._assetGraph) return;
if (!this.multiSelect && ref.path === 'selected' && typeof this.selectedRoute !== 'undefined') {
this._updateSelectedRoute(ref.base);
}
if (this.multiSelect && (ref.path === 'selected' || ref.path === 'selected.splices') && typeof this.selectedRoute !== 'undefined') {
this._updateSelectedRouteMulti(ref.base);
}
}
_checkIfEmpty(item) {
if (Array.isArray(item)) return !!item.length;
return !!item;
}
_updateSelectedRoute(selected) {
if (this._checkIfEmpty(selected)) {
if (!this._assetGraph.hasNode(selected)) {
throw new Error(`The following item could not be found in the items graph:
${JSON.stringify(selected)}`);
}
const route = this._assetGraph.getRoute(selected, this.keys.id);
if (this._routeIsDifferent(route, this.selectedRoute)) {
this._squashSelectedRouteChange = true;
this.selectedRoute = route;
this._squashSelectedRouteChange = false;
}
} else {
this._squashSelectedRouteChange = true;
this.selectedRoute = null;
this._squashSelectedRouteChange = false;
}
}
_updateSelectedRouteMulti(selected) {
if (selected && Array.isArray(selected) && selected.length) {
this._squashSelectedRouteChange = true;
this.selectedRoute = selected.map((item) => {
if (!this._assetGraph.hasNode(item)) {
throw new Error(`The following item could not be found in the items graph:
${JSON.stringify(item)}`);
}
return this._assetGraph.getRoute(item, this.keys.id);
});
this._squashSelectedRouteChange = false;
} else {
this._squashSelectedRouteChange = true;
this.selectedRoute = [];
this._squashSelectedRouteChange = false;
}
}
__selectInitialAssets() {
if (!this.multiSelect && this.selected && !this.selectedRoute) {
this._updateSelectedRoute(this.selected);
} else if (!this.multiSelect && !this.selected && this.selectedRoute) {
this._updateSelectedFromRoute(this.selectedRoute);
} else if (this.multiSelect && !!this.selected && this.selected.length && !this.selectedRoute.length) {
this._updateSelectedRouteMulti(this.selected);
} else if (this.multiSelect && !!this.selected && !this.selected.length && this.selectedRoute.length) {
this._updateSelectedFromRouteMulti(this.selected);
}
}
_routeIsDifferent(r1, r2) {
if (!r1 || !r2) return true;
if (r1.length !== r2.length) return true;
for (let i = 0; i < r1.length; i++) {
if (r1[i] !== r2[i]) return true;
}
return false;
}
_selectAsset(item, source) {
const {
route,
path
} = this._assetGraph.getInfo(item, this.keys.id);
this._lastSelection = {
item,
source,
route
};
if (!this.multiSelect) {
this.selected = item;
}
if (this.multiSelect) {
this.push('selected', item);
}
this.fire('px-app-asset-selected', {
source,
item,
route,
path
});
}
/**
* Fired when a new item is selected. Includes details about how the item
* was selected, and information about the new selected item.
*
* The `source` property is a string describing what triggered
* the selection:
*
* * 'DOM_EVENT' - the user interacted with an item and selected it
* * 'ROUTE_CHANGED' - the array bound to `selectedRoute` changed
* * 'ITEM_CHANGED' - the object bound to `selected` changed
* * 'METHOD' - the `select()` method was called
*
* The event will have the following properties:
*
* * {Object} detail - Contains the event details
* * {String} detail.source - Info about the change trigger, see above
* * {Object} detail.item - Reference to the item
* * {Array} detail.route - Route from the top of the graph to the item
* * {Array} detail.path - Path from the top of the graph to the item
*
* @event px-app-asset-selected
*/
_deselectAsset(item, source) {
const {
route,
path
} = this._assetGraph.getInfo(item, this.keys.id);
this._lastSelection = {
item: null,
source: null,
route: null
};
if (!this.multiSelect) {
this.set('selected', null);
this.fire('px-app-asset-deselected', {
source,
item,
route,
path
});
}
if (this.multiSelect) {
this.splice('selected', this.selected.indexOf(item), 1);
this.fire('px-app-asset-deselected', {
source,
item,
route,
path
});
}
}
/**
* Fired when a new item is deselected. Includes details about how the item
* was deselected.
*
* The `source` property is a string describing what triggered
* the deselection:
*
* * 'DOM_EVENT' - the user interacted with an item and selected it
* * 'ROUTE_CHANGED' - the array bound to `selectedRoute` changed
* * 'ITEM_CHANGED' - the object bound to `selected` changed
* * 'METHOD' - the `select()` method was called
*
* The event will have the following properties:
*
* * {Object} detail - Contains the event details
* * {String} detail.source - Info about the change trigger, see above
* * {Object} detail.item - Reference to the witem
* * {Array} detail.route - Route from the top of the graph to the item
* * {Array} detail.path - Path from the top of the graph to the item
*
* @event px-app-asset-deselected
*/
}