UNPKG

@excelwebzone/symfony-admin-ui

Version:

Symfony Admin UI is a simple set of UI behaviors and components used with your [symfony-admin](https://github.com/excelwebzone/symfony-admin-bundle) application.

378 lines (304 loc) 14.1 kB
import $ from 'jquery'; import axios from '../../lib/utils/axios_utils'; import { objectToFormData } from '../../lib/utils/object_formdata_utils'; import 'jquery-ui/ui/widgets/resizable'; import 'jquery-ui/ui/widgets/sortable'; document.addEventListener('beforeunload', () => { // close any open antiscroll $('.js-antiscroll').antiscroll('destroy'); }); export default class Datagrid { constructor(tableEl) { if ($(tableEl).length === 0) { return; } this.initDomElements(tableEl); this.setAntiscroll(); this.resizeTable(); this.bindEvents(); $(window).on('resize.datagrid', () => this.resizeTable()); } initDomElements(tableEl) { this.$table = $(tableEl); this.isLoaded = this.$table.is(':visible'); } bindEvents() { this.$table.on('mouseover', '.datagrid-body-container .datagrid-table-row', (e) => this.mouseover(e)); this.$table.on('mouseout', '.datagrid-body-container .datagrid-table-row', (e) => this.mouseout(e)); this.$table.on('click', '.datagrid-body-container .datagrid-table-row', (e) => this.selectRow(e)); this.$table.on('dblclick', '.datagrid-body-container .datagrid-table-row', (e) => this.openFullPage(e)); if (this.$table.data('resizable')) { this.$table.find('.datagrid-header-container .datagrid-header-cell:not(.table-header-cell-empty):not(.table-header-cell-select)').resizable({ handles: 'e', resize: this.onColumnResize.bind(this), stop: this.onColumnResize.bind(this) }); } if (this.$table.data('sortable')) { this.$sortableIndicator = this.$table.find('.datagrid-column-sortable-indicator'); if (this.$sortableIndicator.length === 0) { this.$table.append('<div class="datagrid-column-sortable-indicator"></div>'); this.$sortableIndicator = this.$table.find('.datagrid-column-sortable-indicator'); } this.$table.find('.datagrid-header-container .datagrid-right-table-block .datagrid-header-row>div').sortable({ axis: 'x', containment: 'parent', cursor: 'move', helper: 'clone', items: '.datagrid-header-cell:not(.ui-sortable-ignore)', opacity: 0.9, placeholder: 'ui-state-highlight', scroll: true, tolerance: 'pointer', start: this.onColumnSortStart.bind(this), update: this.onColumnSortDone.bind(this), stop: this.onColumnSortStop.bind(this), sort: this.onColumnSortChange.bind(this) }); } } getTable() { return this.$table; } rebindEvents() { this.$table.find('.datagrid-body-container .datagrid-table-row').off('mouseover'); this.$table.find('.datagrid-body-container .datagrid-table-row').off('mouseout'); this.$table.find('.datagrid-body-container .datagrid-table-row').off('click'); this.$table.find('.datagrid-body-container .datagrid-table-row').off('dblclick'); if (this.$table.data('resizable')) { this.$table.find('.datagrid-header-container .datagrid-header-cell:not(.table-header-cell-empty):not(.table-header-cell-select)').resizable('destroy'); } if (this.$table.data('sortable')) { this.$table.find('.datagrid-header-container .datagrid-right-table-block .datagrid-header-row>div').sortable('destroy'); } this.bindEvents(); } onColumnResize(e, ui) { // force xWidth ui.element.resizable('option', { minWidth: ui.element.find('.table-header-cell-content>span').width() + 50, maxWidth: null }); if (ui.element.closest('.datagrid-header-block').hasClass('datagrid-left-table-block')) { ui.element.resizable('option', 'maxWidth', 500); } // change column width ui.element.attr('data-column-width', ui.size.width); ui.element.data('column-width', ui.size.width); this.resizeTable(); if (e.type === 'resizestop') { this.updateUserSettings(); } } onColumnSortStart(e, ui) { ui.item.startPosition = ui.item.index(); } onColumnSortStop() { this.$sortableIndicator.hide(); } onColumnSortChange(e, ui) { const left = ui.placeholder.offset().left - ui.placeholder.closest('.datagrid-tables-container').offset().left; const height = ui.placeholder.closest('.datagrid-tables-container').height(); this.$sortableIndicator.show(); this.$sortableIndicator.css({ left: `${left}px`, height: `${height}px` }); } onColumnSortDone(e, ui) { const $rows = this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row>div'); for (let row of $rows) { const $row = $(row); let index = $row.find('.datagrid-cell:eq(0)').data('index'); $row.find(`.datagrid-cell[data-index="${ui.item.data('index')}"]`).insertAfter( $row.find(`.datagrid-cell[data-index="${ui.item.prev().data('index')}"]`) ); const $columns = $row.find('.datagrid-cell'); for (let column of $columns) { const $column = $(column); // change column index $column.attr('data-index', index); $column.data('index', index); index++; } } this.updateUserSettings(); } updateUserSettings() { let index = 1; const columns = {}; for (let key of ['left', 'right']) { const $columns = this.$table.find(`.datagrid-${key}-table-block .datagrid-header-cell`); if ($columns.length) { columns[key] = []; for (let column of $columns) { const $column = $(column); // change column index $column.attr('data-index', index); $column.data('index', index); index++; if (!$column.data('field')) { continue; } columns[key].push({ field: $column.data('field'), name: $column.find('.table-header-cell-content>span:not(.total-value)').text().trim(), width: $column.data('column-width'), align: $column.hasClass('text-left') ? 'left' : $column.hasClass('text-right') ? 'right' : 'center', sortable: $column.hasClass('table-header-cell-sortable'), mobileCell: $column.hasClass('table-cell-mobile') }); } } } axios.post(this.$table.data('update-columns-endpoint'), objectToFormData({ columns: columns })) .then(() => this.$table.trigger('column:sorted')); } resizeTable() { const useDynamicWidth = this.$table.data('dynamic-width'); const defaultColumnWidth = this.$table.data('default-column-width'); const totalColumns = this.$table.find('.datagrid-header-container .datagrid-header-cell').length; const totalRows = this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').length; const headerHeight = this.$table.data('header-height'); const rowHeight = this.$table.data('row-height'); const tableWidth = this.$table.get(0).offsetWidth; const tableHeight = totalRows * rowHeight; const statsHeight = this.$table.prev('.list-page-stats').length ? this.$table.prev('.list-page-stats').get(0).offsetHeight : 0; const containerHeight = this.$table.parent().get(0).offsetHeight - headerHeight - statsHeight; const dynamicColumnWidth = Math.floor(tableWidth / totalColumns); const maxHeight = tableHeight > containerHeight && containerHeight > 0 ? containerHeight : tableHeight; // calculate row width let rowWidth = 0; let leftBlockWidth = 0; for (let key of ['left', 'right']) { const $columns = this.$table.find(`.datagrid-${key}-table-block .datagrid-header-cell:visible`); for (let column of $columns) { let columnWidth = $(column).data('column-width') || defaultColumnWidth; if (useDynamicWidth && dynamicColumnWidth > columnWidth) { columnWidth = dynamicColumnWidth; } rowWidth += columnWidth; if (key === 'left') { leftBlockWidth += columnWidth; } this.$table.find(`.js-datagrid-column-width[data-index=${$(column).data('index')}]`).css('width', columnWidth); } } let rightBlockWidth = rowWidth - leftBlockWidth; if (tableWidth > rowWidth) { rightBlockWidth += tableWidth - rowWidth; } this.$table.find('.js-datagrid-column-left-width').css('width', leftBlockWidth + 'px'); this.$table.find('.js-datagrid-column-right-width').css('width', (tableWidth - leftBlockWidth) + 'px'); this.$table.find('.js-datagrid-row-left-width').css('width', leftBlockWidth + 'px'); this.$table.find('.js-datagrid-row-right-width').css('width', rightBlockWidth + 'px'); this.$table.css('height', 'auto'); this.$table.find('.js-datagrid-width').css('width', tableWidth + 'px'); this.$table.find('.js-datagrid-height').css('height', tableHeight + 'px'); this.$table.find('.js-datagrid-max-height').css('height', maxHeight + 'px'); this.$table.find('.js-datagrid-header-height').css('height', headerHeight + 'px'); this.$table.find('.js-datagrid-row-height').css('height', rowHeight + 'px'); // if has related object that need width change const $relatedObject = $(this.$table.data('related-object')); if ($relatedObject) { $relatedObject.css('width', rowWidth + 'px').show(); } // main scrollbar this.$table.find('.datagrid-body-container .antiscroll-box').css('width', tableWidth + 'px'); this.$table.find('.datagrid-body-container .antiscroll-box').css('height', maxHeight + 'px'); this.$table.trigger('table:resized'); if (!this.isLoaded) { this.isLoaded = true; this.$table.show().trigger('loaded'); } this.$table.find('.js-antiscroll').trigger('refresh'); } mouseover(e) { const $currentTarget = $(e.currentTarget); if (this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').eq($currentTarget.index()).hasClass('datagrid-selected')) { e.stopPropagation(); return; } this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').eq($currentTarget.index()).addClass('datagrid-hover'); this.$table.find('.datagrid-body-container .datagrid-left-table-block .datagrid-table-row').eq($currentTarget.index()).addClass('datagrid-hover'); } mouseout(e) { const $currentTarget = $(e.currentTarget); this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').eq($currentTarget.index()).removeClass('datagrid-hover'); this.$table.find('.datagrid-body-container .datagrid-left-table-block .datagrid-table-row').eq($currentTarget.index()).removeClass('datagrid-hover'); } selectRow(e) { const $currentTarget = $(e.currentTarget); if (e.target.tagName.toLowerCase() === 'a' || $(e.target).hasClass('checkbox') || $(e.target).closest('.checkbox').length || $(e.target).closest('.import-table-panel').length || this.$table.data('ignore-row-select') ) { return; } if (this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').eq($currentTarget.index()).hasClass('datagrid-selected')) { e.stopPropagation(); return; } this.$table.find('.datagrid-table-row').removeClass('datagrid-selected').removeClass('datagrid-hover'); this.$table.find('.datagrid-body-container .datagrid-right-table-block .datagrid-table-row').eq($currentTarget.index()).addClass('datagrid-selected'); this.$table.find('.datagrid-body-container .datagrid-left-table-block .datagrid-table-row').eq($currentTarget.index()).addClass('datagrid-selected'); this.$table.find('.datagrid-body-container .datagrid-left-table-block .datagrid-table-row').eq($currentTarget.index()).trigger('row:selected'); } openFullPage(e) { const $currentTarget = $(e.currentTarget); const $row = this.$table.find('.datagrid-body-container .datagrid-left-table-block .datagrid-table-row').eq($currentTarget.index()); if ($row.data('link')) { location.href = $row.data('link'); } } setAntiscroll() { for (let element of this.$table.find('.js-antiscroll')) { const $element = $(element); const antiscroll = new $.Antiscroll($element, { x: $element.data('horizontal'), y: $element.data('vertical') }); $element.data('antiscroll', antiscroll); $element.find('>.antiscroll-inner').on('scroll', (e) => { if ($element.data('vertical')) { if ($(e.currentTarget).scrollTop() + $(e.currentTarget).innerHeight() >= e.currentTarget.scrollHeight) { $(e.currentTarget).trigger('reached-bottom'); } } if ($element.data('horizontal')) { const scrollLeft = e.currentTarget.scrollLeft; // existing: keep header/blocks aligned $($element.data('scroll-block')).css('left', -scrollLeft); // keep "load more" centered in the *full table*, not just the right block const $loadMore = $element.find('.datagrid-table-load-more'); if ($loadMore.length) { const tableWidth = this.$table.get(0).offsetWidth; // width of the fixed left block (0 if no left block) const leftW = this.$table.find('.datagrid-left-table-block').length ? this.$table.find('.js-datagrid-column-left-width').outerWidth() || 0 : 0; const btnW = $loadMore.outerWidth() || 0; // desired left position *within right block viewport* const leftWithinRight = (tableWidth / 2) - leftW - (btnW / 2); // prevent the control from drifting past the left gutter (keep a minimum 12px inset) const clamped = Math.max(12, leftWithinRight); // add scrollLeft to pin it while content scrolls $loadMore.css('left', (clamped + scrollLeft) + 'px'); } } }) .trigger('scroll'); $element.on('refresh', () => { $element.data('antiscroll').refresh(); }); } } }