@ishitatsuyuki/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
1,375 lines (1,356 loc) • 69.3 kB
JavaScript
'use strict';
var vue = require('vue');
var helpers = require('./helpers.js');
var config = require('./config.js');
var BaseComponentMixin = require('./BaseComponentMixin-a03c02e3.js');
var Icon = require('./Icon-172f9998.js');
var Input = require('./Input-7b380647.js');
var Button = require('./Button-3722f128.js');
var Checkbox = require('./Checkbox-3a9023a6.js');
var MatchMediaMixin = require('./MatchMediaMixin-08658b15.js');
var Field = require('./Field-b201302d.js');
var Select = require('./Select-db9aed9e.js');
var Loading = require('./Loading-c1d90132.js');
var Pagination = require('./Pagination-e5cf93db.js');
var SlotComponent = require('./SlotComponent-b10f11e8.js');
var script = vue.defineComponent({
name: 'OTableMobileSort',
components: {
[Button.script.name]: Button.script,
[Select.script.name]: Select.script,
[Icon.script.name]: Icon.script,
[Field.script.name]: Field.script
},
inject: ['$table'],
emits: ['sort'],
props: {
currentSortColumn: Object,
columns: Array,
placeholder: String,
iconPack: String,
sortIcon: {
type: String,
default: 'arrow-up'
},
sortIconSize: {
type: String,
default: 'small'
},
isAsc: Boolean
},
data() {
return {
mobileSort: helpers.getValueByPath(this.currentSortColumn, 'newKey'),
defaultEvent: {
shiftKey: true,
altKey: true,
ctrlKey: true
},
ignoreSort: false
};
},
computed: {
showPlaceholder() {
return !this.columns || !this.columns.some((column) => (helpers.getValueByPath(column, 'newKey') === this.mobileSort));
},
sortableColumns() {
if (!this.columns)
return [];
return this.columns.filter(c => c.sortable);
},
isCurrentSort() {
return helpers.getValueByPath(this.currentSortColumn, 'newKey') === this.mobileSort;
}
},
watch: {
mobileSort(value) {
if (this.currentSortColumn.newKey === value)
return;
const column = this.sortableColumns.filter(c => (helpers.getValueByPath(c, 'newKey') === value))[0];
this.$emit('sort', column, this.defaultEvent);
},
currentSortColumn(column) {
this.mobileSort = helpers.getValueByPath(column, 'newKey');
}
},
methods: {
sort() {
const column = this.sortableColumns.filter(c => (helpers.getValueByPath(c, 'newKey') === this.mobileSort))[0];
this.$emit('sort', column, this.defaultEvent);
}
}
});
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_o_select = vue.resolveComponent("o-select");
const _component_o_icon = vue.resolveComponent("o-icon");
const _component_o_button = vue.resolveComponent("o-button");
const _component_o_field = vue.resolveComponent("o-field");
return vue.openBlock(), vue.createBlock("div", {
class: _ctx.$table.mobileSortClasses
}, [vue.createVNode(_component_o_field, null, {
default: vue.withCtx(() => [vue.createVNode(_component_o_select, {
modelValue: _ctx.mobileSort,
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => _ctx.mobileSort = $event),
expanded: ""
}, {
default: vue.withCtx(() => [_ctx.placeholder ? vue.withDirectives((vue.openBlock(), vue.createBlock("option", {
key: 0,
value: {},
selected: "",
disabled: "",
hidden: ""
}, vue.toDisplayString(_ctx.placeholder), 513
/* TEXT, NEED_PATCH */
)), [[vue.vShow, _ctx.showPlaceholder]]) : vue.createCommentVNode("v-if", true), (vue.openBlock(true), vue.createBlock(vue.Fragment, null, vue.renderList(_ctx.sortableColumns, (column, index) => {
return vue.openBlock(), vue.createBlock("option", {
key: index,
value: column.newKey
}, vue.toDisplayString(column.label), 9
/* TEXT, PROPS */
, ["value"]);
}), 128
/* KEYED_FRAGMENT */
))]),
_: 1
}, 8
/* PROPS */
, ["modelValue"]), vue.createVNode(_component_o_button, {
onClick: _ctx.sort
}, {
default: vue.withCtx(() => [vue.withDirectives(vue.createVNode(_component_o_icon, {
icon: _ctx.sortIcon,
pack: _ctx.iconPack,
size: _ctx.sortIconSize,
both: "",
rotation: !_ctx.isAsc ? 180 : 0
}, null, 8
/* PROPS */
, ["icon", "pack", "size", "rotation"]), [[vue.vShow, _ctx.isCurrentSort]])]),
_: 1
}, 8
/* PROPS */
, ["onClick"])]),
_: 1
})], 2
/* CLASS */
);
}
script.render = render;
script.__file = "src/components/table/TableMobileSort.vue";
/**
* @displayName Table Column
*/
var script$1 = vue.defineComponent({
name: 'OTableColumn',
inject: ['$table'],
props: {
label: String,
customKey: [String, Number],
field: String,
meta: [String, Number, Boolean, Function, Object, Array],
width: [Number, String],
numeric: Boolean,
/**
* Optional, position of column content
* @values centered, right
*/
position: {
type: String,
validator(value) {
return [
'left',
'centered',
'right'
].indexOf(value) > -1;
}
},
searchable: Boolean,
sortable: Boolean,
visible: {
type: Boolean,
default: true
},
customSort: Function,
customSearch: Function,
sticky: Boolean,
headerSelectable: Boolean,
/** Adds native attributes to th :th-attrs="(column)" => ({})" */
thAttrs: {
type: Function,
default: () => ({})
},
/** Adds native attributes to td :td-attrs="(row, column)" => ({})" */
tdAttrs: {
type: Function,
default: () => ({})
},
subheading: String
},
data() {
return {
newKey: undefined
};
},
computed: {
style() {
return {
width: helpers.toCssDimension(this.width)
};
},
hasDefaultSlot() {
return this.$slots.default;
},
hasSearchableSlot() {
return this.$slots.searchable;
},
hasHeaderSlot() {
return this.$slots.header;
},
isHeaderUnselectable() {
return !this.headerSelectable && this.sortable;
}
},
created() {
if (!this.$table) {
throw new Error('You should wrap oTableColumn on a oTable');
}
this.newKey = this.$table._nextSequence();
this.$table._addColumn(this);
},
beforeUnmount() {
this.$table._removeColumn(this);
},
render() {
// renderless
return vue.h('span', { 'data-id': this.newKey }, this.label);
}
});
script$1.__file = "src/components/table/TableColumn.vue";
var script$2 = vue.defineComponent({
name: 'OTablePagination',
components: {
[Pagination.script.name]: Pagination.script
},
emits: ['update:currentPage', 'page-change'],
props: {
paginated: Boolean,
total: [Number, String],
perPage: [Number, String],
currentPage: [Number, String],
paginationSimple: Boolean,
paginationSize: String,
rounded: Boolean,
iconPack: String,
rootClass: [String, Array, Object],
ariaNextLabel: String,
ariaPreviousLabel: String,
ariaPageLabel: String,
ariaCurrentLabel: String
},
data() {
return {
newCurrentPage: this.currentPage
};
},
watch: {
currentPage(newVal) {
this.newCurrentPage = newVal;
}
},
methods: {
/**
* Paginator change listener.
*/
pageChanged(page) {
this.newCurrentPage = page > 0 ? page : 1;
this.$emit('update:currentPage', this.newCurrentPage);
this.$emit('page-change', this.newCurrentPage);
}
}
});
const _hoisted_1 = {
key: 0
};
function render$1(_ctx, _cache, $props, $setup, $data, $options) {
const _component_o_pagination = vue.resolveComponent("o-pagination");
return vue.openBlock(), vue.createBlock("div", {
class: _ctx.rootClass
}, [vue.createVNode("div", null, [vue.renderSlot(_ctx.$slots, "default")]), vue.createVNode("div", null, [_ctx.paginated ? (vue.openBlock(), vue.createBlock("div", _hoisted_1, [vue.createVNode(_component_o_pagination, {
"icon-pack": _ctx.iconPack,
total: _ctx.total,
"per-page": _ctx.perPage,
simple: _ctx.paginationSimple,
size: _ctx.paginationSize,
current: _ctx.newCurrentPage,
rounded: _ctx.rounded,
onChange: _ctx.pageChanged,
"aria-next-label": _ctx.ariaNextLabel,
"aria-previous-label": _ctx.ariaPreviousLabel,
"aria-page-label": _ctx.ariaPageLabel,
"aria-current-label": _ctx.ariaCurrentLabel
}, null, 8
/* PROPS */
, ["icon-pack", "total", "per-page", "simple", "size", "current", "rounded", "onChange", "aria-next-label", "aria-previous-label", "aria-page-label", "aria-current-label"])])) : vue.createCommentVNode("v-if", true)])], 2
/* CLASS */
);
}
script$2.render = render$1;
script$2.__file = "src/components/table/TablePagination.vue";
/**
* Tabulated data are sometimes needed, it's even better when it's responsive
* @displayName Table
* @requires ./TableColumn.vue
* @style _table.scss
*/
var script$3 = vue.defineComponent({
name: 'OTable',
components: {
[Button.script.name]: Button.script,
[Checkbox.script.name]: Checkbox.script,
[Icon.script.name]: Icon.script,
[Input.script.name]: Input.script,
[Loading.script.name]: Loading.script,
[SlotComponent.SlotComponent.name]: SlotComponent.SlotComponent,
[script.name]: script,
[script$1.name]: script$1,
[script$2.name]: script$2
},
mixins: [BaseComponentMixin.BaseComponentMixin, MatchMediaMixin.MatchMediaMixin],
configField: 'table',
inheritAttrs: false,
provide() {
return {
$table: this
};
},
emits: [
'page-change', 'click', 'dblclick', 'contextmenu',
'check', 'check-all', 'update:checkedRows',
'select', 'update:selected', 'filters-change', 'details-open', 'details-close', 'update:openedDetailed',
'mouseenter', 'mouseleave', 'sort', 'sorting-priority-removed',
'dragstart', 'dragend', 'drop', 'dragleave', 'dragover', 'cell-click',
'columndragstart', 'columndragend', 'columndrop', 'columndragleave', 'columndragover',
'update:currentPage'
],
props: {
/** Table data */
data: {
type: Array,
default: () => []
},
/** Table columns */
columns: {
type: Array,
default: () => []
},
/** Border to all cells */
bordered: Boolean,
/** Whether table is striped */
striped: Boolean,
/** Makes the cells narrower */
narrowed: Boolean,
/** Rows are highlighted when hovering */
hoverable: Boolean,
/** Loading state */
loading: Boolean,
/** Allow row details */
detailed: Boolean,
/** Rows can be checked (multiple), checked rows will have a .is-checked class if you want to style */
checkable: Boolean,
/** Show check/uncheck all checkbox in table header when checkable */
headerCheckable: {
type: Boolean,
default: true
},
/**
* Position of the checkbox (if checkable is true)
* @values left, right
*/
checkboxPosition: {
type: String,
default: 'left',
validator: (value) => {
return [
'left',
'right'
].indexOf(value) >= 0;
}
},
/** Set which row is selected, use v-model:selected to make it two-way binding */
selected: Object,
/** Custom method to verify if a row is selectable, works when is selected. */
isRowSelectable: {
type: Function,
default: () => true
},
/** Table can be focused and user can navigate with keyboard arrows (require selected) and rows are highlighted when hovering */
focusable: Boolean,
/** Custom method to verify if row is checked, works when is checkable. Useful for backend pagination */
customIsChecked: Function,
/** Custom method to verify if a row is checkable, works when is checkable */
isRowCheckable: {
type: Function,
default: () => true
},
/** Set which rows are checked, use v-model:checkedRows to make it two-way binding */
checkedRows: {
type: Array,
default: () => []
},
/** Rows appears as cards on mobile (collapse rows) */
mobileCards: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'table.mobileCards', true);
}
},
/** Sets the default sort column and order — e.g. ['first_name', 'desc'] */
defaultSort: [String, Array],
/**
* Sets the default sort column direction on the first click
* @values asc, desc
*/
defaultSortDirection: {
type: String,
default: 'asc'
},
/** Sets the header sorting icon */
sortIcon: {
type: String,
default: () => { return helpers.getValueByPath(config.getOptions(), 'table.sortIcon', 'arrow-up'); }
},
/**
* Sets the size of the sorting icon
* @values small, medium, large
*/
sortIconSize: {
type: String,
default: () => { return helpers.getValueByPath(config.getOptions(), 'table.sortIconSize', 'small'); }
},
/** Adds pagination to the table */
paginated: Boolean,
/** Current page of table data (if paginated), use v-model:currentPage to make it two-way binding */
currentPage: {
type: Number,
default: 1
},
/** How many rows per page (if paginated) */
perPage: {
type: [Number, String],
default: () => { return helpers.getValueByPath(config.getOptions(), 'table.perPage', 20); }
},
/** Allow chevron icon and column to be visible */
showDetailIcon: {
type: Boolean,
default: true
},
/** Icon name of detail action */
detailIcon: {
type: String,
default: 'chevron-right'
},
/**
* Pagination position (if paginated)
* @values bottom, top, bot
*/
paginationPosition: {
type: String,
default: () => { return helpers.getValueByPath(config.getOptions(), 'table.paginationPosition', 'bottom'); },
validator: (value) => {
return [
'bottom',
'top',
'both'
].indexOf(value) >= 0;
}
},
/** Columns won't be sorted with Javascript, use with sort event to sort in your backend */
backendSorting: Boolean,
/** Columns won't be filtered with Javascript, use with searchable prop to the columns to filter in your backend */
backendFiltering: Boolean,
/** Add a class to row based on the return */
rowClass: {
type: Function,
default: () => ''
},
/** Allow pre-defined opened details. Ideal to open details via vue-router. (A unique key is required; check detail-key prop) */
openedDetailed: {
type: Array,
default: () => []
},
/** Controls the visibility of the trigger that toggles the detailed rows. */
hasDetailedVisible: {
type: Function,
default: () => true
},
/** Use a unique key of your data Object when use detailed or opened detailed. (id recommended) */
detailKey: {
type: String,
default: ''
},
/** Custom style on details */
customDetailRow: {
type: Boolean,
default: false
},
/* Transition name to use when toggling row details. */
detailTransition: {
type: String,
default: ''
},
/** Rows won't be paginated with Javascript, use with page-change event to paginate in your backend */
backendPagination: Boolean,
/** Total number of table data if backend-pagination is enabled */
total: {
type: [Number, String],
default: 0
},
/** Icon pack to use */
iconPack: String,
/** Text when nothing is selected */
mobileSortPlaceholder: String,
/** Use a unique key of your data Object for each row. Useful if your data prop has dynamic indices. (id recommended) */
customRowKey: String,
/** Allows rows to be draggable */
draggable: {
type: Boolean,
default: false
},
/** Allows columns to be draggable */
draggableColumn: {
type: Boolean,
default: false
},
/** Add a horizontal scrollbar when table is too wide */
scrollable: Boolean,
ariaNextLabel: String,
ariaPreviousLabel: String,
ariaPageLabel: String,
ariaCurrentLabel: String,
/** Show a sticky table header */
stickyHeader: Boolean,
/** Table fixed height */
height: [Number, String],
/** Add a native event to filter */
filtersEvent: {
type: String,
default: ''
},
/** Filtering debounce time (in milliseconds) */
debounceSearch: Number,
/** Show header */
showHeader: {
type: Boolean,
default: () => { return helpers.getValueByPath(config.getOptions(), 'table.showHeader', true); }
},
/** Make the checkbox column sticky when checkable */
stickyCheckbox: {
type: Boolean,
default: false
},
/** Rounded pagination if paginated */
paginationRounded: Boolean,
rootClass: [String, Function, Array],
tableClass: [String, Function, Array],
wrapperClass: [String, Function, Array],
footerClass: [String, Function, Array],
emptyClass: [String, Function, Array],
detailedClass: [String, Function, Array],
borderedClass: [String, Function, Array],
stripedClass: [String, Function, Array],
narrowedClass: [String, Function, Array],
hoverableClass: [String, Function, Array],
thClass: [String, Function, Array],
tdClass: [String, Function, Array],
thPositionClass: [String, Function, Array],
thStickyClass: [String, Function, Array],
thCheckboxClass: [String, Function, Array],
thCurrentSortClass: [String, Function, Array],
thSortableClass: [String, Function, Array],
thUnselectableClass: [String, Function, Array],
thSortIconClass: [String, Function, Array],
thDetailedClass: [String, Function, Array],
tdPositionClass: [String, Function, Array],
tdStickyClass: [String, Function, Array],
tdCheckboxClass: [String, Function, Array],
tdDetailedChevronClass: [String, Function, Array],
trSelectedClass: [String, Function, Array],
stickyHeaderClass: [String, Function, Array],
scrollableClass: [String, Function, Array],
mobileSortClass: [String, Function, Array],
paginationWrapperClass: [String, Function, Array],
mobileClass: [String, Function, Array],
thSubheadingClass: [String, Function, Array]
},
data() {
return {
visibleDetailRows: this.openedDetailed,
newData: this.data,
newDataTotal: this.backendPagination ? this.total : this.data.length,
newCheckedRows: [...this.checkedRows],
lastCheckedRowIndex: null,
newCurrentPage: this.currentPage,
currentSortColumn: {},
isAsc: true,
filters: {},
defaultSlots: [],
firstTimeSort: true,
sequence: 1,
isDraggingRow: false,
isDraggingColumn: false
};
},
mounted() {
this.$nextTick(() => {
this.checkSort();
});
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-table__root'),
{ [this.computedClass('mobileClass', 'o-table__wrapper--mobile')]: this.isMobile }
];
},
tableClasses() {
return [
this.computedClass('tableClass', 'o-table'),
{ [this.computedClass('borderedClass', 'o-table--bordered')]: this.bordered },
{ [this.computedClass('stripedClass', 'o-table--striped')]: this.striped },
{ [this.computedClass('narrowedClass', 'o-table--narrowed')]: this.narrowed },
{ [this.computedClass('hoverableClass', 'o-table--hoverable')]: ((this.hoverable || this.focusable) && this.visibleData.length) },
{ [this.computedClass('emptyClass', 'o-table--table__empty')]: !this.visibleData.length }
];
},
tableWrapperClasses() {
return [
this.computedClass('wrapperClass', 'o-table__wrapper'),
{ [this.computedClass('stickyHeaderClass', 'o-table__wrapper--sticky-header')]: this.stickyHeader },
{ [this.computedClass('scrollableClass', 'o-table__wrapper--scrollable')]: this.isScrollable },
{ [this.computedClass('mobileClass', 'o-table__wrapper--mobile')]: this.isMobile },
];
},
footerClasses() {
return [
this.computedClass('footerClass', 'o-table__footer')
];
},
thBaseClasses() {
return [
this.computedClass('thClass', 'o-table__th')
];
},
tdBaseClasses() {
return [
this.computedClass('tdClass', 'o-table__td')
];
},
thCheckboxClasses() {
return [
...this.thBaseClasses,
this.computedClass('thCheckboxClass', 'o-table__th-checkbox')
];
},
thDetailedClasses() {
return [
...this.thBaseClasses,
this.computedClass('thDetailedClass', 'o-table__th--detailed')
];
},
thSubheadingClasses() {
return [
...this.thBaseClasses,
this.computedClass('thSubheadingClass', 'o-table__th')
];
},
tdCheckboxClasses() {
return [
...this.tdBaseClasses,
this.computedClass('tdCheckboxClass', 'o-table__td-checkbox'),
...this.thStickyClasses({ sticky: this.stickyCheckbox })
];
},
detailedClasses() {
return [
this.computedClass('detailedClass', 'o-table__detail')
];
},
tdDetailedChevronClasses() {
return [
...this.tdBaseClasses,
this.computedClass('tdDetailedChevronClass', 'o-table__td-chevron')
];
},
mobileSortClasses() {
return [
this.computedClass('mobileSortClass', 'o-table__mobile-sort')
];
},
paginationWrapperClasses() {
return [
this.computedClass('paginationWrapperClass', 'o-table__pagination')
];
},
tableWrapperStyle() {
return {
height: helpers.toCssDimension(this.height)
};
},
/**
* Splitted data based on the pagination.
*/
visibleData() {
if (!this.paginated)
return this.newData;
const currentPage = this.newCurrentPage;
const perPage = this.perPage;
if (this.newData.length <= perPage) {
return this.newData;
}
else {
const start = (currentPage - 1) * perPage;
const end = start + parseInt(perPage, 10);
return this.newData.slice(start, end);
}
},
visibleColumns() {
if (!this.newColumns)
return this.newColumns;
return this.newColumns.filter((column) => {
return column.visible || column.visible === undefined;
});
},
/**
* Check if all rows in the page are checked.
*/
isAllChecked() {
const validVisibleData = this.visibleData.filter((row) => this.isRowCheckable(row));
if (validVisibleData.length === 0)
return false;
const isAllChecked = validVisibleData.some((currentVisibleRow) => {
return helpers.indexOf(this.newCheckedRows, currentVisibleRow, this.customIsChecked) < 0;
});
return !isAllChecked;
},
/**
* Check if all rows in the page are checkable.
*/
isAllUncheckable() {
const validVisibleData = this.visibleData.filter((row) => this.isRowCheckable(row));
return validVisibleData.length === 0;
},
/**
* Check if has any sortable column.
*/
hasSortablenewColumns() {
return this.newColumns.some((column) => {
return column.sortable;
});
},
/**
* Check if has any searchable column.
*/
hasSearchablenewColumns() {
return this.newColumns.some((column) => {
return column.searchable;
});
},
/**
* Return total column count based if it's checkable or expanded
*/
columnCount() {
let count = this.visibleColumns.length;
count += this.checkable ? 1 : 0;
count += (this.detailed && this.showDetailIcon) ? 1 : 0;
return count;
},
/**
* return if detailed row tabled
* will be with chevron column & icon or not
*/
showDetailRowIcon() {
return this.detailed && this.showDetailIcon;
},
/**
* return if scrollable table
*/
isScrollable() {
if (this.scrollable)
return true;
if (!this.newColumns)
return false;
return this.newColumns.some((column) => {
return column.sticky;
});
},
newColumns() {
if (this.columns && this.columns.length) {
return this.columns.map((column) => {
const vnode = vue.createVNode(script$1, column, (props) => {
const vnode = vue.h('span', {}, helpers.getValueByPath(props.row, column.field));
return [vnode];
});
return vue.createApp(vnode)
.provide('$table', this)
.mount(document.createElement('div'));
});
}
return this.defaultSlots;
},
isMobile() {
return this.mobileCards && this.isMatchMedia;
},
hasCustomSubheadings() {
if (this.$slots.subheading)
return true;
return this.newColumns.some((column) => {
return column.subheading || (column.$slots.subheading);
});
},
canDragRow() {
return this.draggable && !this.isDraggingColumn;
},
canDragColumn() {
return this.draggableColumn && !this.isDraggingRow;
}
},
watch: {
/**
* When data prop change:
* 1. Update internal value.
* 2. Filter data if it's not backend-filtered.
* 3. Sort again if it's not backend-sorted.
* 4. Set new total if it's not backend-paginated.
*/
data: {
handler(value) {
this.newData = value;
if (!this.backendFiltering) {
this.newData = value.filter((row) => this.isRowFiltered(row));
}
if (!this.backendSorting) {
this.sort(this.currentSortColumn, true);
}
if (!this.backendPagination) {
this.newDataTotal = this.newData.length;
}
},
deep: true,
},
/**
* When Pagination total change, update internal total
* only if it's backend-paginated.
*/
total(newTotal) {
if (!this.backendPagination)
return;
this.newDataTotal = newTotal;
},
currentPage(newValue) {
this.newCurrentPage = newValue;
},
/**
* When checkedRows prop change, update internal value without
* mutating original data.
*/
checkedRows: {
handler(rows) {
this.newCheckedRows = [...rows];
},
deep: true,
},
debounceSearch: {
handler(value) {
this.debouncedHandleFiltersChange = helpers.debounce(this.handleFiltersChange, value);
},
immediate: true
},
filters: {
handler(value) {
if (this.debounceSearch) {
this.debouncedHandleFiltersChange(value);
}
else {
this.handleFiltersChange(value);
}
},
deep: true,
},
/**
* When the user wants to control the detailed rows via props.
* Or wants to open the details of certain row with the router for example.
*/
openedDetailed(expandedRows) {
this.visibleDetailRows = expandedRows;
},
newCurrentPage(newVal) {
this.$emit('update:currentPage', newVal);
}
},
methods: {
thClasses(column) {
return [
...this.thBaseClasses,
...this.thStickyClasses(column),
column.thAttrs && helpers.getValueByPath(column.thAttrs(column), 'class'),
{ [this.computedClass('thCurrentSortClass', 'o-table__th-current-sort')]: (this.currentSortColumn === column) },
{ [this.computedClass('thSortableClass', 'o-table__th--sortable')]: column.sortable },
{ [this.computedClass('thUnselectableClass', 'o-table__th--unselectable')]: column.isHeaderUnselectable },
{ [this.computedClass('thPositionClass', 'o-table__th--', column.position)]: column.position },
];
},
thStickyClasses(column) {
return [
{ [this.computedClass('thStickyClass', 'o-table__th--sticky')]: column.sticky }
];
},
rowClasses(row, index) {
return [
this.rowClass(row, index),
{ [this.computedClass('trSelectedClass', 'o-table__tr--selected')]: this.isRowSelected(row, this.selected) }
];
},
thSortIconClasses() {
return [
this.computedClass('thSortIconClass', 'o-table__th__sort-icon'),
];
},
tdClasses(row, column) {
return [
...this.tdBaseClasses,
column.tdAttrs && helpers.getValueByPath(column.tdAttrs(row, column), 'class'),
{ [this.computedClass('tdPositionClass', 'o-table__td--', column.position)]: column.position },
{ [this.computedClass('tdStickyClass', 'o-table__td--sticky')]: column.sticky }
];
},
onFiltersEvent(event) {
this.$emit(`filters-event-${this.filtersEvent}`, { event, filters: this.filters });
},
handleFiltersChange(value) {
if (this.backendFiltering) {
this.$emit('filters-change', value);
}
else {
this.newData = this.data.filter((row) => this.isRowFiltered(row));
if (!this.backendPagination) {
this.newDataTotal = this.newData.length;
}
if (!this.backendSorting) {
if (Object.keys(this.currentSortColumn).length > 0) {
this.doSortSingleColumn(this.currentSortColumn);
}
}
}
},
/**
* Sort an array by key without mutating original data.
* Call the user sort function if it was passed.
*/
sortBy(array, key, fn, isAsc) {
let sorted = [];
// Sorting without mutating original data
if (fn && typeof fn === 'function') {
sorted = [...array].sort((a, b) => fn(a, b, isAsc));
}
else {
sorted = [...array].sort((a, b) => {
// Get nested values from objects
let newA = helpers.getValueByPath(a, key);
let newB = helpers.getValueByPath(b, key);
// sort boolean type
if (typeof newA === 'boolean' && typeof newB === 'boolean') {
return isAsc ? newA > newB ? 1 : -1 : newA > newB ? -1 : 1;
}
if (!newA && newA !== 0)
return 1;
if (!newB && newB !== 0)
return -1;
if (newA === newB)
return 0;
newA = (typeof newA === 'string')
? newA.toUpperCase()
: newA;
newB = (typeof newB === 'string')
? newB.toUpperCase()
: newB;
return isAsc
? newA > newB ? 1 : -1
: newA > newB ? -1 : 1;
});
}
return sorted;
},
/**
* Sort the column.
* Toggle current direction on column if it's sortable
* and not just updating the prop.
*/
sort(column, updatingData = false, event = null) {
if (!column || !column.sortable)
return;
if (!updatingData) {
this.isAsc = column === this.currentSortColumn
? !this.isAsc
: (this.defaultSortDirection.toLowerCase() !== 'desc');
}
if (!this.firstTimeSort) {
/**
* @property {string} field column field
* @property {boolean} direction 'asc' or 'desc'
* @property {Event} event native event
*/
this.$emit('sort', column.field, this.isAsc ? 'asc' : 'desc', event);
}
if (!this.backendSorting) {
this.doSortSingleColumn(column);
}
this.currentSortColumn = column;
},
doSortSingleColumn(column) {
this.newData = this.sortBy(this.newData, column.field, column.customSort, this.isAsc);
},
isRowSelected(row, selected) {
if (!selected) {
return false;
}
if (this.customRowKey) {
return row[this.customRowKey] === selected[this.customRowKey];
}
return row === selected;
},
/**
* Check if the row is checked (is added to the array).
*/
isRowChecked(row) {
return helpers.indexOf(this.newCheckedRows, row, this.customIsChecked) >= 0;
},
/**
* Remove a checked row from the array.
*/
removeCheckedRow(row) {
const index = helpers.indexOf(this.newCheckedRows, row, this.customIsChecked);
if (index >= 0) {
this.newCheckedRows.splice(index, 1);
}
},
/**
* Header checkbox click listener.
* Add or remove all rows in current page.
*/
checkAll() {
const isAllChecked = this.isAllChecked;
this.visibleData.forEach((currentRow) => {
if (this.isRowCheckable(currentRow)) {
this.removeCheckedRow(currentRow);
}
if (!isAllChecked) {
if (this.isRowCheckable(currentRow)) {
this.newCheckedRows.push(currentRow);
}
}
});
/**
* @property {Array<Object>} newCheckedRows checked rows
*/
this.$emit('check', this.newCheckedRows);
this.$emit('check-all', this.newCheckedRows);
// Emit checked rows to update user variable
this.$emit('update:checkedRows', this.newCheckedRows);
},
/**
* Row checkbox click listener.
*/
checkRow(row, index, event) {
if (!this.isRowCheckable(row))
return;
const lastIndex = this.lastCheckedRowIndex;
this.lastCheckedRowIndex = index;
if (event.shiftKey && lastIndex !== null && index !== lastIndex) {
this.shiftCheckRow(row, index, lastIndex);
}
else if (!this.isRowChecked(row)) {
this.newCheckedRows.push(row);
}
else {
this.removeCheckedRow(row);
}
this.$emit('check', this.newCheckedRows, row);
// Emit checked rows to update user variable
this.$emit('update:checkedRows', this.newCheckedRows);
},
/**
* Check row when shift is pressed.
*/
shiftCheckRow(row, index, lastCheckedRowIndex) {
// Get the subset of the list between the two indicies
const subset = this.visibleData.slice(Math.min(index, lastCheckedRowIndex), Math.max(index, lastCheckedRowIndex) + 1);
// Determine the operation based on the state of the clicked checkbox
const shouldCheck = !this.isRowChecked(row);
subset.forEach((item) => {
this.removeCheckedRow(item);
if (shouldCheck && this.isRowCheckable(item)) {
this.newCheckedRows.push(item);
}
});
},
/**
* Row click listener.
* Emit all necessary events.
*/
selectRow(row, index) {
/**
* @property {Object} row clicked row
* @property {number} index index of clicked row
*/
this.$emit('click', row, index);
if (this.selected === row)
return;
if (!this.isRowSelectable(row))
return;
// Emit new and old row
/**
* @property {Object} row selected row
* @property {Array<Object>} selected selected rows
*/
this.$emit('select', row, this.selected);
// Emit new row to update user variable
this.$emit('update:selected', row);
},
/**
* Toggle to show/hide details slot
*/
toggleDetails(obj) {
const found = this.isVisibleDetailRow(obj);
if (found) {
this.closeDetailRow(obj);
this.$emit('details-close', obj);
}
else {
this.openDetailRow(obj);
this.$emit('details-open', obj);
}
// Syncs the detailed rows with the parent component
this.$emit('update:openedDetailed', this.visibleDetailRows);
},
openDetailRow(obj) {
const index = this.handleDetailKey(obj);
this.visibleDetailRows.push(index);
},
closeDetailRow(obj) {
const index = this.handleDetailKey(obj);
const i = this.visibleDetailRows.indexOf(index);
if (i >= 0) {
this.visibleDetailRows.splice(i, 1);
}
},
isVisibleDetailRow(obj) {
const index = this.handleDetailKey(obj);
return this.visibleDetailRows.indexOf(index) >= 0;
},
isActiveDetailRow(row) {
return this.detailed && !this.customDetailRow && this.isVisibleDetailRow(row);
},
isActiveCustomDetailRow(row) {
return this.detailed && this.customDetailRow && this.isVisibleDetailRow(row);
},
isRowFiltered(row) {
for (const key in this.filters) {
if (!this.filters[key])
continue;
const input = this.filters[key];
const column = this.newColumns.filter((c) => c.field === key)[0];
if (column && column.customSearch && typeof column.customSearch === 'function') {
if (!column.customSearch(row, input))
return false;
}
else {
const value = helpers.getValueByPath(row, key);
if (value == null)
return false;
if (Number.isInteger(value)) {
if (value !== Number(input))
return false;
}
else {
const re = new RegExp(helpers.escapeRegExpChars(input), 'i');
if (Array.isArray(value)) {
const valid = value.some((val) => re.test(helpers.removeDiacriticsFromString(val)) || re.test(val));
if (!valid)
return false;
}
else {
if (!re.test(helpers.removeDiacriticsFromString(value)) && !re.test(value)) {
return false;
}
}
}
}
}
return true;
},
/**
* When the detailKey is defined we use the object[detailKey] as index.
* If not, use the object reference by default.
*/
handleDetailKey(index) {
const key = this.detailKey;
return !key.length || !index
? index
: index[key];
},
/**
* Call initSort only first time (For example async data).
*/
checkSort() {
if (this.newColumns.length && this.firstTimeSort) {
this.initSort();
this.firstTimeSort = false;
}
else if (this.newColumns.length) {
if (Object.keys(this.currentSortColumn).length > 0) {
for (let i = 0; i < this.newColumns.length; i++) {
if (this.newColumns[i].field === this.currentSortColumn.field) {
this.currentSortColumn = this.newColumns[i];
break;
}
}
}
}
},
/**
* Check if footer slot has custom content.
*/
hasCustomFooterSlot() {
if (this.$slots.footer) {
const footer = this.$slots.footer();
if (footer.length > 1)
return true;
const tag = footer[0].tag;
if (tag !== 'th' && tag !== 'td')
return false;
}
return true;
},
/**
* Table arrow keys listener, change selection.
*/
pressedArrow(pos) {
if (!this.visibleData.length)
return;
let index = this.visibleData.indexOf(this.selected) + pos;
// Prevent from going up from first and down from last
index = index < 0
? 0
: index > this.visibleData.length - 1
? this.visibleData.length - 1
: index;
const row = this.visibleData[index];
if (!this.isRowSelectable(row)) {
let newIndex = null;
if (pos > 0) {
for (let i = index; i < this.visibleData.length && newIndex === null; i++) {
if (this.isRowSelectable(this.visibleData[i]))
newIndex = i;
}
}
else {
for (let i = index; i >= 0 && newIndex === null; i--) {
if (this.isRowSelectable(this.visibleData[i]))
newIndex = i;
}
}
if (newIndex >= 0) {
this.selectRow(this.visibleData[newIndex]);
}
}
else {
this.selectRow(row);
}
},
/**
* Focus table element if has selected prop.
*/
focus() {
if (!this.focusable)
return;
this.$el.querySelector('table').focus();
},
/**
* Initial sorted column based on the default-sort prop.
*/
initSort() {
if (!this.defaultSort)
return;
let sortField = '';
let sortDirection = this.defaultSortDirection;
if (Array.isArray(this.defaultSort)) {
sortField = this.defaultSort[0];
if (this.defaultSort[1]) {
sortDirection = this.defaultSort[1];
}
}
else {
sortField = this.defaultSort;
}
const sortColumn = this.newColumns.filter((column) => (column.field === sortField))[0];
if (sortColumn) {
this.isAsc = sortDirection.toLowerCase() !== 'desc';
this.sort(sortColumn, true);
}
},
/**
* Emits drag start event
*/
handleDragStart(event, row, index) {
if (!this.draggable)
return;
this.$emit('dragstart', { event, row, index });
},
/**
* Emits drag leave event
*/
handleDragEnd(event, row, index) {
if (!this.draggable)
return;
this.$emit('dragend', { event, row, index });
},
/**
* Emits drop event
*/
handleDrop(event, row, index) {
if (!this.draggable)
return;
this.$emit('drop', { event, row, index });
},
/**
* Emits drag over event
*/
handleDragOver(event, row, index) {
if (!this.draggable)
return;
this.$emit('dragover', { event, row, index });
},
/**
* Emits drag leave event
*/
handleDragLeave(event, row, index) {
if (!this.draggable)
return;
this.$emit('dragleave', { event, row, index });
},
/**
* Emits drag start event (column)
*/
handleColumnDragStart(event, column, index) {
if (!this.canDragColumn)
return;
this.isDraggingColumn = true;
this.$emit('columndragstart', { event, column, index });
},
/**
* Emits drag leave event (column)
*/
handleColumnDragEnd(event, column, index) {
if (!this.canDragColumn)
return;
this.isDraggingColumn = fals