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

Commit

Permalink
Add Mini Cart block in experimental builds (#4510)
Browse files Browse the repository at this point in the history
* Create MiniCart block prototype

* Use window.onload instead of DOMContentLoaded

* Don't load translations for scripts without localized strings

* Don't try to load cart instance in API requests

* Remove PHP pre-rendering

* Fix some typos

* Move Mini Cart block files under 'cart-checkout' directory

* Update selectors to follow guidelines

* Only enable the MiniCart block in experimental builds

* Fix wrong translations element selector

* Improve lazyLoadScript and preloadScript documentation

* Move lazy-load-script and preload-script to base-utils

* Add function to check if script tag is already in the DOM
  • Loading branch information
Aljullu authored Aug 25, 2021
1 parent e23b511 commit 341be1f
Show file tree
Hide file tree
Showing 13 changed files with 650 additions and 64 deletions.
82 changes: 82 additions & 0 deletions assets/js/base/utils/lazy-load-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
interface lazyLoadScriptParams {
handle: string;
src: string;
version?: string;
after?: string;
before?: string;
translations?: string;
}

/**
* In WP, registered scripts are loaded into the page with an element like this:
* `<script src='...' id='[SCRIPT_ID]'></script>`
* This function checks whether an element matching that selector exists.
* Useful to know if a script has already been appended to the page.
*/
const isScriptTagInDOM = ( scriptId: string ): boolean => {
const scriptElements = document.querySelectorAll( `script#${ scriptId }` );
return scriptElements.length > 0;
};

/**
* Appends a `<script>` tag to the document body based on the src and handle
* parameters. In addition, it appends additional script tags to load the code
* needed for translations and any before and after inline scripts. See these
* documentation pages for more information:
*
* https://developer.wordpress.org/reference/functions/wp_set_script_translations/
* https://developer.wordpress.org/reference/functions/wp_add_inline_script/
*/
const lazyLoadScript = ( {
handle,
src,
version,
after,
before,
translations,
}: lazyLoadScriptParams ): Promise< void > => {
return new Promise( ( resolve, reject ) => {
// Append script translations if they doesn't exist yet in the page.
if (
translations &&
! isScriptTagInDOM( `${ handle }-js-translations` )
) {
const handleTranslations = document.createElement( 'script' );
handleTranslations.innerHTML = translations;
handleTranslations.id = `${ handle }-js-translations`;
document.body.appendChild( handleTranslations );
}
// Append before inline script if it doesn't exist yet in the page.
if ( before && ! isScriptTagInDOM( `${ handle }-js-before` ) ) {
const handleBeforeScript = document.createElement( 'script' );
handleBeforeScript.innerHTML = before;
handleBeforeScript.id = `${ handle }-js-before`;
document.body.appendChild( handleBeforeScript );
}

if ( isScriptTagInDOM( `${ handle }-js` ) ) {
resolve();
} else {
// Append script.
const handleScript = document.createElement( 'script' );
handleScript.src = version ? `${ src }?${ version }` : src;
handleScript.id = `${ handle }-js`;
handleScript.onerror = reject;
handleScript.onload = () => {
// Append after inline script if it doesn't exist yet in the page.
if ( after && ! isScriptTagInDOM( `${ handle }-js-after` ) ) {
const handleAfterScript = document.createElement(
'script'
);
handleAfterScript.innerHTML = after;
handleAfterScript.id = `${ handle }-js-after`;
document.body.appendChild( handleAfterScript );
}
resolve();
};
document.body.appendChild( handleScript );
}
} );
};

export default lazyLoadScript;
30 changes: 30 additions & 0 deletions assets/js/base/utils/preload-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
interface preloadScriptParams {
handle: string;
src: string;
version?: string;
}

/**
* Appends a `<link>` tag to the document head to preload a script based on the
* src and handle parameters.
*/
const preloadScript = ( {
handle,
src,
version,
}: preloadScriptParams ): void => {
const handleScriptElements = document.querySelectorAll(
`#${ handle }-js, #${ handle }-js-prefetch`
);

if ( handleScriptElements.length === 0 ) {
const prefetchLink = document.createElement( 'link' );
prefetchLink.href = version ? `${ src }?${ version }` : src;
prefetchLink.rel = 'preload';
prefetchLink.as = 'script';
prefetchLink.id = `${ handle }-js-prefetch`;
document.head.appendChild( prefetchLink );
}
};

export default preloadScript;
6 changes: 3 additions & 3 deletions assets/js/base/utils/render-frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const isElementInsideWrappers = ( el, wrappers ) => {
const renderBlockInContainers = ( {
Block,
containers,
getProps = () => {},
getErrorBoundaryProps = () => {},
getProps = () => ( {} ),
getErrorBoundaryProps = () => ( {} ),
} ) => {
if ( containers.length === 0 ) {
return;
Expand All @@ -49,7 +49,7 @@ const renderBlockInContainers = ( {
const errorBoundaryProps = getErrorBoundaryProps( el, i );
const attributes = {
...el.dataset,
...props.attributes,
...( props.attributes || {} ),
};
el.classList.remove( 'is-loading' );

Expand Down
81 changes: 39 additions & 42 deletions assets/js/blocks/cart-checkout/cart/full-cart/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ table.wc-block-cart-items {
}

// Loading placeholder state.
.wc-block-cart--is-loading {
.wc-block-cart--is-loading,
.wc-block-mini-cart-items--is-loading {
th span,
h2 span {
@include placeholder();
Expand All @@ -97,47 +98,43 @@ table.wc-block-cart-items {
h2 span {
min-width: 33%;
}
.wc-block-cart-items {
.wc-block-cart-items__row {
.wc-block-cart-item__price,
.wc-block-cart-item__individual-price,
.wc-block-cart-item__product-metadata,
.wc-block-cart-item__image > *,
.wc-block-components-quantity-selector {
@include placeholder();
}
.wc-block-cart-item__product-name {
@include placeholder();
@include force-content();
min-width: 84px;
display: inline-block;
}
.wc-block-cart-item__product-metadata {
margin-top: 0.25em;
min-width: 8em;
}
.wc-block-cart-item__remove-link {
visibility: hidden;
}
.wc-block-cart-item__image a {
display: block;
}
.wc-block-cart-item__individual-price {
@include force-content();
max-width: 3em;
display: block;
margin-top: 0.25em;
}
.wc-block-cart-item__total {
> span,
> div {
display: none;
}
.wc-block-cart-item__price {
@include force-content();
display: block;
}
}
.wc-block-cart-item__price,
.wc-block-cart-item__individual-price,
.wc-block-cart-item__product-metadata,
.wc-block-cart-item__image > *,
.wc-block-components-quantity-selector {
@include placeholder();
}
.wc-block-cart-item__product-name {
@include placeholder();
@include force-content();
min-width: 84px;
display: inline-block;
}
.wc-block-cart-item__product-metadata {
margin-top: 0.25em;
min-width: 8em;
}
.wc-block-cart-item__remove-link {
visibility: hidden;
}
.wc-block-cart-item__image a {
display: block;
}
.wc-block-cart-item__individual-price {
@include force-content();
max-width: 3em;
display: block;
margin-top: 0.25em;
}
.wc-block-cart-item__total {
> span,
> div {
display: none;
}
.wc-block-cart-item__price {
@include force-content();
display: block;
}
}
.wc-block-cart__sidebar .components-card {
Expand Down
37 changes: 37 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { renderFrontend } from '@woocommerce/base-utils';
import { useStoreCart } from '@woocommerce/base-context/hooks';
import {
withStoreCartApiHydration,
withRestApiHydration,
} from '@woocommerce/block-hocs';

/**
* Internal dependencies
*/
import CartLineItemsTable from '../cart/full-cart/cart-line-items-table';

const MiniCartContents = () => {
const { cartItems, cartIsLoading } = useStoreCart();

if ( cartItems.length === 0 ) {
return <>{ __( 'Cart is empty', 'woo-gutenberg-products-block' ) }</>;
}

return (
<CartLineItemsTable
lineItems={ cartItems }
isLoading={ cartIsLoading }
/>
);
};

renderFrontend( {
selector: '.wc-block-mini-cart__contents',
Block: withStoreCartApiHydration(
withRestApiHydration( MiniCartContents )
),
} );
33 changes: 33 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { _n, sprintf } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import type { ReactElement } from 'react';

const MiniCartBlock = (): ReactElement => {
const blockProps = useBlockProps( {
className: 'wc-block-mini-cart',
} );

const productCount = 0;

return (
<div { ...blockProps }>
<button className="wc-block-mini-cart__button">
{ sprintf(
/* translators: %d is the number of products in the cart. */
_n(
'%d product',
'%d products',
productCount,
'woo-gutenberg-products-block'
),
productCount
) }
</button>
</div>
);
};

export default MiniCartBlock;
75 changes: 75 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/frontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* External dependencies
*/
import { getSetting } from '@woocommerce/settings';
import preloadScript from '@woocommerce/base-utils/preload-script';
import lazyLoadScript from '@woocommerce/base-utils/lazy-load-script';

interface dependencyData {
src: string;
version?: string;
after?: string;
before?: string;
translations?: string;
}

// eslint-disable-next-line @wordpress/no-global-event-listener
window.onload = () => {
const miniCartBlocks = document.querySelectorAll( '.wc-block-mini-cart' );

if ( miniCartBlocks.length === 0 ) {
return;
}

const dependencies = getSetting(
'mini_cart_block_frontend_dependencies',
{}
) as Record< string, dependencyData >;

// Preload scripts
for ( const dependencyHandle in dependencies ) {
const dependency = dependencies[ dependencyHandle ];
preloadScript( {
handle: dependencyHandle,
...dependency,
} );
}

miniCartBlocks.forEach( ( miniCartBlock ) => {
const miniCartButton = miniCartBlock.querySelector(
'.wc-block-mini-cart__button'
);
const miniCartContents = miniCartBlock.querySelector(
'.wc-block-mini-cart__contents'
);

if ( ! miniCartButton || ! miniCartContents ) {
// Markup is not correct, abort.
return;
}

const showContents = async () => {
miniCartContents.removeAttribute( 'hidden' );

// Load scripts
for ( const dependencyHandle in dependencies ) {
const dependency = dependencies[ dependencyHandle ];
await lazyLoadScript( {
handle: dependencyHandle,
...dependency,
} );
}
};
const hideContents = () =>
miniCartContents.setAttribute( 'hidden', 'true' );

miniCartButton.addEventListener( 'mouseover', showContents );
miniCartButton.addEventListener( 'mouseleave', hideContents );

miniCartContents.addEventListener( 'mouseover', showContents );
miniCartContents.addEventListener( 'mouseleave', hideContents );

miniCartButton.addEventListener( 'focus', showContents );
miniCartButton.addEventListener( 'blur', hideContents );
} );
};
Loading

0 comments on commit 341be1f

Please sign in to comment.