285 lines
7.5 KiB
JavaScript
Executable File
285 lines
7.5 KiB
JavaScript
Executable File
import ngModule from '../../module';
|
|
import {buildFilter} from 'vn-loopback/util/filter';
|
|
import './style.scss';
|
|
|
|
export default class Contextmenu {
|
|
constructor($element, $, $transclude) {
|
|
this.$element = $element;
|
|
this.element = $element[0];
|
|
this.$ = $;
|
|
this.$transclude = $transclude;
|
|
}
|
|
|
|
get targets() {
|
|
return this._targets;
|
|
}
|
|
|
|
set targets(value) {
|
|
this._targets = value;
|
|
|
|
if (!value) return;
|
|
|
|
for (let selector of value) {
|
|
const target = document.querySelector(selector);
|
|
if (!target) continue;
|
|
|
|
target.addEventListener('contextmenu', event => {
|
|
this.target = event.target;
|
|
|
|
if (!event.defaultPrevented)
|
|
event.preventDefault();
|
|
|
|
if (!this.isMenuEnabled()) return;
|
|
|
|
const parent = this.$.contextmenu;
|
|
parent.style.top = event.pageY + 'px';
|
|
parent.style.left = event.pageX + 'px';
|
|
|
|
this.$.menu.show(parent);
|
|
});
|
|
}
|
|
}
|
|
|
|
get row() {
|
|
if (!this.target) return null;
|
|
|
|
return this.target.closest('[ng-repeat]');
|
|
}
|
|
|
|
get rowIndex() {
|
|
if (!this.row) return null;
|
|
|
|
const table = this.row.closest('table, vn-table, .vn-table');
|
|
const rows = table.querySelectorAll('[ng-repeat]');
|
|
|
|
return Array.from(rows).findIndex(
|
|
rowItem => rowItem == this.row
|
|
);
|
|
}
|
|
|
|
get rowData() {
|
|
const model = this.model;
|
|
const rowData = model.data[this.rowIndex];
|
|
|
|
return rowData;
|
|
}
|
|
|
|
get cell() {
|
|
if (!this.target) return null;
|
|
|
|
return this.target.closest('td, vn-td, .vn-td, vn-td-editable');
|
|
}
|
|
|
|
get cellIndex() {
|
|
if (!this.row) return null;
|
|
|
|
const cells = this.row.querySelectorAll('td, vn-td, .vn-td, vn-td-editable');
|
|
return Array.from(cells).findIndex(
|
|
cellItem => cellItem == this.cell
|
|
);
|
|
}
|
|
|
|
get rowHeader() {
|
|
if (!this.row) return null;
|
|
|
|
const table = this.row.closest('table, vn-table, .vn-table');
|
|
const headerCells = table && table.querySelectorAll('thead th, vn-thead vn-th');
|
|
const headerCell = headerCells && headerCells[this.cellIndex];
|
|
|
|
return headerCell;
|
|
}
|
|
|
|
/**
|
|
* Selected model field name
|
|
*
|
|
* @return {string}
|
|
*/
|
|
get fieldName() {
|
|
if (!this.rowHeader) return null;
|
|
|
|
return this.rowHeader.getAttribute('field');
|
|
}
|
|
|
|
/**
|
|
* Selected field value
|
|
*
|
|
* @return {any}
|
|
*/
|
|
get fieldValue() {
|
|
return this.rowData[this.fieldName];
|
|
}
|
|
|
|
/**
|
|
* Returns true if filter is not disabled
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
isFilterEnabled() {
|
|
if (!this.rowHeader) return true;
|
|
const isEnabled = this.rowHeader.getAttribute('filter-enabled');
|
|
|
|
return isEnabled != 'false';
|
|
}
|
|
|
|
isMenuEnabled() {
|
|
if (!this.rowHeader) return true;
|
|
const isEnabled = this.rowHeader.getAttribute('menu-enabled');
|
|
|
|
return isEnabled != 'false';
|
|
}
|
|
|
|
/**
|
|
* Returns true if filter
|
|
* by selection is enabled and
|
|
* the menu can be interacted
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
isFilterAllowed() {
|
|
return this.isActionAllowed() && this.isFilterEnabled();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the
|
|
* context menu can be interacted
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
isActionAllowed() {
|
|
if (!this.target) return false;
|
|
const isTableCell = this.target.closest('td, vn-td, .vn-td');
|
|
|
|
return isTableCell && this.fieldName;
|
|
}
|
|
|
|
/**
|
|
* Filter by current field selection
|
|
*/
|
|
filterBySelection() {
|
|
let where = {[this.fieldName]: this.fieldValue};
|
|
if (this.exprBuilder) {
|
|
where = buildFilter(where, (param, value) =>
|
|
this.exprBuilder({param, value})
|
|
);
|
|
}
|
|
|
|
this.model.addFilter({where});
|
|
}
|
|
|
|
/**
|
|
* Exclude by current field selection
|
|
*/
|
|
excludeSelection() {
|
|
let where = {[this.fieldName]: {neq: this.fieldValue}};
|
|
if (this.exprBuilder) {
|
|
where = {[this.fieldName]: this.fieldValue};
|
|
where = buildFilter(where, (param, value) => {
|
|
const expr = this.exprBuilder({param, value});
|
|
const props = Object.keys(expr);
|
|
let newExpr = {};
|
|
for (let prop of props) {
|
|
if (expr[prop].like) {
|
|
const operator = expr[prop].like;
|
|
newExpr[prop] = {nlike: operator};
|
|
} else if (expr[prop].between) {
|
|
const operator = expr[prop].between;
|
|
newExpr = {
|
|
or: [
|
|
{[prop]: {lt: operator[0]}},
|
|
{[prop]: {gt: operator[1]}},
|
|
]
|
|
};
|
|
} else
|
|
newExpr[prop] = {neq: this.fieldValue};
|
|
}
|
|
return newExpr;
|
|
});
|
|
}
|
|
|
|
this.model.addFilter({where});
|
|
}
|
|
|
|
removeFilter() {
|
|
const userFilter = this.model.userFilter;
|
|
const userParams = this.model.userParams;
|
|
const where = userFilter && userFilter.where;
|
|
|
|
let filterKey = this.fieldName;
|
|
if (this.exprBuilder) {
|
|
const param = this.exprBuilder({
|
|
param: filterKey,
|
|
value: null
|
|
});
|
|
if (param) [filterKey] = Object.keys(param);
|
|
}
|
|
|
|
if (!where) return;
|
|
|
|
const whereKeys = Object.keys(where);
|
|
for (let key of whereKeys) {
|
|
removeProp(where, filterKey, key);
|
|
|
|
if (!Object.keys(where))
|
|
delete userFilter.where;
|
|
}
|
|
|
|
function removeProp(instance, findProp, prop) {
|
|
if (prop == findProp)
|
|
delete instance[prop];
|
|
|
|
if (prop === 'and' || prop === 'or') {
|
|
const instanceCopy = instance[prop].slice();
|
|
for (let param of instanceCopy) {
|
|
const [key] = Object.keys(param);
|
|
const index = instance[prop].findIndex(param => {
|
|
return Object.keys(param)[0] == key;
|
|
});
|
|
if (key == findProp)
|
|
instance[prop].splice(index, 1);
|
|
|
|
if (param[key] instanceof Array)
|
|
removeProp(param, filterKey, key);
|
|
}
|
|
|
|
if (instance[prop].length == 0)
|
|
delete instance[prop];
|
|
}
|
|
}
|
|
|
|
this.model.applyFilter(userFilter, userParams);
|
|
}
|
|
|
|
/**
|
|
* Removes all applied filters
|
|
*/
|
|
removeAllFilters() {
|
|
const userParams = this.model.userParams;
|
|
this.model.applyFilter(null, userParams);
|
|
}
|
|
|
|
/**
|
|
* Copies the current field
|
|
* value to the clipboard
|
|
*/
|
|
copyValue() {
|
|
const cell = angular.element(this.cell);
|
|
if (navigator && navigator.clipboard)
|
|
navigator.clipboard.writeText(cell.text());
|
|
}
|
|
}
|
|
|
|
Contextmenu.$inject = ['$element', '$scope', '$transclude'];
|
|
|
|
ngModule.vnComponent('vnContextmenu', {
|
|
controller: Contextmenu,
|
|
template: require('./index.html'),
|
|
bindings: {
|
|
targets: '<?',
|
|
model: '<?',
|
|
exprBuilder: '&?'
|
|
},
|
|
transclude: {
|
|
menu: '?slotMenu'
|
|
}
|
|
});
|