UNPKG

@mui/x-data-grid

Version:

The community edition of the data grid component (MUI X).

239 lines (198 loc) 9.87 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import { ownerDocument } from '@mui/material/utils'; import { useGridLogger } from '../../utils/useGridLogger'; import { gridVisibleRowCountSelector } from '../filter/gridFilterSelector'; import { gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector } from '../columns/gridColumnsSelector'; import { gridDensityTotalHeaderHeightSelector } from '../density/densitySelector'; import { gridClasses } from '../../../constants/gridClasses'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector'; import { getColumnsToExport } from './utils'; import { useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GridPrintExportMenuItem } from '../../../components/toolbar/GridToolbarExport'; import { jsx as _jsx } from "react/jsx-runtime"; /** * @requires useGridColumns (state) * @requires useGridFilter (state) * @requires useGridSorting (state) * @requires useGridParamsApi (method) */ export const useGridPrintExport = (apiRef, props) => { const logger = useGridLogger(apiRef, 'useGridPrintExport'); const doc = React.useRef(null); const previousGridState = React.useRef(null); const previousColumnVisibility = React.useRef({}); React.useEffect(() => { 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 const updateGridColumnsForPrint = React.useCallback((fields, allColumns) => new Promise(resolve => { if (!fields && !allColumns) { resolve(); return; } const exportedColumnFields = getColumnsToExport({ apiRef, options: { fields, allColumns } }).map(column => column.field); const columns = gridColumnDefinitionsSelector(apiRef); const newColumnVisibilityModel = {}; columns.forEach(column => { newColumnVisibilityModel[column.field] = exportedColumnFields.includes(column.field); }); apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel); resolve(); }), [apiRef]); const buildPrintWindow = React.useCallback(title => { const iframeEl = document.createElement('iframe'); iframeEl.id = 'grid-print-window'; // Without this 'onload' event won't fire in some browsers iframeEl.src = window.location.href; iframeEl.style.position = 'absolute'; iframeEl.style.width = '0px'; iframeEl.style.height = '0px'; iframeEl.title = title || document.title; return iframeEl; }, []); const handlePrintWindowLoad = React.useCallback((printWindow, options) => { const normalizeOptions = _extends({ copyStyles: true, hideToolbar: false, hideFooter: false }, options); // Some agents, such as IE11 and Enzyme (as of 2 Jun 2020) continuously call the // `onload` callback. This ensures that it is only called once. printWindow.onload = null; const printDoc = printWindow.contentDocument || printWindow.contentWindow?.document; if (!printDoc) { return; } const totalHeaderHeight = gridDensityTotalHeaderHeightSelector(apiRef); const rowsMeta = gridRowsMetaSelector(apiRef.current.state); const gridRootElement = apiRef.current.rootElementRef.current; const gridClone = gridRootElement.cloneNode(true); const gridCloneViewport = gridClone.querySelector(`.${gridClasses.virtualScroller}`); // Expand the viewport window to prevent clipping gridCloneViewport.style.height = 'auto'; gridCloneViewport.style.width = 'auto'; gridCloneViewport.parentElement.style.width = 'auto'; gridCloneViewport.parentElement.style.height = 'auto'; // Allow to overflow to not hide the border of the last row const gridMain = gridClone.querySelector(`.${gridClasses.main}`); gridMain.style.overflow = 'visible'; const columnHeaders = gridClone.querySelector(`.${gridClasses.columnHeaders}`); const columnHeadersInner = columnHeaders.querySelector(`.${gridClasses.columnHeadersInner}`); columnHeadersInner.style.width = '100%'; let gridToolbarElementHeight = gridRootElement.querySelector(`.${gridClasses.toolbarContainer}`)?.clientHeight || 0; let gridFooterElementHeight = gridRootElement.querySelector(`.${gridClasses.footerContainer}`)?.clientHeight || 0; if (normalizeOptions.hideToolbar) { gridClone.querySelector(`.${gridClasses.toolbarContainer}`)?.remove(); gridToolbarElementHeight = 0; } if (normalizeOptions.hideFooter) { gridClone.querySelector(`.${gridClasses.footerContainer}`)?.remove(); gridFooterElementHeight = 0; } // Expand container height to accommodate all rows gridClone.style.height = `${rowsMeta.currentPageTotalHeight + totalHeaderHeight + gridToolbarElementHeight + gridFooterElementHeight}px`; // Remove all loaded elements from the current host printDoc.body.innerHTML = ''; printDoc.body.appendChild(gridClone); const defaultPageStyle = typeof normalizeOptions.pageStyle === 'function' ? normalizeOptions.pageStyle() : normalizeOptions.pageStyle; if (typeof defaultPageStyle === 'string') { // TODO custom styles should always win const styleElement = printDoc.createElement('style'); styleElement.appendChild(printDoc.createTextNode(defaultPageStyle)); printDoc.head.appendChild(styleElement); } if (normalizeOptions.bodyClassName) { printDoc.body.classList.add(...normalizeOptions.bodyClassName.split(' ')); } if (normalizeOptions.copyStyles) { const headStyleElements = doc.current.querySelectorAll("style, link[rel='stylesheet']"); for (let i = 0; i < headStyleElements.length; i += 1) { const node = headStyleElements[i]; if (node.tagName === 'STYLE') { const newHeadStyleElements = printDoc.createElement(node.tagName); const sheet = node.sheet; if (sheet) { let styleCSS = ''; // NOTE: for-of is not supported by IE for (let j = 0; j < sheet.cssRules.length; j += 1) { if (typeof sheet.cssRules[j].cssText === 'string') { styleCSS += `${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 const newHeadStyleElements = printDoc.createElement(node.tagName); for (let j = 0; j < node.attributes.length; j += 1) { const attr = node.attributes[j]; if (attr) { newHeadStyleElements.setAttribute(attr.nodeName, attr.nodeValue || ''); } } printDoc.head.appendChild(newHeadStyleElements); } } } // Trigger print if (process.env.NODE_ENV !== 'test') { printWindow.contentWindow.print(); } }, [apiRef, doc]); const handlePrintWindowAfterPrint = React.useCallback(printWindow => { // Remove the print iframe doc.current.body.removeChild(printWindow); // Revert grid to previous state apiRef.current.restoreState(previousGridState.current || {}); if (!previousGridState.current?.columns?.columnVisibilityModel) { // if the apiRef.current.exportState(); did not exported the column visibility, we update it apiRef.current.setColumnVisibilityModel(previousColumnVisibility.current); } apiRef.current.unstable_enableVirtualization(); // Clear local state previousGridState.current = null; previousColumnVisibility.current = {}; }, [apiRef]); const exportDataAsPrint = React.useCallback(async options => { logger.debug(`Export data as Print`); if (!apiRef.current.rootElementRef.current) { throw new Error('MUI: No grid root element available.'); } previousGridState.current = apiRef.current.exportState(); // It appends that the visibility model is not exported, especially if columnVisibility is not controlled previousColumnVisibility.current = gridColumnVisibilityModelSelector(apiRef); if (props.pagination) { const visibleRowCount = gridVisibleRowCountSelector(apiRef); apiRef.current.setPageSize(visibleRowCount); } await updateGridColumnsForPrint(options?.fields, options?.allColumns); apiRef.current.unstable_disableVirtualization(); const printWindow = buildPrintWindow(options?.fileName); doc.current.body.appendChild(printWindow); if (process.env.NODE_ENV === 'test') { // In test env, run the all pipeline without waiting for loading handlePrintWindowLoad(printWindow, options); handlePrintWindowAfterPrint(printWindow); } else { printWindow.onload = () => handlePrintWindowLoad(printWindow, options); printWindow.contentWindow.onafterprint = () => handlePrintWindowAfterPrint(printWindow); } }, [props, logger, apiRef, buildPrintWindow, handlePrintWindowLoad, handlePrintWindowAfterPrint, updateGridColumnsForPrint]); const printExportApi = { exportDataAsPrint }; useGridApiMethod(apiRef, printExportApi, 'GridPrintExportApi'); /** * PRE-PROCESSING */ const addExportMenuButtons = React.useCallback((initialValue, options) => { if (options.printOptions?.disableToolbarButton) { return initialValue; } return [...initialValue, { component: /*#__PURE__*/_jsx(GridPrintExportMenuItem, { options: options.printOptions }), componentName: 'printExport' }]; }, []); useGridRegisterPipeProcessor(apiRef, 'exportMenu', addExportMenuButtons); };