@nuxeo/nuxeo-ui-elements
Version:
Nuxeo UI Web Components.
1,031 lines (938 loc) • 34 kB
JavaScript
/**
@license
Copyright 2016 Sauli Tähkäpää
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.
*/
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { dom } from '@polymer/polymer/lib/legacy/polymer.dom.js';
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
import { IronFormElementBehavior } from '@polymer/iron-form-element-behavior/iron-form-element-behavior.js';
import { IronValidatableBehavior } from '@polymer/iron-validatable-behavior/iron-validatable-behavior.js';
import '@nuxeo/nuxeo-elements/nuxeo-element.js';
import '@polymer/iron-list/iron-list.js';
import '@polymer/iron-scroll-threshold/iron-scroll-threshold.js';
import { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/paper-input/paper-input.js';
import '@polymer/paper-checkbox/paper-checkbox.js';
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
import '@polymer/paper-styles/paper-styles-classes.js';
import '@polymer/polymer/lib/elements/dom-if.js';
import '@polymer/polymer/lib/elements/dom-repeat.js';
import { microTask, timeOut } from '@polymer/polymer/lib/utils/async.js';
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
import '../widgets/nuxeo-dialog.js';
import './data-table-column.js';
import './data-table-column-sort.js';
import './data-table-column-filter.js';
import './data-table-cell.js';
import './data-table-row.js';
import './data-table-row-detail.js';
import './data-table-checkbox.js';
import './data-table-settings.js';
import './default-styles.js';
import './data-table-icons.js';
import './nuxeo-data-table-row-actions.js';
import './nuxeo-data-table-form.js';
import { PageProviderDisplayBehavior } from '../nuxeo-page-provider-display-behavior.js';
import { DraggableListBehavior } from '../nuxeo-draggable-list-behavior.js';
import '../nuxeo-button-styles.js';
{
/**
* An element to display a page provider result within a table with infinite scrolling.
*
* Example:
*
* <nuxeo-page-provider id="cvProvider" auto
* provider="default_search"
* page-size="40"
* aggregations="{{aggregations}}">
* </nuxeo-page-provider>
*
* <nuxeo-data-table id="datatable"
* nx-provider="cvProvider">
* <nuxeo-data-table-column name="Full text search" flex="100" filter-by="ecm_fulltext" sort-by="dc:title">
* <template>
* <a class="title ellipsis">[[item.title]]</a>
* </template>
* </nuxeo-data-table-column>
* <nuxeo-data-table-column filter-by="dc_modified_agg" flex="50" sort-by="dc:modified">
* <template is="header">
* <nuxeo-dropdown-aggregation
* placeholder="Modified"
* data="[[aggregations.dc_modified_agg]]"
* value="{{column.filterValue}}" multiple>
* </nuxeo-dropdown-aggregation>
* </template>
* <template>
* [[item.properties.dc:modified]]
* </template>
* </nuxeo-data-table-column>
* <nuxeo-data-table-column filter-by="dc_creator_agg" flex="50">
* <template is="header">
* <nuxeo-dropdown-aggregation
* placeholder="Author"
* data="[[aggregations.dc_creator_agg]]"
* value="{{column.filterValue}}" multiple>
* </nuxeo-dropdown-aggregation>
* </template>
* <template>
* <span class="tag user">[[item.properties.dc:creator]]</span>
* </template>
* </nuxeo-data-table-column>
* </nuxeo-data-table>
*
* @appliesMixin Polymer.IronResizableBehavior
* @appliesMixin Polymer.IronFormElementBehavior
* @appliesMixin Polymer.IronValidatableBehavior
* @appliesMixin Nuxeo.PageProviderDisplayBehavior
* @appliesMixin Nuxeo.DraggableListBehavior
* @memberof Nuxeo
* @demo demo/nuxeo-data-table/index.html
*/
class DataTable extends mixinBehaviors(
[
IronResizableBehavior,
IronFormElementBehavior,
IronValidatableBehavior,
PageProviderDisplayBehavior,
DraggableListBehavior,
],
Nuxeo.Element,
) {
static get template() {
return html`
<style include="nuxeo-button-styles">
:host {
display: block;
position: relative;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
min-height: 300px;
--iron-data-table;
}
:host([draggable]) ::slotted(nuxeo-data-table-row[selected]) {
cursor: -webkit-grab;
cursor: grab;
}
:host .droptarget-hover ::slotted(nuxeo-data-table-row) {
border: 2px dashed var(--nuxeo-primary-color, blue);
}
/* scrollbars */
:host ::-webkit-scrollbar-track {
width: 12px !important;
height: 3px;
}
:host ::-webkit-scrollbar {
background-color: rgba(0, 0, 0, 0.03);
width: 12px !important;
height: 3px;
}
:host ::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.15);
border-radius: 1px !important;
}
:host([required]) label::after {
display: inline-block;
content: '*';
margin-left: 4px;
color: var(--paper-input-container-invalid-color, #de350b);
}
:host([settings-enabled]) ::slotted(nuxeo-data-table-row:not([header])) {
padding-inline-end: 1.5em;
}
[hidden] {
display: none !important;
}
#container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
}
#header {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
padding-inline-start: 2px;
transition: box-shadow 200ms;
-webkit-transition: box-shadow 200ms;
--iron-data-table-header;
}
#header.scrolled {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06), 0 2px 0 rgba(0, 0, 0, 0.075), 0 3px 0 rgba(0, 0, 0, 0.05),
0 4px 0 rgba(0, 0, 0, 0.015);
}
#list {
overflow-x: hidden !important;
overflow-y: auto !important;
flex: 1;
transition: opacity 200ms;
-webkit-transition: opacity 200ms;
}
#list .item {
background: var(--nuxeo-table-items-background, #ffffff);
}
.emptyResult {
opacity: 0.5;
display: block;
font-weight: 300;
padding: 1.5em 0.7em;
text-align: center;
font-size: 1.1em;
}
.error {
color: var(--paper-input-container-invalid-color, #de350b);
}
label {
display: block;
--nuxeo-label;
}
</style>
<div id="container">
<slot name="nuxeo-selection-toolbar"></slot>
<label>[[label]]</label>
<label class="error" hidden$="[[!invalid]]">[[errorMessage]]</label>
<div id="header">
<nuxeo-data-table-row header>
<nuxeo-data-table-checkbox
style$="[[_computeSelectAllVisibility(selectionEnabled, selectAllEnabled, multiSelection)]]"
checked="[[selectAllActive]]"
on-click="_toggleSelectAll"
></nuxeo-data-table-checkbox>
<dom-repeat items="[[columns]]" as="column">
<template>
<nuxeo-data-table-cell
header
align-right="[[column.alignRight]]"
before-bind="[[beforeCellBind]]"
column="[[column]]"
flex="[[column.flex]]"
hidden="[[column.hidden]]"
order="[[column.order]]"
table="[[_this]]"
template="[[column.headerTemplate]]"
width="[[column.width]]"
>
<nuxeo-data-table-column-sort
sort-order="[[sortOrder]]"
path="[[column.sortBy]]"
on-sort-direction-changed="_sort"
hidden$="[[!column.sortBy]]"
>
</nuxeo-data-table-column-sort>
</nuxeo-data-table-cell>
</template>
</dom-repeat>
<div style$="[[_computeActionsStyle(editable, orderable)]]">
<nuxeo-data-table-cell></nuxeo-data-table-cell>
</div>
<nuxeo-data-table-settings
columns="{{columns}}"
hidden$="[[!settingsEnabled]]"
></nuxeo-data-table-settings>
</nuxeo-data-table-row>
</div>
<dom-if if="[[_isEmpty]]">
<template>
<div class="emptyResult">[[_computedEmptyLabel]]</div>
</template>
</dom-if>
<iron-list
id="list"
items="[[items]]"
as="item"
selected-items="{{selectedItems}}"
selected-item="{{selectedItem}}"
on-scroll="_scroll"
>
<template>
<div class="item">
<nuxeo-data-table-row
on-click="_onRowClick"
before-bind="[[beforeRowBind]]"
even$="[[!_isEven(index)]]"
expanded="[[_isExpanded(item, _expandedItems, _expandedItems.*)]]"
index="[[index]]"
item="[[item]]"
tabindex="-1"
selected="[[_isSelected(item, selectedItems, selectedItems.*)]]"
>
<nuxeo-data-table-checkbox
hidden$="[[!selectionEnabled]]"
tabindex="0"
checked$="[[selected]]"
on-click="_onCheckBoxTap"
on-keydown="_onCheckBoxKeydown"
disabled$="[[selectAllActive]]"
></nuxeo-data-table-checkbox>
<dom-repeat items="[[columns]]" as="column" index-as="colIndex">
<template>
<nuxeo-data-table-cell
template="[[column.template]]"
table="[[_this]]"
align-right="[[column.alignRight]]"
column="[[column]]"
expanded="[[_isExpanded(item, _expandedItems, _expandedItems.*)]]"
flex="[[column.flex]]"
hidden="[[column.hidden]]"
index="[[index]]"
item="[[item]]"
order="[[column.order]]"
selected="[[_isSelected(item, selectedItems, selectedItems.*)]]"
width="[[column.width]]"
before-bind="[[beforeCellBind]]"
></nuxeo-data-table-cell>
</template>
</dom-repeat>
<dom-if if="[[_isExpanded(item, _expandedItems)]]" on-dom-change="_updateSizeForItem">
<template>
<nuxeo-data-table-row-detail
index="[[index]]"
item="[[item]]"
expanded="[[_isExpanded(item, _expandedItems, _expandedItems.*)]]"
selected="[[_isSelected(item, selectedItems, selectedItems.*)]]"
before-bind="[[beforeDetailsBind]]"
table="[[_this]]"
template="[[rowDetail]]"
></nuxeo-data-table-row-detail>
</template>
</dom-if>
<div style$="[[_computeActionsStyle(editable, orderable)]]">
<nuxeo-data-table-row-actions
index="[[index]]"
editable="[[editable]]"
orderable="[[orderable]]"
template="[[rowForm]]"
item="[[item]]"
size="[[items.length]]"
table="[[_this]]"
>
</nuxeo-data-table-row-actions>
</div>
</nuxeo-data-table-row>
</div>
</template>
</iron-list>
<dom-if if="[[editable]]">
<template>
<paper-button id="addEntry" class="secondary" noink on-click="_createEntry">
+ [[i18n('command.add')]]
</paper-button>
</template>
</dom-if>
<iron-scroll-threshold
id="scrollThreshold"
scroll-target="list"
on-lower-threshold="_threshold"
></iron-scroll-threshold>
</div>
<slot id="columns"></slot>
<nuxeo-dialog id="dialog" with-backdrop on-opened-changed="_formDialogOpenedChanged">
<h2>[[i18n('command.add')]]</h2>
<paper-dialog-scrollable>
<slot id="form" name="form"></slot>
</paper-dialog-scrollable>
<div class="buttons">
<paper-button noink dialog-dismiss class="secondary">[[i18n('command.cancel')]]</paper-button>
<paper-button id="save" noink on-click="_validateEntry" class="primary"
>[[i18n('command.ok')]]</paper-button
>
</div>
</nuxeo-dialog>
`;
}
static get is() {
return 'nuxeo-data-table';
}
static get properties() {
return {
/**
* @ignore
* A function that is called before data is bound to a row or header cell.
* Can be used to customize the cell element depending on the data.
* #### Example:
* ```js
* function(data, cell) {
* cell.toggleClass('custom', data.useCustomClass);
* }
* ```
*/
beforeCellBind: Object,
/**
* @ignore
* A function that is called before data is bound to a row details element.
* Can be used to customize the element depending on the data.
* #### Example:
* ```js
* function(data, details) {
* details.toggleClass('custom', data.useCustomClass);
* }
* ```
*/
beforeDetailsBind: Object,
/**
* @ignore
* A function that is called before data is bound to a row.
* Can be used to customize the row element depending on the data.
* #### Example:
* ```js
* function(data, row) {
* row.toggleClass('custom', data.useCustomClass);
* }
* ```
*/
beforeRowBind: Object,
/**
* @ignore
* If `true`, tapping a row will expand the item details, if available.
*/
detailsEnabled: {
type: Boolean,
value: false,
},
/**
* @ignore
* An array of `data-table-column` elements which contain the templates
* to be stamped with items.
*/
columns: {
type: Array,
notify: true,
value() {
return [];
},
observer: '_columnsChanged',
},
_expandedItems: {
type: Array,
value() {
return [];
},
},
_this: {
type: Object,
value() {
return this;
},
},
label: {
type: String,
},
required: {
type: Boolean,
value: false,
},
errorMessage: {
type: String,
},
/**
* If enabled, it allows user to select which result columns to display in the search results.
*/
settingsEnabled: {
type: Boolean,
reflectToAttribute: true,
value: false,
},
/**
* If enabled, it allows to select multiple documents and apply an action on them,
* like moving them to trash, to favorites or to a collection.
*/
multiSelection: {
type: Boolean,
value: true,
},
/**
* @ignore
*/
editable: {
type: Boolean,
value: false,
},
/**
* In the context of multivalued fields, it displays icons to reorder the properties up or down.
*/
orderable: {
type: Boolean,
value: false,
},
/**
* If enabled, pagination will be used instead of lazy loading.
*/
paginable: {
type: Boolean,
value: false,
},
};
}
static get observers() {
return ['_alignHeaderRow(items.length)'];
}
constructor() {
super();
this.handlesSorting = true;
this.handlesSelectAll = true;
this._observer = dom(this).observeNodes((info) => {
const hasColumns = function(node) {
return node.nodeType === Node.ELEMENT_NODE && node instanceof Nuxeo.DataTableColumn;
};
const hasDetails = function(node) {
return (
node.nodeType === Node.ELEMENT_NODE &&
node.tagName === 'TEMPLATE' &&
node.hasAttribute('is') &&
node.getAttribute('is') === 'row-detail'
);
};
if (info.addedNodes.filter(hasColumns).length > 0 || info.removedNodes.filter(hasColumns).length > 0) {
this.set('columns', this.$.columns.assignedNodes().filter(hasColumns));
this._backupColumnsState();
this.notifyResize();
}
if (info.addedNodes.filter(hasDetails).length > 0) {
this.set('rowDetail', this.getContentChildren('[select="template[is=row-detail]"]')[0]);
// assuming parent element is always a Polymer element.
// set dataHost to the same context the template was declared in
const parent = dom(this.rowDetail).parentNode;
this.rowDetail._rootDataHost = parent.dataHost ? parent.dataHost._rootDataHost || parent.dataHost : parent;
}
});
}
ready() {
super.ready();
this.addEventListener('iron-resize', this._resizeCellContainers);
this.addEventListener('item-changed', this._itemChanged);
this.addEventListener('scroll', this._onHorizontalScroll);
this.addEventListener('edit-entry', this._editEntry);
this.addEventListener('delete-entry', this._deleteEntry);
this.addEventListener('move-upward', this._moveItemUpward);
this.addEventListener('move-downward', this._moveItemDownward);
this.$.list._selectionHandler = function(e) {
const model = this.modelForElement(e.target);
if (!model) {
return;
}
this.toggleSelectionForItem(model[this.as]);
};
// listen for changes in form slot contents to make sure we disable the form when it is stamped.
// the form will be enabled/disabled when the dialog is opened/closed.
// this allow us to bypass the form validation when it's not visible.
const slot = this.shadowRoot.querySelector('#form');
slot.addEventListener('slotchange', () => {
const form = this.getContentChildren('#form')[0];
form.disabled = true;
});
this.setAttribute('role', 'table');
this.setAttribute('aria-multiselectable', this.multiSelection);
}
_computeActionsStyle() {
if (this.editable && this.orderable) {
return 'flex: 0 0 172px;';
}
if (this.editable || this.orderable) {
return 'flex: 0 0 92px;';
}
return 'display: none;';
}
_computeSelectAllVisibility() {
if (this.selectionEnabled) {
return !this.selectAllEnabled || !this.multiSelection ? 'visibility: hidden;' : '';
}
return 'display: none;';
}
_alignHeaderRow() {
afterNextRender(this, () => {
if (this.$.list.scrollHeight >= this.$.list.clientHeight) {
// add scrollbar width as padding
this.$.header.style.paddingRight = `${this.$.list.offsetWidth - this.$.list.clientWidth}px`;
} else {
this.$.header.style.paddingRight = '0';
}
});
}
_itemChanged(e) {
if (this.items) {
let { index } = e.target;
if (index === undefined) {
index = this.items.indexOf(e.detail.item);
}
if (index >= 0) {
let path = `items.${index}`;
if (e.detail.path) {
path += `.${e.detail.path}`;
}
this.set(path, e.detail.value);
}
}
}
_backupColumnsState() {
this.columns.forEach((col) => {
col.hiddenBack = col.hidden;
});
}
_bind(item, index) {
if (index !== undefined) {
return {
item,
index,
};
}
return {
column: item,
};
}
_isEven(index) {
return index % 2 === 0;
}
_columnsChanged(columns, oldColumns) {
if (oldColumns) {
oldColumns.forEach((column) => {
this.unlisten(column, 'filter-value-changed');
});
}
if (columns) {
columns.forEach((column) => {
column.table = this;
this.listen(column, 'filter-value-changed', '_onColumnFilterChanged');
});
}
}
_resizeCellContainers() {
// reset header width first to make the cells and scroll width to reset their widths.
this.$.container.style.width = '';
microTask.run(() => {
this.$.container.style.width = `${Math.min(this.scrollWidth, this.clientWidth + this.scrollLeft)}px`;
// add scrollbar width as padding
this.$.header.style.paddingRight = `${this.$.list.offsetWidth - this.$.list.clientWidth}px`;
});
}
_onHorizontalScroll() {
if (!this.isDebouncerActive('scrolling')) {
this.$.container.style.width = `${this.scrollWidth}px`;
this._debouncer = Debouncer.debounce(this._debouncer, timeOut.after(1000), () => {
// long timeout here to prevent jerkiness with the rubberband effect on iOS especially.
this.$.container.style.width = `${Math.min(this.scrollWidth, this.clientWidth + this.scrollLeft)}px`;
});
}
}
_updateSizeForItem(event) {
if (event.model.get('item')) {
// notifyResize() doesn't do anything on iOS if the viewport size hasn't changed
// so calling updateSizeForItem(item) is more reliable.
// TODO: However, since we're reusing the same items array in most cases,
// the _collection item map inside <iron-list> gets out of sync and
// that breaks things like selection and updateSizeForItem.
// To mitigate the issue, we'll update height of every row element.
// Can be optimized later if needed to update only the row that has
// expanded or collapsed.
const itemSet = [];
for (let i = 0; i < this.$.list._physicalItems.length; i++) {
itemSet.push(i);
}
// extracted from updateSizeFromItem(item) in <iron-list>
this.$.list._updateMetrics(itemSet);
this.$.list._positionItems();
}
}
/**
* Expands the row details for this item, if available.
*/
expandItem(item) {
if (this.rowDetail && this._expandedItems && !this._isExpanded(item, this._expandedItems)) {
// replacing the whole array here to simplify the observers.
this._expandedItems.push(item);
this._expandedItems = this._expandedItems.slice(0);
}
}
/**
* Collapses the row details for this item, if expanded.
*/
collapseItem(item) {
if (this.rowDetail && this._expandedItems && this._isExpanded(item, this._expandedItems)) {
const index = this._expandedItems.indexOf(item);
// replacing the whole array here to simplify the obsevers.
this._expandedItems.splice(index, 1);
this._expandedItems = this._expandedItems.slice(0);
}
}
_isExpanded(item, items) {
return items && items.indexOf(item) > -1;
}
_isFocusable(target) {
// eslint-disable-next-line no-constant-condition
if (false) {
// https://nemisj.com/focusable/
// tabIndex is not reliable in IE.
return target.tabIndex >= 0;
}
// unreliable with Shadow, document.activeElement doesn't go inside
// the shadow root.
return (
target.contains(dom(document.activeElement).node) ||
target instanceof Nuxeo.DataTableCheckbox ||
target.tagName === 'A'
);
}
/**
* Fired when user clicks on a item to select it. Note that this event is
* not fired when user clicks on a multi-selection checkbox.
*
* @event selecting-item
* @param {Object} detail
* @param {Object} detail.item item to be selected
*/
/**
* Fired when user clicks on a item to deselect it. Note that this event is
* not fired when user clicks on a multi-selection checkbox.
*
* @event deselecting-item
* @param {Object} detail
* @param {Object} detail.item item to be deselected
*/
/**
* Fired when user clicks on a item to expand it.
*
* @event expanding-item
* @param {Object} detail
* @param {Object} detail.item item to be expanded
*/
/**
* Fired when user clicks on a item to collapse it.
*
* @event collapsing-item
* @param {Object} detail
* @param {Object} detail.item item to be collapsed
*/
// we need to listen to click instead of tap because on mobile safari, the
// document.activeElement has not been updated (focus has not been shifted)
// yet at the point when tap event is being executed.
_onRowClick(ev) {
// Prevent item selection if row itself is not focused. This means that
// an element inside the row has been focused.
// Mobile devices don't move focus from body unless it's an input element that is focused, so this element
// will never get focused.
if (!this._isFocusable(dom(ev).localTarget)) {
const fireEvent = function(eventName, item, defaultAction) {
const e = new CustomEvent(eventName, {
cancelable: true,
composed: true,
bubbles: true,
detail: { item },
});
this.dispatchEvent(e);
if (!e.defaultPrevented) {
defaultAction.call(this, item);
}
}.bind(this);
if (this.rowDetail && this.detailsEnabled) {
if (this._isExpanded(ev.model.item, this._expandedItems)) {
fireEvent('collapsing-item', ev.model.item, this.collapseItem);
} else {
fireEvent('expanding-item', ev.model.item, this.expandItem);
}
} else {
this.dispatchEvent(
new CustomEvent('row-clicked', {
composed: true,
bubbles: true,
detail: { item: ev.model.item, index: ev.model.index },
}),
);
}
}
}
get settings() {
const tableSettings = {};
tableSettings.columns = {};
if (this.columns) {
this.columns.forEach((column, idx) => {
tableSettings.columns[column.field ? column.field : `col-${idx}`] = { hidden: column.hidden };
});
}
return tableSettings;
}
set settings(settings) {
if (settings) {
if (this.columns && settings.columns) {
this.columns.forEach(function(column, idx) {
this.set(`columns.${idx}.hidden`, settings.columns[column.field ? column.field : `col-${idx}`].hidden);
}, this);
}
}
}
_onCheckBoxTap(e) {
if (this.selectionEnabled && !this.selectAllActive) {
// _selectionHandler isn't called if selectOnTap is true
if (this.selectOnTap) {
this.$.list.toggleSelectionForIndex(e.model.index);
}
const target = e.target || e.srcElement;
target.dispatchEvent(
new CustomEvent('selected', {
composed: true,
bubbles: true,
detail: { index: e.model.index, shiftKey: e.shiftKey },
}),
);
this._updateFlags();
}
}
_onCheckBoxKeydown(e) {
// check for enter or space
if (e.keyCode === 13 || e.keyCode === 32) {
this._onCheckBoxTap(e);
}
}
_editEntry(e) {
e.stopPropagation();
this._toggleEditDialog(e.detail.index);
}
_validateEntry() {
const dtform = this.getContentChildren('#form')[0];
if (dtform.validateItem()) {
const item = this._deepCopy(dtform.item);
if (dtform.index > -1) {
this.set(`items.${dtform.index}`, item);
} else {
this.push('items', item);
}
this.notifyResize();
this.$.dialog.close();
}
}
_deepCopy(obj) {
let cache = [];
const result = JSON.parse(
JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
}),
);
// Allow GC
cache = null;
return result;
}
_toggleEditDialog(itemIndex) {
const dtform = this.getContentChildren('#form')[0];
if (typeof itemIndex !== 'undefined') {
dtform.index = itemIndex;
dtform.item = this._deepCopy(this.items[itemIndex]);
} else {
dtform.index = -1;
if ((this.items.length > 1 && typeof this.items[0] !== 'object') || this.columns.length === 1) {
// dirty but will work with primitive such as string, number, etc.
dtform.item = '';
} else {
dtform.item = {};
}
}
this.$.dialog.toggle();
}
_deleteEntry(e) {
e.stopPropagation();
this.splice('items', e.detail.index, 1);
this.notifyResize();
}
_createEntry() {
if (!this.items) {
this.items = [];
}
this.notifyResize();
this._toggleEditDialog();
}
_moveItemUpward(e) {
e.stopPropagation();
if (e.detail.index > 0) {
const item = this.items[e.detail.index];
this.splice('items', e.detail.index, 1);
this.splice('items', e.detail.index - 1, 0, item);
this.notifyResize();
}
}
_moveItemDownward(e) {
e.stopPropagation();
if (this.items.length - 1 > e.detail.index) {
const item = this.items[e.detail.index];
this.splice('items', e.detail.index, 1);
this.splice('items', e.detail.index + 1, 0, item);
this.notifyResize();
}
}
_patchOverlay(e) {
if (e.target.withBackdrop) {
e.target.parentNode.insertBefore(e.target.backdropElement, e.target);
}
}
fetch() {
if (this._hasPageProvider()) {
if (this.paginable) {
const result = this._fetchPage(this.nxProvider.page, this.nxProvider.pageSize);
if (result) {
return result.then(() => {
this.nxProvider.page += 1;
this.$.scrollThreshold.clearTriggers();
this.$.list.notifyResize();
});
}
this.$.scrollThreshold.clearTriggers();
this.$.list.notifyResize();
} else {
return this._fetchRange(0, this.nxProvider.pageSize, true);
}
}
}
_threshold() {
if (this.paginable) {
this.fetch();
}
}
_scroll() {
if (!this.paginable) {
this._scrollChanged();
}
}
_sort(e) {
if (this.paginable) {
this.nxProvider.page = 1;
}
this._sortDirectionChanged(e);
}
/* Override method from Polymer.IronValidatableBehavior. */
_getValidity() {
return this.required ? this.items && this.items.length > 0 : true;
}
draggableFilter(el) {
const row = el.closest('nuxeo-data-table-row');
return row && row.selected;
}
_formDialogOpenedChanged(e) {
const form = this.getContentChildren('#form')[0];
if (form) {
// disable form when the dialog is closed, to make sure we bypass validation when the form is not visible
form.disabled = !e.detail.value;
}
}
}
customElements.define(DataTable.is, DataTable);
Nuxeo.DataTable = DataTable;
}