diff --git a/code_samples/search/faceted_search/assets/styles/app.scss b/code_samples/search/faceted_search/assets/styles/app.scss new file mode 100644 index 0000000000..f003ec2ceb --- /dev/null +++ b/code_samples/search/faceted_search/assets/styles/app.scss @@ -0,0 +1,112 @@ +.ibexa-blog-posts-list { + display: flex; + align-items: flex-start; + + &__aside { + flex-basis: 300px; + margin-right: 20px; + background-color: #f9f9fa; + padding: 24px; + border-radius: 8px; + } + + &__content { + flex-basis: calc(100% - 320px); + } +} + +.ibexa-blog-post { + &__body { + display: flex; + align-items: flex-start; + margin-top: 30px; + } + + .ezimage-field { + margin: 0 !important; + } + + &__body { + padding: 30px; + margin: 0 30px; + } + + &__aside { + background-color: #f9f9fa; + border-radius: 8px; + padding: 24px; + margin-left: 30px; + min-width: 300px; + width: 100%; + } + + &__header { + margin: 0 60px; + } + + &__image { + img { + border: 1px solid #ccc; + border-radius: 20px; + width: 100%; + } + } + + .ibexa-blog-post-meta { + margin: 20px 0; + + &__label { + font-weight: bold; + display: block; + margin-bottom: 5px; + } + } + + &__categories { + margin: 5px 0; + } + + &__category { + background: #eee; + border: 1px solid #ccc; + border-radius: 3px; + display: inline-block; + font-size: 0.9em; + font-weight: normal; + padding: 3px; + text-decoration: none; + } +} + +.ibexa-blog-post-line { + display: flex; + align-items: flex-start; + + &__image { + flex-basis: 200px; + + .ezimage-field { + margin: 10px 10px 10px 0; + padding: 0; + } + + img { + border: 1px solid #ccc; + } + } + + &__categories { + margin: 5px 0; + } + + &__category { + background: #dddddd; + border: 1px solid #666; + border-radius: 3px; + display: inline-block; + font-size: 0.8em; + font-weight: normal; + padding: 3px; + text-decoration: none; + } +} \ No newline at end of file diff --git a/code_samples/search/faceted_search/config/packages/ibexa.yaml b/code_samples/search/faceted_search/config/packages/ibexa.yaml new file mode 100644 index 0000000000..5947f2006e --- /dev/null +++ b/code_samples/search/faceted_search/config/packages/ibexa.yaml @@ -0,0 +1,6 @@ + site: + image_variations: + cover: + reference: original + filters: + geometry/scalewidth: [ 1024 ] \ No newline at end of file diff --git a/code_samples/search/faceted_search/config/packages/ibexa_views.yaml b/code_samples/search/faceted_search/config/packages/ibexa_views.yaml new file mode 100644 index 0000000000..b062143673 --- /dev/null +++ b/code_samples/search/faceted_search/config/packages/ibexa_views.yaml @@ -0,0 +1,24 @@ +ibexa: + system: + site: + content_view: + full: + blog: + match: + Identifier\ContentType: ['blog'] + controller: 'App\Controller\BlogController' + template: '@ibexadesign/full/blog.html.twig' + blog_post: + match: + Identifier\ContentType: ['blog_post'] + template: '@ibexadesign/full/blog_post.html.twig' + category: + match: + Identifier\ContentType: ['tag'] + controller: 'App\Controller\BlogController' + template: '@ibexadesign/full/blog.html.twig' + line: + blog_post: + match: + Identifier\ContentType: ['blog_post'] + template: '@ibexadesign/line/blog_post.html.twig' \ No newline at end of file diff --git a/code_samples/search/faceted_search/config/services.yaml b/code_samples/search/faceted_search/config/services.yaml new file mode 100644 index 0000000000..a015c41bb2 --- /dev/null +++ b/code_samples/search/faceted_search/config/services.yaml @@ -0,0 +1,13 @@ + App\Menu\Builder\CategoriesMenuBuilder: + public: true + tags: + - name: knp_menu.menu_builder + method: build + alias: app.menu.categories_facets + + App\Menu\Builder\PublicationDateMenuBuilder: + public: true + tags: + - name: knp_menu.menu_builder + method: build + alias: app.menu.publication_date_facets \ No newline at end of file diff --git a/code_samples/search/faceted_search/src/Controller/BlogController.php b/code_samples/search/faceted_search/src/Controller/BlogController.php new file mode 100644 index 0000000000..bfc2ad1ea8 --- /dev/null +++ b/code_samples/search/faceted_search/src/Controller/BlogController.php @@ -0,0 +1,55 @@ +queryType->getQuery($this->getQueryParams($request, $view)); + + $posts = new Pagerfanta(new LocationSearchAdapter($query, $this->searchService)); + $posts->setCurrentPage($request->query->getInt('page')); + $posts->setMaxPerPage(10); + + $view->addParameters([ + 'blog_posts' => $posts, + ]); + + return $view; + } + + /** + * Creates query parameters from current request. + */ + private function getQueryParams(Request $request, ContentView $view): array + { + $parameters = []; + + $content = $view->getContent(); + if ($content->getContentType()->identifier === 'tag') { + $parameters['category'] = $this->taxonomyService->loadEntryByContentId($content->id); + } + + return $parameters; + } +} diff --git a/code_samples/search/faceted_search/src/Menu/Builder/AbstractFacetsMenuBuilder.php b/code_samples/search/faceted_search/src/Menu/Builder/AbstractFacetsMenuBuilder.php new file mode 100644 index 0000000000..0f747fe266 --- /dev/null +++ b/code_samples/search/faceted_search/src/Menu/Builder/AbstractFacetsMenuBuilder.php @@ -0,0 +1,59 @@ + + */ +abstract class AbstractFacetsMenuBuilder +{ + public function __construct( + protected readonly FactoryInterface $itemFactory, + protected readonly RequestStack $requestStack + ) { + } + + /** + * @param array $options + */ + final public function build(array $options): ItemInterface + { + $menu = $this->itemFactory->createItem('root'); + $this->doBuild($menu, $this->resolveOptions($options)); + + return $menu; + } + + /** + * @param TOptions $options + */ + abstract protected function doBuild(ItemInterface $menu, array $options): void; + + protected function configureOptions(OptionsResolver $resolver): void + { + // No options + } + + /** + * @param array $options + * + * @return TOptions + */ + private function resolveOptions(array $options): array + { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + + /** @var TOptions */ + return $resolver->resolve($options); + } +} diff --git a/code_samples/search/faceted_search/src/Menu/Builder/CategoriesMenuBuilder.php b/code_samples/search/faceted_search/src/Menu/Builder/CategoriesMenuBuilder.php new file mode 100644 index 0000000000..ecb0aeeb7b --- /dev/null +++ b/code_samples/search/faceted_search/src/Menu/Builder/CategoriesMenuBuilder.php @@ -0,0 +1,90 @@ +taxonomyService = $taxonomyService; + } + + protected function doBuild(ItemInterface $menu, array $options): void + { + $this->createTaxonomyEntryWithChildren( + $menu, + $this->taxonomyService->loadRootEntry('tags'), + $options, + ); + } + + protected function configureOptions(OptionsResolver $resolver): void + { + $resolver->define('aggregation_result')->required()->allowedTypes(TermAggregationResult::class); + } + + private function createTaxonomyEntryWithChildren( + ItemInterface $menu, + TaxonomyEntry $parent, + array $options, + int $depth = 3, + ): void { + $children = $this->taxonomyService->loadEntryChildren($parent); + + foreach ($children as $child) { + $count = $this->getCount($options['aggregation_result'], $child); + if ($count > 1) { + $item = $this->createMenuItem($child, $count, $options); + if ($depth > 1) { + $this->createTaxonomyEntryWithChildren($item, $child, $options, $depth - 1); + } + + $menu->addChild($item); + } + } + } + + private function createMenuItem(TaxonomyEntry $entry, int $count): ItemInterface + { + return $this->itemFactory->createItem( + 'taxonomy_entry_' . $entry->id, + [ + 'label' => sprintf('%s (%d)', $entry->getName(), $count), + 'route' => 'ibexa.url.alias', + 'routeParameters' => [ + 'contentId' => $entry->getContent()->id, + ] + $this->requestStack->getCurrentRequest()->query->all(), + ] + ); + } + + private function getCount(?TermAggregationResult $aggregations, TaxonomyEntry $needle): int + { + if ($aggregations !== null) { + foreach ($aggregations as $entry => $count) { + if ($entry->getIdentifier() === $needle->getIdentifier()) { + return $count; + } + } + } + + return 0; + } +} diff --git a/code_samples/search/faceted_search/src/Menu/Builder/PublicationDateMenuBuilder.php b/code_samples/search/faceted_search/src/Menu/Builder/PublicationDateMenuBuilder.php new file mode 100644 index 0000000000..18d44e0b3a --- /dev/null +++ b/code_samples/search/faceted_search/src/Menu/Builder/PublicationDateMenuBuilder.php @@ -0,0 +1,62 @@ + $count) { + if ($count > 0) { + $menu->addChild($this->createMenuItem($range, $count, $options['route'])); + } + } + } + + protected function configureOptions(OptionsResolver $resolver): void + { + $resolver->define('aggregation_result')->required()->allowedTypes(RangeAggregationResult::class); + $resolver->define('route')->required()->allowedTypes(RouteReference::class); + } + + private function createMenuItem(Range $range, int $count, RouteReference $route): ItemInterface + { + $parameters = $route->getParams(); + $parameters['start'] = $range->getFrom()?->getTimestamp(); + $parameters['end'] = $range->getTo()?->getTimestamp(); + + return $this->itemFactory->createItem( + 'range_' . $range, + [ + 'label' => $this->getLabel($range, $count), + 'route' => $route->getRoute(), + 'routeParameters' => $parameters, + ] + ); + } + + private function getLabel(Range $range, int $count): string + { + $date = $range->getFrom(); + + return sprintf( + '%s (%d)', + $date !== null ? $date->format('F Y') : 'Older then 12 months', + $count + ); + } +} diff --git a/code_samples/search/faceted_search/src/QueryType/BlogPostsQueryType.php b/code_samples/search/faceted_search/src/QueryType/BlogPostsQueryType.php new file mode 100644 index 0000000000..8e58a01722 --- /dev/null +++ b/code_samples/search/faceted_search/src/QueryType/BlogPostsQueryType.php @@ -0,0 +1,48 @@ +define('category')->default(null)->allowedTypes(TaxonomyEntry::class, 'null'); + } + + protected function doGetQuery(array $parameters): LocationQuery + { + $query = new LocationQuery(); + $query->filter = $this->createFilters($parameters); + $query->sortClauses[] = new DatePublished(Query::SORT_DESC); + + return $query; + } + + public static function getName(): string + { + return 'BlogPosts'; + } + + private function createFilters(array $parameters): Criterion + { + $filters = []; + $filters[] = new ContentTypeIdentifier('blog_post'); + + if ($parameters['category'] !== null) { + $filters[] = new TaxonomyEntryId($parameters['category']->id); + } + + return count($filters) > 1 ? new LogicalAnd($filters) : $filters[0]; + } +} diff --git a/code_samples/search/faceted_search/templates/themes/storefront/facets_menu.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/facets_menu.html.twig new file mode 100644 index 0000000000..72aa96487f --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/facets_menu.html.twig @@ -0,0 +1,12 @@ +{% set menu = knp_menu_get(menu, [], menu_params) %} + +{% if menu.hasChildren() %} +
+

+ {{ name }} +

+
+ {{ knp_menu_render(menu) }} +
+
+{% endif %} \ No newline at end of file diff --git a/code_samples/search/faceted_search/templates/themes/storefront/full/blog.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/full/blog.html.twig new file mode 100644 index 0000000000..d8e768f26a --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/full/blog.html.twig @@ -0,0 +1,30 @@ +{% extends '@ibexadesign/layout.html.twig' %} + +{% block content %} +
+
+

+ {{ ibexa_content_name(content) }} +

+ +
+ {% for blog_post in blog_posts %} + {{ ibexa_render(blog_post, { viewType: 'line' }) }} + {% endfor %} +
+ +
+ {{ pagerfanta(blog_posts, 'default', { + 'routeName': 'ibexa.url.alias', + 'routeParams': { location }, + 'prev_message': '<', + 'next_message': '>', + }) }} +
+
+ + +
+{% endblock %} \ No newline at end of file diff --git a/code_samples/search/faceted_search/templates/themes/storefront/full/blog_post.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/full/blog_post.html.twig new file mode 100644 index 0000000000..ccb0e93725 --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/full/blog_post.html.twig @@ -0,0 +1,60 @@ +{% extends '@ibexadesign/layout.html.twig' %} + +{% block content %} +
+
+
+ {% set categories = ibexa_field_value(content, 'categories') %} + {% for category in categories.getTaxonomyEntries() %} + + {% endfor %} +
+ +

{{ ibexa_content_name(content) }}

+
+ +
+ {{ ibexa_render_field(content, 'image', { parameters: { alias: 'cover' }}) }} +
+ +
+
+ {{ ibexa_render_field(content, 'content') }} +
+ + +
+
+{% endblock %} \ No newline at end of file diff --git a/code_samples/search/faceted_search/templates/themes/storefront/full/blog_sidebar.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/full/blog_sidebar.html.twig new file mode 100644 index 0000000000..ded26e75c5 --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/full/blog_sidebar.html.twig @@ -0,0 +1,37 @@ +{% if aggregations.has('content_by_publication_date') %} + {% include '@ibexadesign/facets_menu.html.twig' with { + name: 'Publication Date', + menu: 'app.menu.publication_date_facets', + menu_params: { + route: ibexa_route(null, app.request.query.all()), + aggregation_result: aggregations.get('content_by_publication_date'), + } + } %} +{% endif %} + +{% if aggregations.has('content_by_tag') %} + {% include '@ibexadesign/facets_menu.html.twig' with { + name: 'Categories', + menu: 'app.menu.categories_facets', + menu_params: { + aggregation_result: aggregations.get('content_by_tag') + } + } %} +{% endif %} + +{% if aggregations.has('rate') %} +
+

Rate

+ +
+ {% set min = aggregations.get('rate').getMin() %} + {% set max = aggregations.get('rate').getMax() %} + +
+ {{ min|number_format(2) }} + + {{ max|number_format(2) }} +
+
+
+{% endif %} diff --git a/code_samples/search/faceted_search/templates/themes/storefront/layout.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/layout.html.twig new file mode 100644 index 0000000000..637401b687 --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/layout.html.twig @@ -0,0 +1,11 @@ +{% extends '@ibexadesign/storefront/layout.html.twig' %} + +{% block stylesheets %} + {{ parent() }} + {{ encore_entry_link_tags('app') }} +{% endblock %} + +{% block javascripts %} + {{ parent() }} + {{ encore_entry_script_tags('app') }} +{% endblock %} \ No newline at end of file diff --git a/code_samples/search/faceted_search/templates/themes/storefront/line/blog_post.html.twig b/code_samples/search/faceted_search/templates/themes/storefront/line/blog_post.html.twig new file mode 100644 index 0000000000..3cd9fcc646 --- /dev/null +++ b/code_samples/search/faceted_search/templates/themes/storefront/line/blog_post.html.twig @@ -0,0 +1,22 @@ +
+
+ {{ ibexa_render_field(content, 'image', { parameters: { alias: 'medium' } }) }} +
+ +
+

+ + {{ ibexa_content_name(content) }} + +

+ +
+ {% set categories = ibexa_field_value(content, 'categories') %} + {% for category in categories.getTaxonomyEntries() %} + + {{ category.getName() }} + + {% endfor %} +
+
+
\ No newline at end of file diff --git a/docs/search/extensibility/create_faceted_search.md b/docs/search/extensibility/create_faceted_search.md new file mode 100644 index 0000000000..ccd1e3f0b3 --- /dev/null +++ b/docs/search/extensibility/create_faceted_search.md @@ -0,0 +1,277 @@ +--- +description: Create faceted search with aggregation API. +--- + +# Search for the results + +[[= product_name =]] supports many search methods, using [search engines](search_engines.md) and several built-in [Search Criteria and Sort Clauses](search_criteria_and_sort_clauses.md). +While searching for content you can also use filters and facets to narrow down the results. + +## Filters vs facets + +Both filters and facets are used to limit large set of search results to smaller subsets that match specified criteria. + +![Filters vs facets](filters_vs_facets.png) + +### Filters + +Filters use a certain attribute and they are typically used as predetermined list of products based on one specific criterion. +As a result, users can narrow a wide range of search results to just that ones that meet their needs. + +Filters are frequently used while restricted search is needed for internal reasons and hidden for end-user. + +### Facets + +Facets are a subset of filtering and they're used to group results based on shared characteristics. + +Facets are used to create a user experience - end-user can interact with them to narrow down search results. +Comparing to filters, facets go one step further and let users narrow results by multiple dimensions at once. + +## Aggregation API + +[Aggregation](aggregation_reference.md) is used to group search results into categories and returns a single value from a bunch of values. +Aggregations allow you to count the number of search results for each form of aggregations. +You can define limits for the results and group them into categories based on the value of a certain Field. + +## Create faceted search + +Faceted search allows you to focus and lower the amount of search results by using multiple filters at the same time. +You can find the content you're looking for without having to run multiple searches. +What's more, you never have an empty search result set, because narrowing attributes are obtained from the search result set. + +### Faceted search in business + +In the following example, you can see the usage of faceted search in the business model - company blog. + +In the blog, user can search for articles and documents using available filters and facets. +Facets are important when it comes to customer experience - they are more flexible and exclude any content parts that don’t meet certain criteria. + +You can implement different types of facets. In the following example, you can learn how to implement three different facets: + +- **date**: searching by publication date (June 2023, July 2023, August 2023, September 2023) +- **rate** (with the minimum and maximum value): searching for materials depending on the star rating given by users, range 2-5 stars +- **category**: searching by category (News, Editorial insights, Product, Marketer insights) + +![Blog overview](blog.png) + +### Create tags + +One of the types of facets that is implemented in the following example, is searching by category. +To be able to search for created posts by their category, you must first create tags +and then assign them to individual content types - blog posts. + +One of the elements of the blog post is **Categories** - this field works with taxonomy entry assignment. +Thanks to this mechanism, user can search by category: News, Editorial insights, Product, Marketer insights. + +To create new tag: + +1\. Go to the Back Office -> **Tags**. + +2\. Click **Create**. + +3\. Input the Tag's name and identifier: + +- Name: News, identifier: news +- Name: Editorial insights, identifier: editorial_insights +- Name: Product, identifier: product +- Name: Marketer insights, identifier: marketer_insights + +4\. Click **Save and close**. + +### Create blog and blog posts + +Next step in this example, is to create two new content types: + +-`Blog` - defining all the structure for the blog. +-`Blog Post` - defining the entire post structure, including all components, selection of required or optional fields, and any additional elements. + +To create new Content Type: + +1\. Go to the Back Office -> **Content Types**. + +2\. Enter the Content group and click **Create**. + +3\. Input the Content Type's name and identifier: + +=== "Blog" + + - **Name**: Blog + - **Identifier**: blog + +=== "Blog Post" + + - **Name**: Blog Post + - **Identifier**: blog_post + +4\. In the Field definitions area, add following Fields with settings: + +- **Blog** + +|Field|Name|Identifier|Description|Required|Searchable|Translatable| +|----|----|----|----|----|----|----| +|`Text line`|Title|title|Insert title of the blog|Yes|Yes|Yes| + +- **Blog Post** + +|Field|Name|Identifier|Description|Required|Searchable|Translatable| +|----|----|----|----|----|----|----| +|`Text line`|Title|title|Insert title of the post|Yes|Yes|Yes| +|`Rich Text`|Content|content|Isert content body of the post|Yes|Yes|Yes| +|`Image`|Image|image|Upload selected image|No|Yes|Yes| +|`Taxonomy Entry Assignment`|Categories|categories|Assign one or more tag to allow searching by category|No|Yes|Yes| +|`Date and time`|Publication date|publication_date|Select publication date to allow searching by publication date|No|Yes|Yes| +|`Authors`|Author|author|Insert author's name|No|Yes|Yes| +|`Float`|Rate|rate|Choose rate number for the post|No|Yes|Yes| + +5\. Click **Save and close**. + +## Create templates + +Create a templates for post and blog posts, including full layout, and line preview. + +In the `templates/themes/storefront` directory, add the following file: + +- `layout` template: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/layout.html.twig') =]] +``` + +Then, in `storefront` add two new folders: `full` and `line`. + +`Full` folder includes templates, that define full view of the blog. +In this folder create two templates: + +- `blog` template defining the preview of the blog: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/full/blog.html.twig') =]] +``` + +- `blog_post` template defining the preview of the post published on the blog in the post view: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/full/blog_post.html.twig') =]] +``` + +`Line` folder includes template, that defines line view of the blog post, so how the blog posts look like in the list. +In this folder create new template: + +- `blog_post` template defining the preview of the post published on the blog in the list view: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/line/blog_post.html.twig') =]] +``` + +### Create QueryType class + +Next step in the process of creating faceted search, is to create QueryType class. +Query allows to quickly find specific data by filtering on requested criteria. +Seleted class facilitates parameters handling by using Symfony's `OptionsResolver`. +This component that enables you to build an options system that includes validation, normalization, required options, defaults, and more. + +Created QueryType class consists of number of functions: + +- `configureOptions` - configures the OptionsResolver for the QueryType. +- `doGetQuery` - builds and returns the Query object. +- `getName` - returns the QueryType name. +- `createFilters` - creates filters based on specific Criterion. +- `createPublicationDateAggregation` - creates aggregation that collects related items depending on publication date. + +In the `src/QueryType` directory, create `BlogPostsQueryType` class with the following code: + +``` php +[[= include_file('code_samples/search/faceted_search/src/QueryType/BlogPostsQueryType.php') =]] +``` +### Create controller + +A controller is a PHP function you write that constructs and returns a `Response` object after reading data from the `Request` object. + +`BlogController` class from the following example, uses a PHP package called Pagerfanta - it aids in the computation +and presentation of paginated lists. + +Controller creates query parameters from current request. + +``` php +[[= include_file('code_samples/search/faceted_search/src/Controller/BlogController.php') =]] +``` + +### Add new abstract class + +You need to add an abstract class - it is a class that is designed to be specifically used as a base class. +Add it in the `src/Menu/Builder` localization. Create a `AbstractFacetsMenuBuilder.php` file with the following code: + +``` php +[[= include_file('code_samples/search/faceted_search/src/Menu/Builder/AbstractFacetsMenuBuilder.php') =]] +``` + +### Add styling + +Next step is to add styling for the project. +To do it, in the `assets/styles` add `app.scss` file. +Then, paste a code that includes styling: + +``` scss +[[= include_file('code_samples/search/faceted_search/assets/styles/app.scss') =]] +``` + +Next, clear the cache by runnig the following command: + +```bash +php bin/console cache:clear +``` + +### Implement facets + +#### Publication date facet + +This facet finds results based on publication date. +To implement it, in the `src/Menu/Builder` add `PublicationDateMenuBuilder.php` file with the following code: + +``` php +[[= include_file('code_samples/search/faceted_search/src/Menu/Builder/PublicationDateMenuBuilder.php') =]] +``` + +Then, register a configuration in `services`: + +``` yaml +[[= include_file('code_samples/search/faceted_search/config/services.yaml') =]] +``` + +#### Rate range facet + +To define a facet that searches by rate range, in the `src/Menu/Builder`, add the following configuration: + + +#### Category facet + +Last type of facets in this example, is the one, that looks for the results depending on the category. +In the `src/Menu/Builder` add `CategoriesMenuBuilder.php` file with the following configuration: + + +``` php +[[= include_file('code_samples/search/faceted_search/src/Menu/Builder/CategoriesMenuBuilder.php') =]] +``` + +### Add templates + +In order to display and render properly the side bar menu, you need to add templates. + +The first file, `facets_menu.html.twig`, includes a template that defines, to the facets menu is rendered. +It searches the page to check whether it contains relevant search results that meet the criteria, that is they belong to one of the given facets categories. +In case that there are no search results, specific category is not displayed in the menu. + +Add the following code in the `templates/themes/storefront/full` localization: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/full/facets_menu.html.twig') =]] +``` + +The second file, `blog_sidebar.hmtl.twig`, creates a template for three categories: Publication Date, Categories and Rate. +It includes `facets_menu` file for menu rendering. + +Add the following code in the `templates/themes/storefront/full` localization: + +``` html+twig +[[= include_file('code_samples/search/faceted_search/templates/themes/storefront/full/blog_sidebar.html.twig', 1, 10) =]] +``` \ No newline at end of file diff --git a/docs/search/img/blog.png b/docs/search/img/blog.png new file mode 100644 index 0000000000..e46c4ddaae Binary files /dev/null and b/docs/search/img/blog.png differ diff --git a/docs/search/img/filters_vs_facets.png b/docs/search/img/filters_vs_facets.png new file mode 100644 index 0000000000..7fd49204f0 Binary files /dev/null and b/docs/search/img/filters_vs_facets.png differ diff --git a/mkdocs.yml b/mkdocs.yml index e578c1e89f..f26097a21b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -655,6 +655,7 @@ nav: - Create custom Search Criterion: search/extensibility/create_custom_search_criterion.md - Create custom Sort Clause: search/extensibility/create_custom_sort_clause.md - Create custom Aggregation: search/extensibility/create_custom_aggregation.md + - Create faceted search: search/extensibility/create_faceted_search.md - Solr document field mappers: search/extensibility/solr_document_field_mappers.md - Index custom Elasticsearch data: search/extensibility/index_custom_elasticsearch_data.md - Customize Elasticsearch index structure: search/extensibility/customize_elasticsearch_index_structure.md