From a343fc08c6d02ac9476ba6131eb8f8a3e03fb7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 5 Aug 2020 15:17:08 +0200 Subject: [PATCH] Refacot renderFrontend code to make it easier to understand --- assets/js/base/utils/render-frontend.js | 199 +++++++++++++++++------- 1 file changed, 140 insertions(+), 59 deletions(-) diff --git a/assets/js/base/utils/render-frontend.js b/assets/js/base/utils/render-frontend.js index 5e5edc721c3..ec83d91e43f 100644 --- a/assets/js/base/utils/render-frontend.js +++ b/assets/js/base/utils/render-frontend.js @@ -4,14 +4,14 @@ import { render, Suspense } from '@wordpress/element'; import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; -// Some blocks take care of rendering their inner blocks automatically. For example, -// the empty cart. In those cases, we don't want to trigger the render function of -// inner components on load. Instead, the wrapper block can trigger the event -// `wc-blocks_render_blocks_frontend` to render its inner blocks. +// Some blocks take care of rendering their inner blocks automatically. For +// example, the empty cart. In those cases, we don't want to trigger the render +// function of inner components on load. Instead, the wrapper block can trigger +// the event `wc-blocks_render_blocks_frontend` to render its inner blocks. const selectorsToSkipOnLoad = [ '.wp-block-woocommerce-cart' ]; -// Given an element and a list of wrappers, check if the element is inside at least -// one of the wrappers. +// Given an element and a list of wrappers, check if the element is inside at +// least one of the wrappers. const isElementInsideWrappers = ( el, wrappers ) => { return Array.prototype.some.call( wrappers, @@ -20,84 +20,165 @@ const isElementInsideWrappers = ( el, wrappers ) => { }; /** - * Renders a block component in the place of a specified set of selectors. + * Renders a block component in each `containers` node. * * @param {Object} props Render props. - * @param {Function} props.Block React component to use as a replacement. - * @param {string} props.selector CSS selector to match the elements to replace. - * @param {Function} [props.getProps] Function to generate the props object for the block. - * @param {Function} [props.getErrorBoundaryProps] Function to generate the props object for the error boundary. - * @param {Element} [props.wrapper] Element to query the selector inside. Defaults to the document body. - * @param {Element[]} [props.wrappersToSkip] Don't render inner blocks of this parent. + * @param {Function} props.Block React component to use as a + * replacement. + * @param {NodeList} props.containers Containers to replace with + * the Block component. + * @param {Function} [props.getProps] Function to generate the + * props object for the block. + * @param {Function} [props.getErrorBoundaryProps] Function to generate the + * props object for the error + * boundary. */ -const renderBlockFrontend = ( { +const renderBlockInContainers = ( { Block, - selector, + containers, getProps = () => {}, getErrorBoundaryProps = () => {}, - wrapper = document.body, - wrappersToSkip = [], } ) => { - const containers = wrapper.querySelectorAll( selector ); + if ( containers.length === 0 ) { + return; + } - if ( containers.length ) { - // @todo Remove Suspense compatibility fix once WP 5.2 is no longer supported. - // If Suspense is not available (WP 5.2), use a noop component instead. - const noopComponent = ( { children } ) => { - return <>{ children }; - }; - const SuspenseComponent = Suspense || noopComponent; + // @todo Remove Suspense compatibility fix once WP 5.2 is no longer supported. + // If Suspense is not available (WP 5.2), use a noop component instead. + const noopComponent = ( { children } ) => { + return <>{ children }; + }; + const SuspenseComponent = Suspense || noopComponent; - // Use Array.forEach for IE11 compatibility. - Array.prototype.forEach.call( containers, ( el, i ) => { - if ( - wrappersToSkip.length > 0 && - isElementInsideWrappers( el, wrappersToSkip ) - ) { - return; - } + // Use Array.forEach for IE11 compatibility. + Array.prototype.forEach.call( containers, ( el, i ) => { + const props = getProps( el, i ); + const errorBoundaryProps = getErrorBoundaryProps( el, i ); + const attributes = { + ...el.dataset, + ...props.attributes, + }; + el.classList.remove( 'is-loading' ); - const props = getProps( el, i ); - const errorBoundaryProps = getErrorBoundaryProps( el, i ); - const attributes = { - ...el.dataset, - ...props.attributes, - }; - el.classList.remove( 'is-loading' ); + render( + + } + > + + + , + el + ); + } ); +}; - render( - - } - > - - - , - el - ); +/** + * Renders the block frontend in the elements matched by the selector which are + * outside the wrapper elements. + * + * @param {Object} props Render props. + * @param {Function} props.Block React component to use as a + * replacement. + * @param {string} props.selector CSS selector to match the + * elements to replace. + * @param {Function} [props.getProps] Function to generate the + * props object for the block. + * @param {Function} [props.getErrorBoundaryProps] Function to generate the + * props object for the error + * boundary. + * @param {NodeList} props.wrappers All elements matched by the + * selector which are inside + * the wrapper will be ignored. + */ +const renderBlockOutsideWrappers = ( { + Block, + getProps, + getErrorBoundaryProps, + selector, + wrappers, +} ) => { + const containers = document.body.querySelectorAll( selector ); + // Filter out blocks inside the wrappers. + if ( wrappers.length > 0 ) { + Array.prototype.filter.call( containers, ( el ) => { + return ! isElementInsideWrappers( el, wrappers ); } ); } + renderBlockInContainers( { + Block, + containers, + getProps, + getErrorBoundaryProps, + } ); }; /** - * Adds the event listeners necessary to render the block frontend. + * Renders the block frontend in the elements matched by the selector inside the + * wrapper element. * - * @param {Object} props Render props. + * @param {Object} props Render props. + * @param {Function} props.Block React component to use as a + * replacement. + * @param {string} props.selector CSS selector to match the + * elements to replace. + * @param {Function} [props.getProps] Function to generate the + * props object for the block. + * @param {Function} [props.getErrorBoundaryProps] Function to generate the + * props object for the error + * boundary. + * @param {Element} props.wrapper Wrapper element to query the + * selector inside. + */ +const renderBlockInsideWrapper = ( { + Block, + getProps, + getErrorBoundaryProps, + selector, + wrapper, +} ) => { + const containers = wrapper.querySelectorAll( selector ); + renderBlockInContainers( { + Block, + containers, + getProps, + getErrorBoundaryProps, + } ); +}; + +/** + * Renders the block frontend on page load. If the block is contained inside a + * wrapper element that should be excluded from initial load, it adds the + * appropriate event listeners to render the block when the + * `blocks_render_blocks_frontend` event is triggered. + * + * @param {Object} props Render props. + * @param {Function} props.Block React component to use as a + * replacement. + * @param {string} props.selector CSS selector to match the + * elements to replace. + * @param {Function} [props.getProps] Function to generate the + * props object for the block. + * @param {Function} [props.getErrorBoundaryProps] Function to generate the + * props object for the error + * boundary. */ export const renderFrontend = ( props ) => { + // Some blocks might want to handle rendering inner blocks themselves. For + // example, the empty Cart template should render inner blocks when it + // becomes visible. const wrappersToSkipOnLoad = document.body.querySelectorAll( selectorsToSkipOnLoad.join( ',' ) ); - // Render on page load. - renderBlockFrontend( { + renderBlockOutsideWrappers( { ...props, - wrappersToSkip: wrappersToSkipOnLoad, + wrappers: wrappersToSkipOnLoad, } ); - // Render wrappers inner blocks when the event `wc-blocks_render_blocks_frontend` - // is triggered. + // For each wrapper, add an event listener to render the inner blocks when + // `wc-blocks_render_blocks_frontend` event is triggered. Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => { - wrapper.addEventListener( 'wc-blocks_render_blocks_frontend', ( e ) => { - renderBlockFrontend( { ...props, wrapper: e.target } ); + wrapper.addEventListener( 'wc-blocks_render_blocks_frontend', () => { + renderBlockInsideWrapper( { ...props, wrapper } ); } ); } ); };