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

Commit

Permalink
Make Attribute Filter block compatible with PHP rendered Classic Temp…
Browse files Browse the repository at this point in the history
…late block (#6204)

* Enable Attribute Filter block to work with the PHP rendered Classic Template block

* Check for presence of option before rendering it

* improve filter product by attribute

* fix pagination problem

* fix check when two filter block with same attribute are used

Co-authored-by: Luigi <[email protected]>
  • Loading branch information
tjcafferkey and gigitux authored Apr 14, 2022
1 parent 4fe44b9 commit 520997b
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 17 deletions.
6 changes: 6 additions & 0 deletions assets/js/base/components/dropdown-selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,16 @@ const DropdownSelector = ( {
const option = options.find(
( o ) => o.value === value
);

if ( ! option ) {
return null;
}

const onRemoveItem = ( val ) => {
onChange( val );
inputRef.current.focus();
};

return multiple ? (
<DropdownSelectorSelectedChip
key={ value }
Expand Down
119 changes: 118 additions & 1 deletion assets/js/blocks/attribute-filter/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
import { decodeEntities } from '@wordpress/html-entities';
import { Notice } from '@wordpress/components';
import classNames from 'classnames';
import { getSettingWithCoercion } from '@woocommerce/settings';
import { getQueryArgs, removeQueryArgs } from '@wordpress/url';
import { isBoolean, isString } from '@woocommerce/types';

/**
* Internal dependencies
Expand All @@ -28,6 +31,21 @@ import { updateAttributeFilter } from '../../utils/attributes-query';
import { previewAttributeObject, previewOptions } from './preview';
import { useBorderProps } from '../../hooks/style-attributes';
import './style.scss';
import {
formatParams,
getActiveFilters,
areAllFiltersRemoved,
isQueryArgsEqual,
} from './utils';

/**
* Formats filter values into a string for the URL parameters needed for filtering PHP templates.
*
* @param {string} url Current page URL.
* @param {Array} params Parameters and their constraints.
*
* @return {string} New URL with query parameters in it.
*/

/**
* Component displaying an attribute filter.
Expand All @@ -40,12 +58,31 @@ const AttributeFilterBlock = ( {
attributes: blockAttributes,
isEditor = false,
} ) => {
const filteringForPhpTemplate = getSettingWithCoercion(
'is_rendering_php_template',
false,
isBoolean
);

const pageUrl = getSettingWithCoercion(
'page_url',
window.location.href,
isString
);

const [ hasSetPhpFilterDefaults, setHasSetPhpFilterDefaults ] = useState(
false
);

const attributeObject =
blockAttributes.isPreview && ! blockAttributes.attributeId
? previewAttributeObject
: getAttributeFromID( blockAttributes.attributeId );

const [ checked, setChecked ] = useState( [] );
const [ checked, setChecked ] = useState(
getActiveFilters( filteringForPhpTemplate, attributeObject )
);

const [ displayedOptions, setDisplayedOptions ] = useState(
blockAttributes.isPreview && ! blockAttributes.attributeId
? previewOptions
Expand Down Expand Up @@ -83,6 +120,10 @@ const AttributeFilterBlock = ( {
},
queryState: {
...queryState,
// The PHP template renders only the products with the visibility set to catalog
...( filteringForPhpTemplate && {
catalog_visibility: 'catalog',
} ),
attributes: filterAvailableTerms ? queryState.attributes : null,
},
} );
Expand Down Expand Up @@ -332,6 +373,82 @@ const AttributeFilterBlock = ( {
]
);

/**
* Important: For PHP rendered block templates only.
*
* When we render the PHP block template (e.g. Classic Block) we need to set the default checked values,
* and also update the URL when the filters are clicked/updated.
*/
useEffect( () => {
if ( filteringForPhpTemplate && attributeObject ) {
if (
areAllFiltersRemoved( {
currentCheckedFilters: checked,
hasSetPhpFilterDefaults,
} )
) {
setChecked( [] );
const currentQueryArgKeys = Object.keys(
getQueryArgs( window.location.href )
);

const url = currentQueryArgKeys.reduce(
( currentUrl, queryArg ) =>
removeQueryArgs( currentUrl, queryArg ),
window.location.href
);

const newUrl = formatParams( url, productAttributesQuery );
window.location.href = newUrl;
}

setChecked( checked );
const newUrl = formatParams( pageUrl, productAttributesQuery );
const currentQueryArgs = getQueryArgs( window.location.href );
const newUrlQueryArgs = getQueryArgs( newUrl );

if ( ! isQueryArgsEqual( currentQueryArgs, newUrlQueryArgs ) ) {
window.location.href = newUrl;
}
}
}, [
filteringForPhpTemplate,
productAttributesQuery,
attributeObject,
checked,
blockAttributes.queryType,
pageUrl,
hasSetPhpFilterDefaults,
] );

/**
* Important: For PHP rendered block templates only.
*
* When we set the default parameter values which we get from the URL in the above useEffect(),
* we need to run onSubmit which will set these values in state for the Active Filters block.
*/
useEffect( () => {
if ( filteringForPhpTemplate ) {
if (
checked.length > 0 &&
! hasSetPhpFilterDefaults &&
! attributeTermsLoading
) {
setHasSetPhpFilterDefaults( true );
if ( ! blockAttributes.showFilterButton ) {
onSubmit( checked );
}
}
}
}, [
onSubmit,
filteringForPhpTemplate,
checked,
hasSetPhpFilterDefaults,
attributeTermsLoading,
blockAttributes.showFilterButton,
] );

// Short-circuit if no attribute is selected.
if ( ! attributeObject ) {
if ( isEditor ) {
Expand Down
91 changes: 91 additions & 0 deletions assets/js/blocks/attribute-filter/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* External dependencies
*/
import { addQueryArgs, removeQueryArgs } from '@wordpress/url';
import { QueryArgs } from '@wordpress/url/build-types/get-query-args';

/**
* Internal dependencies
*/
import { getUrlParameter } from '../../utils/filters';

interface Param {
attribute: string;
operator: string;
slug: Array< string >;
}

export const formatParams = ( url: string, params: Array< Param > = [] ) => {
const paramObject: Record< string, string > = {};

params.forEach( ( param ) => {
const { attribute, slug, operator } = param;

// Custom filters are prefix with `pa_` so we need to remove this.
const name = attribute.replace( 'pa_', '' );
const values = slug.join( ',' );
const queryType = `query_type_${ name }`;
const type = operator === 'in' ? 'or' : 'and';

// The URL parameter requires the prefix filter_ with the attribute name.
paramObject[ `filter_${ name }` ] = values;
paramObject[ queryType ] = type;
} );

// Clean the URL before we add our new query parameters to it.
const cleanUrl = removeQueryArgs( url, ...Object.keys( paramObject ) );

return addQueryArgs( cleanUrl, paramObject );
};

export const areAllFiltersRemoved = ( {
currentCheckedFilters,
hasSetPhpFilterDefaults,
}: {
currentCheckedFilters: Array< string >;
hasSetPhpFilterDefaults: boolean;
} ) => hasSetPhpFilterDefaults && currentCheckedFilters.length === 0;

export const getActiveFilters = (
isFilteringForPhpTemplateEnabled: boolean,
attributeObject: Record< string, string > | undefined
) => {
if ( isFilteringForPhpTemplateEnabled && attributeObject ) {
const defaultAttributeParam = getUrlParameter(
`filter_${ attributeObject.name }`
);
const defaultCheckedValue =
typeof defaultAttributeParam === 'string'
? defaultAttributeParam.split( ',' )
: [];

return defaultCheckedValue;
}

return [];
};

export const isQueryArgsEqual = (
currentQueryArgs: QueryArgs,
newQueryArgs: QueryArgs
) => {
// The user can add same two filter blocks for the same attribute.
// We removed the query type from the check to avoid refresh loop.
const filteredNewQueryArgs = Object.entries( newQueryArgs ).reduce(
( acc, [ key, value ] ) => {
return key.includes( 'query_type' )
? acc
: {
...acc,
[ key ]: value,
};
},
{}
);

return Object.entries( filteredNewQueryArgs ).reduce(
( isEqual, [ key, value ] ) =>
currentQueryArgs[ key ] === value ? isEqual : false,
true
);
};
20 changes: 4 additions & 16 deletions assets/js/blocks/price-filter/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,15 @@ import { useDebouncedCallback } from 'use-debounce';
import PropTypes from 'prop-types';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
import { getSetting } from '@woocommerce/settings';
import { getQueryArg, addQueryArgs, removeQueryArgs } from '@wordpress/url';
import { addQueryArgs, removeQueryArgs } from '@wordpress/url';

/**
* Internal dependencies
*/
import usePriceConstraints from './use-price-constraints.js';
import { getUrlParameter } from '../../utils/filters';
import './style.scss';

/**
* Returns specified parameter from URL
*
* @param {string} paramName Parameter you want the value of.
*/
function findGetParameter( paramName ) {
if ( ! window ) {
return null;
}
return getQueryArg( window.location.href, paramName );
}

/**
* Formats filter values into a string for the URL parameters needed for filtering PHP templates.
*
Expand Down Expand Up @@ -71,6 +60,8 @@ const PriceFilterBlock = ( { attributes, isEditor = false } ) => {
''
);

const minPriceParam = getUrlParameter( 'min_price' );
const maxPriceParam = getUrlParameter( 'max_price' );
const [ queryState ] = useQueryStateByContext();
const { results, isLoading } = useCollectionData( {
queryPrices: true,
Expand All @@ -79,9 +70,6 @@ const PriceFilterBlock = ( { attributes, isEditor = false } ) => {

const currency = getCurrencyFromPriceResponse( results.price_range );

const minPriceParam = findGetParameter( 'min_price' );
const maxPriceParam = findGetParameter( 'max_price' );

const [ minPriceQuery, setMinPriceQuery ] = useQueryStateByKey(
'min_price',
Number( minPriceParam ) * 10 ** currency.minorUnit || null
Expand Down
16 changes: 16 additions & 0 deletions assets/js/utils/filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* External dependencies
*/
import { getQueryArg } from '@wordpress/url';

/**
* Returns specified parameter from URL
*
* @param {string} name Parameter you want the value of.
*/
export function getUrlParameter( name: string ) {
if ( ! window ) {
return null;
}
return getQueryArg( window.location.href, name );
}
7 changes: 7 additions & 0 deletions src/BlockTypes/ClassicTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ protected function render( $attributes, $content ) {
true,
null
);

$this->asset_data_registry->add(
'page_url',
html_entity_decode( get_pagenum_link() ),
''
);

return $this->render_archive_product();
} else {
ob_start();
Expand Down

0 comments on commit 520997b

Please sign in to comment.