Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Refacot renderFrontend code to make it easier to understand
Browse files Browse the repository at this point in the history
  • Loading branch information
Aljullu committed Aug 5, 2020
1 parent 04ae943 commit a343fc0
Showing 1 changed file with 140 additions and 59 deletions.
199 changes: 140 additions & 59 deletions assets/js/base/utils/render-frontend.js
Expand Up @@ -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,
Expand All @@ -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(
<BlockErrorBoundary { ...errorBoundaryProps }>
<SuspenseComponent
fallback={ <div className="wc-block-placeholder" /> }
>
<Block { ...props } attributes={ attributes } />
</SuspenseComponent>
</BlockErrorBoundary>,
el
);
} );
};

render(
<BlockErrorBoundary { ...errorBoundaryProps }>
<SuspenseComponent
fallback={ <div className="wc-block-placeholder" /> }
>
<Block { ...props } attributes={ attributes } />
</SuspenseComponent>
</BlockErrorBoundary>,
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 } );
} );
} );
};
Expand Down

0 comments on commit a343fc0

Please sign in to comment.