@mui/x-data-grid
Version:
The community edition of the data grid component (MUI X).
316 lines (304 loc) • 14.7 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _extends from "@babel/runtime/helpers/esm/extends";
import _regeneratorRuntime from "@babel/runtime/regenerator";
import * as React from 'react';
import { unstable_ownerDocument as ownerDocument } from '@mui/utils';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridExpandedRowCountSelector } from '../filter/gridFilterSelector';
import { gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector } from '../columns/gridColumnsSelector';
import { gridClasses } from '../../../constants/gridClasses';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector';
import { getColumnsToExport } from './utils';
import { mergeStateWithPaginationModel } from '../pagination/useGridPagination';
import { useGridRegisterPipeProcessor } from '../../core/pipeProcessing';
import { GridPrintExportMenuItem } from '../../../components/toolbar/GridToolbarExport';
import { getTotalHeaderHeight } from '../columns/gridColumnsUtils';
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSelectionColDef';
import { jsx as _jsx } from "react/jsx-runtime";
function raf() {
return new Promise(function (resolve) {
requestAnimationFrame(function () {
resolve();
});
});
}
function buildPrintWindow(title) {
var iframeEl = document.createElement('iframe');
iframeEl.style.position = 'absolute';
iframeEl.style.width = '0px';
iframeEl.style.height = '0px';
iframeEl.title = title || document.title;
return iframeEl;
}
/**
* @requires useGridColumns (state)
* @requires useGridFilter (state)
* @requires useGridSorting (state)
* @requires useGridParamsApi (method)
*/
export var useGridPrintExport = function useGridPrintExport(apiRef, props) {
var logger = useGridLogger(apiRef, 'useGridPrintExport');
var doc = React.useRef(null);
var previousGridState = React.useRef(null);
var previousColumnVisibility = React.useRef({});
var previousRows = React.useRef([]);
React.useEffect(function () {
doc.current = ownerDocument(apiRef.current.rootElementRef.current);
}, [apiRef]);
// Returns a promise because updateColumns triggers state update and
// the new state needs to be in place before the grid can be sized correctly
var updateGridColumnsForPrint = React.useCallback(function (fields, allColumns, includeCheckboxes) {
return new Promise(function (resolve) {
var exportedColumnFields = getColumnsToExport({
apiRef: apiRef,
options: {
fields: fields,
allColumns: allColumns
}
}).map(function (column) {
return column.field;
});
var columns = gridColumnDefinitionsSelector(apiRef);
var newColumnVisibilityModel = {};
columns.forEach(function (column) {
newColumnVisibilityModel[column.field] = exportedColumnFields.includes(column.field);
});
if (includeCheckboxes) {
newColumnVisibilityModel[GRID_CHECKBOX_SELECTION_COL_DEF.field] = true;
}
apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel);
resolve();
});
}, [apiRef]);
var updateGridRowsForPrint = React.useCallback(function (getRowsToExport) {
var rowsToExportIds = getRowsToExport({
apiRef: apiRef
});
var newRows = rowsToExportIds.map(function (id) {
return apiRef.current.getRow(id);
});
apiRef.current.setRows(newRows);
}, [apiRef]);
var handlePrintWindowLoad = React.useCallback(function (printWindow, options) {
var _querySelector, _querySelector2;
var normalizeOptions = _extends({
copyStyles: true,
hideToolbar: false,
hideFooter: false,
includeCheckboxes: false
}, options);
var printDoc = printWindow.contentDocument;
if (!printDoc) {
return;
}
var rowsMeta = gridRowsMetaSelector(apiRef.current.state);
var gridRootElement = apiRef.current.rootElementRef.current;
var gridClone = gridRootElement.cloneNode(true);
// Allow to overflow to not hide the border of the last row
var gridMain = gridClone.querySelector(".".concat(gridClasses.main));
gridMain.style.overflow = 'visible';
// See https://support.google.com/chrome/thread/191619088?hl=en&msgid=193009642
gridClone.style.contain = 'size';
var columnHeaders = gridClone.querySelector(".".concat(gridClasses.columnHeaders));
var columnHeadersInner = columnHeaders.querySelector(".".concat(gridClasses.columnHeadersInner));
columnHeadersInner.style.width = '100%';
var gridToolbarElementHeight = ((_querySelector = gridRootElement.querySelector(".".concat(gridClasses.toolbarContainer))) == null ? void 0 : _querySelector.offsetHeight) || 0;
var gridFooterElementHeight = ((_querySelector2 = gridRootElement.querySelector(".".concat(gridClasses.footerContainer))) == null ? void 0 : _querySelector2.offsetHeight) || 0;
if (normalizeOptions.hideToolbar) {
var _gridClone$querySelec;
(_gridClone$querySelec = gridClone.querySelector(".".concat(gridClasses.toolbarContainer))) == null || _gridClone$querySelec.remove();
gridToolbarElementHeight = 0;
}
if (normalizeOptions.hideFooter) {
var _gridClone$querySelec2;
(_gridClone$querySelec2 = gridClone.querySelector(".".concat(gridClasses.footerContainer))) == null || _gridClone$querySelec2.remove();
gridFooterElementHeight = 0;
}
// Expand container height to accommodate all rows
var computedTotalHeight = rowsMeta.currentPageTotalHeight + getTotalHeaderHeight(apiRef, props.columnHeaderHeight) + gridToolbarElementHeight + gridFooterElementHeight;
gridClone.style.height = "".concat(computedTotalHeight, "px");
// The height above does not include grid border width, so we need to exclude it
gridClone.style.boxSizing = 'content-box';
// the footer is always being placed at the bottom of the page as if all rows are exported
// so if getRowsToExport is being used to only export a subset of rows then we need to
// adjust the footer position to be correctly placed at the bottom of the grid
if (options != null && options.getRowsToExport) {
var gridFooterElement = gridClone.querySelector(".".concat(gridClasses.footerContainer));
gridFooterElement.style.position = 'absolute';
gridFooterElement.style.width = '100%';
gridFooterElement.style.top = "".concat(computedTotalHeight - gridFooterElementHeight, "px");
}
// printDoc.body.appendChild(gridClone); should be enough but a clone isolation bug in Safari
// prevents us to do it
var container = document.createElement('div');
container.appendChild(gridClone);
printDoc.body.innerHTML = container.innerHTML;
var defaultPageStyle = typeof normalizeOptions.pageStyle === 'function' ? normalizeOptions.pageStyle() : normalizeOptions.pageStyle;
if (typeof defaultPageStyle === 'string') {
// TODO custom styles should always win
var styleElement = printDoc.createElement('style');
styleElement.appendChild(printDoc.createTextNode(defaultPageStyle));
printDoc.head.appendChild(styleElement);
}
if (normalizeOptions.bodyClassName) {
var _printDoc$body$classL;
(_printDoc$body$classL = printDoc.body.classList).add.apply(_printDoc$body$classL, _toConsumableArray(normalizeOptions.bodyClassName.split(' ')));
}
var stylesheetLoadPromises = [];
if (normalizeOptions.copyStyles) {
var rootCandidate = gridRootElement.getRootNode();
var root = rootCandidate.constructor.name === 'ShadowRoot' ? rootCandidate : doc.current;
var headStyleElements = root.querySelectorAll("style, link[rel='stylesheet']");
var _loop = function _loop() {
var node = headStyleElements[i];
if (node.tagName === 'STYLE') {
var newHeadStyleElements = printDoc.createElement(node.tagName);
var sheet = node.sheet;
if (sheet) {
var styleCSS = '';
// NOTE: for-of is not supported by IE
for (var j = 0; j < sheet.cssRules.length; j += 1) {
if (typeof sheet.cssRules[j].cssText === 'string') {
styleCSS += "".concat(sheet.cssRules[j].cssText, "\r\n");
}
}
newHeadStyleElements.appendChild(printDoc.createTextNode(styleCSS));
printDoc.head.appendChild(newHeadStyleElements);
}
} else if (node.getAttribute('href')) {
// If `href` tag is empty, avoid loading these links
var _newHeadStyleElements = printDoc.createElement(node.tagName);
for (var _j = 0; _j < node.attributes.length; _j += 1) {
var attr = node.attributes[_j];
if (attr) {
_newHeadStyleElements.setAttribute(attr.nodeName, attr.nodeValue || '');
}
}
stylesheetLoadPromises.push(new Promise(function (resolve) {
_newHeadStyleElements.addEventListener('load', function () {
return resolve();
});
}));
printDoc.head.appendChild(_newHeadStyleElements);
}
};
for (var i = 0; i < headStyleElements.length; i += 1) {
_loop();
}
}
// Trigger print
if (process.env.NODE_ENV !== 'test') {
// wait for remote stylesheets to load
Promise.all(stylesheetLoadPromises).then(function () {
printWindow.contentWindow.print();
});
}
}, [apiRef, doc, props.columnHeaderHeight]);
var handlePrintWindowAfterPrint = React.useCallback(function (printWindow) {
var _previousGridState$cu;
// Remove the print iframe
doc.current.body.removeChild(printWindow);
// Revert grid to previous state
apiRef.current.restoreState(previousGridState.current || {});
if (!((_previousGridState$cu = previousGridState.current) != null && (_previousGridState$cu = _previousGridState$cu.columns) != null && _previousGridState$cu.columnVisibilityModel)) {
// if the apiRef.current.exportState(); did not exported the column visibility, we update it
apiRef.current.setColumnVisibilityModel(previousColumnVisibility.current);
}
apiRef.current.unstable_setVirtualization(true);
apiRef.current.setRows(previousRows.current);
// Clear local state
previousGridState.current = null;
previousColumnVisibility.current = {};
previousRows.current = [];
}, [apiRef]);
var exportDataAsPrint = React.useCallback( /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
var visibleRowCount, paginationModel, printWindow;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
logger.debug("Export data as Print");
if (apiRef.current.rootElementRef.current) {
_context.next = 3;
break;
}
throw new Error('MUI: No grid root element available.');
case 3:
previousGridState.current = apiRef.current.exportState();
// It appends that the visibility model is not exported, especially if columnVisibility is not controlled
previousColumnVisibility.current = gridColumnVisibilityModelSelector(apiRef);
previousRows.current = apiRef.current.getSortedRows();
if (props.pagination) {
visibleRowCount = gridExpandedRowCountSelector(apiRef);
paginationModel = {
page: 0,
pageSize: visibleRowCount
};
apiRef.current.updateControlState('pagination',
// Using signature `DataGridPro` to allow more than 100 rows in the print export
mergeStateWithPaginationModel(visibleRowCount, 'DataGridPro', paginationModel));
apiRef.current.forceUpdate();
}
_context.next = 9;
return updateGridColumnsForPrint(options == null ? void 0 : options.fields, options == null ? void 0 : options.allColumns, options == null ? void 0 : options.includeCheckboxes);
case 9:
if (options != null && options.getRowsToExport) {
updateGridRowsForPrint(options.getRowsToExport);
}
apiRef.current.unstable_setVirtualization(false);
_context.next = 13;
return raf();
case 13:
// wait for the state changes to take action
printWindow = buildPrintWindow(options == null ? void 0 : options.fileName);
if (process.env.NODE_ENV === 'test') {
doc.current.body.appendChild(printWindow);
// In test env, run the all pipeline without waiting for loading
handlePrintWindowLoad(printWindow, options);
handlePrintWindowAfterPrint(printWindow);
} else {
printWindow.onload = function () {
handlePrintWindowLoad(printWindow, options);
var mediaQueryList = printWindow.contentWindow.matchMedia('print');
mediaQueryList.addEventListener('change', function (mql) {
var isAfterPrint = mql.matches === false;
if (isAfterPrint) {
handlePrintWindowAfterPrint(printWindow);
}
});
};
doc.current.body.appendChild(printWindow);
}
case 15:
case "end":
return _context.stop();
}
}, _callee);
}));
return function (_x) {
return _ref.apply(this, arguments);
};
}(), [props, logger, apiRef, handlePrintWindowLoad, handlePrintWindowAfterPrint, updateGridColumnsForPrint, updateGridRowsForPrint]);
var printExportApi = {
exportDataAsPrint: exportDataAsPrint
};
useGridApiMethod(apiRef, printExportApi, 'public');
/**
* PRE-PROCESSING
*/
var addExportMenuButtons = React.useCallback(function (initialValue, options) {
var _options$printOptions;
if ((_options$printOptions = options.printOptions) != null && _options$printOptions.disableToolbarButton) {
return initialValue;
}
return [].concat(_toConsumableArray(initialValue), [{
component: /*#__PURE__*/_jsx(GridPrintExportMenuItem, {
options: options.printOptions
}),
componentName: 'printExport'
}]);
}, []);
useGridRegisterPipeProcessor(apiRef, 'exportMenu', addExportMenuButtons);
};