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: '