salix/front/core/components/contextmenu/index.js

288 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(obj, targetProp, prop) {
if (prop == targetProp)
delete obj[prop];
if (prop === 'and' || prop === 'or') {
const arrayCopy = obj[prop].slice();
for (let param of arrayCopy) {
const [key] = Object.keys(param);
const index = obj[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == targetProp)
obj[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, filterKey, key);
if (Object.keys(param).length == 0)
obj[prop].splice(index, 1);
}
if (obj[prop].length == 0)
delete obj[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'
}
});