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.

499 lines (412 loc) 17.1 kB
import $ from 'jquery'; import axios from '../../lib/utils/axios_utils'; import toaster from '../../lib/utils/toaster'; import Datagrid from './datagrid'; import { modifiedValues } from '../../lib/utils/modified_values'; const EMPTY_CELL = index => `<div class="datagrid-cell is-empty text-left js-datagrid-column-width js-draggable-cell" data-index="${index}"></div>`; export default class Cardgrid { /** * { * setEmptyContent: ($cell) => {..}, * onMoveCell: ($cell) => {..}, * onFieldChange: (field, value, $cell) => {..}, * onDragEnd: ($field, newValue, data, $cell) => {..}, * prepareValue: (newValue) => {.. return newValue; } * } */ constructor(callback, allowEmptyFields) { this.callback = callback || {}; this.allowEmptyFields = allowEmptyFields || []; this.initDomElements(); this.bindEvents(); } initDomElements() { this.dragSourceEl = null; this.dragTargetEl = null; this.$table = $('.list-page-table'); this.datagrid = new Datagrid(this.$table.find('.js-datagrid')); } bindEvents() { this.$table.on('table:resized', (e) => this.tableResized(e)); this.$table.on('data:loaded', (e, data) => this.listLoaded(e, data)); $(document).on('click', '.js-drawer-close', (e) => this.unselectItem(e)); $(document).on('click', '.cardgrid-component-model-cell', (e) => this.selectItem(e)); $(document).on('modal:shown', '.js-cardgrid-delete-item', (e, modal) => this.deleteItem(e, modal)); $(document).on('field:updated', '.js-cardgrid-form', (e, data) => this.fieldUpdated(e, data)); $(document).on('dragstart', '.js-draggable-cell:not(.is-empty)', (e) => this.dragStart(e)); $(document).on('dragenter', '.js-draggable-cell', (e) => this.dragEnter(e)); $(document).on('dragend', '.js-draggable-cell', (e) => this.dragEnd(e)); } tableResized(e) { const $container = this.$table.find('.datagrid-body-container'); const height = $container.height() + 16; if ($(window).height() > $container.offset().top + height) { $container.css('height', height + 'px'); $container.find('.antiscroll-box').css('height', height + 'px'); $container.find('.datagrid-right-table-block').css('height', height + 'px'); $container.find('.datagrid-left-table-block').css('height', height + 'px'); } // remove is-hover form cells $(e.currentTarget).find('.datagrid-body-container .datagrid-table-row .datagrid-cell .cardgrid-component-model-cell.is-hover').removeClass('is-hover'); // display empty cell content (icon + title) if (typeof this.callback.setEmptyContent === 'function') { for (let cell of $(e.currentTarget).find('.datagrid-body-container .datagrid-table-row .datagrid-cell.is-empty.is-last')) { $(cell).removeClass('is-last'); $(cell).html(''); } for (let cell of $(e.currentTarget).find('.datagrid-body-container .datagrid-table-row:eq(0) .datagrid-cell.is-empty')) { $(cell).addClass('is-last'); this.callback.setEmptyContent($(cell)); } } } listLoaded(e, data) { if (data.page === 1 && typeof data.columns === 'object' ) { for (let [key, value] of Object.entries(data.columns)) { if (typeof value === 'object') { for (let [k, v] of Object.entries(value)) { this.$table.find(`.datagrid-header-cell[data-value="${key}"]`) .find(`span.${k}`) .text(`${v}`); } } else { this.$table.find(`.datagrid-header-cell[data-value="${key}"]`) .find('span.counter') .text(`${value}`); } } } for (let column of this.$table.find('.datagrid-header-container .datagrid-header-cell')) { const index = $(column).data('index'); for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); const $rowCell = $row.find(`.datagrid-cell[data-index="${index}"]`); if ($rowCell.length) { if ($rowCell.hasClass('is-empty')) { for (let subRow of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $subRow = $(subRow); if ($row.index() < $subRow.index()) { const $subRowCell = $subRow.find(`.datagrid-cell[data-index="${index}"]:not(.is-empty)`); if ($subRowCell.length) { $rowCell.replaceWith($subRowCell.clone(true)); $subRowCell.replaceWith(EMPTY_CELL(index)); break; } } } } else { const $duplicateCells = this.$table.find(`.datagrid-cell[data-index="${index}"][data-id="${$rowCell.data('id')}"]`); let i = 0; for (let dupCell of $duplicateCells) { if (i++ > 0) { $(dupCell).replaceWith(EMPTY_CELL(index)); } } } } } } this.markLastCells(); this.removeExtraRow(); this.datagrid.resizeTable(); this.datagrid.rebindEvents(); } markLastCells() { for (let column of this.$table.find('.datagrid-header-container .datagrid-header-cell')) { const index = $(column).data('index'); let $preCell = null; for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); const $rowCell = $row.find(`.datagrid-cell[data-index="${index}"]`); if ($rowCell.length) { if (!$preCell) { $preCell = $rowCell; } if (!$rowCell.hasClass('is-empty')) { $preCell.removeClass('is-last'); $preCell = $rowCell; $preCell.addClass('is-last'); } } } } } removeExtraRow() { let $lastRow, cells, emptyCells; do { $lastRow = this.$table.find('.datagrid-body-container .datagrid-table-row:last-child:not(.datagrid-table-load-more-row)'); if (!$lastRow.length) { break; } cells = $lastRow.find('.datagrid-cell').length; emptyCells = $lastRow.find('.datagrid-cell.is-empty').length; if (cells === emptyCells) { $lastRow.remove(); } } while ($lastRow.length && cells === emptyCells); } unselectItem(e) { this.$table.find('.cardgrid-component-model-cell.is-current').removeClass('is-current'); } selectItem(e) { if ($(e.target).hasClass('checkbox') || $(e.target).closest('.checkbox').length ) { return; } this.$table.find('.cardgrid-component-model-cell').removeClass('is-current'); $(e.currentTarget).addClass('is-current'); $(e.currentTarget) .closest('.js-entity-drawer') .trigger('row:selected'); } deleteItem(e, modal) { const $target = $(e.currentTarget); const $cell = this.$table.find('.cardgrid-component-model-cell.is-current').closest('.js-entity-drawer'); $(modal).on('modal:hidden', (e, data) => { $(modal).off('modal:hidden'); const $drawer = $target.closest('.drawer-frame'); if ($drawer.length) { $drawer.find('.js-drawer-close').click(); } this.deleteCell($cell); }); } deleteCell($cell, cleanup = true) { const index = $cell.data('index'); const $emptyCell = $(EMPTY_CELL(index)); // set as empty $cell.replaceWith($emptyCell); let $prevRow = $emptyCell.closest('.datagrid-table-row'); for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); if ($row.index() > $prevRow.index()) { const $prevCell = $prevRow.find(`.datagrid-cell[data-index="${index}"]`); const $rowCell = $row.find(`.datagrid-cell[data-index="${index}"]`); if (!$rowCell.hasClass('js-entity-drawer')) { break; } $prevCell.replaceWith($rowCell.clone(true)); $rowCell.replaceWith($emptyCell); $prevRow = $row; } } if (cleanup) { this.removeExtraRow(); } this.datagrid.resizeTable(); this.datagrid.rebindEvents(); const $counter = this.$table.find(`.datagrid-header-cell[data-index="${index}"]`).find('span.counter'); $counter.text(parseInt($counter.text()) - 1); } moveCell($cell, value, $targetCell = null) { let $column = null; for (let column of this.$table.find('.datagrid-header-container .datagrid-header-cell')) { if (value === $(column).data('value')) { $column = $(column); } } if (!$column || ($column.data('index') === $cell.data('index') && !$targetCell)) { return; } // get column index const columnIndex = $column.data('index'); // clone and delete original cell let $cloneCell = $cell.clone(true); this.deleteCell($cell, false); // add empty cells let $lastRow = this.$table.find('.datagrid-body-container .datagrid-table-row:last-child'); if (!$lastRow.find(`.datagrid-cell[data-index="${columnIndex}"]`).hasClass('is-empty')) { $lastRow.after($lastRow.clone(true)); $lastRow = this.$table.find('.datagrid-body-container .datagrid-table-row:last-child'); for (let cell of $lastRow.find('.datagrid-cell')) { $(cell).replaceWith(EMPTY_CELL($(cell).data('index'))); } } // set target-cell to first empty cell if (!$targetCell || $targetCell.hasClass('is-empty')) { for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); const $rowCell = $row.find(`.datagrid-cell[data-index="${columnIndex}"]`); if ($rowCell.hasClass('is-empty')) { $targetCell = $rowCell; break; } } } // set row index const rowIndex = $targetCell.closest('.datagrid-table-row').index(); // replace with target and place last let $preCell = null; for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); if ($row.index() >= rowIndex) { const $rowCell = $row.find(`.datagrid-cell[data-index="${columnIndex}"]`); if ($rowCell.length) { $preCell = $rowCell.clone(true); $rowCell.replaceWith($cloneCell); $cloneCell.data('index', $rowCell.data('index')); $cloneCell.attr('data-index', $rowCell.data('index')); $cloneCell = $preCell; } } } this.markLastCells(); this.removeExtraRow(); this.datagrid.resizeTable(); this.datagrid.rebindEvents(); const $counter = this.$table.find(`.datagrid-header-cell[data-index="${columnIndex}"]`).find('span.counter'); $counter.text(parseInt($counter.text()) + 1); if (typeof this.callback.onMoveCell === 'function') { let $lastCell = null; for (let row of this.$table.find('.datagrid-body-container .datagrid-table-row')) { const $row = $(row); const $rowCell = $row.find(`.datagrid-cell[data-index="${columnIndex}"].is-last`); if ($rowCell.length) { $lastCell = $rowCell; break; } } if ($lastCell.length) { this.callback.onMoveCell($lastCell); } } } fieldUpdated(e, data) { const $cell = this.$table.find('.cardgrid-component-model-cell.is-current').closest('.js-entity-drawer'); if (data.fields) { const fields = {}; let found = false; for (let [field, value] of Object.entries(data.fields)) { if (field === $cell.data('target-field')) { found = true; } else { fields[field] = value; } } if (found) { fields[$cell.data('target-field')] = data.fields[$cell.data('target-field')]; } for (let [field, value] of Object.entries(fields)) { if (value || this.allowEmptyFields.indexOf(field) !== -1) { if (typeof this.callback.onFieldChange === 'function') { this.callback.onFieldChange(field, value, $cell); } else if (field === $cell.data('target-field')) { this.moveCell($cell, value); } else { $cell.find(`[data-value="${field}"]`).html(value); } } else { if (field === $cell.data('target-field')) { this.deleteCell($cell); } else { $cell.find(`[data-value="${field}"]`).html(''); } } } } } dragStart(e) { this.dragSourceEl = $(e.currentTarget); this.$table.find('.cardgrid-component').addClass('is-dragging'); const $clone = this.dragSourceEl.clone(true); $clone .css('position', 'absolute') .css('border', 'none') .css('height', 'auto') .css('z-index', '99999') .addClass('is-clone') .find('.cardgrid-component-model-cell') .addClass('drag-shadow') .css('width', '300px') .css('height', this.dragSourceEl.css('height')); $('body').append($clone); e.originalEvent.dataTransfer.setData('text/plain', $clone.data('id')); e.originalEvent.dataTransfer.setDragImage($clone.get(0), 0, 0); this.dragSourceEl .find('.cardgrid-component-model-cell') .addClass('shadow-model'); } dragEnter(e) { this.$table.find('.cardgrid-component-model-cell').removeClass('is-hover'); this.dragTargetEl = $(e.currentTarget); this.dragTargetEl.find('.cardgrid-component-model-cell:not(.shadow-model)').addClass('is-hover'); const index = this.dragTargetEl.data('index'); this.$table.find('.datagrid-header-cell.active').removeClass('active'); this.$table.find(`.datagrid-header-cell[data-index="${index}"]`).addClass('active'); } dragEnd(e) { if (e.stopPropagation) { e.stopPropagation(); } this.$table.find('.cardgrid-component').removeClass('is-dragging'); this.$table.find('.datagrid-header-cell').removeClass('active'); this.$table.find('.cardgrid-component-model-cell').removeClass('shadow-model'); $('body').find('.js-draggable-cell.is-clone').remove(); // don't do anything if we're dropping on the same column we're dragging. if (this.dragSourceEl.data('id') !== this.dragTargetEl.data('id')) { const index = this.dragTargetEl.data('index'); const cellValue = this.$table.find(`.datagrid-header-cell[data-index="${index}"]`).data('value'); let newValue = cellValue; if (typeof this.callback.prepareValue === 'function') { newValue = this.callback.prepareValue(newValue); } const params = {}; params[this.dragSourceEl.data('target-field')] = newValue; // pass sorting value (target id) if (this.dragSourceEl.data('target-sort-field')) { params[this.dragSourceEl.data('target-sort-field')] = !this.dragTargetEl.hasClass('is-empty') ? this.dragTargetEl.data('id') : null; } axios.put(this.dragSourceEl.data('update-fields-endpoint'), params) .then(({ data }) => { if (data.error && data.error.message) { toaster(data.error.message, 'error'); return; } if (data.fields) { for (let [field, value] of Object.entries(data.fields)) { this.dragSourceEl.find(`[data-value="${field}"]`).html(value); } } if (this.dragSourceEl.data('target-sort-field')) { this.moveCell(this.dragSourceEl, cellValue, this.dragTargetEl); } else { this.moveCell(this.dragSourceEl, cellValue); } // try to find the drawer/form/field if it's open. if not found we'll still invoke onDragEnd with $field = null let $field = null; const $drawer = $(`.drawer-frame[data-id="${this.dragSourceEl.data('id')}"]`); if ($drawer.length) { const $form = $drawer.find('.entity-details'); const $container = $form.length ? $($form.data('container') || 'body') : $('body'); if (data.fields) { for (let [field, value] of Object.entries(data.fields)) { modifiedValues($drawer, field, value, $container); } } if ($form.length) { $field = $form.find(`[id$="_${this.dragSourceEl.data('target-field')}"]`); if ($field.length === 0) { $field = $form.find(`[id$="_${this.dragSourceEl.data('target-field')}_date"]`); } } } // original cell element (the active card) const $cell = $(this.dragSourceEl); // always notify callback.onDragEnd if provided, even when there is no open drawer/form if (typeof this.callback.onDragEnd === 'function') { this.callback.onDragEnd($field, cellValue, data, $cell); } else if ($field && $field.length) { // fallback behavior: if no callback provided but a field exists, set its value $field.val(newValue); } }); } } }