openmct/src/selection/Selection.js
Scott Bell 4a9744e916
touch every file on earth to update copyright (#4729)
* touch every file on earth to update copyright

* update one more

* pick up 2017 2018 2019

* revert fuchs copyright

* revert other bundle copyrights

* somehow missed one
2022-01-18 11:27:28 -08:00

270 lines
8.9 KiB
JavaScript

/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'EventEmitter',
'lodash'
],
function (
EventEmitter,
_
) {
/**
* Manages selection state for Open MCT
* @private
*/
function Selection(openmct) {
EventEmitter.call(this);
this.openmct = openmct;
this.selected = [];
}
Selection.prototype = Object.create(EventEmitter.prototype);
/**
* Gets the selected object.
* @public
*/
Selection.prototype.get = function () {
return this.selected;
};
/**
* Selects the selectable object and emits the 'change' event.
*
* @param {object} selectable an object with element and context properties
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
* @private
*/
Selection.prototype.select = function (selectable, isMultiSelectEvent) {
if (!Array.isArray(selectable)) {
selectable = [selectable];
}
let multiSelect = isMultiSelectEvent
&& this.parentSupportsMultiSelect(selectable)
&& this.isPeer(selectable)
&& !this.selectionContainsParent(selectable);
if (multiSelect) {
this.handleMultiSelect(selectable);
} else {
this.handleSingleSelect(selectable);
}
};
/**
* @private
*/
Selection.prototype.handleMultiSelect = function (selectable) {
if (this.elementSelected(selectable)) {
this.remove(selectable);
} else {
this.addSelectionAttributes(selectable);
this.selected.push(selectable);
}
this.emit('change', this.selected);
};
/**
* @private
*/
Selection.prototype.handleSingleSelect = function (selectable) {
if (!_.isEqual([selectable], this.selected)) {
this.setSelectionStyles(selectable);
this.selected = [selectable];
this.emit('change', this.selected);
}
};
/**
* @private
*/
Selection.prototype.elementSelected = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath, selectable));
};
/**
* @private
*/
Selection.prototype.remove = function (selectable) {
this.selected = this.selected.filter(selectionPath => !_.isEqual(selectionPath, selectable));
if (this.selected.length === 0) {
this.removeSelectionAttributes(selectable);
selectable[1].element.click(); // Select the parent if there is no selection.
} else {
this.removeSelectionAttributes(selectable, true);
}
};
/**
* @private
*/
Selection.prototype.setSelectionStyles = function (selectable) {
this.selected.forEach(selectionPath => this.removeSelectionAttributes(selectionPath));
this.addSelectionAttributes(selectable);
};
Selection.prototype.removeSelectionAttributes = function (selectionPath, keepParentStyle) {
if (selectionPath[0] && selectionPath[0].element) {
selectionPath[0].element.removeAttribute('s-selected');
}
if (selectionPath[1] && selectionPath[1].element && !keepParentStyle) {
selectionPath[1].element.removeAttribute('s-selected-parent');
}
};
/*
* Adds selection attributes to the selected element and its parent.
* @private
*/
Selection.prototype.addSelectionAttributes = function (selectable) {
if (selectable[0] && selectable[0].element) {
selectable[0].element.setAttribute('s-selected', "");
}
if (selectable[1] && selectable[1].element) {
selectable[1].element.setAttribute('s-selected-parent', "");
}
};
/**
* @private
*/
Selection.prototype.parentSupportsMultiSelect = function (selectable) {
return selectable[1] && selectable[1].context.supportsMultiSelect;
};
/**
* @private
*/
Selection.prototype.selectionContainsParent = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath[0], selectable[1]));
};
/**
* @private
*/
Selection.prototype.isPeer = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath[1], selectable[1]));
};
/**
* @private
*/
Selection.prototype.isSelectable = function (element) {
if (!element) {
return false;
}
return Boolean(element.closest('[data-selectable]'));
};
/**
* @private
*/
Selection.prototype.capture = function (selectable) {
let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable);
if (!this.capturing || capturingContainsSelectable) {
this.capturing = [];
}
this.capturing.push(selectable);
};
/**
* @private
*/
Selection.prototype.selectCapture = function (selectable, event) {
if (!this.capturing) {
return;
}
let reversedCapturing = this.capturing.reverse();
delete this.capturing;
this.select(reversedCapturing, event.shiftKey);
};
/**
* Attaches the click handlers to the element.
*
* @param element an html element
* @param context object which defines item or other arbitrary properties.
* e.g. {
* item: domainObject,
* elementProxy: element,
* controller: fixedController
* }
* @param select a flag to select the element if true
* @returns a function that removes the click handlers from the element
* @public
*/
Selection.prototype.selectable = function (element, context, select) {
if (!this.isSelectable(element)) {
return () => {};
}
let selectable = {
context: context,
element: element
};
const capture = this.capture.bind(this, selectable);
const selectCapture = this.selectCapture.bind(this, selectable);
let removeMutable = false;
element.addEventListener('click', capture, true);
element.addEventListener('click', selectCapture);
if (context.item && context.item.isMutable !== true) {
removeMutable = true;
context.item = this.openmct.objects._toMutable(context.item);
}
if (select) {
if (typeof select === 'object') {
element.dispatchEvent(select);
} else if (typeof select === 'boolean') {
element.click();
}
}
return (function () {
element.removeEventListener('click', capture, true);
element.removeEventListener('click', selectCapture);
if (context.item !== undefined && context.item.isMutable && removeMutable === true) {
this.openmct.objects.destroyMutable(context.item);
}
}).bind(this);
};
return Selection;
});